Tags: alexta69/metube
Tags
fix: stop folder errors from reading as a successful add (closes #1082) __add_download returns an error when the download's folder can't be used (CUSTOM_DIRS off, or the folder missing with CREATE_CUSTOM_DIRS off), but __add_entry discarded it and returned ok. A subscription then marked the video seen with nothing queued and no error shown, so it was never offered again; a direct add reported success and nothing appeared. The error is now returned. A subscription shows it and retries the video on its next check instead of marking it seen. Saved queue and pending items that can't be restored at startup are kept in history as failed instead of vanishing. Identical per-item errors are reported once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build: pin the frontend builder image to the last QEMU-safe digest Docker Hub rebuilt node:22-alpine on 2026-09-17 with the same Node (22.23.2) but refreshed Alpine layers. That rebuild dies under QEMU while cross-building the arm64 leg: `qemu: uncaught target signal 4 (Illegal instruction)`, exit 132, during `pnpm install`. Three consecutive release builds failed identically on it. Every other input was unchanged and verified by digest — runner image 20260907.300.1, tonistiigi/binfmt sha256:400a4873, pnpm pinned at 11.5.2, lockfile untouched — and quality-checks, which runs natively, passed every time. The tag was the only floating input: last green build 09-15 node:22-alpine@sha256:c610fcdf built 2026-07-29 failing 09-20 node:22-alpine@sha256:b6f26b36 built 2026-09-17 Pinned to the older of the two. It ships nothing: the builder stage is discarded and only ui/dist is copied out, so no Alpine bytes reach the runtime image and holding an older base costs no exposure. This is a stopgap for the emulated build, not a fix for it. The durable answer is building the arm64 leg on a native runner, which removes the whole failure class. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
build: upgrade Python and npm dependencies Full `uv lock --upgrade` and `pnpm update --latest`. Python: curl-cffi 0.15 -> 0.16.3, python-socketio 5.16 -> 5.17, anyio, multidict, urllib3, websockets, yarl and the pylint/isort dev chain. yt-dlp and aiohttp were already current. Frontend: Angular 22.1.2 -> 22.1.6 (build/cli 22.1.8), angular-eslint 22.5.0, typescript-eslint 8.70.0, eslint 10, jsdom 30, plus three runtime majors -- @ng-select/ng-select 24, @fortawesome/angular-fontawesome 5 and zone.js 0.16. Two upgrades are deliberately held back, both pinned by @angular/build's peer ranges rather than by anything of ours: * typescript stays on 6.0.x (peer `>=6.0 <6.1`). 7.0.2 breaks the build outright -- @angular/compiler-cli's readConfiguration throws on it. * vitest stays on 4.x (peer `^4.0.8`), which is also what the Angular unit-test builder is written against. 4.1.11 carries the @vitest/mocker fix, so this costs no coverage -- it is what dependabot's #1076 should have proposed. `pnpm audit` is clean afterwards, which clears all nine open alerts (fast-uri, js-yaml, hono, vitest) without touching the runtime image either way: the Dockerfile only copies `dist` out of the builder stage. Verified with the backend suite, `pnpm run lint`, `ng test`, a production `pnpm run build`, and by running the app: icons, the collapsible sections, the ng-select preset dropdown, and a queued item arriving over the websocket all behave. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat: collapse the Downloading, Completed and Subscriptions sections (c… …loses #1070) All three section headers become toggles. Collapsing removes the section from the DOM rather than hiding it, so a long completed list stops costing render work while it is put away. The chevron sits at the right edge of the header. Putting it before the label indents that title past the ones without a chevron, and the three section titles are a left-aligned column; keeping them aligned matters more than keeping the affordance next to the word. Direction follows the Advanced Options disclosure already in the form — down when open, right when closed. The request covered Completed and Subscriptions only, but leaving Downloading as the one fixed section is arbitrary once its neighbours move. Each section remembers its own state in a metube_* cookie, matching how every other client-side preference here is persisted; the request asked for localStorage, but a second mechanism for the same job is not worth it. All three default to expanded, so an upgrade doesn't hide anything a user was already looking at. The Downloading and Completed blocks hold the viewChild.required targets behind their select-all checkboxes. Nothing reads them while a section is collapsed: the only caller is the checkbox's own (changed) output, and the queueChanged/doneChanged subscriptions reach it through an optional viewChild. Verified live on a download that finished while the section was put away.
fix: move persistent queue state writes off the event loop (#980) PersistentQueue.put/delete wrote the whole queue inline: serialize, write a temp file, fsync it, rename, then fsync the directory. All of that ran synchronously inside async callers, so on a slow or contended filesystem a single queue mutation stalled every other request for as long as the two fsyncs took. Adds and completions are exactly when it fires, which matches the reported "hiccups happen when something is pushing into the queue". put/delete are now coroutines. The payload is still serialized on the event loop -- it is pure CPU and sub-millisecond -- and only the write goes to a thread, so the writer never walks live DownloadInfo objects while the loop mutates them. Each queue gets its own single-worker executor rather than sharing the default one, because extract_info can hold default-executor threads for minutes and would leave state writes queued behind exactly when they are needed. Awaiting the write makes interleaving possible where it was not before, so a lock now covers the mutate-write-rollback section: the invariant that in-memory state never diverges from what is on disk is unchanged, including the rollback when a write fails. On shutdown the queues are drained rather than cancelled, so a write in flight still lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge PR #1028: first-class SponsorBlock toggle Adds a "Remove sponsor segments" switch to the shared options panel, building the same SponsorBlock + ModifyChapters pair the CLI's --sponsorblock-remove sponsor does, and carries the flag through subscriptions so the panel's control applies to both forms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat: let a subscription carry clip bounds (closes #1049) A subscription already carries every other download option and applies it to each video it queues. Clip bounds were the one exception: 4f83174 added them for one-off downloads and carved subscriptions out, rejecting the fields in the subscribe route and stripping them in the UI before the request was built. So the fields sat visible in the shared advanced-options panel while quietly doing nothing for a subscription. Carry them like the rest: two fields on SubscriptionInfo, threaded through the check flow to add_entry. Stored records that predate the fields take the defaults, since _from_stored filters by field name. One thing does not carry over. parse_download_options reads a YouTube t= timestamp from the URL and turns it into a clip start, which is right when you paste a link to a moment in a video you want. A subscription URL is a channel or a playlist, so a timestamp left on it says nothing about the videos that feed will yield, and honouring it would silently truncate every future download. The subscribe route therefore takes clip bounds only when the caller sent the fields explicitly; the t= param is still stripped from the stored URL. Worth knowing when using this: the range is a fixed offset applied to every video, so it suits feeds with a consistent shape - a standing intro, a fixed sponsor read - and will cut in the wrong place on a feed without one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs: note that a configured proxy needs no ALLOW_PRIVATE_ADDRESSES The reporter of #1055 reached for ALLOW_PRIVATE_ADDRESSES to make a LAN proxy work, which switches off the SSRF guard wholesale. The proxy allowance is automatic; say so where the flag is documented. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PreviousNext