Skip to content

Add yt-dlp web UI import#40

Merged
nmbrthirteen merged 1 commit into
nmbrthirteen:mainfrom
teethatkamsai:codex/yt-dlp-web-ui
Jul 5, 2026
Merged

Add yt-dlp web UI import#40
nmbrthirteen merged 1 commit into
nmbrthirteen:mainfrom
teethatkamsai:codex/yt-dlp-web-ui

Conversation

@teethatkamsai

@teethatkamsai teethatkamsai commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds yt-dlp support to the web UI so users can paste a YouTube or direct video URL into the existing video source field, download it locally, and continue through the current preview/transcription workflow.

Changes

  • Add yt-dlp to backend runtime requirements.
  • Add POST /api/download-video for server-side URL validation and downloads.
  • Use Node as yt-dlp’s JavaScript runtime and enable the EJS remote component required for current YouTube extraction.
  • Combine local path and URL entry into one source field.
  • Disable the Download button for local paths and enable it only for HTTP(S) URLs.

Validation

  • npm.cmd run build
  • npm.cmd test
  • Manually verified the reported YouTube URL downloads successfully through /api/download-video.

Summary by CodeRabbit

  • New Features
    • Added support for downloading videos from HTTP/HTTPS links directly in the app, including progress feedback and automatic switching to the downloaded local file.
  • Bug Fixes
    • Improved handling of local file paths vs. web URLs so previews and transcription behave correctly.
    • Reset transcription, clip-selection, and related results state after a download completes so the workflow restarts cleanly.
    • Blocked generation (“Find best moments” and prompt generation) while the source is still a URL to prevent invalid workflows.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@teethatkamsai, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8984b51b-5c14-47b8-aecd-73360973f012

📥 Commits

Reviewing files that changed from the base of the PR and between 4ad4ecc and 26e61c5.

📒 Files selected for processing (4)
  • backend/requirements-runtime.txt
  • backend/requirements.txt
  • src/ui/client/EpisodeWorkspace.jsx
  • src/ui/web-server.ts
📝 Walkthrough

Walkthrough

Adds a yt-dlp runtime dependency, a backend video-download endpoint with URL validation and job tracking, and frontend changes that distinguish remote URLs from local files to download, gate, and reset video-processing flows.

Changes

Video URL Download Feature

Layer / File(s) Summary
Backend download endpoint and job type
backend/requirements-runtime.txt, backend/requirements.txt, src/ui/web-server.ts
Adds yt-dlp>=2025.6.30, extends JobState.type with "download_video", adds URL validation and SSRF-style preflight checks, and implements POST /api/download-video to spawn yt_dlp, track progress, verify the downloaded file, persist state, and broadcast job/state events.
URL detection and download state
src/ui/client/EpisodeWorkspace.jsx
Adds URL detection, download-state wiring, download submission/completion handling, local-path preview and auto-transcribe gating, and transcript/results reset after a remote video becomes a local file.
Video and generate controls
src/ui/client/EpisodeWorkspace.jsx
Reworks the Video input UI to show download controls and progress for URL sources, and blocks generation actions until the source is downloaded locally.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding yt-dlp support for importing/downloading web video URLs in the UI.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

socket-security Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​yt-dlp@​2026.7.475100100100100

View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ui/web-server.ts (1)

363-506: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Restrict download targets to public hosts. validateDownloadUrl only checks the scheme, so an exposed /api/download-video endpoint can fetch loopback, link-local, or RFC1918 URLs server-side and register the result for streaming. Add host/IP blocking, with DNS resolution, before invoking yt-dlp.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/web-server.ts` around lines 363 - 506, The download URL validation in
validateDownloadUrl only enforces http/https, so /api/download-video can still
reach loopback, private, or link-local targets server-side. Extend
validateDownloadUrl (and the /api/download-video flow that calls it) to reject
non-public hosts by resolving the hostname and blocking private/reserved IP
ranges before spawning yt-dlp. Make sure the check covers direct IPs and
DNS-resolved addresses, and return a 400 with a clear validation error when a
target is not allowed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ui/client/EpisodeWorkspace.jsx`:
- Around line 1118-1119: The “Find best moments” flow in EpisodeWorkspace.jsx
needs to block generation when the source is still a URL and hasn’t been
downloaded yet. Update the button logic near the Generate action to use the
existing isProcessing and sourceIsUrl flags together, so the action is disabled
or short-circuited for un-downloaded URL sources. In the Generate handler/button
wiring, reference the existing sourceIsUrl and isProcessing checks to show a
clear “download the video first” message instead of letting /select-file fail on
the raw URL.

