Files
FlightTube/README.md
T
vincent 3296b5436a feat: bundle yt-dlp and ffmpeg as sidecars
The app is now self-contained: yt-dlp, ffmpeg and ffprobe ship inside
the bundle and are resolved beside the executable, so a fresh Mac needs
nothing installed. A system copy still wins when present, which is how
to run a newer yt-dlp than the bundled one. yt-dlp is pointed at the
bundled ffmpeg explicitly, since a bundled app has no reason to have one
on PATH.

The binaries stay out of git (~140MB); scripts/fetch-binaries.sh pulls
them. Homebrew's ffmpeg cannot be used — it links a dozen dylibs and
does not relocate — so the static arm64 build is used instead.

Version checks moved to the async Command. yt-dlp is a PyInstaller
bundle that unpacks ~37MB on first run, and the blocking call was
stalling a runtime worker for roughly twenty seconds.

Bundle size goes from 19MB to 153MB.
2026-08-29 11:41:14 +02:00

130 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FlightTube
A desktop app that merges all your YouTube subscriptions into one newest-first feed,
downloads the videos you pick, and — when you have no connection — shows only what you
already downloaded. Built for the flight.
Tauri 2 · Rust · React · Tailwind CSS 4 · SQLite · self-contained (bundles yt-dlp + ffmpeg)
Styled to `DESIGN-SYSTEM.md`: slate and sky, a 915px type ladder, outline-first
controls, borders for separation and shadows only for elevation, with light and dark
both designed rather than one derived from the other.
## How it works
**Subscriptions come from Google Takeout, not the API.** Export *YouTube subscriptions*
from [Takeout](https://takeout.google.com/) and import the `subscriptions.csv` in
Settings — which carries a seven-step walkthrough with the exact URLs. No OAuth, no API
key, no Google Cloud project, nothing secret on disk.
Importing **replaces** your subscription list: the CSV becomes the whole truth. Channels
no longer in it are removed along with their videos and downloaded files. A confirmation
dialog names exactly what will be deleted before anything happens.
**Video metadata comes from YouTube's public Atom feed** — one request per channel to
`youtube.com/feeds/videos.xml?channel_id=…`, fetched 8 at a time and merged into a single
feed sorted by publish date. Thumbnails are mirrored to local disk so the feed still
renders with no network.
**Undownloaded videos stream in-app.** YouTube's iframe embed refuses a Tauri window —
its origin is `tauri://localhost`, not an http(s) origin, which produces "Error 153" — so
`yt-dlp` resolves YouTube's HLS master playlist instead. Its variants are H.264 + AAC up
to 1080p, which AVFoundation plays natively in WKWebView with adaptive bitrate. One player
element serves both local files and streams.
**Downloads shell out to `yt-dlp`,** pinned to H.264 video + AAC audio in an MP4
container. That caps quality at 1080p — YouTube only serves H.264 that high, and anything
above it is VP9 or AV1, which the app's own player cannot reliably decode. The tradeoff is
deliberate: every download is guaranteed to play inside FlightTube.
## Requirements
None at runtime — `yt-dlp`, `ffmpeg` and `ffprobe` ship inside the app as Tauri sidecars,
so a fresh Mac needs nothing installed. Settings shows each tool's version and whether it
came from the bundle or the system; a copy on your machine takes precedence, which is how
you run a newer yt-dlp than the bundled one.
The binaries are not in git (~140 MB together). Fetch them once before building:
```bash
./scripts/fetch-binaries.sh
```
That pulls yt-dlp from its official GitHub release and static arm64 ffmpeg/ffprobe from
osxexperts.net — Homebrew's ffmpeg links a dozen dylibs and cannot be relocated into an
app bundle. Note that static ffmpeg builds are GPL, which matters if you redistribute.
## Running it
```bash
npm install && npm run tauri dev
```
Build and install a real app bundle:
```bash
npm run tauri build -- --bundles app && cp -R src-tauri/target/release/bundle/macos/FlightTube.app /Applications/
```
## Using it
1. **Settings** — follow the seven-step Takeout guide, then **Import subscriptions.csv**.
2. **Refresh** — pulls the latest videos from every channel.
3. **Download** on any video — progress shows live on the button; click again to cancel.
4. Click any video to play it **in the app** — a downloaded one from disk, anything else
streamed. Nothing hands off to a browser.
5. **List or Tiles** — switch layouts in the top bar; the choice is remembered.
6. **Offline** — the app detects a lost connection and collapses the feed to your
downloads. The connectivity pill also toggles a forced offline mode for testing.
Videos land in `~/Movies/FlightTube` (changeable in Settings). Appearance follows the
system by default; Settings offers System / Light / Dark.
## Limitations
- The Atom feed returns only the **~15 most recent videos per channel**. There is no
backfill and no pagination — this is a rolling recent window, not an archive.
- Quality tops out at 1080p, by the deliberate choice described above.
- The bundled `yt-dlp` is frozen at build time and YouTube changes often. Re-run
`./scripts/fetch-binaries.sh` and rebuild, or install a newer one on your system —
the app prefers a system copy when it finds one.
- Downloading videos is contrary to YouTube's Terms of Service.
## Tests
```bash
cd src-tauri && cargo test
```
42 unit tests cover the three places malformed input actually bites: Takeout CSV parsing,
Atom feed parsing (against a captured real response), and `yt-dlp` progress-line parsing —
plus the database rules that a refresh must never clobber download state and that a
replacing import drops exactly the right channels.
Two network-dependent tests are excluded by default:
```bash
cargo test --test pipeline -- --ignored --nocapture # full pipeline vs. live feeds
cargo test --test seed_real_db -- --ignored --nocapture # populate the installed app's db
```
## Layout
```
src-tauri/src/
takeout.rs subscriptions.csv -> channels (pure, tested)
feed.rs Atom XML -> videos, plus fetching (parse is pure, tested)
downloader.rs yt-dlp arguments and progress lines (pure, tested)
db.rs schema and every SQL statement
thumbs.rs local thumbnail cache
net.rs reachability probe
commands.rs Tauri command surface — delegates only
src/
components/ Sidebar, TopBar, VideoRow, VideoTile, DownloadButton, Player, Settings
components/ui.tsx the design system's component vocabulary, in one place
components/TakeoutGuide the seven-step export walkthrough
hooks/ useFeed, useDownloads, useConnectivity, useAppearance
```
Design notes and the implementation plan are in `docs/superpowers/`.