Skip to content

Commit

Permalink
[postprocessor:ugoira] support setting timecodes with mkvmerge
Browse files Browse the repository at this point in the history
by selecting the "mkvmerge" demuxer

(#1550)
  • Loading branch information
mikf committed Mar 26, 2022
1 parent 71bba77 commit e718dd7
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 32 deletions.
21 changes: 17 additions & 4 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3250,11 +3250,13 @@ ugoira.ffmpeg-demuxer
Type
``string``
Default
``image2``
``concat``
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>`_".
FFmpeg demuxer to read and process input files with. Possible values are

* "`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
Expand All @@ -3267,6 +3269,17 @@ Description
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
--------------------
Type
Expand Down
117 changes: 89 additions & 28 deletions gallery_dl/postprocessor/ugoira.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,26 @@ def __init__(self, job, options):
ffmpeg = options.get("ffmpeg-location")
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")
if rate != "auto":
self.calculate_framerate = lambda _: (None, rate)

if options.get("ffmpeg-demuxer") == "image2":
self._process = self._image2
demuxer = options.get("ffmpeg-demuxer")
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:
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):
# get last video-codec argument
Expand Down Expand Up @@ -89,13 +101,12 @@ def convert(self, pathfmt):
return

# process frames and collect command-line arguments
args = self._process(tempdir)
pathfmt.set_extension(self.extension)
args = self._process(pathfmt, tempdir)
if self.args:
args += self.args
self.log.debug("ffmpeg args: %s", args)

# invoke ffmpeg
pathfmt.set_extension(self.extension)
try:
if self.twopass:
if "-f" not in self.args:
Expand All @@ -106,6 +117,8 @@ def convert(self, pathfmt):
else:
args.append(pathfmt.realpath)
self._exec(args)
if self._finalize:
self._finalize(pathfmt, tempdir)
except OSError as exc:
print()
self.log.error("Unable to invoke FFmpeg (%s: %s)",
Expand All @@ -121,37 +134,28 @@ def convert(self, pathfmt):
else:
pathfmt.set_extension("zip")

def _concat(self, path):
ffconcat = path + "/ffconcat.txt"

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("")

with open(ffconcat, "w") as file:
file.write("\n".join(content))
def _exec(self, args):
self.log.debug(args)
out = None if self.output else subprocess.DEVNULL
return subprocess.Popen(args, stdout=out, stderr=out).wait()

def _process_concat(self, pathfmt, tempdir):
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)
args += ("-i", self._write_ffmpeg_concat(tempdir))
if rate_out:
args += ("-r", str(rate_out))
return args

def _image2(self, path):
path += "/"
def _process_image2(self, pathfmt, tempdir):
tempdir += "/"

# adjust frame mtime values
ts = 0
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

return [
Expand All @@ -160,12 +164,69 @@ def _image2(self, path):
"-ts_from_file", "2",
"-pattern_type", "sequence",
"-i", "{}%06d.{}".format(
path.replace("%", "%%"), frame["file"].rpartition(".")[2]),
tempdir.replace("%", "%%"),
frame["file"].rpartition(".")[2]
),
]

def _exec(self, args):
out = None if self.output else subprocess.DEVNULL
return subprocess.Popen(args, stdout=out, stderr=out).wait()
def _process_mkvmerge(self, pathfmt, tempdir):
self._realpath = pathfmt.realpath
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
def calculate_framerate(framelist):
Expand Down

0 comments on commit e718dd7

Please sign in to comment.