Skip to content

feat: generate playlists for torrent media folders - #192

Merged
baairon merged 2 commits into
baairon:mainfrom
ralyodio:feat/automatic-media-playlists
Sep 15, 2026
Merged

baairon merged 2 commits into
baairon:mainfrom
ralyodio:feat/automatic-media-playlists

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

What and why

Course torrents often spread lessons across nested module folders. Generate playlist.m3u automatically after a torrent finishes downloading or a seed verifies, in every ancestor folder within that torrent containing at least two audio/video files. A whole-course playlist includes all lessons; each module with multiple lessons also gets its own. Single-media folders and the shared downloads directory get no playlist.

  • Enable generation by default in the TUI, watch, serve, and seed modes. --no-playlist or TORLINK_NO_PLAYLIST=1 disables creation without deleting existing playlists.
  • Sort filenames naturally (2 before 10) and write relative, percent-encoded media URIs, including nested paths, spaces, Unicode, and reserved characters. Exclude documents, subtitles, images, and course source files.
  • Preserve existing playlists and files declared in torrent metadata. Create files exclusively in existing directories, refuse paths outside the downloads root, and log write failures without interrupting completion or seeding.
  • Serve .m3u with a playlist content type through torlnk files.

Validation

  • npm run typecheck
  • npm test — 419 tests passed across 55 files
  • npm run build
  • Built CLI smoke check using temporary course fixtures: default creation, --no-playlist, environment opt-out, and a single-media torrent, all through real local torrent verification.
  • Built HTTP server smoke check: fetched the generated playlist and resolved all three relative media links, including filenames with spaces, Unicode, #, and %.

Checklist

  • npm run typecheck is clean
  • npm test passes
  • New logic has vitest coverage, including ancestor grouping, natural ordering, single-media exclusions, encoded paths, existing files, symlinks, and completion/opt-out behavior
  • No new key bindings; key hints unchanged
  • No new Store fields; preview store unchanged
  • Filesystem code uses Node's cross-platform path/fs APIs; tests use native paths and Windows-compatible directory junctions. Local validation ran on Linux; the upstream CI matrix covers Windows/macOS.
  • One concern, with a Conventional Commits title

ralyodio and others added 2 commits September 14, 2026 23:22
Playlist entries are plain relative paths instead of percent-encoded
URIs. mpv joins a non-URL entry onto the playlist's folder verbatim,
so names with spaces or non-ASCII characters never opened.

Playlists are written from DownloadQueue.complete() instead of the
engine's done event. That event also fires when a restored seed passes
verification, which wrote playlists into existing seed folders at
launch and brought back playlists the user had deleted.

seed never writes playlists and rejects --no-playlist. A playlist left
in the seeded folder was hashed into the next run's torrent, which
changed the magnet.

The README section is cut to one paragraph, and the help text matches
the new behavior.
@baairon
baairon merged commit 0eef299 into baairon:main Sep 15, 2026
9 checks passed
@ralyodio
ralyodio deleted the feat/automatic-media-playlists branch September 15, 2026 05:16
@ralyodio

Copy link
Copy Markdown
Contributor Author

thank you sir!

WarlaxZ added a commit to WarlaxZ/torlink that referenced this pull request Sep 17, 2026
* feat(serve): per-torrent seed time over the API (baairon#186)

* fix(download): disable uTP to prevent ENOBUFS socket exhaustion crashes (baairon#185)

* fix(download): restore uTP by default, add TORLINK_NO_UTP opt-out

baairon#185 disabled uTP on every platform to stop ENOBUFS socket exhaustion
crashes. That is wider than the failure it fixes: every major BitTorrent
client ships with TCP and uTP both enabled, and turning uTP off also stops
torlink accepting inbound uTP and narrows the NAT port mapping to TCP only.

The exhaustion is a webtorrent dial-path defect rather than a cost of uTP.
utp-native multiplexes fine, and webtorrent already holds a bound uTP socket
on the TCP port, but lib/torrent.js dials through the module-level
UTP.connect, which allocates a fresh UDP socket per outgoing peer. Sockets
then scale with peer count until the ephemeral port pool or buffer space
runs out, and a failed bind is re-emitted on an emitter nothing listens to,
so it arrives as an uncaughtException.

uTP is on again by default, as it was before baairon#185. TORLINK_NO_UTP disables
it for anyone hitting the crash before that dial path is fixed upstream.

* feat: generate playlists for torrent media folders (baairon#192)

Co-authored-by: bairon.dev <hi@bairon.dev>

* fix: give test fake queues a getHistory() now the seed reaper always calls it

runServe now starts the seed reaper unconditionally (it's a no-op without a
--seed-time or a per-torrent override), so any fake Runtime.queue used in a
test that exercises runServe or statusPayload needs getHistory() too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113GE7YUVVDMZFBrrsUL4PK

---------

Co-authored-by: Anthony Ettinger <anthony@chovy.com>
Co-authored-by: Christian Kaiser <5065635+kaiserc@users.noreply.github.com>
Co-authored-by: bairon.dev <hi@bairon.dev>
Co-authored-by: Ash <ashley.russell@alliants.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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