Addresses a full-project review. Backend correctness and availability:
- ytdl: cancel() only SIGKILLs the child's process group when the child
actually became its own group leader, so a race (or failed setpgrp)
can no longer kill the whole server; kill the group on cancel and on
shutdown to avoid orphaned ffmpeg children
- ytdl: dedicated ThreadPoolExecutor for download supervision so active
downloads can't starve extract_info / live probes on the default pool
- ytdl/main/subscriptions: route fire-and-forget tasks through a
bg_tasks helper that keeps a strong ref and logs failures
- subscriptions: run flat-playlist extraction in an executor and check
feeds with bounded concurrency so one slow feed can't block the loop;
set last_checked on failure so broken feeds aren't retried every 60s
- main: validate ids on /start & /delete and numeric env vars at startup;
return 400 (not 500) on bad subscriptions/update input; serve /history
from memory; move get_custom_dirs off the event loop; restrict t=
stripping to YouTube hosts; drop double percent-decode in state guard
- dl_formats/ytdl: enforce requested caption format via
FFmpegSubtitlesConvertor and strip VTT header metadata only in the
pre-cue region so real dialogue is preserved
- ytdl: throttle progress events, dedup adds against pending, clear
filename/size on error and reject out-of-dir trashcan deletes, pin
fork start-method on Linux only
Frontend:
- retry deletes the done record only after a successful re-add
- surface HTTP errors for delete/start and reset the deleting flag
- ignore late 'updated' events for rows no longer in the queue
- track table rows by map key; FileSizePipe uses base-1024
Also: HTTPS-aware Docker healthcheck, dead-code removal, and shared
helpers for path-containment and yt-dlp option merging. Adds/updates
unit tests throughout (250 backend tests passing).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
corepack prepare pnpm --activate without a version was resolving to
12.0.0-alpha.0 (broken pre-release), causing the dockerhub-build-push
job to fail. Adding packageManager field pins it to a stable release.
iOS doesn't have hover, so the tooltip only ever showed on desktop —
where the share-arrow glyph is universally recognised anyway. Aria-
label stays for screen readers.
Web Share fails silently when iOS' share sheet refuses the payload,
typically because the file exceeds the platform's soft size limit
(~50–100 MB depending on iOS version). The previous patch logged to
the console but the user saw nothing — staring at a button that
'does nothing' is poor UX.
Adds two layers of feedback:
1. Pre-flight size check (SHARE_SIZE_WARN_BYTES = 80 MB, conservative
relative to iOS' actual limit) with a confirm() dialog before the
fetch. Avoids spending bandwidth pulling a 150 MB blob into the
browser only for navigator.canShare to reject it.
2. Surfaces canShare-rejection AND share()-failure as a visible
alert() suggesting the user fall back to the download link next
to the share button.
Tested locally with files from 0.7 MB up to 150.7 MB: small files
share unchanged, the 150 MB file now produces a pre-flight warning
the user can dismiss, and any subsequent rejection produces a clear
alert instead of silently no-op'ing.
Adds a share button to the completed-list action row that hands the
downloaded file off to the platform share sheet via navigator.share().
On iOS Safari/Chrome this surfaces the native Save-to-Photos / Save-to-
Files / AirDrop options for videos and images, and Files / 3rd-party
app targets for audio. On platforms without Web Share support (Desktop
Firefox/Chrome/Safari) the button hides itself; the existing download
link remains the universal fallback.
Implementation notes:
- canShareDownloads() requires both navigator.share AND navigator.canShare
(Desktop Safari has the former without the latter; we always intend
to share a file, not a URL)
- shareDownload() fetches the file via the existing buildDownloadLink()
helper, wraps it in a File, then runs canShare() before share() so we
can bail out cleanly on platforms that reject the MIME type
- AbortError (user dismisses sheet) is silenced; other errors logged
- Tooltip on the button explains the iOS behaviour briefly
Refs alexta69/metube#582 — addresses the 'add to Photos.app' request
without depending on the iOS Shortcut, which has had reliability issues
(cf #763).
Allow users to prefer a specific video codec (H.264, H.265, AV1, VP9)
when adding downloads. The selector filters available formats via
yt-dlp format strings, falling back to best available if the preferred
codec is not found. The completed downloads table now shows Quality
and Codec columns.
Display the completion time for each download in the done list.
The backend already stores a nanosecond timestamp on DownloadInfo; this wires it up to the frontend using Angular's DatePipe.