add --chapters

This commit is contained in:
Martin Sekera 2021-02-19 01:54:11 +01:00
parent 9b2477a39b
commit 37b4d2d34d
2 changed files with 46 additions and 3 deletions

View file

@ -35,6 +35,7 @@ command.sub_vp9 = Command("-codec:v", "libvpx-vp9", "-crf", "QUALITY", "-b:v", "
command.sub_av1 = Command("-strict", "experimental", "-codec:v", "libaom-av1", "-crf", "QUALITY", "-b:v", "0", "VFILTER")
command.sub_normalize = Command("-filter:a", "LOUDNORM_INCANTATION", "-ar", "48k")
command.sub_vfilter = Command("-filter:v", "ARGS")
command.merge_chapters = Command("mkvmerge", "--chapters", "CHAPTERS_FILE", "-o", "OUTFILE", "INFILE")
command.force_yuv420p = Command("-pix_fmt", "yuv420p")
command.sub_copy_audio = Command("-codec:a", "copy")
command.sub_copy_video = Command("-codec:v", "copy")
@ -52,6 +53,7 @@ if __name__ == "__main__":
main.add_option("normalize", "Normalize the audio track without clipping. May use dynamic range compression.", bool, [True], abbr="n")
main.add_option("ffmpeg-vfilter", 'Raw ffmpeg -filter:v options, e.g. "<M>scale=1280:trunc(ow/a/2)*2,transpose=dir=1<.>"', str, abbr="F", count=1)
main.add_option("ffmpeg-map", "Raw ffmpeg <c>-map<.> options, e.g. <W>--<g>map<.> <M>0:1<.> <W>--<g>map<.> <M>0:2<.>. This is a drop-in fix until we get proper stream selection.", str, abbr="M", overwrite=False, count=1)
main.add_option("chapters", "Path to a Matroska chapters file. See [<W>Chapters<.>].", str, count=1)
main.add_option("cut", "Start timestamp and the duration of the portion to use. Uses native ffmpeg <c>-ss<.> and <c>-to<.> format, so it's either seconds from start or <M>[<HH>:]<MM>:<SS>[.<<m>...]<.>. Example: <W>--<g>cut<.> <M>25 10<.> uses 10 seconds of video starting at 25s, <W>--<g>cut<.> <M>1:10:45 13:9.5<.> uses video from 4245s to 5034.5s.", over.callback.strings, abbr="X", count=2)
main.add_option("fps", "Override input framerate.", float, abbr="f", count=1)
main.add_option("output", "Force an output filename. Note that this overrides <W>--<g>container<.> as we're relying on ffmpeg's container detection by reading the suffix. Pass an empty string to use the container's default suffix.", str, [""], count=1)
@ -64,6 +66,7 @@ if __name__ == "__main__":
main.add_doc("Description", ["A video converter meant to coerce all video formats into one format with properly normalized audio. It can also be used to extract audio from video files, resizing, or very basic cutting."])
main.add_doc("Known good encoder settings", ["<W>vp9<.>: <W>--<g>video<.> <M>vp9<.> <W>--<g>video-quality<.> <M>31<.> <W>--<g>audio<.> <M>opus<.> (this is the default and should provide best overall results)", "<W>x264<.>: <W>--<g>video<.> <M>x264<.> <W>--<g>video-preset<.> <M>slow<.> <W>--<g>video-quality<.> <M>22<.>", "<W>x265<.>: <W>--<g>video<.> <M>x265<.> <W>--<g>video-preset<.> <M>medium<.> <W>--<g>video-quality<.> <M>20<.>"])
main.add_doc("Performance", ["Good bitstreams take obscene amounts of CPU time to produce. See /doc/codec-comparison.tsv for a table of various configs encoding a 1080p video.", "AV1 is currently unusable due to the amount of time it takes to produce a single frame."])
main.add_doc("Chapters", ["Over-video can add chapters to a MKV file. The definition is taken from a separate file with the following syntax:", "CHAPTER<m>xx<.>=HH:MM:SS.SSS\n CHAPTER<m>xx<.>NAME=chapter's name\n (...)", "(where <m>xx<.> is a counter starting from 01)"])
main.setup()
@ -107,6 +110,16 @@ if __name__ == "__main__":
elif main.cfg.audio == "opus":
files.container = "opus"
if main.cfg.chapters:
if main.cfg.container != "mkv":
main.print("unable to use <W>--<G>chapters<.> with <W>--<G>container<.> <R>%s<.>" %(main.cfg.container))
main.exit(1)
files.container = "mka"
if not os.path.exists(main.cfg.chapters):
raise FileNotFoundError(main.cfg.chapters)
main.print("settings", main.print.tl.start, end=":\n")
main.print("audio: %s" %(", ".join(audio_words)))
main.print("video: %s" %(", ".join(video_words)))
@ -282,7 +295,7 @@ if __name__ == "__main__":
loudnorm_dict = json.loads(output[output.index(b"{"):].decode("ascii"))
info.loudnorm = over.types.ndict({k: aux.float_or_string(v) for k, v in loudnorm_dict.items()})
else:
main.print("<r>unexpected ffmpeg output<.>, dump follows", main.print.tl.fail, suffix=":\n")
main.print("<r>unexpected ffmpeg output<.>, dump follows", main.print.tl.fail, end=":\n")
print(output.decode("utf-8"))
raise RuntimeError
@ -465,6 +478,36 @@ if __name__ == "__main__":
main.print("<r>encoding failed<.>, ffmpeg returned <y>%d<.>" %(encode_cmd.returncode), main.print.tl.fail)
raise RuntimeError
# --------------------------------------------------
# inject chapters
if main.cfg.chapters:
if main.cfg.dump_commands or main.cfg.armed:
files.tmpfile_nochapters = aux.to_Path("nochapter-" + str(files.tmpfile)) # really pathlib?
command.merge_chapters.reset()
command.merge_chapters.CHAPTERS_FILE = main.cfg.chapters
command.merge_chapters.OUTFILE = files.tmpfile
command.merge_chapters.INFILE = files.tmpfile_nochapters
cmd = " ".join(command.merge_chapters.dump(pretty=True))
if main.cfg.armed:
main.print("moving <W>%s<.> -> <W>%s<.>" %(files.tmpfile, files.tmpfile_nochapters), main.print.tl.start)
files.tmpfile.rename(files.tmpfile_nochapters)
main.print("executing <W>%s<.>" %(cmd), main.print.tl.start)
command.merge_chapters.run()
_ = command.merge_chapters.get_all_output()
main.print("deleting <W>%s<.>" %(files.tmpfile_nochapters))
files.tmpfile_nochapters.unlink()
else:
main.print("will move <W>%s<.> -> <W>%s<.>" %(files.tmpfile, files.tmpfile_nochapters))
main.print("will execute <W>%s<.>" %(cmd))
main.print("will delete <W>%s<.>" %(files.tmpfile_nochapters))
else:
main.print("will add chapters from <W>%s<.>" %(main.cfg.chapters))
# --------------------------------------------------
# shuffle files around

View file

@ -2,7 +2,7 @@
# encoding: utf-8
major = 1 # VERSION_MAJOR_IDENTIFIER
minor = 114 # VERSION_MINOR_IDENTIFIER
# VERSION_LAST_MM 1.114
minor = 115 # VERSION_MINOR_IDENTIFIER
# VERSION_LAST_MM 1.115
patch = 0 # VERSION_PATCH_IDENTIFIER
str = ".".join(str(v) for v in (major, minor, patch))