normalization pre-pass almost done, ffmpeg output reading lacking proper termination
This commit is contained in:
parent
3243f46772
commit
5c3df8f2fc
2 changed files with 170 additions and 4 deletions
96
aux.py
96
aux.py
|
@ -1,9 +1,32 @@
|
||||||
#! /bin/env python3
|
#! /bin/env python3
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
|
import queue
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import over # FIXME
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def capture_output(stream, fifo):
|
||||||
|
while True:
|
||||||
|
chunk = stream.read1(100)
|
||||||
|
|
||||||
|
if chunk:
|
||||||
|
fifo.put(chunk)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
stream.close()
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
def __init__(self, sequence):
|
def __init__(self, sequence):
|
||||||
self.__dict__["sequence"] = list(sequence)
|
self.__dict__["sequence"] = list(sequence)
|
||||||
|
self.__dict__["thread"] = None
|
||||||
|
self.__dict__["fifo"] = None
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
found = False
|
found = False
|
||||||
|
@ -29,3 +52,76 @@ class Command:
|
||||||
out.append(str(item))
|
out.append(str(item))
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def run(self, async=False, stderr=False):
|
||||||
|
"""
|
||||||
|
Executes the command in the current environment.
|
||||||
|
|
||||||
|
async return immediatelly, use poll_output to get output
|
||||||
|
stderr capture stderr instead of stdout
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.__dict__["process"] = subprocess.Popen(self.dump(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1)
|
||||||
|
self.__dict__["fifo"] = queue.Queue()
|
||||||
|
self.__dict__["thread"] = threading.Thread(
|
||||||
|
target=capture_output,
|
||||||
|
args=(self.process.stderr if stderr else self.process.stdout, self.fifo)
|
||||||
|
)
|
||||||
|
self.thread.daemon = True # thread dies with the program
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
if not async:
|
||||||
|
buffer = []
|
||||||
|
probably_dead = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
probably_dead = self.process.poll() is not None
|
||||||
|
|
||||||
|
chunk = self.poll_output()
|
||||||
|
|
||||||
|
if chunk:
|
||||||
|
buffer.append(chunk)
|
||||||
|
else:
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
if not chunk and self.process.poll() is not None and probably_dead:
|
||||||
|
break
|
||||||
|
|
||||||
|
return b"".join(buffer)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def poll_output(self):
|
||||||
|
"""
|
||||||
|
Returns the output of a currently running process and clears the buffer.
|
||||||
|
|
||||||
|
Returns None if no process is running.
|
||||||
|
|
||||||
|
Blocking - always returns at least one char.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.fifo.empty():
|
||||||
|
return None
|
||||||
|
|
||||||
|
else:
|
||||||
|
buffer = []
|
||||||
|
|
||||||
|
while not self.fifo.empty():
|
||||||
|
buffer.append(self.fifo.get_nowait())
|
||||||
|
|
||||||
|
return b"".join(buffer)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def running(self):
|
||||||
|
return self.process.poll() is None
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
def parse_fps(raw):
|
||||||
|
if "/" in raw:
|
||||||
|
num, den = (int(x) for x in raw.split("/"))
|
||||||
|
|
||||||
|
return float(num) / float(den)
|
||||||
|
|
||||||
|
return float(raw)
|
||||||
|
|
|
@ -6,8 +6,9 @@ import os
|
||||||
import over
|
import over
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
from aux import Command
|
from aux import Command, parse_fps
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
@ -38,7 +39,11 @@ if __name__ == "__main__":
|
||||||
main.add_help("Description", ["Over-Video is a simple video converter."])
|
main.add_help("Description", ["Over-Video is a simple video converter."])
|
||||||
main.parse()
|
main.parse()
|
||||||
|
|
||||||
|
_print("using profile §b%s§/: video_quality=%.1f, audio_quality=%.1f, normalize=%s" %(main.cfg.profile, main.cfg.video_quality, main.cfg.audio_quality, "%.1f dB" %(main.cfg.normalize_mean) if main.cfg.normalize else "None"))
|
||||||
|
|
||||||
for tgt in main.targets:
|
for tgt in main.targets:
|
||||||
|
print()
|
||||||
|
|
||||||
if not os.path.exists(tgt) or os.path.isdir(tgt):
|
if not os.path.exists(tgt) or os.path.isdir(tgt):
|
||||||
_print("target §y%s§/ §ris not a readable file§/, skipping" %(tgt), prefix.fail)
|
_print("target §y%s§/ §ris not a readable file§/, skipping" %(tgt), prefix.fail)
|
||||||
continue
|
continue
|
||||||
|
@ -47,7 +52,72 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
identify_cmd = Command(command.identify)
|
identify_cmd = Command(command.identify)
|
||||||
identify_cmd.INFILE = tgt
|
identify_cmd.INFILE = tgt
|
||||||
identify_raw = subprocess.check_output(identify_cmd.dump()).decode("utf-8")
|
identify_raw = identify_cmd.run()
|
||||||
identify_dict = json.loads(identify_raw)
|
identify_dict = json.loads(identify_raw.decode("utf-8"))
|
||||||
|
|
||||||
print(identify_dict)
|
info = over.core.types.ndict()
|
||||||
|
info.duration = float(identify_dict["format"]["duration"])
|
||||||
|
|
||||||
|
video_streams = [s for s in identify_dict["streams"] if s["codec_type"] == "video"]
|
||||||
|
audio_streams = [s for s in identify_dict["streams"] if s["codec_type"] == "audio"]
|
||||||
|
|
||||||
|
amount_vs = len(video_streams)
|
||||||
|
amount_as = len(audio_streams)
|
||||||
|
|
||||||
|
if amount_vs != 1:
|
||||||
|
_print("detected §r%d§/ video streams" %(amount_vs), prefix.fail)
|
||||||
|
main.exit(1)
|
||||||
|
|
||||||
|
if amount_as != 1:
|
||||||
|
_print("detected §r%d§/ audio streams" %(amount_as), prefix.fail)
|
||||||
|
main.exit(1)
|
||||||
|
|
||||||
|
video = video_streams[0]
|
||||||
|
audio = audio_streams[0]
|
||||||
|
|
||||||
|
info.video_codec = video["codec_name"]
|
||||||
|
info.video_size_x = video["width"]
|
||||||
|
info.video_size_y = video["height"]
|
||||||
|
info.video_fps = over.core.textui.Unit(parse_fps(video["r_frame_rate"]), "Hz")
|
||||||
|
info.video_bitrate = over.core.textui.Unit(audio["bit_rate"], "b/s")
|
||||||
|
|
||||||
|
info.audio_codec = audio["codec_name"]
|
||||||
|
info.audio_channels = audio["channels"]
|
||||||
|
info.audio_samplerate = over.core.textui.Unit(audio["sample_rate"], "Hz")
|
||||||
|
info.audio_language = audio["tags"]["language"]
|
||||||
|
info.audio_bitrate = over.core.textui.Unit(audio["bit_rate"], "b/s")
|
||||||
|
|
||||||
|
_print("§mvideo§/: size=§b%d§/x§b%d§/ px, framerate=%s, codec=%s, bitrate=%s" %(info.video_size_x, info.video_size_y, info.video_fps, info.video_codec, info.video_bitrate))
|
||||||
|
_print("§caudio§/: channels=§b%d§/, samplerate=%s, codec=%s, bitrate=%s, language=%s" %(info.audio_channels, info.audio_samplerate, info.audio_codec, info.audio_bitrate, info.audio_language))
|
||||||
|
|
||||||
|
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(async=True, stderr=True)
|
||||||
|
|
||||||
|
pb = over.core.textui.ProgressBar(50, int(info.video_fps.value * info.duration), "frames")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
probably_dead = not norm_pre_cmd.running
|
||||||
|
|
||||||
|
time.sleep(.25)
|
||||||
|
|
||||||
|
o = norm_pre_cmd.poll_output()
|
||||||
|
|
||||||
|
if o:
|
||||||
|
if b"frame=" in o:
|
||||||
|
frame_id = re.findall(b"frame= *(\d+) ", o)[0]
|
||||||
|
pb.update(int(frame_id))
|
||||||
|
|
||||||
|
elif b"mean_volume: " in o:
|
||||||
|
info.mean_volume = float(re.findall(b"mean_volume: (-\d+\.\d+) dB", o)[0])
|
||||||
|
info.volume_correction = info.mean_volume - main.cfg.normalize_mean
|
||||||
|
else:
|
||||||
|
print(o)
|
||||||
|
|
||||||
|
elif not norm_pre_cmd.running and probably_dead:
|
||||||
|
break
|
||||||
|
|
||||||
|
_print("detected volume %.2f dB, correction %.2f dB" %(info.mean_volume, info.volume_correction))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue