[postprocessor:ugoira] support setting timecodes with mkvmerge

by selecting the "mkvmerge" demuxer

(#1550)
pull/2474/head
Mike Fährmann 3 years ago
parent 71bba774da
commit e718dd7b32
No known key found for this signature in database
GPG Key ID: 5680CA389D365A88

@ -3250,11 +3250,13 @@ ugoira.ffmpeg-demuxer
Type Type
``string`` ``string``
Default Default
``image2`` ``concat``
Description Description
FFmpeg demuxer to read input files with. Possible values are FFmpeg demuxer to read and process 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>`_". * "`concat <https://ffmpeg.org/ffmpeg-formats.html#concat-1>`_" (inaccurate frame timecodes)
* "`image2 <https://ffmpeg.org/ffmpeg-formats.html#image2-1>`_" (accurate timecodes, not usable on Windows)
* "mkvmerge" (accurate timecodes, only WebM or MKV, requires `mkvmerge <ugoira.mkvmerge-location_>`__)
ugoira.ffmpeg-location ugoira.ffmpeg-location
@ -3267,6 +3269,17 @@ Description
Location of the ``ffmpeg`` (or ``avconv``) executable to use. Location of the ``ffmpeg`` (or ``avconv``) executable to use.
ugoira.mkvmerge-location
------------------------
Type
|Path|_
Default
``"mkvmerge"``
Description
Location of the ``mkvmerge`` executable for use with the
`mkvmerge demuxer <ugoira.ffmpeg-demuxer_>`__.
ugoira.ffmpeg-output ugoira.ffmpeg-output
-------------------- --------------------
Type Type

@ -32,14 +32,26 @@ class UgoiraPP(PostProcessor):
ffmpeg = options.get("ffmpeg-location") ffmpeg = options.get("ffmpeg-location")
self.ffmpeg = util.expand_path(ffmpeg) if ffmpeg else "ffmpeg" self.ffmpeg = util.expand_path(ffmpeg) if ffmpeg else "ffmpeg"
mkvmerge = options.get("mkvmerge-location")
self.mkvmerge = util.expand_path(mkvmerge) if mkvmerge else "mkvmerge"
rate = options.get("framerate", "auto") rate = options.get("framerate", "auto")
if rate != "auto": if rate != "auto":
self.calculate_framerate = lambda _: (None, rate) self.calculate_framerate = lambda _: (None, rate)
if options.get("ffmpeg-demuxer") == "image2": demuxer = options.get("ffmpeg-demuxer")
self._process = self._image2 if demuxer == "image2":
self._process = self._process_image2
self._finalize = None
self.log.debug("using image2 demuxer")
elif demuxer == "mkvmerge":
self._process = self._process_mkvmerge
self._finalize = self._finalize_mkvmerge
self.log.debug("using image2+mkvmerge demuxer")
else: else:
self._process = self._concat self._process = self._process_concat
self._finalize = None
self.log.debug("using concat demuxer")
if options.get("libx264-prevent-odd", True): if options.get("libx264-prevent-odd", True):
# get last video-codec argument # get last video-codec argument
@ -89,13 +101,12 @@ class UgoiraPP(PostProcessor):
return return
# process frames and collect command-line arguments # process frames and collect command-line arguments
args = self._process(tempdir) pathfmt.set_extension(self.extension)
args = self._process(pathfmt, tempdir)
if self.args: if self.args:
args += self.args args += self.args
self.log.debug("ffmpeg args: %s", args)
# invoke ffmpeg # invoke ffmpeg
pathfmt.set_extension(self.extension)
try: try:
if self.twopass: if self.twopass:
if "-f" not in self.args: if "-f" not in self.args:
@ -106,6 +117,8 @@ class UgoiraPP(PostProcessor):
else: else:
args.append(pathfmt.realpath) args.append(pathfmt.realpath)
self._exec(args) self._exec(args)
if self._finalize:
self._finalize(pathfmt, tempdir)
except OSError as exc: except OSError as exc:
print() print()
self.log.error("Unable to invoke FFmpeg (%s: %s)", self.log.error("Unable to invoke FFmpeg (%s: %s)",
@ -121,37 +134,28 @@ class UgoiraPP(PostProcessor):
else: else:
pathfmt.set_extension("zip") pathfmt.set_extension("zip")
def _concat(self, path): def _exec(self, args):
ffconcat = path + "/ffconcat.txt" self.log.debug(args)
out = None if self.output else subprocess.DEVNULL
content = ["ffconcat version 1.0"] return subprocess.Popen(args, stdout=out, stderr=out).wait()
append = content.append
for frame in self._frames:
append("file '{}'\nduration {}".format(
frame["file"], frame["delay"] / 1000))
if self.repeat:
append("file '{}'".format(frame["file"]))
append("")
with open(ffconcat, "w") as file:
file.write("\n".join(content))
def _process_concat(self, pathfmt, tempdir):
rate_in, rate_out = self.calculate_framerate(self._frames) rate_in, rate_out = self.calculate_framerate(self._frames)
args = [self.ffmpeg, "-f", "concat"] args = [self.ffmpeg, "-f", "concat"]
if rate_in: if rate_in:
args += ("-r", str(rate_in)) args += ("-r", str(rate_in))
args += ("-i", ffconcat) args += ("-i", self._write_ffmpeg_concat(tempdir))
if rate_out: if rate_out:
args += ("-r", str(rate_out)) args += ("-r", str(rate_out))
return args return args
def _image2(self, path): def _process_image2(self, pathfmt, tempdir):
path += "/" tempdir += "/"
# adjust frame mtime values # adjust frame mtime values
ts = 0 ts = 0
for frame in self._frames: for frame in self._frames:
os.utime(path + frame["file"], ns=(ts, ts)) os.utime(tempdir + frame["file"], ns=(ts, ts))
ts += frame["delay"] * 1000000 ts += frame["delay"] * 1000000
return [ return [
@ -160,12 +164,69 @@ class UgoiraPP(PostProcessor):
"-ts_from_file", "2", "-ts_from_file", "2",
"-pattern_type", "sequence", "-pattern_type", "sequence",
"-i", "{}%06d.{}".format( "-i", "{}%06d.{}".format(
path.replace("%", "%%"), frame["file"].rpartition(".")[2]), tempdir.replace("%", "%%"),
frame["file"].rpartition(".")[2]
),
] ]
def _exec(self, args): def _process_mkvmerge(self, pathfmt, tempdir):
out = None if self.output else subprocess.DEVNULL self._realpath = pathfmt.realpath
return subprocess.Popen(args, stdout=out, stderr=out).wait() pathfmt.realpath = tempdir + "/temp." + self.extension
return [
self.ffmpeg,
"-f", "image2",
"-pattern_type", "sequence",
"-i", "{}/%06d.{}".format(
tempdir.replace("%", "%%"),
self._frames[0]["file"].rpartition(".")[2]
),
]
def _finalize_mkvmerge(self, pathfmt, tempdir):
args = [
self.mkvmerge,
"-o", self._realpath,
"--timecodes", "0:" + self._write_mkvmerge_timecodes(tempdir),
]
if self.extension == "webm":
args.append("--webm")
args.append(pathfmt.realpath)
pathfmt.realpath = self._realpath
self._exec(args)
def _write_ffmpeg_concat(self, tempdir):
content = ["ffconcat version 1.0"]
append = content.append
for frame in self._frames:
append("file '{}'\nduration {}".format(
frame["file"], frame["delay"] / 1000))
if self.repeat:
append("file '{}'".format(frame["file"]))
append("")
ffconcat = tempdir + "/ffconcat.txt"
with open(ffconcat, "w") as file:
file.write("\n".join(content))
return ffconcat
def _write_mkvmerge_timecodes(self, tempdir):
content = ["# timecode format v2"]
append = content.append
delay_sum = 0
for frame in self._frames:
append(str(delay_sum))
delay_sum += frame["delay"]
append(str(delay_sum))
append("")
timecodes = tempdir + "/timecodes.tc"
with open(timecodes, "w") as file:
file.write("\n".join(content))
return timecodes
@staticmethod @staticmethod
def calculate_framerate(framelist): def calculate_framerate(framelist):

Loading…
Cancel
Save