aboutsummaryrefslogtreecommitdiff
path: root/src/components/video.py
diff options
context:
space:
mode:
authorAeliton G. Silva2026-01-12 22:39:55 -0300
committerAeliton G. Silva2026-01-13 04:22:25 -0300
commitf975144f25d34f97329b2d4e52891061573cea08 (patch)
tree226fe223b31af6f217b1dd413629ab2cf26964d4 /src/components/video.py
parentb8703752ffc7768b0275897b3c2a869ff41504e5 (diff)
Use pyproject.toml + uv_build
This replaces setup.py by a modern pyproject.toml using uv_build backend. Dependencies are being also managed by uv, so to install dependencies and run the project one can execute: ``` uv sync uv run pytest # optional python -m avp ``` To build the both source and binary (wheel) distribution package run: ``` uv build ``` Uv can be installed with `pip install uv`. The directory structure has been changed to reflect best practices. - src/* -> src/avp/ - src/tests -> ../tests
Diffstat (limited to 'src/components/video.py')
-rw-r--r--src/components/video.py254
1 files changed, 0 insertions, 254 deletions
diff --git a/src/components/video.py b/src/components/video.py
deleted file mode 100644
index 65a05af..0000000
--- a/src/components/video.py
+++ /dev/null
@@ -1,254 +0,0 @@
-from PIL import Image
-from PyQt6 import QtGui, QtCore, QtWidgets
-import os
-import math
-import subprocess
-import logging
-
-from ..component import Component
-from ..toolkit.frame import BlankFrame, scale
-from ..toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo
-from ..toolkit import checkOutput
-
-
-log = logging.getLogger("AVP.Components.Video")
-
-
-class Component(Component):
- name = "Video"
- version = "1.0.0"
-
- def widget(self, *args):
- self.videoPath = ""
- self.badAudio = False
- self.x = 0
- self.y = 0
- self.loopVideo = False
- super().widget(*args)
- self._image = BlankFrame(self.width, self.height)
- self.page.pushButton_video.clicked.connect(self.pickVideo)
- self.trackWidgets(
- {
- "videoPath": self.page.lineEdit_video,
- "loopVideo": self.page.checkBox_loop,
- "useAudio": self.page.checkBox_useAudio,
- "distort": self.page.checkBox_distort,
- "scale": self.page.spinBox_scale,
- "volume": self.page.spinBox_volume,
- "xPosition": self.page.spinBox_x,
- "yPosition": self.page.spinBox_y,
- },
- presetNames={
- "videoPath": "video",
- "loopVideo": "loop",
- "xPosition": "x",
- "yPosition": "y",
- },
- relativeWidgets=[
- "xPosition",
- "yPosition",
- ],
- )
-
- def update(self):
- if self.page.checkBox_useAudio.isChecked():
- self.page.label_volume.setEnabled(True)
- self.page.spinBox_volume.setEnabled(True)
- else:
- self.page.label_volume.setEnabled(False)
- self.page.spinBox_volume.setEnabled(False)
-
- def previewRender(self):
- self.updateChunksize()
- frame = self.getPreviewFrame(self.width, self.height)
- if not frame:
- return BlankFrame(self.width, self.height)
- else:
- return frame
-
- def properties(self):
- props = []
- outputFile = None
- if hasattr(self.parent, "lineEdit_outputFile"):
- # check only happens in GUI mode
- outputFile = self.parent.lineEdit_outputFile.text()
-
- if not self.videoPath:
- self.lockError("There is no video selected.")
- elif not os.path.exists(self.videoPath):
- self.lockError("The video selected does not exist!")
- elif outputFile and os.path.realpath(self.videoPath) == os.path.realpath(
- outputFile
- ):
- self.lockError("Input and output paths match.")
-
- if self.useAudio:
- props.append("audio")
- if not testAudioStream(self.videoPath) and self.error() is None:
- self.lockError("Could not identify an audio stream in this video.")
-
- return props
-
- def audio(self):
- params = {}
- if self.volume != 1.0:
- params["volume"] = "=%s:replaygain_noclip=0" % str(self.volume)
- return (self.videoPath, params)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- self.updateChunksize()
- self.video = (
- FfmpegVideo(
- inputPath=self.videoPath,
- filter_=self.makeFfmpegFilter(),
- width=self.width,
- height=self.height,
- chunkSize=self.chunkSize,
- frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent,
- loopVideo=self.loopVideo,
- component=self,
- )
- if os.path.exists(self.videoPath)
- else None
- )
-
- def frameRender(self, frameNo):
- if FfmpegVideo.threadError is not None:
- raise FfmpegVideo.threadError
- return self.finalizeFrame(self.video.frame(frameNo))
-
- def postFrameRender(self):
- closePipe(self.video.pipe)
-
- def pickVideo(self):
- imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
- filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.page,
- "Choose Video",
- imgDir,
- "Video Files (%s)" % " ".join(self.core.videoFormats),
- )
- if filename:
- self.settings.setValue("componentDir", os.path.dirname(filename))
- self.mergeUndo = False
- self.page.lineEdit_video.setText(filename)
- self.mergeUndo = True
-
- def getPreviewFrame(self, width, height):
- if not self.videoPath or not os.path.exists(self.videoPath):
- return
-
- command = [
- self.core.FFMPEG_BIN,
- "-thread_queue_size",
- "512",
- "-i",
- self.videoPath,
- "-f",
- "image2pipe",
- "-pix_fmt",
- "rgba",
- ]
- command.extend(self.makeFfmpegFilter())
- command.extend(
- [
- "-codec:v",
- "rawvideo",
- "-",
- "-ss",
- "90",
- "-frames:v",
- "1",
- ]
- )
-
- if self.core.logEnabled:
- logFilename = os.path.join(
- self.core.logDir, "preview_%s.log" % str(self.compPos)
- )
- log.debug("Creating ffmpeg process (log at %s)" % logFilename)
- with open(logFilename, "w") as logf:
- logf.write(" ".join(command) + "\n\n")
- with open(logFilename, "a") as logf:
- pipe = openPipe(
- command,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=logf,
- bufsize=10**8,
- )
- else:
- pipe = openPipe(
- command,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL,
- bufsize=10**8,
- )
-
- byteFrame = pipe.stdout.read(self.chunkSize)
- closePipe(pipe)
-
- frame = self.finalizeFrame(byteFrame)
- return frame
-
- def makeFfmpegFilter(self):
- return [
- "-filter_complex",
- "[0:v] scale=%s:%s" % scale(self.scale, self.width, self.height, str),
- ]
-
- def updateChunksize(self):
- if self.scale != 100 and not self.distort:
- width, height = scale(self.scale, self.width, self.height, int)
- else:
- width, height = self.width, self.height
- self.chunkSize = 4 * width * height
-
- def command(self, arg):
- if "=" in arg:
- key, arg = arg.split("=", 1)
- if key == "path" and os.path.exists(arg):
- if "*%s" % os.path.splitext(arg)[1] in self.core.videoFormats:
- self.page.lineEdit_video.setText(arg)
- self.page.spinBox_scale.setValue(100)
- self.page.checkBox_loop.setChecked(True)
- return
- else:
- print("Not a supported video format")
- quit(1)
- elif arg == "audio":
- if not self.page.lineEdit_video.text():
- print("'audio' option must follow a video selection")
- quit(1)
- self.page.checkBox_useAudio.setChecked(True)
- return
- super().command(arg)
-
- def commandHelp(self):
- print("Load a video:\n path=/filepath/to/video.mp4")
- print("Using audio:\n path=/filepath/to/video.mp4 audio")
-
- def finalizeFrame(self, imageData):
- try:
- if self.distort:
- image = Image.frombytes("RGBA", (self.width, self.height), imageData)
- else:
- image = Image.frombytes(
- "RGBA",
- scale(self.scale, self.width, self.height, int),
- imageData,
- )
- self._image = image
- except ValueError:
- # use last good frame
- image = self._image
-
- if self.scale != 100 or self.xPosition != 0 or self.yPosition != 0:
- frame = BlankFrame(self.width, self.height)
- frame.paste(image, box=(self.xPosition, self.yPosition))
- else:
- frame = image
- return frame