Add docs/tartube-spec.md: structured analysis of Tartube's codebase

To make the "surpass Tartube" roadmap concrete, document Tartube's
v2.5.231 codebase as a specification we can refer to:

- Module map: 146k lines of Python across 19 modules, with per-module
  responsibilities (mainapp / mainwin / config / downloads / media /
  options / wizwin / process / refresh / tidy / info / updates /
  ffmpeg_tartube / formats / ttutils / dialogue / files / xdg).
- Data model: container hierarchy (Folder → Channel | Playlist → Video)
  with N-level Folder nesting, system folders that act as smart filters
  (Bookmarks, Favourites, Waiting, New, Live, Missing, Recent,
  Temporary), and the full per-video flag inventory.
- OptionsManager: the headline feature — 164 distinct yt-dlp flags
  attachable to any container or video with cascading resolution. The
  spec lists the option groups (Behaviour, Network, Playlist, Filters,
  Download, Filesystem, Auth, Anti-bot, Format, Subtitles,
  Post-processing, Cookies, Output, Tartube-specific) and points to a
  concrete SQLite + Rust struct design for our Phase 1.1.
- Seven threaded operation managers (Download, Update, Refresh, Tidy,
  Info, Process, Livestream/Stream) and three downloader strategies
  (VideoDownloader, ClipDownloader, StreamDownloader).
- Custom downloads (named recipes) and profiles (saved container
  groups) — feeding our Phase 1 + 3.
- UI surface inventory: five primary tabs (Videos, Progress, Classic
  Mode, Drag and Drop, Errors), ~80 specialised dialogs, three
  wizards (Setup, Import YT subs, Tutorial), and the ~30-tab
  SystemPrefWin.
- SponsorBlock + chapter persistence (slice_list / stamp_list) — a
  Phase 1 add-on after per-channel options ship.
- Notable corners: master/slave dbid dedup, RSS-driven discovery,
  external_dir per-channel storage, livestream message parsing, full
  gettext i18n.
- Things Tartube does better (15 items, mapped to roadmap entries),
  things we already do better (10 items, to keep momentum honest).
- A 12-checkbox "Tartube parity" milestone definition — when all are
  ticked, the comparison table at the top of ROADMAP.md goes from
  8-ahead-8-behind to 8-ahead-0-behind, and "surpass" begins.

ROADMAP.md gains a pointer to the spec just under the north-star line.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Luna 2026-05-25 10:20:56 -07:00
parent 555e0e1574
commit c713d05db8
2 changed files with 622 additions and 0 deletions

View file