In `@src/ui/web-server.ts`:
- Around line 397-420: The /api/download-video handler is holding the request
open for the full yt-dlp run without exposing the created job to the client.
Update the download-video flow in web-server.ts to return the generated jobId
immediately, and hook the existing jobs/job broadcast pattern so progress and
completion can be polled or streamed like the transcribe job flow. Also add a
req.on("close") cleanup path in this handler to terminate the spawned proc when
the client disconnects, and ensure any status updates keep using the existing
JobState/job tracking symbols.
- Around line 422-444: The yt-dlp spawn setup in web-server.ts is passing a full
Node executable via --js-runtimes, which exposes the web server’s permissions to
remote EJS execution. Update the arguments built in the yt-dlp launcher to avoid
using process.execPath as the runtime; either switch the runtime to a sandboxed
alternative such as Deno or remove the remote component path entirely. If Node
must remain, explicitly gate it behind a trusted-input check and document the
trust boundary in the same spawn/args setup.

---

Outside diff comments:
In `@src/ui/web-server.ts`:
- Around line 363-506: The download URL validation in validateDownloadUrl only
enforces http/https, so /api/download-video can still reach loopback, private,
or link-local targets server-side. Extend validateDownloadUrl (and the
/api/download-video flow that calls it) to reject non-public hosts by resolving
the hostname and blocking private/reserved IP ranges before spawning yt-dlp.
Make sure the check covers direct IPs and DNS-resolved addresses, and return a
400 with a clear validation error when a target is not allowed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10703565-5133-43d9-92d1-b92e8823bef7

📥 Commits

Reviewing files that changed from the base of the PR and between 2139b6b and 94fcce3.

📒 Files selected for processing (4)
  • backend/requirements-runtime.txt
  • backend/requirements.txt
  • src/ui/client/EpisodeWorkspace.jsx
  • src/ui/web-server.ts

Comment thread src/ui/client/EpisodeWorkspace.jsx
Comment thread src/ui/web-server.ts
Comment thread src/ui/web-server.ts
@teethatkamsai teethatkamsai force-pushed the codex/yt-dlp-web-ui branch from 94fcce3 to 6a1ef2c Compare July 5, 2026 06:56
@teethatkamsai

Copy link
Copy Markdown
Contributor Author

Verified all findings against current code; all were still valid and fixed.

Fixed:

  • Generate action now blocks raw URL sources and shows “Download the video first, then find best moments.”
  • /api/download-video now returns job_id immediately and updates the existing JobState for progress/completion/error.
  • Added request-close cleanup before the immediate response path.
  • Removed the remote EJS component path from yt-dlp args and documented that Node must not execute remote EJS.
  • Added public-host validation using DNS resolution, blocking loopback/private/link-local/reserved IPs.

Validated:

  • npm.cmd run build
  • npm.cmd test
  • http://127.0.0.1:3847/ rejected with 400
  • http://localhost:3847/ rejected with 400
  • public sample video returned job_id, then completed via /api/job/:id

Amended and force-pushed:

  • Branch: codex/yt-dlp-web-ui
  • Commit: 6a1ef2c Add yt-dlp web UI import

@nmbrthirteen nmbrthirteen left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice feature, wiring looks clean and filename handling is safe. Two things to look at before merge (details inline). Tested yt-dlp with these exact args locally to confirm.

Comment thread src/ui/web-server.ts
Comment thread src/ui/web-server.ts
Comment thread src/ui/web-server.ts
Comment thread src/ui/web-server.ts Outdated
@teethatkamsai teethatkamsai force-pushed the codex/yt-dlp-web-ui branch from 6a1ef2c to 4ad4ecc Compare July 5, 2026 09:09
@teethatkamsai

