Skip to content

fix: resolve file:// URLs correctly on Windows - #69

Open
khnhlt wants to merge 6 commits into
crisng95:mainfrom
khnhlt:fix/windows-file-url-paths
Open

khnhlt wants to merge 6 commits into
crisng95:mainfrom
khnhlt:fix/windows-file-url-paths

Conversation

@khnhlt

@khnhlt khnhlt commented Sep 29, 2026

Copy link
Copy Markdown

Summary

On Windows every local clip/image looked "missing" to the pipeline, so review, concat/finalize, thumbnail generation and the assistant provider all failed even though the files were on disk.

Root cause. Local media is stored as "file://" + str(path). On POSIX that is file:///tmp/x.mp4 and urlparse(url).path reads it back fine. On Windows it is file://C:\...\x.mp4, which urlparse splits into netloc="C:\...\x.mp4" and path="" — so every Path(parsed.path) caller got an empty path, decided the file did not exist, and fell through to an HTTP download of a file:// URL (InvalidUrlClientError: backslash not allowed in authority).

Fix. Add agent.utils.paths.file_url_to_path() — netloc + path through urllib.request.url2pathname, so both the RFC 8089 form Path.as_uri() produces (file:///C:/x/y.png, percent-encoded) and the naive stored form work on every OS — and use it in the five places that parsed file:// by hand:

  • agent/services/video_reviewer.py — _local_media_path (also treats a bare C:\... path as a path, not a scheme)
  • agent/api/videos.py — _resolve_media_local (concat / finalize)
  • agent/api/projects.py — thumbnail copy from provider
  • agent/sdk/services/assistant_provider.py — register_existing_image, _audio_result

Two test_cli_providers assertions hard-coded /tmp; they now spell it via str(Path(...)) so they hold on any OS. No behaviour change on POSIX.

Test plan

  • New tests/unit/test_file_url_paths.py (6 cases: naive form, as_uri() form with %20, POSIX form, non-file / empty / None, bare path, Windows drive-letter form) — written red first, green after the fix
  • Windows 10, Python 3.11.15: full suite 12 failed → 0 failed, 445 passed
  • python -m agent.main starts, /health → status: ok

Notes

/tmp in test_cli_providers resolves to \tmp on Windows — the assertions were the only thing wrong there; the code under test is fine.

The pipeline stores local media as "file://" + str(path). On Windows that
is file://C:\...\x.mp4, which urlparse splits into netloc="C:\..." and an
empty path, so every Path(parsed.path) caller decided the clip was missing
and fell through to an HTTP download that then failed.

Add agent.utils.paths.file_url_to_path (netloc+path via url2pathname, so
both as_uri() and the naive form work) and use it in video_reviewer,
api/videos, api/projects and assistant_provider. Make the two
test_cli_providers assertions spell /tmp the way the host OS does.

Windows: 12 failed -> 0 failed (445 passed).
@crisng95

Copy link
Copy Markdown
Owner

is that fixes work for both other OS systems?

OmniVoice.generate() returns list[numpy.ndarray] (1-D). torchaudio.save
needs (channels, samples), so every TTS call failed at the last step with
'Expected 2D Tensor, got 1D' after the model had generated fine. Wrap the
item via a shared _to_wav_tensor helper in both inline scripts.

Also pass config.TTS_DEVICE through to device_map instead of hardcoding
cpu — TTS_DEVICE already existed in config but was never used. On an RTX
3060 a template takes ~23s including model load vs minutes on CPU.
PeerapolSelanon added a commit to PeerapolSelanon/flowkit that referenced this pull request Sep 29, 2026
Port of crisng95#69 (khnhlt). "file://" + str(path) on Windows parses
with the path in netloc and an empty .path, so every local clip/image looked
missing to review, concat/finalize, thumbnails and the assistant provider.
Adds agent.utils.paths.file_url_to_path and routes all callers through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Khanh added 4 commits September 30, 2026 12:59
The queue path (GENERATE_VIDEO -> FlowProvider._run_video) was hardwired
to Veo. Omni Flash was reachable only through the ad-hoc
/api/flow/generate-video endpoint, which does not persist results to the
scene, so choosing Omni meant leaving the pipeline (no review, no concat,
no retry-repoll).

Add project.video_model_family ('veo' | 'omni_flash', default veo, with
schema migration), thread it through ProjectCreate/ProjectUpdate, the SDK
model/repository, and into job.extra. _run_video dispatches on it:
omni_flash -> generate_omni_flash_first_frame_video / _first_last_video,
which return the same batch-operation shape, so polling and the
already-submitted guard are shared. Defaults come from
OMNI_FLASH_DURATION_S / OMNI_FLASH_RESOLUTION.
Each generate mints a reCAPTCHA in the Flow tab. A worker firing ~30 of
them 10s apart from a background tab degrades the session score until
Flow returns PUBLIC_ERROR_UNUSUAL_ACTIVITY on every call, while the same
account still generates fine by hand. Expose the provider's max_concurrent
and cooldown_s via env so operators can slow down without editing code;
defaults unchanged.
…id-vowel

OmniVoice returns audio that stops at the last voiced sample: zero release
tail, final vowel clipped. Any pipeline that trims video to narrator length
then cuts exactly where the voice is still ringing, which reads as an abrupt
scene change. Fade the last 60 ms and append 0.4 s of silence inside the
save path so every wav ends cleanly and downstream trims get a breath.
A scene whose narration runs 7.6s cannot breathe inside an 8s clip; the
only post fix is a frozen last frame, which reads as a glitch. Pass
scene.duration into job.extra and round it UP to the next supported Omni
Flash step (4/6/8/10) in the provider — rounding down would clip the
voice. Veo ignores the field. Defaults unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants