diff --git a/doc/codec-comparison.tsv b/doc/codec-comparison.tsv new file mode 100644 index 0000000..8b9caa9 --- /dev/null +++ b/doc/codec-comparison.tsv @@ -0,0 +1,25 @@ +name size encoding time quality +x265-medium-35 20.9E+06 366 poor +x265-slow-35 21.2E+06 748 poor +x264-slow-35 31.1E+06 172 poor +x265-medium-30 37.9E+06 407 noisy +x265-slow-30 39.8E+06 866 noisy +vp9-43 44.8E+06 967 slightly noisy +x264-slow-30 54.5E+06 204 very noisy +vp9-40 55.0E+06 1009 youtube quality +vp9-37 69.1E+06 1095 still noisy +x265-medium-25 73.9E+06 483 tiny noise +x265-slow-25 78.4E+06 1040 near original +vp9-34 86.5E+06 1181 near original +x264-slow-25 101.3E+06 262 near original +vp9-31 106.4E+06 1290 perfect +x265-medium-22 111.0E+06 491 perfect +x265-slow-22 118.9E+06 1245 perfect +vp9-28 130.7E+06 1384 +x264-slow-22 145.0E+06 304 +vp9-25 161.6E+06 1641 +x265-medium-18 188.4E+06 555 +x265-slow-18 200.8E+06 1366 +x264-slow-18 217.9E+06 364 +x265-medium-16 239.9E+06 590 +x265-slow-16 254.9E+06 1484 diff --git a/over-video.py b/over-video.py index 6da1228..3cb1e7e 100755 --- a/over-video.py +++ b/over-video.py @@ -10,6 +10,7 @@ import tempfile import time import version import aux +import multiprocessing Command = over.cmd.Command @@ -22,7 +23,7 @@ X264_BANNED_PIXFMTS = {"bgr24", "yuv422p"} command = over.types.ndict() command.identify = Command("ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", "INFILE") command.normalize_prepass = Command("ffmpeg", "-i", "INFILE", "-max_muxing_queue_size", "512", "-filter:a", "loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json", "-f", "null", "/dev/null") -command.encode_generic = Command("ffmpeg", "FPS", "CUT_FROM", "-i", "INFILE", "-max_muxing_queue_size", "512", "CUT_TO", "MAP", "VIDEO", "AUDIO", "-sn", "OUTFILE") +command.encode_generic = Command("ffmpeg", "FPS", "CUT_FROM", "-i", "INFILE", "-max_muxing_queue_size", "512", "-threads", str(multiprocessing.cpu_count()), "CUT_TO", "MAP", "VIDEO", "AUDIO", "-sn", "OUTFILE") command.sub_opus = Command("-codec:a", "libopus", "NORMALIZE") command.sub_vorbis = Command("-codec:a", "libvorbis", "-qscale:a", "QUALITY", "NORMALIZE") command.sub_pcm = Command("-codec:a", "pcm_s16le", "NORMALIZE") @@ -44,19 +45,21 @@ if __name__ == "__main__": main.add_option("video", "Video codec to use, either vp9<.>, x265<.>, x264<.>, copy<.> or drop<.>.", str, ["vp9"], abbr="v", count=1) main.add_option("video-preset", "Video encoding preset to use by --video<.> x264<.> and x265<.>.", str, ["slow"], abbr="P", count=1) main.add_option("video-quality", "Video encoding quality (CRF). Use 0<.>-51<.> for --video<.> x264<.> and x265<.> (0<.> being lossless, 18<.>-28<.> is reasonable) and 0<.>-63<.> for --video<.> vp9<.> (0<.> being highest, 15<.>-35<.> typical, and 31<.> recommended for HD video).", float, [31], abbr="Q", count=1) - main.add_option("container", "The initial container type. Either mkv<.> or webm<.> (or anything else supported by ffmpeg).", str, ["mkv"], count=1) + main.add_option("container", "The initial container type. Either mkv<.> or webm<.> (or anything else supported by ffmpeg).", str, ["webm"], count=1) 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. "scale=1280:trunc(ow/a/2)*2,transpose=dir=1<.>"', str, abbr="F", count=1) main.add_option("ffmpeg-map", "Raw ffmpeg -map<.> options, e.g. --map<.> 0:1<.> --map<.> 0:2<.>. This is a drop-in fix until we get proper stream selection.", str, abbr="M", overwrite=False, count=1) main.add_option("cut", "Start timestamp and the duration of the portion to use. Uses native ffmpeg -ss<.> and -to<.> format, so it's either seconds from start or [:]:[.<...]<.>. Example: --cut<.> 25 10<.> uses 10 seconds of video starting at 25s, --cut<.> 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 --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) main.add_option("move-source", "Move source file to this directory after conversion. Pass an empty string to disable.", str, ["processed"], count=1) main.add_option("dump-commands", "Print ffmpeg commands that would be executed. If --normalize<.> is in effect, the normalization pre-pass will still be performed so that the proper volume correction can be computed.", bool, [False], abbr="D", in_cfg_file=False) main.add_option("probe", "Print the raw dict (JSON-esque) output of ffprobe and exit.", bool, [False], abbr="p", in_cfg_file=False) main.add_option("armed", "Perform the suggested action.", bool, [False], abbr="A", in_cfg_file=False) 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("Good encoder settings", ["vp9<.>: --video<.> vp9<.> --video-quality<.> 31<.> --audio<.> opus<.> (this is the default and should provide best overall results)", "x264<.>: --video<.> x264<.> --video-preset<.> slow<.> --video-quality<.> 22<.>", "x265<.>: --video<.> x265<.> --video-preset<.> medium<.> --video-quality<.> 20<.>"]) + main.add_doc("Known good encoder settings", ["vp9<.>: --video<.> vp9<.> --video-quality<.> 31<.> --audio<.> opus<.> (this is the default and should provide best overall results)", "x264<.>: --video<.> x264<.> --video-preset<.> slow<.> --video-quality<.> 22<.>", "x265<.>: --video<.> x265<.> --video-preset<.> medium<.> --video-quality<.> 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."]) main.setup() @@ -122,7 +125,10 @@ if __name__ == "__main__": files.infile = aux.to_Path(tgt) files.tmpfile = aux.to_Path(tempfile.mktemp(suffix="." + files.container, dir=".")) - files.outfile = files.infile.parent / (str(files.infile.stem) + "." + files.container) + if main.cfg.output: + files.outfile = main.cfg.output + else: + files.outfile = files.infile.parent / (str(files.infile.stem) + "." + files.container) files.move_infile_to = aux.to_Path(main.cfg.move_source) / files.infile.name if main.cfg.move_source else None if not os.path.exists(tgt) or os.path.isdir(tgt): diff --git a/version.py b/version.py index f8fed45..b8ca209 100644 --- a/version.py +++ b/version.py @@ -4,5 +4,5 @@ major = 1 # VERSION_MAJOR_IDENTIFIER minor = 110 # VERSION_MINOR_IDENTIFIER # VERSION_LAST_MM 1.110 -patch = 0 # VERSION_PATCH_IDENTIFIER +patch = 1 # VERSION_PATCH_IDENTIFIER str = ".".join(str(v) for v in (major, minor, patch))