diff --git a/aux.py b/aux.py index 2be8b60..e111f5c 100644 --- a/aux.py +++ b/aux.py @@ -40,11 +40,9 @@ class Command: """ - def __init__(self, sequence): - self.__dict__["sequence"] = list(sequence) - self.__dict__["thread"] = None - self.__dict__["fifo"] = None - self.__dict__["terminated"] = False + def __init__(self, *sequence): + self.__dict__["sequence_original"] = list(sequence) + self.reset() def __setattr__(self, name, value): found = False @@ -54,8 +52,14 @@ class Command: self.sequence[i] = value found = True - if not found: - raise AttributeError("Command has no attribute \'%s\'" %(name)) + #if not found: + #raise AttributeError("Command has no attribute \'%s\'" %(name)) + + def reset(self): + self.__dict__["sequence"] = list(self.sequence_original) + self.__dict__["thread"] = None + self.__dict__["fifo"] = None + self.__dict__["terminated"] = False def dump(self, sequence=None): out = [] @@ -66,7 +70,9 @@ class Command: for item in sequence: if type(item) is list: out += self.dump(item) - else: + elif type(item) is Command: + out += item.dump() + elif item is not None: out.append(str(item)) return out diff --git a/over-video.py b/over-video.py index 73be4b6..49a3a64 100755 --- a/over-video.py +++ b/over-video.py @@ -6,7 +6,9 @@ import os import over import re import subprocess +import tempfile import time +import pathlib # FIXME sort from aux import Command, parse_fps @@ -18,17 +20,18 @@ _print = over.core.textui.Output("over.video") # -------------------------------------------------- command = over.core.types.ndict() -command.identify = ("ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", "INFILE") -command.normalize_prepass = ("ffmpeg", "-i", "INFILE", "-af", "volumedetect", "-f", "null", "/dev/null") -command.encode_theora = ("ffmpeg", "-i", "INFILE", "-codec:v", "libtheora", "-qscale:v", "VQ", "-codec:a", "libvorbis", "-qscale:a", "AQ", "NORMALIZE", "OUTFILE") -command.encode_x264 = ("ffmpeg", "-i", "INFILE", "-c:v", "libx264", "-preset", "slow", "-crf", "VQ", "-profile:v", "high", "-level", "4.2", "-codec:a", "libvorbis", "-qscale:a", "AQ", "NORMALIZE", "OUTFILE") -command.normalize = ("-filter:a", "VOLUME") +command.identify = Command("ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", "INFILE") +command.normalize_prepass = Command("ffmpeg", "-i", "INFILE", "-af", "volumedetect", "-f", "null", "/dev/null") +command.encode_theora = Command("ffmpeg", "-i", "INFILE", "-codec:v", "libtheora", "-qscale:v", "VQ", "-codec:a", "libvorbis", "-qscale:a", "AQ", "NORMALIZE", "OUTFILE") +command.encode_x264 = Command("ffmpeg", "-i", "INFILE", "-c:v", "libx264", "-preset", "slow", "-crf", "VQ", "-profile:v", "high", "-level", "4.2", "-codec:a", "libvorbis", "-qscale:a", "AQ", "NORMALIZE", "OUTFILE") +command.encode_wav = Command("ffmpeg", "-i", "INFILE", "-codec:a", "pcm_s16le", "NORMALIZE", "OUTFILE") +command.normalize = Command("-filter:a", "VOLUME") # -------------------------------------------------- if __name__ == "__main__": main = over.core.app.Main("Over-Video", "0.1", "AWARE-Overwatch Joint Software License", "~/.over/video.cfg") - main.add_option("profile", "str", "x264", "Encoding profile to use. Available are either §mtheora§/ for Theora and Vorbis in an Ogg container, or §mx264§/ for H.264 and Vorbis in an MP4 container.") + main.add_option("profile", "str", "x264", "Encoding profile to use. Available are either §mtheora§/ for Theora and Vorbis in an Ogg container, §mx264§/ for H.264 and Vorbis in an MP4 container, or §mwav§/ which just extracts audio into a wav for further processing.") main.add_option("video-quality", "float", 22, "Video encoding quality. Use 0-10 for Theora (0 being the lowest, 5-7 is generally watchable) and 0-51 for x264 (0 being lossless, 18-28 is reasonable).", short_name="v") main.add_option("audio-quality", "float", 2, "Audio encoding quality with -1 being the worst and 10 being the best.", short_name="a") main.add_option("normalize", "bool", True, "Normalize the audio track.", short_name="N") @@ -50,10 +53,10 @@ if __name__ == "__main__": _print("processing §B%s§/" %(tgt), prefix.start) - identify_cmd = Command(command.identify) - identify_cmd.INFILE = tgt - identify_cmd.run() - identify_raw = identify_cmd.get_all_output() + command.identify.reset() + command.identify.INFILE = tgt + command.identify.run() + identify_raw = command.identify.get_all_output() identify_dict = json.loads(identify_raw.decode("utf-8")) info = over.core.types.ndict() @@ -94,18 +97,18 @@ if __name__ == "__main__": if (main.cfg.armed or main.cfg.dump_commands) and main.cfg.normalize: _print("running normalization pre-pass") - norm_pre_cmd = Command(command.normalize_prepass) - norm_pre_cmd.INFILE = tgt - norm_pre_cmd.run(stderr=True) + command.normalize_prepass.reset() + command.normalize_prepass.INFILE = tgt + command.normalize_prepass.run(stderr=True) - pb = over.core.textui.ProgressBar(50, int(info.video_fps.value * info.duration), "frames") + pb = over.core.textui.ProgressBar(60, int(info.video_fps.value * info.duration), "frames") output_buffer = [] while True: time.sleep(.25) - out = norm_pre_cmd.get_output() + out = command.normalize_prepass.get_output() if out: output_buffer.append(out) @@ -130,4 +133,65 @@ if __name__ == "__main__": pb.blank() _print("detected volume %.2f dB, correction %.2f dB" %(info.mean_volume, info.volume_correction)) + # select encoding command + if main.cfg.profile == "theora": + encode_cmd = command.encode_theora + info.tmp_file = tempfile.mktemp(suffix='.ogv', dir='.') + elif main.cfg.profile == "x264": + encode_cmd = command.encode_x264 + info.tmp_file = tempfile.mktemp(suffix='.mp4', dir='.') + elif main.cfg.profile == "wav": + encode_cmd = command.encode_wav + info.tmp_file = tempfile.mktemp(suffix='.wav', dir='.') + else: + _print("§runknown profile selected§/: §r%s§/" %(main.cfg.profile), prefix.fail) + raise RuntimeError + # populate its arguments + encode_cmd.reset() + encode_cmd.INFILE = tgt + encode_cmd.OUTFILE = info.tmp_file + encode_cmd.AQ = main.cfg.audio_quality + encode_cmd.VQ = main.cfg.video_quality + + if main.cfg.normalize: + command.normalize.reset() + command.normalize.VOLUME = "volume=%.2fdB" %(info.volume_correction) + encode_cmd.NORMALIZE = command.normalize + else: + encode_cmd.NORMALIZE = None + + if main.cfg.dump_commands: + _print("will execute §B%s§/" %(" ".join(encode_cmd.dump())), prefix.start) + + if main.cfg.armed: + pb = over.core.textui.ProgressBar(60, int(info.video_fps.value * info.duration), "frames") + + encode_cmd.run(stderr=True) + + while True: + time.sleep(.25) + + out = encode_cmd.get_output() + + if out: + if b"frame=" in out: + frame_id = re.findall(b"frame= *(\d+) ", out)[0] + pb.update(int(frame_id)) + + elif out is None: + break + + original_filesize = over.core.textui.Unit(pathlib.Path(tgt).stat().st_size, "B") + new_filesize = over.core.textui.Unit(pathlib.Path(info.tmp_file).stat().st_size, "B") + + pb.blank() + _print("encoding finished: %s -> %s" %(original_filesize, new_filesize), prefix.done) + + new_filename = pathlib.Path(tgt).stem + pathlib.Path(info.tmp_file).suffix + + if main.cfg.dump_commands: + _print('will execute §Bmv "%s" "%s"§/' %(info.tmp_file, new_filename), prefix.start) + + if main.cfg.armed: + pathlib.Path(info.tmp_file).rename(new_filename)