From a7d6a4e1ed047ccb1ba83cf26ea7e77a2e8a7be4 Mon Sep 17 00:00:00 2001 From: hbrain Date: Fri, 29 May 2026 08:05:23 +0000 Subject: [PATCH] mvlog_worker: upload audio files to remote in-dir; update README --- README.md | 2 ++ mvlog_worker.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d00ed86..5b635e3 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,8 @@ The worker: 7. removes the old output if the regenerated filename changed 8. clears the web metadata cache +Note: The worker now also uploads audio files found in the local input directory back to the webserver's in-dir (the same job subdirectory) after rendering. It skips files that already exist on the server. Supported extensions include: .mp3, .wav, .m4a, .aac, .flac, .ogg, .opus, .wma, .aiff, .aif. When running against a remote host the worker uploads using scp (upload to a temporary filename then move into place); when run with --local it copies files into the remote path. Audio upload failures are reported as warnings and do not prevent publishing the generated MP4. After successful uploads the worker recomputes the job fingerprint and records it in the job state so subsequent runs avoid unnecessary re-rendering. + Run once: ```bash diff --git a/mvlog_worker.py b/mvlog_worker.py index a9dc23d..ad3b48e 100755 --- a/mvlog_worker.py +++ b/mvlog_worker.py @@ -185,6 +185,39 @@ def publish_output(host: str, remote_root: str, local_mp4: Path, old_output: str return out_name +def upload_audio_files(host: str | None, job_dir: str, local_input: Path) -> list[str]: + """Upload audio files found inside local_input to the remote job_dir/in-dir if they are missing. + + Returns a list of uploaded relative paths. + """ + AUDIO_EXTS = {".mp3", ".wav", ".m4a", ".aac", ".flac", ".ogg", ".opus", ".wma", ".aiff", ".aif"} + uploaded: list[str] = [] + for path in sorted(local_input.rglob("*")): + if not path.is_file(): + continue + if path.suffix.lower() not in AUDIO_EXTS: + continue + rel = path.relative_to(local_input).as_posix() + remote_path = f"{job_dir}/{rel}" + if remote_file_exists(host, remote_path): + print(f"audio exists on server, skipping: {rel}") + continue + parent = str(Path(remote_path).parent) + if host is None: + Path(parent).mkdir(parents=True, exist_ok=True) + shutil.copy2(str(path), remote_path) + else: + # Ensure parent directory exists on the remote host + ssh(host, f"mkdir -p {remote_q(parent)}") + remote_tmp = f"{remote_path}.tmp.$$" + # Upload to a temporary file then move into place for (some) atomicity + subprocess.run(["scp", str(path), f"{host}:{remote_tmp}"], check=True) + ssh(host, f"mv {remote_q(remote_tmp)} {remote_q(remote_path)}") + uploaded.append(rel) + print(f"uploaded audio: {rel}") + return uploaded + + def render_job(local_input: Path, local_out: Path, extra_args: list[str]) -> Path: local_out.mkdir(parents=True, exist_ok=True) intro_logo = INTRO_LOGO if INTRO_LOGO.exists() else None @@ -293,11 +326,18 @@ def process_job(host: str, remote_root: str, job: str, work_dir: Path, extra_arg if preview_mode: preview_output = publish_output(host, remote_root, local_mp4, old_preview_output) + # Upload any audio files present in the local input directory to the remote in-dir if missing. + try: + upload_audio_files(host, job_dir, local_input) + except Exception as exc: # Upload failure should not prevent publishing the preview + print(f"warning: audio upload failed: {exc}") + # Recompute fingerprint now that audio files may have been uploaded. + final_fingerprint = remote_fingerprint(host, job_dir) new_state = dict(state) new_state.update({ "status": "done", "mode": "preview", - "preview_fingerprint": fingerprint_after, + "preview_fingerprint": final_fingerprint, "preview_output": preview_output, "preview_generated_at": now(), }) @@ -306,11 +346,16 @@ def process_job(host: str, remote_root: str, job: str, work_dir: Path, extra_arg return output = publish_output(host, remote_root, local_mp4, old_output) + try: + upload_audio_files(host, job_dir, local_input) + except Exception as exc: + print(f"warning: audio upload failed: {exc}") + final_fingerprint = remote_fingerprint(host, job_dir) new_state = dict(state) new_state.update({ "status": "done", "mode": "full", - "fingerprint": fingerprint_after, + "fingerprint": final_fingerprint, "output": output, "generated_at": now(), })