fix(videos): real ffmpeg progress via -progress pipe, exclude .converting.mp4

- ffmpeg -progress pipe:1 streams out_time_ms lines; we parse them in the
  reading loop so % = out_time_s / duration_s (actual encode position)
- find_video_files() skips *.converting.mp4 temp files so they are never
  listed, uploaded, or shown in the UI

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 02:13:45 -04:00
co-authored by Copilot
parent afce229310
commit b711d421cc
2 changed files with 22 additions and 5 deletions
+3 -1
View File
@@ -232,10 +232,12 @@ def unmount_device(mount_point=DEFAULT_MOUNT_POINT):
def find_video_files(source_folder): def find_video_files(source_folder):
"""Walk source_folder, returning (absolute_path, relative_path) for every """Walk source_folder, returning (absolute_path, relative_path) for every
file whose extension looks like a video, regardless of folder layout.""" file whose extension looks like a video, excluding in-progress conversion temps."""
found = [] found = []
for root, _dirs, files in os.walk(source_folder): for root, _dirs, files in os.walk(source_folder):
for file in files: for file in files:
if file.endswith(".converting.mp4"):
continue
if os.path.splitext(file)[1].lower() in VIDEO_EXTENSIONS: if os.path.splitext(file)[1].lower() in VIDEO_EXTENSIONS:
abs_path = os.path.join(root, file) abs_path = os.path.join(root, file)
rel_path = os.path.relpath(abs_path, source_folder) rel_path = os.path.relpath(abs_path, source_folder)
+19 -4
View File
@@ -71,19 +71,33 @@ def _convert_bg(relative_path):
if relative_path in _converting: if relative_path in _converting:
_converting[relative_path]["duration_s"] = duration_s _converting[relative_path]["duration_s"] = duration_s
_converting[relative_path]["tmp_path"] = tmp_path _converting[relative_path]["tmp_path"] = tmp_path
_converting[relative_path]["out_time_s"] = 0.0
cmd = [ cmd = [
"ffmpeg", "-nostdin", "-loglevel", "error", "-y", "ffmpeg", "-nostdin", "-loglevel", "error", "-y",
"-i", source_path, "-i", source_path,
"-c:v", "h264_v4l2m2m", "-c:v", "h264_v4l2m2m",
"-b:v", "4M", # 4 Mbps — good quality, ~100MB per 3-min clip "-b:v", "4M",
"-c:a", "aac", "-c:a", "aac",
"-b:a", "128k", "-b:a", "128k",
"-movflags", "faststart", "-movflags", "faststart",
"-f", "mp4", "-f", "mp4",
"-progress", "pipe:1",
tmp_path, tmp_path,
] ]
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
proc.wait() # real thread — blocking wait is fine text=True, bufsize=1)
# Read progress lines from stdout
for line in proc.stdout:
line = line.strip()
if line.startswith("out_time_ms="):
try:
ms = int(line.split("=", 1)[1])
with _converting_lock:
if relative_path in _converting:
_converting[relative_path]["out_time_s"] = ms / 1_000_000.0
except (ValueError, IndexError):
pass
proc.wait()
if proc.returncode == 0 and os.path.isfile(tmp_path) and os.path.getsize(tmp_path) > 0: if proc.returncode == 0 and os.path.isfile(tmp_path) and os.path.getsize(tmp_path) > 0:
os.replace(tmp_path, mp4_path) os.replace(tmp_path, mp4_path)
# Delete original now that MP4 is ready # Delete original now that MP4 is ready
@@ -152,7 +166,8 @@ def list_local_videos():
if converting and conv_info: if converting and conv_info:
elapsed = time.time() - conv_info["started_at"] elapsed = time.time() - conv_info["started_at"]
duration_s = conv_info.get("duration_s", 0) duration_s = conv_info.get("duration_s", 0)
pct = min(round(elapsed / duration_s * 100), 99) if duration_s > 0 else 0 out_time_s = conv_info.get("out_time_s", 0.0)
pct = min(round(out_time_s / duration_s * 100), 99) if duration_s > 0 else 0
convert_progress = { convert_progress = {
"elapsed_s": int(elapsed), "elapsed_s": int(elapsed),
"pct": pct, "pct": pct,