[postprocessor:ugoira] use 'image2' demuxer (#1550)

Ensures exact frame timecodes with no duplicate frames.

Possible issues are the duration the last frame in an Ugoira with variable
frame durations is shown and insufficient timestamp precision of the
underlying file system (e.g. FAT32, ext3; works on ext4, tmpfs, NTFS).
pull/1633/head
Mike Fährmann 3 years ago
parent b8de0671e5
commit be9547a5f3
No known key found for this signature in database
GPG Key ID: 5680CA389D365A88

@ -2498,6 +2498,18 @@ Description
Additional FFmpeg command-line arguments.
ugoira.ffmpeg-demuxer
---------------------
Type
``string``
Default
``image2``
Description
FFmpeg demuxer to read input files with. Possible values are
"`image2 <https://ffmpeg.org/ffmpeg-formats.html#image2-1>`_" and
"`concat <https://ffmpeg.org/ffmpeg-formats.html#concat-1>`_".
ugoira.ffmpeg-location
----------------------
Type

@ -34,6 +34,11 @@ class UgoiraPP(PostProcessor):
if rate != "auto":
self.calculate_framerate = lambda _: (None, rate)
if options.get("ffmpeg-demuxer") == "concat":
self._process = self._concat
else:
self._process = self._image2
if options.get("libx264-prevent-odd", True):
# get last video-codec argument
vcodec = None
@ -72,38 +77,17 @@ class UgoiraPP(PostProcessor):
if not self._frames:
return
rate_in, rate_out = self.calculate_framerate(self._frames)
with tempfile.TemporaryDirectory() as tempdir:
# extract frames
try:
with zipfile.ZipFile(pathfmt.temppath) as zfile:
zfile.extractall(tempdir)
except FileNotFoundError:
pathfmt.temppath = pathfmt.realpath
pathfmt.realpath = pathfmt.temppath
return
# write ffconcat file
ffconcat = tempdir + "/ffconcat.txt"
with open(ffconcat, "w") as file:
file.write("ffconcat version 1.0\n")
for frame in self._frames:
file.write("file '{}'\n".format(frame["file"]))
file.write("duration {}\n".format(frame["delay"] / 1000))
if self.extension != "gif":
# repeat the last frame to prevent it from only being
# displayed for a very short amount of time
file.write("file '{}'\n".format(self._frames[-1]["file"]))
# collect command-line arguments
args = [self.ffmpeg]
if rate_in:
args += ("-r", str(rate_in))
args += ("-i", ffconcat)
if rate_out:
args += ("-r", str(rate_out))
if self.prevent_odd:
args += ("-vf", "crop=iw-mod(iw\\,2):ih-mod(ih\\,2)")
# process frames and collect command-line arguments
args = self._process(tempdir)
if self.args:
args += self.args
self.log.debug("ffmpeg args: %s", args)
@ -112,7 +96,7 @@ class UgoiraPP(PostProcessor):
pathfmt.set_extension(self.extension)
try:
if self.twopass:
if "-f" not in args:
if "-f" not in self.args:
args += ("-f", self.extension)
args += ("-passlogfile", tempdir + "/ffmpeg2pass", "-pass")
self._exec(args + ["1", "-y", os.devnull])
@ -131,6 +115,45 @@ class UgoiraPP(PostProcessor):
else:
pathfmt.set_extension("zip")
def _concat(self, path):
# write ffconcat file
ffconcat = path + "/ffconcat.txt"
with open(ffconcat, "w") as file:
file.write("ffconcat version 1.0\n")
for frame in self._frames:
file.write("file '{}'\n".format(frame["file"]))
file.write("duration {}\n".format(frame["delay"] / 1000))
if self.extension != "gif":
# repeat the last frame to prevent it from only being
# displayed for a very short amount of time
file.write("file '{}'\n".format(frame["file"]))
rate_in, rate_out = self.calculate_framerate(self._frames)
args = [self.ffmpeg, "-f", "concat"]
if rate_in:
args += ("-r", str(rate_in))
args += ("-i", ffconcat)
if rate_out:
args += ("-r", str(rate_out))
return args
def _image2(self, path):
path += "/"
# adjust frame mtime values
ts = 0
for frame in self._frames:
os.utime(path + frame["file"], ns=(ts, ts))
ts += frame["delay"] * 1000000
return [
self.ffmpeg,
"-f", "image2",
"-ts_from_file", "2",
"-pattern_type", "sequence",
"-i", path.replace("%", "%%") + "%06d.jpg",
]
def _exec(self, args):
out = None if self.output else subprocess.DEVNULL
return subprocess.Popen(args, stdout=out, stderr=out).wait()

Loading…
Cancel
Save