@ -4,6 +4,11 @@
**Surpass [Tartube](https://github.com/axcore/tartube) in every dimension.** **Surpass [Tartube](https://github.com/axcore/tartube) in every dimension.**
A structured analysis of Tartube's codebase, data model, operations, and
configuration surface lives at [`docs/tartube-spec.md`](docs/tartube-spec.md).
It enumerates the exact features we need to match — every Phase 1 item
below traces back to a specific Tartube subsystem.
Tartube is the mature open-source yt-dlp GUI and the obvious benchmark for a Tartube is the mature open-source yt-dlp GUI and the obvious benchmark for a
project in this space. yt-offline has architectural advantages Tartube can't project in this space. yt-offline has architectural advantages Tartube can't
catch up on quickly (Rust + axum + a real web UI + bundled toolchain + a catch up on quickly (Rust + axum + a real web UI + bundled toolchain + a

617
docs/tartube-spec.md Normal file
View file

@ -0,0 +1,617 @@
# Tartube Specification
A structured analysis of [Tartube v2.5.231](https://github.com/axcore/tartube)
(2026-05-24), our project's primary feature benchmark. Written to give
yt-offline a concrete target to work toward — every section ends with a
"What we can do" note about whether to adopt, adapt, or skip.
Tartube codebase: ~146,000 lines of Python 3 across 19 modules under
`tartube/`, plus locale, icons, sounds, screenshots. GTK 3 frontend.
GPL-3-or-later. Single-user desktop application.
## 1. Module map
| File | Lines | Responsibility |
| --- | --- | --- |
| `mainapp.py` | 30,565 | The `TartubeApp` Gtk.Application — owns 1,601 instance variables / 791 boolean toggles, every config field, every operation entry point. |
| `mainwin.py` | 42,074 | `MainWin` Gtk window + ~80 specialised `Gtk.Dialog` subclasses + the Video Catalogue widgets. |
| `config.py` | 36,659 | Preferences and per-object editor windows (per-channel options, FFmpeg options, scheduled actions, custom-download recipes). |
| `downloads.py` | 12,188 | Download orchestration: `DownloadManager` (worker pool), `DownloadList` (queue), `DownloadWorker`, three downloader strategies (`VideoDownloader`, `ClipDownloader`, `StreamDownloader`), JSON fetchers. |
| `ttutils.py` | 5,015 | Cross-cutting helpers: path manipulation, livestream-message parsing, locale, sorting. |
| `media.py` | 4,684 | Hierarchical data model: `Video`, `Channel`, `Playlist`, `Folder`, `Scheduled`, plus `GenericMedia` / `GenericContainer` / `GenericRemoteContainer` mixins. |
| `wizwin.py` | 4,113 | Setup wizard, YouTube subscriptions import wizard, tutorial wizard. |
| `options.py` | 2,284 | `OptionsManager`: a 164-field bag of yt-dlp / youtube-dl flags that can be attached to any Video / Channel / Playlist / Folder, plus an `OptionsParser` that compiles them into a yt-dlp CLI invocation. |
| `tidy.py` | 1,636 | `TidyManager`: verify-on-disk operation that finds phantom DB records, missing files, orphaned thumbnails. |
| `ffmpeg_tartube.py` | 1,303 | `FFmpegManager` + `FFmpegOptionsManager`: post-processing pipeline with named presets. |
| `formats.py` | 1,289 | Canonical lists: SponsorBlock categories/actions, audio/video format codes, language codes, livestream message patterns. |
| `updates.py` | 1,180 | `UpdateManager`: self-update yt-dlp via pip / system package / git inside Tartube. |
| `process.py` | 1,012 | `ProcessManager`: chains FFmpeg invocations after a download completes. |
| `info.py` | 671 | `InfoManager`: yt-dlp `--dump-json` + capability probes, version checks. |
| `refresh.py` | 617 | `RefreshManager`: re-import disk state into the media registry. |
| `dialogue.py` | 522 | Small/common dialog primitives (yes-no, message, OK). |
| `classes.py` | small | Misc shared helpers. |
| `files.py` | 167 | File-existence checks. |
| `xdg_tartube.py` | 183 | XDG base-directory resolution for config/data paths. |
Auxiliary directories:
- `locale/``gettext` translations (many languages).
- `icons/`, `sounds/` — assets.
- `nsis/`, `pack/` — Windows installer + per-distro packaging.
- `docs/` — manual pages.
**What we can do:** Our entire codebase is ~9k lines of Rust right now.
Tartube's volume comes from (a) the sheer number of UI dialogs (~80) and
(b) `OptionsManager` cascading through every code path. Both are
mechanical work to match, not architectural blockers.
## 2. Data model
Tartube's central insight is a **container hierarchy** where everything
nests:
```
Folder ─┬─ Folder (recursive)
├─ Channel ── Video
└─ Playlist ── Video
```
`Folder` is the freeform organisation layer (user-created and
system-created — see §2.1). `Channel` and `Playlist` are "remote
containers" with a `source` URL and download semantics. `Video` is a
leaf with on-disk file references plus state flags.
### 2.1 System folders
Tartube ships with a fixed set of system `Folder` objects users can't
delete:
- **All Videos** — every video across the registry.
- **Bookmarks** — videos with `bookmark_flag = True`.
- **Favourite Videos** — videos with `fav_flag = True`, inherited from
any ancestor.
- **Live Videos** — videos with `live_mode > 0`.
- **Missing Videos** — videos that existed but are gone from the source.
- **New Videos** — downloaded but not yet watched.
- **Recent Videos** — last N downloads.
- **Waiting Videos** — videos manually queued for later attention.
- **Temporary Videos** — one-off Classic-Mode downloads.
System folders are virtual: they don't own their videos, they're
filtered views of the registry by flag. Adding a video to "Bookmarks"
sets `bookmark_flag` on the underlying video; removing the bookmark
removes the video from the folder.
**What we can do:** This pattern is *exactly* the activity feed / smart
filter mechanism we should adopt. Our `SidebarView::Recent` is one of
these, hand-coded. Generalising into a `SmartFolder` enum with a
predicate would let us add Bookmarks / Favourites / Waiting / New
mechanically. Tartube parity-and-then-some.
### 2.2 `media.Video` field inventory
Beyond what we have today, Tartube tracks per video:
| Field | Purpose | Have it? |
| --- | --- | --- |
| `dbid` | Integer primary key | No (we key by yt-dlp ID string) |
| `vid` | Extractor's video ID | Yes (`Video::id`) |
| `name` vs `nickname` | Filename vs display title | No — one `title` field |
| `natname` | Natural-sort key | No — sorted at query time |
| `source` | Original URL | No on `Video`; we keep `source_url` per Channel |
| `file_name`, `file_ext` | Separate parts | No — one `PathBuf` |
| `file_size` | bytes | Yes |
| `upload_time` (unix) | Upload date as seconds | Partial — we have `YYYYMMDD` string |
| `receive_time` (unix) | When Tartube downloaded it | We have `mtime_unix`, equivalent |
| `duration` (int seconds) | Yes (`duration_secs`) |
| `index` | Position in channel/playlist | No |
| `author` | Uploader (distinct from channel) | No |
| `descrip`, `short` | Full + summary description | We have full via sidecar |
| `live_mode` (0/1/2) | Not-live / scheduled / broadcasting | We have `live: bool` at download time only |
| `live_debut_flag` | YouTube "premiere" videos | No |
| `was_live_flag` | Once-was-live, prevents re-marking | No |
| `live_time` | Approximate start time | No |
| `live_msg` | Parsed livestream message | No |
| `archive_flag` | Skip auto-deletion | No |
| `bookmark_flag` | Bookmarked | No |
| `fav_flag` | Favourited (cascades from ancestors) | No |
| `missing_flag` | Removed from source after download | No (we have a maintenance scan) |
| `new_flag` | Downloaded but unwatched | Inverse of our `watched` set |
| `waiting_flag` | Manually queued for attention | No |
| `block_flag` | Censored / age-restricted | No |
| `split_flag` | Clip extracted from a parent video | No |
| `orig_parent_obj` | Where the video originally came from | No |
| `subs_list` | Available subtitle languages | Yes (`subtitles: Vec<Subtitle>`) |
| `stamp_list` | Chapter timestamps (manual or extracted) | Partial — we read chapters from info.json, no manual editing |
| `slice_list` | Per-video SponsorBlock data | No — we use `--sponsorblock-mark` at download time |
| `comment_list` | Comments (yt-dlp `--write-comments`) | No |
| `error_list`, `warning_list` | Last download attempt messages | No persisted; we have rolling Job log only |
### 2.3 `media.Channel` / `media.Playlist` / `media.Folder`
Container fields (shared via `GenericContainer`):
- `dbid`, `name`, `nickname`, `natname`, `parent_obj`, `child_list`.
- `options_obj` — per-container `OptionsManager` override (§3).
- Counters: `vid_count`, `bookmark_count`, `dl_count`, `fav_count`,
`live_count`, `missing_count`, `new_count`, `waiting_count`.
- `fav_flag` — cascades to all descendants.
- `error_list`, `warning_list`.
- `external_dir` — optional override storage path.
- `master_dbid` / `slave_dbid_list` — alias resolution (a video uploaded
to two channels with different IDs can be deduplicated by pointing one
to the other).
Channels & Playlists additionally have:
- `source` (URL).
- `rss` — RSS feed URL (for fast new-video discovery).
- `playlist_id_dict` — set of playlist IDs already imported.
- `dl_no_db_flag` — download to disk but skip the database.
- `dl_sim_flag` — always simulate (check-only, never actually fetch).
- `dl_disable_flag` — skip during operations.
### 2.4 Storage
- **`tartube.db`** — Python `pickle` of the entire media registry, not
SQLite. Atomic save-on-modify with periodic backups (configurable
`db_backup_mode = 'always' | 'every_session' | 'daily' | 'never'`).
- **Per-channel folder layout**:
```
<data_dir>/
<channel_name>/
<video_name>.mp4
<video_name>.info.json
<video_name>.description
<video_name>.<ext>.<lang>.vtt
.ytdl-archive (yt-dlp's downloaded-IDs record)
...
```
- **External directories** — a channel can be redirected to a separate
drive via `external_dir`.
**What we can do:** Pickle is a misfeature — schema migrations are
nightmarish and a corrupt pickle locks out the whole library. Our
SQLite-backed approach is strictly better. Don't adopt the storage
model; do adopt the per-channel layout (which matches ours already).
## 3. `OptionsManager` — the headline feature
`options.OptionsManager` is the single biggest gap between us and
Tartube. It holds **164 distinct yt-dlp / youtube-dl flags** and can
be attached to:
- A specific `Video`.
- A `Channel` / `Playlist` / `Folder`.
- The application globally (the "default" manager).
Resolution at download time: `Video.options_obj``parent.options_obj`
→ ancestor chain → app global. First non-`None` wins. A user can
create *named* OptionsManager instances and apply the same one to many
channels.
### 3.1 Canonical option groups
From the docstring at `options.py:69`:
- **Behaviour**: ignore_errors, abort_on_error, live_from_start, wait_for_video_min.
- **Network**: proxy, socket_timeout, source_address, force_ipv4/6,
geo_verification_proxy, geo_bypass*, geo_bypass_country, geo_bypass_ip_block.
- **Playlist selection**: playlist_start, playlist_end, playlist_items,
max_downloads, playlist_reverse, playlist_random,
skip_playlist_after_errors, break_on_existing, break_on_reject.
- **Filters**: min_filesize, max_filesize, date, date_before, date_after,
min_views, max_views, match_filter, age_limit, include_ads,
match_title_list, reject_title_list.
- **Download**: limit_rate, retries, abort_on_unavailable_fragment,
native_hls, hls_prefer_ffmpeg, external_downloader, external_arg_string,
concurrent_fragments, throttled_rate.
- **Filesystem**: restrict_filenames, nomtime, write_description,
write_info, write_annotations, cookies_path, write_thumbnail,
force_encoding, output_format, output_template, output_format_list,
output_path_list, windows_filenames, trim_filenames, no_overwrites,
force_overwrites.
- **Auth**: username, password, two_factor, net_rc, video_password,
ap_mso, ap_username, ap_password.
- **Anti-bot**: no_check_certificate, prefer_insecure, user_agent,
referer, min_sleep_interval, max_sleep_interval.
- **Format**: video_format, all_formats, prefer_free_formats, yt_skip_dash,
merge_output_format, video_format_list, video_format_mode.
- **Subtitles**: write_subs, write_auto_subs, write_all_subs, subs_format,
subs_lang, subs_lang_list.
- **Post-processing**: extract_audio, audio_format, audio_quality,
recode_video, pp_args, keep_video, embed_subs, embed_thumbnail,
add_metadata, fixup_policy, prefer_avconv, prefer_ffmpeg.
- **Cookies**: no_cookies, cookies_from_browser, no_cookies_from_browser.
- **Output sidecars**: write_link, write_url_link, write_webloc_link,
write_desktop_link.
- **Tartube-specific**: keep_description / keep_info / keep_thumbnail /
keep_annotations (move sidecars vs delete), sim_keep_* (same in
simulate mode), move_description (move out of data dir after
download), extra_cmd_string (raw passthrough), direct_cmd_flag /
direct_url_flag (escape hatches), fetch_formats_cmd_string,
fetch_subtitles_cmd_string, downloader_config (write a yt-dlp config
file), check_fetch_comments / dl_fetch_comments /
store_comments_in_db, extractor_args_list.
### 3.2 `OptionsParser`
A second class that walks the canonical option list (a registry of
`OptionHolder` entries) and emits a yt-dlp CLI: `--retries 30
--match-filter "duration > 60" -o "<template>" --cookies <path>` and
so on. Each `OptionHolder` knows its option name, its CLI switch,
default value, and `requirement_list` (other options that must be set).
**What we can do:** This is Phase 1.1 of our ROADMAP. The concrete
design:
1. Add a `download_options` table to SQLite: `(id, name, parent_id,
json_blob)`.
2. Attach to channels via `channels` table additions: `options_id`.
3. New `DownloadOptions` Rust struct with serde — start with maybe 20
most-used flags, not all 164. Skip the ones we don't need
(`force_ipv4` etc.).
4. Resolver: `Video.options``Channel.options` → app default.
5. Per-channel "Edit options…" dialog mirrors the structure of
`OptionsEditWin`.
## 4. Operations model
Tartube has **seven distinct operations**, each its own threaded class.
Only one operation can run at a time except for live-stream monitoring,
which runs concurrently.
| Operation | Class | Purpose |
| --- | --- | --- |
| Download | `DownloadManager` | Actually fetch videos via yt-dlp |
| Update | `UpdateManager` | Self-update yt-dlp (pip / system / git) |
| Refresh | `RefreshManager` | Re-import disk state into the media registry |
| Tidy | `TidyManager` | Verify files-on-disk vs DB, clean phantoms |
| Info | `InfoManager` | yt-dlp `--dump-json` probes, version checks, test runs |
| Process | `ProcessManager` | Post-download FFmpeg pipeline |
| Livestream | `StreamManager` | Background polling for live channels |
### 4.1 `DownloadManager`
Threading model:
- `DownloadManager` is a `threading.Thread`. It owns a worker pool of
`DownloadWorker` threads (configurable max, default 2).
- The `DownloadList` is a queue of `DownloadItem` objects. Each item
references a `media.Video` / `Channel` / `Playlist` plus an
`operation_type` (sim / real / classic_sim / classic_real /
custom_sim / custom_real).
- A worker pulls the next item, instantiates a `VideoDownloader` /
`ClipDownloader` / `StreamDownloader`, runs it, reports back.
- Bypass workers handle livestreams without consuming a normal slot.
- Progress is reported via a `data_callback` that updates the Progress
tab in real time.
### 4.2 `VideoDownloader` vs `ClipDownloader` vs `StreamDownloader`
- **`VideoDownloader`** — the standard case. Builds a yt-dlp CLI from
the resolved `OptionsManager`, runs it, parses stdout/stderr line by
line, updates the `media.Video` record on completion.
- **`ClipDownloader`** — uses yt-dlp's `--download-sections` /
`--postprocessor-args "ffmpeg:-ss …"` to download just one timestamp
range. Powers the "Edit timestamps" → "Download clip" UI.
- **`StreamDownloader`** — wraps `yt-dlp --live-from-start` with
Tartube-specific monitoring, restart-on-disconnect, and a max-duration
cap. Used by `StreamManager`'s livestream auto-record.
### 4.3 `Tidy`, `Refresh`, `Info`
- **Tidy** — like our maintenance scan but more aggressive: finds
duplicate dbids, orphaned files, channels whose folder vanished,
videos that exist on disk but not in the DB.
- **Refresh** — opposite direction: walks the disk and imports any
unrecognised file into the DB.
- **Info** — runs yt-dlp `--dump-json` on a URL to fetch metadata
without downloading; also `--test` mode that downloads only a
fragment to verify settings work.
### 4.4 Update operation
Three backends, user-selectable:
- pip (`pip install -U yt-dlp`)
- system package (`apt install --reinstall yt-dlp`, etc.)
- git pull on a local clone
**What we can do:** Our bundled-venv install is conceptually the
"pip" path. We don't expose a "git pull from upstream master" mode but
arguably that's overkill for a single-user tool.
## 5. Custom downloads
`CustomDLManager` adds a second axis of configuration on top of
`OptionsManager`. A custom download is a *recipe*:
- Which sources to use (channels / playlists / folders / specific
videos).
- Whether to download or just check.
- Whether to delete originals after a successful post-process.
- A specific `OptionsManager`.
- A scheduled time (via `Scheduled`).
Users define multiple named custom-DL recipes and trigger them
manually from the Drag-and-Drop tab or via schedule.
**What we can do:** Our "Music mode" + "Live mode" + "Twitch clips
only" toggles are early hints of this. The principled version is
*named profiles* the user can construct and reuse.
## 6. UI surfaces
`MainWin` is one Gtk window with five primary tabs:
1. **Videos** — the main library browser. Sidebar = container tree;
main area = "Video Catalogue" (the video card grid). Three render
modes: Simple (list), Complex (cards with metadata), Grid
(thumbnail wall). Filters (by name/author/desc/comment) + sort.
2. **Progress** — live download status: per-job rows with status,
percent, ETA, current file. Like our jobs list.
3. **Classic Mode** — one-off downloads. Paste a URL, pick destination
folder, pick format, queue. Bypasses the database entirely.
4. **Drag and Drop** — "drop zones" the user creates, each with a
pre-configured `OptionsManager`. Dropping a URL onto a zone
downloads with that zone's settings.
5. **Errors / Warnings** — accumulated diagnostic messages.
Around the central tabs:
- Menu bar: Media (add channel / playlist / folder / video, bulk add,
insert video, import YouTube subscriptions…), Operations (download,
check, tidy, refresh, info, update), Options (edit general /
custom-DL / FFmpeg / preferences), Help (about, manual, tutorial).
- Toolbar: hide / squeeze, custom icons, custom DL button.
- Status icon (system tray): tray menu, restore from tray, minimize to
tray.
### 6.1 Dialogs (~80 of them)
A representative sample:
- **AddChannelDialogue / AddPlaylistDialogue / AddFolderDialogue**
add a remote container with name + URL + parent folder.
- **AddBulkDialogue** — paste many URLs, autodetect type per URL.
- **AddStampDialogue** — manually add chapter timestamps.
- **AddVideoDialogue** — add a single video URL to an existing channel.
- **AddDropZoneDialogue** — create a Drag-and-Drop zone.
- **ApplyOptionsDialogue** — pick an `OptionsManager` to apply to one
or more containers.
- **CalendarDialogue** — date picker for filters.
- **ChangeThemeDialogue** — switch GTK theme.
- **CreateProfileDialogue** — group containers into a "profile" you can
download all at once.
- **DeleteContainerDialogue / DeleteVideoDialogue / DeleteDropZoneDialogue**
— confirm-before-delete with the usual checkboxes.
- **DuplicateVideoDialogue** — what to do when the same video ID
appears in two channels.
- **ExportDialogue / ImportDialogue** — DB export/import as JSON.
- **ExtractorCodeDialogue** — manual yt-dlp extractor selection.
- **FormatsSubsDialogue** — pick subtitle formats for a download.
- **InsertVideoDialogue** — insert a specific video at a position in a
playlist.
- **MountDriveDialogue** — handle external-drive containers when the
drive isn't mounted.
Plus per-object edit windows in `config.py`:
- `VideoEditWin` — edit a single video's metadata, flags, stamps,
slices.
- `ChannelPlaylistEditWin` — channel/playlist editor.
- `FolderEditWin` — folder editor.
- `OptionsEditWin` — a giant tabbed editor for an `OptionsManager`.
- `CustomDLEditWin` — recipe editor.
- `FFmpegOptionsEditWin` — preset editor for the post-process pipeline.
- `ScheduledEditWin` — schedule entry editor.
- `SystemPrefWin` — application-wide preferences (the equivalent of
our Settings modal but with ~30 tabs).
### 6.2 Wizards
- **SetupWizWin** — first-run wizard: pick data dir, pick downloader,
optionally install yt-dlp / FFmpeg, optionally enable the database.
- **ImportYTWizWin** — import a YouTube subscriptions OPML/JSON file as
channels.
- **TutorialWizWin** — embedded tutorial.
**What we can do:** Our `Settings` modal covers maybe 15% of what
`SystemPrefWin` handles. Some of those — proxy config, retry tuning,
sleep intervals — are reasonable to add. Others (Adobe Pass auth, geo
bypass, restrict filenames) are corner cases that bloat the surface
without serving a clear use case. Be selective.
## 7. Configuration / preferences
Tartube's `SystemPrefWin` covers (each is a tab):
- General
- Files (data dir, external dirs, backups)
- Downloads (worker count, retry limits, bandwidth)
- Filters (default catalogue filters)
- Display (themes, drag formats, status icon)
- Operations (sim vs real defaults, error visibility)
- yt-dlp (path to binary, update mode, extra args)
- FFmpeg (post-process presets, codec defaults)
- Livestreams (polling interval, max duration)
- Notifications (system, dialog, sound)
- Scheduling (default times, day-of-week toggles)
- Profiles (named container groups)
- Cookies (path, browser, refresh policy)
- Anti-bot (sleep intervals, user agents, impersonation hints —
available since yt-dlp added curl_cffi)
- SponsorBlock (categories, actions, manual editing toggle)
- Comments (fetch / store / display)
- Advanced (locks, debug toggles, raw command-line passthrough)
The 791 boolean flags in `mainapp.py` cover all of these plus their
"…also show this…" / "…also do this in classic mode…" variants.
**What we can do:** A handful of these tabs map to features we
literally don't have (FFmpeg presets, comments, profiles, scheduling
calendar). They're roadmap items, not config surface. The actual
*configuration surface* of yt-offline is currently:
- backup dir, max concurrent, bundled-or-system yt-dlp
- player command, browser (for `--cookies-from-browser`)
- UI theme
- web port, bind interface, transcode, source URL
- scheduler enabled + interval hours
- Plex library path
- cookies (paste/file)
- password (Argon2)
Adding ~58 fields (proxy, retry overrides, sleep interval, default
options manager) would close most of the practical gap. Adding 50 more
matches Tartube but rapidly costs more than it earns.
## 8. SponsorBlock + Stamp lists
Tartube goes further than yt-dlp's built-in SponsorBlock support:
- `media.Video.slice_list` stores per-video segments with `(category,
action, start_time, stop_time)`.
- The user can manually add slices via a UI editor.
- The slice list survives re-downloads — Tartube re-applies them.
- Slices can be set to "skip" (cut from output) instead of just "mark"
(chapter marker).
`stamp_list` is the same idea for chapters: extracted from
description/metadata at download time, editable by the user, used by
`ClipDownloader` to slice the source video into named clips.
**What we can do:** Our SponsorBlock support is "pass `--sponsorblock-mark
all` to yt-dlp and hope". Adopting Tartube's persistence model would
let users:
- Manually mark intros / outros / sponsor reads on any video.
- Generate clips by chapter (download a 30-minute video, save 5 clips
named after their chapter titles).
- Hide already-cut segments from the player.
Reasonable Phase 1 add-on after per-channel options ships.
## 9. Notable corners
- **Master/slave dbid** — alias resolution for re-uploads. A video
uploaded to two channels with different IDs can be marked as a
slave of another, so the dedupe scan treats them as the same.
- **Profiles** — named groups of `Channel` / `Playlist` / `Folder`
references. The user can "Download Profile 'Daily'" and get every
member checked in one batch.
- **Newbie dialog** — pop-up that surfaces useful tips for new users.
- **RSS feed discovery**`media.Channel.rss` lets Tartube use a
channel's RSS feed for fast new-video discovery instead of doing a
full yt-dlp playlist scan.
- **Livestream message parsing**`ttutils.extract_livestream_data()`
parses YouTube's "Scheduled for…" / "Premieres in 3 days" strings
into structured fields used for sorting + display.
- **External directories** — a single library can span multiple drives
by setting `media.Channel.external_dir` per-channel.
- **Block flag** — videos that are age-restricted / geo-blocked /
censored. Tartube tracks them separately so the UI shows them with
a different colour and the download skips them.
- **Locale** — full `gettext` i18n with ~10 translations. We have
none.
## 10. Things Tartube does better than us
Items where we *currently lose* and should adopt:
1. **Per-target download options with cascade resolution** (`OptionsManager`)
— Phase 1.1 of our roadmap. Largest gap.
2. **Folder hierarchy** — Phase 1.2.
3. **System folders as smart filters** — Bookmarks, Favourites, Waiting,
New, Live, Missing. Generalises our `SidebarView::Recent`.
4. **Multiple per-video state flags** — bookmark / favourite / archive /
missing / waiting / block / new. Currently we only track watched.
5. **Master / slave dedup** — alias one upload to another.
6. **Manual chapter / clip editing UI**`stamp_list` + `ClipDownloader`.
7. **Manual SponsorBlock editing + persistence**`slice_list`.
8. **Comments capture and viewer**`--write-comments` + `comment_list`.
9. **Profiles** — named container groups for batch operations.
10. **Tidy + Refresh operations** — full bidirectional sync between
registry and disk. Our maintenance scan covers only half.
11. **Setup wizard** — first-run handholding for the data dir / yt-dlp
/ FFmpeg.
12. **i18n / `gettext`** — every locale Tartube has, we don't.
13. **Per-distro installer ecosystem** — .deb, .rpm, MSI, .dmg,
Chocolatey, AUR. We ship a PKGBUILD.
14. **RSS-driven new-video discovery** — quicker than a full yt-dlp
scan.
15. **`extra_cmd_string` / passthrough** — escape hatch for any yt-dlp
flag we haven't surfaced. We have nothing.
## 11. Things we do better than Tartube
For the record (to keep momentum honest):
1. **Single static binary** vs Python + GTK + dozens of pip deps.
2. **Web UI accessible from any device** vs desktop-only GTK window.
3. **Modern security stack** — Argon2 sessions, CSP, rate-limit, body
limit, ETag — vs none.
4. **First-class Plex export** with NFO sidecars and show folders.
5. **Bundled curl_cffi venv install** — Tartube users do this manually
per platform.
6. **Real multi-platform routing** — separate `tiktok/`, `twitch/`,
`vimeo/` etc. with `.source-url` sidecars. Tartube treats every
source identically.
7. **Themes** — 10 of ours vs default GTK plus a single optional dark
theme.
8. **Performance** — Rust parallel scan, gzip + ETag on
`/api/library`, adaptive polling, DB connection pool. Tartube's
pickle load is the bottleneck for large libraries.
9. **SHA-256-verified bundled-binary install**.
10. **Activity feed / Recent additions sidebar entry** — Tartube has
a Recent Videos folder but no per-week activity histogram.
## 12. Specification for "Tartube parity" milestone
Working definition: a yt-offline release is "at Tartube parity" when
the following are all true.
- [ ] **Per-target download options** with cascade resolution and a
per-channel editor.
- [ ] **Folder hierarchy** with N-level nesting, drag-to-reparent, and
per-folder default options.
- [ ] **Smart folders** for Bookmarks / Favourites / Waiting / New /
Live / Missing, on top of our existing Recent + Continue
Watching.
- [ ] **Per-video state flags** persisted (extending the SQLite
schema): `bookmark`, `favourite`, `archive`, `waiting`,
`block`, plus a `missing` derived flag from the maintenance
scan.
- [ ] **Profiles** — saved named groups of channels with a "Download
this profile" action.
- [ ] **Tidy + Refresh** unified into a single bidirectional sync
operation in the maintenance window.
- [ ] **Comments capture and viewer** — opt-in `--write-comments`
flag; new viewer in the video player modal.
- [ ] **Manual chapter / SponsorBlock editing UI** — edit / persist /
re-apply on re-download.
- [ ] **Setup wizard** — first-run flow for empty installs that
handles data dir, yt-dlp install, optional FFmpeg, optional
Plex.
- [ ] **Per-distro release artifacts** — .deb, .rpm, MSI, .app/.dmg,
AUR, Chocolatey or winget.
- [ ] **`extra_args` passthrough** field in download options for any
flag we don't surface natively.
- [ ] **i18n scaffolding** — even one alt locale to prove the build.
When all twelve land, the comparison table at the top of `ROADMAP.md`
goes from 8-ahead-8-behind to 8-ahead-0-behind. After that, the
"surpass" phase consists of leaning on our architectural advantages:
WebSocket progress, Android client, federation, perceptual-hash dedup,
plugins.
## 13. References
- Source: <https://github.com/axcore/tartube>
- Local clone for this analysis: `/tmp/tartube` (axcore/tartube
v2.5.231).
- Modules read in full: `media.py` (data model), `options.py` (option
registry), `downloads.py` (operations + workers), `mainapp.py`
(config surface).
- Modules summarised: every other `.py` file under `tartube/`.
- Cross-referenced with our `ROADMAP.md` Phase 1 items.