Skip to content

Tags: alexta69/metube

Tags

2026.09.23

Toggle 2026.09.23's commit message
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>

2026.09.20

Toggle 2026.09.20's commit message
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>

2026.09.15

Toggle 2026.09.15's commit message
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>

2026.08.28

Toggle 2026.08.28's commit message
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.

2026.08.21

Toggle 2026.08.21's commit message
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>

2026.08.20

Toggle 2026.08.20's commit message
upgrade yt-dlp from 2026.7.4 to 2026.8.19

2026.08.18

Toggle 2026.08.18's commit message
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>

2026.08.17

Toggle 2026.08.17's commit message
Merge PR #1058: DEFAULT_FOLDER pre-selects a download folder

2026.08.16

Toggle 2026.08.16's commit message
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>

2026.08.15

Toggle 2026.08.15's commit message
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>