Copy link
Copy Markdown
Contributor Author

Addressed the review comments and pushed the updated PR branch.

Fixed:

  • Progress now uses --newline --progress --progress-template and parses podcli-progress: from both stdout/stderr.
  • Filepath extraction still uses --print after_move:filepath.
  • SSRF validation comment now states it is a best-effort preflight because yt-dlp handles redirects/DNS at download time.
  • Replaced the misleading remote-components comment with the accurate trust boundary: Node is local JS runtime only; remote EJS components stay disabled.

Skipped:

  • b[ext=mp4]/b comment: left as-is intentionally to avoid requiring ffmpeg merge. That tradeoff caps some YouTube downloads to pre-muxed formats.

Validated:

  • npm.cmd run build
  • npm.cmd test
  • localhost URL rejected with 400
  • public sample URL returns job_id and completes
  • direct yt-dlp run emits parseable podcli-progress: lines

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/ui/client/EpisodeWorkspace.jsx (1)

913-935: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unguarded downloadStream.result access.

const d = downloadStream.result; setFile(d); setVideoPath(d.file_path); assumes result is always populated when status === 'done'. If the backend ever marks the job done without a result payload, this throws inside an effect. The sibling batchStream handling elsewhere uses optional chaining (batchStream.result?.results || []) for the same kind of access — worth being consistent here as a defensive measure.

-        if (downloadStream.status === 'done') {
-          const d = downloadStream.result;
+        if (downloadStream.status === 'done') {
+          const d = downloadStream.result || {};
+          if (!d.file_path) { setError('Download completed but no file was returned.'); setDownloadingVideo(false); setDownloadJobId(null); return; }
           setFile(d);
           setVideoPath(d.file_path);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/client/EpisodeWorkspace.jsx` around lines 913 - 935, Guard the
`downloadStream.result` access inside `EpisodeWorkspace`’s `useEffect` that
handles `downloadStream.status === 'done'`; `result` may be missing even when
the job is done, so update the `setFile`/`setVideoPath` flow to safely handle a
nullish payload instead of assuming `d.file_path` always exists. Use the
existing `downloadStream` effect and its sibling `batchStream` optional-chaining
pattern as a guide, and make sure the effect still clears download state cleanly
when no result is present.
src/ui/web-server.ts (1)

499-513: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Filepath detection via "last non-bracket line" is fragile.

outputFilePath is set to any trimmed line that isn't a progress match, doesn't start with [, and isn't a WARNING:. Any other plain stdout/stderr line printed after after_move:filepath (e.g. postprocessor notices like "Deleting original file …", or a podcli-progress: line whose percent renders as N/A/---.- and fails the numeric regex) would overwrite the real path and trigger a false "did not report an output file" error at Line 547. Prefer a unique sentinel on the print template and parse that explicitly.

♻️ Suggested approach
     "--print",
-    "after_move:filepath",
+    "after_move:podcli-filepath:%(filepath)s",
       const trimmed = line.trim();
-      if (trimmed && !trimmed.startsWith("[") && !trimmed.startsWith("WARNING:")) {
-        outputFilePath = trimmed;
-      }
+      const fp = trimmed.match(/^podcli-filepath:(.+)$/);
+      if (fp) outputFilePath = fp[1];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/web-server.ts` around lines 499 - 513, The current output path
detection in readYtDlpOutput is too broad because it treats the last
non-bracket, non-warning line as the filepath, which can be overwritten by
unrelated yt-dlp output. Update the parsing in readYtDlpOutput and the
corresponding after_move handling to look for a unique sentinel/tagged line
emitted by the print template, and only assign outputFilePath from that explicit
marker. This should prevent postprocessor notices or malformed podcli-progress
lines from clobbering the real path and avoid the false missing-output error
later in the flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ui/client/EpisodeWorkspace.jsx`:
- Around line 902-911: The download flow in downloadVideo leaves
downloadingVideo stuck true whenever api('/download-video') returns an error or
throws, which blocks the workspace UI. Update EpisodeWorkspace’s downloadVideo
handler to always reset downloadingVideo in every failure path, ideally via a
finally block or explicit reset before returning on d.error and in the catch
branch, while preserving the existing success path that sets downloadJobId.

In `@src/ui/web-server.ts`:
- Around line 382-391: The isPublicIp IPv6 handling currently treats IPv4-mapped
IPv6 values like ::ffff:127.0.0.1 as public because they skip the private-range
checks. Update the isPublicIp logic to unwrap mapped/embedded IPv4 addresses
before the family === 6 branch, then apply the existing IPv4 private-range
validation to the extracted IPv4 value. Keep the fix localized around isPublicIp
and its IPv6 normalization path.
- Line 446: The `app.post("/api/download-video", async ...)` handler in
`web-server.ts` does not handle a rejected `mkdir(uploadDir, { recursive: true
})`, so wrap the upload directory setup and subsequent logic in a `try/catch`
and either return a 500 response or pass the error to `next(err)` from that
handler. Keep the fix localized around the `mkdir` call and the surrounding
route logic so failures cannot escape the Express 4.21.0 request lifecycle
unresolved.

---

Nitpick comments:
In `@src/ui/client/EpisodeWorkspace.jsx`:
- Around line 913-935: Guard the `downloadStream.result` access inside
`EpisodeWorkspace`’s `useEffect` that handles `downloadStream.status ===
'done'`; `result` may be missing even when the job is done, so update the
`setFile`/`setVideoPath` flow to safely handle a nullish payload instead of
assuming `d.file_path` always exists. Use the existing `downloadStream` effect
and its sibling `batchStream` optional-chaining pattern as a guide, and make
sure the effect still clears download state cleanly when no result is present.

In `@src/ui/web-server.ts`:
- Around line 499-513: The current output path detection in readYtDlpOutput is
too broad because it treats the last non-bracket, non-warning line as the
filepath, which can be overwritten by unrelated yt-dlp output. Update the
parsing in readYtDlpOutput and the corresponding after_move handling to look for
a unique sentinel/tagged line emitted by the print template, and only assign
outputFilePath from that explicit marker. This should prevent postprocessor
notices or malformed podcli-progress lines from clobbering the real path and
avoid the false missing-output error later in the flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6026df89-cf49-46c8-a11e-1d4c4fe21119

📥 Commits

Reviewing files that changed from the base of the PR and between 94fcce3 and 4ad4ecc.

📒 Files selected for processing (4)
  • backend/requirements-runtime.txt
  • backend/requirements.txt
  • src/ui/client/EpisodeWorkspace.jsx
  • src/ui/web-server.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/requirements-runtime.txt
  • backend/requirements.txt

Comment thread src/ui/client/EpisodeWorkspace.jsx
Comment thread src/ui/web-server.ts
Comment thread src/ui/web-server.ts Outdated
@teethatkamsai teethatkamsai force-pushed the codex/yt-dlp-web-ui branch from 4ad4ecc to 26e61c5 Compare July 5, 2026 09:20
@teethatkamsai

Copy link
Copy Markdown
Contributor Author

Verified against current code; all listed findings were still valid and fixed.

Fixed:

  • downloadVideo now resets downloadingVideo on API error, missing job_id, and thrown request errors.
  • Download completion now guards missing downloadStream.result?.file_path and clears state with a clear error.
  • isPublicIp now unwraps IPv4-mapped IPv6 like ::ffff:127.0.0.1 and applies the existing IPv4 private-range checks.
  • /api/download-video now catches mkdir(uploadDir) failures and returns a 500 instead of leaking an unresolved Express async rejection.
  • yt-dlp filepath parsing now uses an explicit sentinel: podcli-filepath:%(filepath)s, so unrelated output cannot clobber outputFilePath.

Validated:

  • npm.cmd run build
  • npm.cmd test
  • http://localhost:3847/ rejected
  • http://[::ffff:127.0.0.1]:3847/ rejected
  • public sample download returned job_id and completed with the sentinel-parsed file path

@nmbrthirteen nmbrthirteen merged commit d34d2ef into nmbrthirteen:main Jul 5, 2026
5 checks passed
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