diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml new file mode 100644 index 0000000..cc15538 --- /dev/null +++ b/.github/workflows/wiki.yml @@ -0,0 +1,68 @@ +name: Wiki + +# docs/ is the source of the GitHub wiki. Every change to the documentation +# checks its links; a push to main publishes the result, so the wiki follows +# main. Pull requests only check: nothing unmerged reaches the wiki. Run it from +# the Actions tab to publish another ref, such as a release tag. +on: + push: + branches: [main] + paths: + - 'docs/**' + - 'images/**' + - 'README.md' + - 'scripts/publish-wiki.sh' + - '.github/workflows/wiki.yml' + pull_request: + paths: + - 'docs/**' + - 'images/**' + - 'README.md' + - 'scripts/publish-wiki.sh' + - '.github/workflows/wiki.yml' + workflow_dispatch: + inputs: + ref: + description: 'Tag or branch to publish' + required: false + default: 'main' + type: string + +permissions: + contents: read + +jobs: + check: + name: Check documentation links + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref || github.sha }} + + - name: Check links + run: scripts/publish-wiki.sh --check "$RUNNER_TEMP" + + publish: + name: Publish to the wiki + needs: check + # Pull requests never publish. + if: github.event_name != 'pull_request' + runs-on: ubuntu-24.04 + concurrency: + group: wiki-publish + cancel-in-progress: false + permissions: + contents: write + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref || github.sha }} + + - name: Publish + env: + WIKI_REMOTE: https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.wiki.git + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' + scripts/publish-wiki.sh --push "$RUNNER_TEMP" diff --git a/docs/ACCOUNT_AND_SYNC.md b/docs/ACCOUNT_AND_SYNC.md new file mode 100644 index 0000000..827a4ef --- /dev/null +++ b/docs/ACCOUNT_AND_SYNC.md @@ -0,0 +1,69 @@ +# Account and sync + +Signing in to Proton is optional. Without it, the app plays public share links and keeps +everything on one device. Signing in adds two things: + +- **Folders of your own Drive** as shares, with no link to copy. +- **Sync** of your place in every episode, your shares and your settings, across every device + you sign in on. + +## Signing in + +The **Proton account** section is at the top of the **Shares** page. + +1. Enter your Proton address and password, then **Sign in**. +2. If your account has two-factor authentication, enter the code from your authenticator app. +3. If your account has a separate mailbox password, enter it and choose **Unlock**. + +Proton sometimes asks to confirm you are human. The app then opens a small window with Proton's +own CAPTCHA page. Solve it, and sign-in continues. + +The app keeps a session, not your password. The session goes into the system keyring. **Sign +out** ends it on Proton's side and deletes it here. + +Not supported yet: + +- **Security keys** (FIDO2). An account whose only second factor is a security key cannot sign + in. Add an authenticator app as a second method in your Proton settings. +- **Email or SMS verification** instead of a CAPTCHA. + +## Adding a folder of your Drive + +1. Sign in. +2. Under **Add from your Drive**, choose **Browse your Drive**. +3. Open folders until you reach the one that holds your videos. +4. Choose **Add this folder**. + +The folder becomes a share like any link: it is crawled, shown in the library and streamed. It is +read through your account, so there is no link or password to store. The same folder has the +same identity on every device, so watch history follows it. + +## Sync + +When you are signed in, each device keeps a small file in your Drive. You find them under +**Computers** on the Proton Drive website, in a device called `proton-stream`, in the folder +`watch-history`. + +**What syncs:** + +- Where you stopped in each episode, and which episodes you watched +- Your shares, including their links and passwords (end-to-end encrypted, like every file in + your Drive) +- Settings: playback, appearance and posters and descriptions, including a TMDB key +- Matches you picked by hand, and your audio and subtitle choice for each show + +**What stays on each device:** volume and mute, the streaming cache size, text size, Wi-Fi only, +background audio, hardware decoding, window size, and the library's filter and sort order. + +**When it syncs:** when the app starts, every 5 minutes while it is open, when you stop playing, +and when it closes. **Sync now** runs it at once. Android also syncs in the background shortly +after you leave the app. + +**How conflicts are settled:** each device writes only its own file, and reads everyone else's. +For each episode and each setting, the newest change wins. A device that signs in for the first +time takes the others' state and does not overwrite it with its own defaults. + +A device that has not synced for 90 days has its file moved to the Drive trash. + +The status line under your address says how the last sync went, for example *Synced 3 min ago · +2 positions from other devices*. To stop syncing, sign out. diff --git a/docs/CLI.md b/docs/CLI.md new file mode 100644 index 0000000..5c8d8b2 --- /dev/null +++ b/docs/CLI.md @@ -0,0 +1,96 @@ +# Command-line reference + +The desktop packages install two programs: + +- `proton-stream`, the app +- `pstr`, a command-line tool that does the app's work without a window + +Both use the same shares, library, settings and keyring, so a share added with `pstr` shows up in +the app. + +## proton-stream + +```bash +proton-stream # open the app +proton-stream 'https://drive.proton.me/urls/TOKEN#KEY' # open it with the Add form filled in +``` + +Only one window runs at a time. A second `proton-stream` brings the open window forward, hands it +any share link it was given, and exits. + +A link given as an argument is visible to other users of the computer in the process list while +the command runs, and it may land in your shell history. Pasting it into the window with +**Ctrl+V** does neither. + +## pstr + +`pstr --help` lists every command, and `pstr --help` explains one. `pstr` never takes a +password or key as an argument; it asks for them. + +### Shares + +| Command | Does | +|---|---| +| `pstr add --name NAME URL` | Add a public share link. Quote the URL, because of the `#`. | +| `pstr add --name NAME URL --custom-password` | The same, and ask for the link's own password | +| `pstr add-folder --name NAME VOLUME:LINK` | Add a folder of your Drive, by the id `pstr browse` prints | +| `pstr shares` | List the shares and their ids | +| `pstr remove ID` | Remove a share and delete its link and password from the keyring | + +### Account + +| Command | Does | +|---|---| +| `pstr login ADDRESS` | Sign in. Asks for the password, a two-factor code and a mailbox password as the account needs them. | +| `pstr logout` | Sign out and delete the stored session | +| `pstr account` | Show who is signed in | +| `pstr browse [VOLUME:LINK]` | List your Drive's top-level places, or one folder | +| `pstr sync` | Sync watch history now | + +`pstr login` cannot show a CAPTCHA. If Proton asks for one, sign in once in the app instead. + +### Library + +| Command | Does | +|---|---| +| `pstr crawl [--share ID]` | Read every share, or one, into the library | +| `pstr list [--share ID]` | Print what the library holds | + +### Posters and descriptions + +| Command | Does | +|---|---| +| `pstr metadata status` | Show whether lookups are on, and which provider | +| `pstr metadata set --on --provider anilist` | Turn lookups on with AniList | +| `pstr metadata set --on --provider tmdb --api-key` | Turn lookups on with TMDB, and ask for the key | +| `pstr metadata set --off` | Turn lookups off, and delete every stored answer | +| `pstr metadata match [--force] [--limit N]` | Look up titles now. `--force` asks again about titles that already have an answer. | + +### Testing playback + +These two are for finding out which layer is slow or broken. + +```bash +pstr bench --file Frieren --verify # measure seek time, throughput and memory, no player +pstr play --file Frieren --disk-cache # play in a plain mpv window +``` + +| Option | Does | +|---|---| +| `--file TEXT` | The first file whose name or title contains `TEXT` | +| `--share ID` | Look in one share only | +| `--disk-cache` | Keep decrypted blocks on disk, as the app does | +| `--readahead N` | Blocks to fetch ahead of the reader | +| `--ring-mib N` | Memory for blocks, in MiB (128 by default) | +| `--verify` | `bench` only: compare a read against the SDK's own download | +| `--throughput-mib N` | `bench` only: how much to read for the speed test (32 by default) | +| `--start SECONDS` | `play` only: start at this position | +| `--headless` | `play` only: decode with no window and no sound | +| `--for-seconds S` | `play` only: stop after this many seconds | + +## Environment variables + +| Variable | Does | +|---|---| +| `RUST_LOG` | How much both programs log to the terminal, for example `RUST_LOG=debug` | +| `PSTR_FRAME_CAP` | The app's frame rate limit on Wayland. 60 by default; `0` turns it off. | diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 0000000..040acc1 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,63 @@ +# FAQ + +## Is this an official Proton app? + +No. It is an unofficial, open-source client written by one developer. Proton AG has not reviewed, +endorsed or supported it. "Proton" and "Proton Drive" are Proton AG's trademarks. + +## Do I need a Proton account? + +No, not to watch a public share link. You need one to add folders of your own Drive and to sync +your place across devices. See [Account and sync](ACCOUNT_AND_SYNC.md). + +## Does it download the whole file before playing? + +No. Proton Drive stores a file as blocks that each decrypt on their own, so the app fetches only +the blocks around the point you are watching. A seek fetches the block it lands on. On a +26-minute 1080p episode the first frame takes about 2.3 seconds. + +## Does it transcode? + +No. The video plays as it is stored, through libmpv. HEVC 10-bit, HDR and ASS subtitles all work, +with hardware decoding where the device has it. + +## Does it provide any videos? + +No. It plays what you keep in Proton Drive or what someone shares with you. It has no catalogue, +no search of other people's shares and no content of its own. + +## Why are there no posters? + +Posters and descriptions are off by default, because looking them up sends your title names to +AniList or TMDB. Turn them on in **Settings → Posters and descriptions**. See +[Privacy](PRIVACY.md). + +## A title is matched to the wrong show. How do I fix it? + +Open the title and choose **Match…** (or **Change match**). Search for the right entry and pick +it. A match you pick is never replaced, and it syncs to your other devices. + +## The episodes of one show appear as separate titles. + +The app takes the show's name from its folder. Put every episode of a show in one folder, or in +season folders below it. [How the library reads your +folders](USER_GUIDE.md#how-the-library-reads-your-folders) has examples. + +## Can several people share one library? + +Yes. Send them the same share link. Each person's app keeps its own watch history; sync only +connects devices that sign in to the same Proton account. + +## Is there a macOS version? + +The packaging scripts exist, but no build has been run on a Mac yet, so there is no release for +it. + +## Can I cast to a TV? + +Not yet. On an Android TV, install the APK and run the app on the TV itself. + +## Where do I report a bug? + +Open an issue on [GitHub](https://github.com/narrrl/proton-stream/issues). Say which platform +and version you use, and what you did. diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md new file mode 100644 index 0000000..3e2b928 --- /dev/null +++ b/docs/GETTING_STARTED.md @@ -0,0 +1,66 @@ +# Getting started + +This page takes you from nothing to a playing episode. It takes a few minutes. + +## 1. Install the app + +Download the package for your system from the +[latest release](https://github.com/narrrl/proton-stream/releases/latest) and install it. +[Installation](INSTALL.md) has the commands for each platform. + +## 2. Add a share + +A *share* is a folder the app reads videos from. There are two kinds: + +- **A public share link.** Someone sends you a Proton Drive link such as + `https://drive.proton.me/urls/ABC123#xyz`. You need no Proton account for this. +- **A folder of your own Drive.** Sign in to your Proton account, then pick a folder. See + [Account and sync](ACCOUNT_AND_SYNC.md). + +To add a public link on the desktop: + +1. Copy the whole link, including the part after `#`. That part is the key that decrypts the + share, so the link does not work without it. +2. Open the **Shares** page, or press **Ctrl+V** anywhere outside a text field. +3. Paste the link into **Link**. +4. If the person who shared it set a password of their own, turn on **The link asks for a + password** and type it. +5. Click **Add share**. + +The app opens the link to check it before it saves anything. If the link is wrong, it tells you +why and stores nothing. + +On Android and iOS, tap **Add share** on the **Shares** tab. On Android you can also share the +link to the app from your browser. [Android and iOS](MOBILE.md) has the details. + +## 3. Wait for the library to fill + +The app reads the share's folders. This is called a *crawl*. Titles appear in the **Library** as +they are found. A large share takes a minute or two. + +The folder layout decides how titles are grouped. Each show should have its own folder: + +```text +Sousou no Frieren/ + Season 01/ + S01E01.mkv + S01E02.mkv +Paprika (2006).mkv +``` + +A file without episode numbers is shown as a film. [How the library reads your +folders](USER_GUIDE.md#how-the-library-reads-your-folders) has the full rules. + +## 4. Press play + +Open a title and click **Play**. The episode starts in a few seconds, and you can drag the seek +bar to any point. The app remembers where you stopped, so **Resume** takes you back. + +## Next steps + +- **Posters and descriptions.** Turn them on in **Settings → Posters and descriptions**. They are + off by default, because the app then sends your title names to AniList or TMDB. See + [Privacy](PRIVACY.md). +- **Your place on every device.** Sign in to Proton on each device, and your position in every + episode follows you. See [Account and sync](ACCOUNT_AND_SYNC.md). +- **Keyboard.** Press **?** on the desktop for the list of shortcuts. diff --git a/docs/INSTALL.md b/docs/INSTALL.md new file mode 100644 index 0000000..0876eea --- /dev/null +++ b/docs/INSTALL.md @@ -0,0 +1,123 @@ +# Installation + +Every [release](https://github.com/narrrl/proton-stream/releases/latest) has packages for Linux +and Windows, an APK for Android and an IPA for iOS, plus a `SHA256SUMS` file to check them +against. The version is part of each file name. + +| Platform | File | Needs | +|---|---|---| +| Debian, Ubuntu | `proton-stream_-1_amd64.deb` | x86_64 | +| Fedora | `proton-stream--1.x86_64.rpm` | x86_64 | +| Other Linux | `proton-stream--x86_64-unknown-linux-gnu.tar.gz` | x86_64, libmpv 2 | +| Arch Linux | Built from the repository with `makepkg` | | +| Windows | `proton-stream--x64.msi` or `…-x86_64-windows.zip` | x64 | +| Android, Android TV | `proton-stream--android.apk` | Android 12 or newer, arm64 or x86_64 | +| iOS, iPadOS | `proton-stream--ios.ipa` | iOS 17 or newer, SideStore or AltStore | + +## Linux + +The desktop app needs libmpv, WebKitGTK 4.1 (for the CAPTCHA window when you sign in) and a +Secret Service keyring such as GNOME Keyring or KWallet. The packages pull in everything except +the keyring, which most desktops already have. + +### Debian and Ubuntu + +Download the `.deb`, then: + +```bash +sudo apt install ./proton-stream_*_amd64.deb +``` + +### Fedora + +Download the `.rpm`, then: + +```bash +sudo dnf install ./proton-stream-*.x86_64.rpm +``` + +### Arch Linux + +The `PKGBUILD` in `packaging/` builds the checkout it sits in. It needs network access, so cargo +can fetch crates: + +```bash +git clone https://github.com/narrrl/proton-stream.git +cd proton-stream/packaging +makepkg -si +``` + +### Other distributions + +Unpack the tarball anywhere. It holds `proton-stream` (the app), `pstr` (the +[command line](CLI.md)) and the icon. Install libmpv 2 and WebKitGTK 4.1 from your +distribution first. + +Both packages install: + +- `/usr/bin/proton-stream`, the app, listed as **Proton Stream** in your applications menu +- `/usr/bin/pstr`, the command line + +## Windows + +Download either file from the release: + +- **The `.msi` installer** installs to `Program Files\Proton Stream`, adds a Start menu entry + and puts `pstr` on the PATH. +- **The portable `.zip`** runs from any folder. Keep every file in it together: the app does not + start without `libmpv-2.dll` and `WebView2Loader.dll` beside it. + +The binaries are not code-signed yet, so SmartScreen may warn the first time. Choose **More +info**, then **Run anyway**. The sign-in window uses the WebView2 runtime that comes with Windows +10 and 11. + +## Android and Android TV + +1. Download `proton-stream--android.apk` on the device. +2. Open it. Android asks once to allow installs from your browser or file manager. +3. Install. + +The APK is signed, so later versions install over it and keep your library. On Android TV the app +appears in the launcher; installing needs a file manager or `adb install`. + +## iOS and iPadOS + +The `.ipa` is unsigned. [SideStore](https://sidestore.io) or [AltStore](https://altstore.io) +signs it with your own Apple ID when it installs: + +1. Install SideStore or AltStore by following its own guide. +2. Download `proton-stream--ios.ipa`. +3. Open it with SideStore or AltStore. + +A free Apple ID has to refresh the app every 7 days. SideStore and AltStore do that for you. + +## Updating + +Install the new release over the old one. Your shares, library, watch history and settings stay. +The app does not check for updates by itself; watch the +[releases](https://github.com/narrrl/proton-stream/releases) on GitHub. + +## Uninstalling + +Remove the package the way you installed it, for example `sudo apt remove proton-stream`. +This leaves your data behind. To remove that too: + +1. In the app, remove each share on the **Shares** page. This deletes its link and password + from the keyring and its downloads from the disk. +2. Sign out on the **Shares** page, if you signed in. +3. Delete the app's folders, listed in [Privacy](PRIVACY.md#what-stays-on-your-device). + +On Android and iOS, uninstalling the app deletes everything it stored. + +## Building from source + +Rust 1.96 or newer, plus the libmpv (≥ 2.0) development headers, and WebKitGTK 4.1's on Linux: + +```bash +git clone https://github.com/narrrl/proton-stream.git +cd proton-stream +cargo build --release +``` + +[Development](DEVELOPMENT.md) and `packaging/README.md` have the details, including Windows, the +Android toolchain and iOS builds on macOS. diff --git a/docs/MOBILE.md b/docs/MOBILE.md new file mode 100644 index 0000000..e8e1686 --- /dev/null +++ b/docs/MOBILE.md @@ -0,0 +1,84 @@ +# Android and iOS + +The Android and iOS apps share the desktop app's engine, so a library, its watch history and its +settings behave the same everywhere. This page covers what is different on a phone, a tablet and +a TV. [Installation](INSTALL.md) explains how to install each one. + + + + + + + + +
HomeAll titlesA title pageEpisodes and downloads
+ +## Adding a share + +Tap **Add share** on the **Shares** tab, paste the link, and add a custom password if the link has +one. The app checks the link before it saves it. + +- **Android:** you can also share a link to the app from your browser or a chat app, or open a + `drive.proton.me/urls/…` link directly if you allow the app to open supported links in the + system settings. +- **iOS:** there is no share sheet. Copy the link, then tap **Paste link** in the dialog. + +Folders of your own Drive work as on the desktop: sign in on the **Shares** tab, then **Add from +your Drive**. See [Account and sync](ACCOUNT_AND_SYNC.md). + +## The player + +- Tap to show or hide the controls. +- **Double-tap** the left or right third of the screen to go back or forward 10 seconds. Double-tap + the middle to pause. +- The menu has **Episodes**, **Chapters**, **Playback speed**, the audio and subtitle tracks, and + **Lock to landscape**. +- Sound keeps playing with the screen off or the app in the background, with controls on the lock + screen. Turn off **Background audio** in Settings to stop that. + +Picture-in-Picture is not available on either platform. + +## Android TV + +The app appears in the TV launcher. With the controls hidden: + +| Remote | Does | +|---|---| +| Left / right | Back or forward 10 seconds | +| Centre, up or down | Show the controls | +| Play/pause | Pause and play | + +There is no separate TV layout yet, and the app has not been tested on a real TV. Reports are +welcome. + +## Downloads + +Download an episode, a season or a show from its title page, and play it from the **Downloads** +tab without a connection. + +- **Android** waits for Wi-Fi unless you turn off **Download on Wi-Fi only**. Downloads continue + with the app closed. +- **iOS** downloads only while the app is open, and for the short time iOS allows after you leave + it. A stopped download resumes where it stopped. + +**Delete all offline episodes** in Settings frees the space in one step. + +## New episodes + +When a share gains episodes of a show, the app sends a notification. + +- **Android** checks about every 6 hours, when the device has a network and the battery is not + low. Turn it off with **New episode notifications** in Settings. +- **iOS** asks the system to check every 6 hours or so; iOS decides when it actually runs. + +## Where your secrets are kept + +- **Android:** in the app's private storage, encrypted with a key in the Android Keystore. If you + change or remove the device's screen lock, Android may delete that key. The app then asks you + to **Re-enter link** for each share. +- **iOS:** in the Keychain, only on this device, and not in backups. + +## Requirements + +- **Android 12** or newer, on an arm64 or x86_64 device. 32-bit devices are not supported. +- **iOS 17** or newer, on iPhone or iPad. diff --git a/docs/PRIVACY.md b/docs/PRIVACY.md new file mode 100644 index 0000000..550bd3f --- /dev/null +++ b/docs/PRIVACY.md @@ -0,0 +1,69 @@ +# Privacy + +proton-stream has no server and no account with its developer. Each app talks to Proton directly, +and to nothing else unless you turn on posters and descriptions. This page lists what goes where. + +## What the app sends, and to whom + +| To | What | When | +|---|---|---| +| Proton | Requests for the shares you added and, if you signed in, your account's Drive | Always | +| AniList and ani.zip, or TMDB | The names of the titles in your library, and the ids of matched titles | Only with **Posters and descriptions** on | + +Nothing else leaves your device. There is no telemetry, no crash reporting and no update check. + +**Posters and descriptions** are off by default. When they are on, the provider learns the names +of the titles in your library. It does not learn your files, your watch history or your shares. +Turning them off deletes every stored answer. + +## Secrets + +- **The part of a share link after `#`** is the key that decrypts the share. The app treats it as + a password. +- **A link's own password**, if it has one. +- **Your Proton session**, if you signed in. The app keeps the session, not your password. + +All of these go into the system's credential store, never into a config file: + +| Platform | Store | +|---|---| +| Linux | Secret Service (GNOME Keyring, KWallet, KeePassXC) | +| Windows | Credential Manager | +| Android | App storage, encrypted with a key in the Android Keystore | +| iOS | Keychain, this device only | + +If you sign in, your shares, with their links and passwords, also sync through your own Drive. +Like every file there, they are end-to-end encrypted. See [Account and sync](ACCOUNT_AND_SYNC.md). + +## How the player gets the video + +The player is fed through a callback inside the app's own process. There is no local web server, +so there is no port another program on the computer could ask for the decrypted video. The +player is handed an opaque token, not a link, so no secret ends up in its log or window title. + +## What stays on your device + +| What | Where on Linux | +|---|---| +| Settings and the share list (without secrets) | `~/.config/proton-stream/` | +| The library and watch history (`catalog.db`) and downloads (`offline/`) | `~/.local/share/proton-stream/` | +| Streamed video (`blocks/`), thumbnails and posters | `~/.cache/proton-stream/` | + +On Windows the same folders are under `%APPDATA%\narl\proton-stream\` and +`%LOCALAPPDATA%\narl\proton-stream\`. On macOS they are under `~/Library/Application Support/` +and `~/Library/Caches/`, in `io.narl.proton-stream`. + +**The streaming cache and downloads hold decrypted video.** Anyone who can read your user's files +can play them. Empty the cache in **Settings → Storage**, and delete downloads on the +**Downloads** page. + +The library database holds file names and your watch history, in plain text. It holds no +secrets. + +## Other people on the same computer + +- A share link given on the command line, as in `proton-stream 'https://…#key'`, is visible in + the process list while the command runs, and may land in your shell history. Paste links into + the window instead. +- Your desktop's media controls are given the title, the episode and a picture already on disk. + They are never given a provider link, so the desktop does not contact a provider by itself. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..cfe2305 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,41 @@ +# proton-stream documentation + +proton-stream turns the videos in your Proton Drive, and the share links people send you, into +a library you can browse and stream. It runs on Linux, Windows, Android, Android TV and iOS. +This is an unofficial client, not a Proton product. + +The library in the desktop app + +## Start here + +- [Getting started](GETTING_STARTED.md): install, add your first share, press play. +- [Installation](INSTALL.md): every platform, and how to update and uninstall. +- [FAQ](FAQ.md): short answers to the questions people ask first. + +## Using it + +- [User guide](USER_GUIDE.md): the library, title pages, the player, downloads, history and + settings. +- [Account and sync](ACCOUNT_AND_SYNC.md): signing in, folders of your own Drive, and keeping + your place on every device. +- [Android and iOS](MOBILE.md): what is different on a phone, a tablet and Android TV. + +## Reference + +- [Command-line reference](CLI.md): the `pstr` tool and the `proton-stream` launcher. +- [Privacy](PRIVACY.md): what goes where, and what stays on your device. + +## When something goes wrong + +- [Troubleshooting](TROUBLESHOOTING.md): links that will not add, sign-in problems, playback + problems, and where the logs are. + +## Contributing + +- [Development](DEVELOPMENT.md): the state of each layer, the build order and the traps. +- [Architecture](ARCHITECTURE.md): the crates, the data flow when an episode plays, and where + secrets live. +- [Testing](TESTING.md), [Metadata](METADATA.md), [Design tokens](DESIGN-TOKENS.md), + [Android development](ANDROID.md) and [iOS development](IOS.md). + +Bugs are tracked in [BUGS.md](BUGS.md) in the repository. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..1be5b2b --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,114 @@ +# Troubleshooting + +## Adding a share + +### "Proton share URL has no #password fragment; copy the full share link" + +The link is missing the part after `#`, which is its decryption key. Copy the link again from +where you got it, all of it. + +### "This link has a password of its own. Enter it to add the link." + +The person who shared it also set a password. Turn on **The link asks for a password** (on a +phone, fill in **Custom password**) and type it. + +### "That share is already added." + +The link, or the same folder of your Drive, is already on the **Shares** page. + +### "Last crawl failed: …" + +The share could not be read. The link may have expired or been revoked, or its password +changed. If the password changed, choose **Re-enter link**. + +## The keyring + +### "share … has no stored credentials; remove and re-add it" + +The link and password are gone from the keyring. This happens when a keyring is reset, or on +Android when the screen lock changes. Choose **Re-enter link** on the **Shares** page. Your +library, history and downloads stay. + +### "credential store: …" on Linux + +The app found no Secret Service. Install and start one, such as GNOME Keyring, KWallet or +KeePassXC with its Secret Service integration, and unlock it. + +## Signing in + +### "Proton did not accept that address and password" + +Check both. Sign in on proton.me to make sure the account works. + +### "Proton has blocked sign-ins from this app for now" + +Proton's abuse protection stopped the sign-in. Wait a while and try again, or sign in once on +proton.me first. + +### "This account's only second factor is a security key" + +The app cannot use security keys yet. Add an authenticator app as a second method in your +Proton account settings, then sign in with its code. + +### The CAPTCHA window does not open on Linux + +The window needs WebKitGTK 4.1. The `.deb`, `.rpm` and Arch packages pull it in; with the +tarball, install it yourself. + +### `pstr login` asks for a CAPTCHA + +The command line cannot show one. Sign in once in the app. + +## Playback + +### Green blocks, a black picture or torn frames + +Turn off **Hardware decoding** in Settings. It applies from the next file. + +### An episode takes long to start + +The first seconds of a file are fetched over the network. On a slow connection, start the +episode and give it a moment, or download it first. A second viewing comes from the disk cache. + +### No skip button for the opening + +The skip button comes from the file's chapter names. A file without named chapters gets none. + +### The wrong subtitle track plays + +Choose the track in the player. The app remembers it for the rest of the show. Set the +language to start in under **Settings → Playback**. + +## Windows + +### The app does not start from the portable `.zip` + +Keep every file from the archive in one folder. The app needs `libmpv-2.dll` and +`WebView2Loader.dll` next to `proton-stream.exe`. + +### SmartScreen warns about the installer + +The binaries are not code-signed yet. Choose **More info**, then **Run anyway**. + +### A pinned taskbar icon shows a blank picture after an update + +Unpin it and pin it again once. + +## Linux + +### "Not responding" when the window is on another workspace + +Fixed. On Wayland the app limits its own frame rate instead of waiting for vsync, which a +compositor withholds from a window it is not showing. Update to the latest release. + +## Logs + +Start the app from a terminal to see its log: + +```bash +RUST_LOG=debug proton-stream +``` + +The same works for `pstr`. When you +[open an issue](https://github.com/narrrl/proton-stream/issues), include the lines around the +error, but **remove any share link first**: the part after `#` is a key. diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md new file mode 100644 index 0000000..6dafbf9 --- /dev/null +++ b/docs/USER_GUIDE.md @@ -0,0 +1,198 @@ +# User guide + +This page describes the desktop app. The Android and iOS apps have the same pages as tabs at the +bottom of the screen; [Android and iOS](MOBILE.md) covers what is different there. + +The app has five pages, in the bar at the top: **Library**, **History**, **Shares**, +**Downloads** and **Settings**. **Ctrl+1** to **Ctrl+5** open them. + +## Library + +The whole library, filtered and sorted + +The library shows every title from every share in one place. Two shares that hold the same show +show it once. + +- **The banner** at the top shows the title you watched last, with **Resume** and **More info**. +- **Continue watching** has the episode you stopped in for each show, or the next one when you + finished an episode. To take a show off this row, right-click it and choose **Remove from + Continue watching**. +- **Shelves** such as **Airing now**, **Directed by Satoshi Kon** or **From MADHOUSE** appear when + posters and descriptions are on. The app shows at most four, and only for a director, studio or + genre with at least three titles. +- **All titles** is the full grid. Choose a filter and a sort order above it. + +| Filters | Sort orders | +|---|---| +| All, Series, Films, Watching, Not started, Downloaded | A – Z, Recently watched, Recently added, Newest, Highest rated, Most popular | + +**Group franchises** folds a series, its sequels and its films into one tile with a **+N** badge. +It is on by default, and needs posters and descriptions to know what belongs together. + +**Search** with **Ctrl+F** or **/**. It matches every name a title has: the English one, the +romaji one and the name of its folder. + +**Refresh** (or **F5**) reads every share again. The app also does this by itself when a share +has not been read for 6 hours. + +### How the library reads your folders + +The folder decides which show a file belongs to. The file name gives the season and episode. + +| Layout | Read as | +|---|---| +| `Frieren/Season 01/S01E05.mkv` | Frieren, season 1, episode 5 | +| `Frieren/S01E05 - The Hero's Statue.mkv` | The same, with the episode title | +| `Bocchi the Rock/[Group] Bocchi the Rock - 07 [1080p].mkv` | Bocchi the Rock, episode 7 | +| `Spy x Family/Season 2 - 06.mkv` | Spy x Family, season 2, episode 6 | +| `Akira (1988).mkv` | A film, from 1988 | + +- Season folders can be called `Season 01`, `Season 1`, `S01` or `3rd Season`. +- Folders such as `Specials`, `Extras`, `OVA`, `NCOP` or `Subs` are not taken as a show's name; + the folder above them is. +- An episode title is read only after a dash, as in `S01E01 - Name`. +- A file with no episode number is a film. + +The app plays `mkv`, `mp4`, `m4v`, `avi`, `mov`, `webm`, `ts`, `m2ts` and most other common video +containers. + +## A title's page + +Click a title to open its page. **Esc** goes back. + +- **Resume S01E03 · 12:04** continues where you stopped. **Start over** plays the episode from + the beginning. +- **Download show** keeps every episode on this device. See [Downloads](#downloads). +- **Match…** opens the matcher, where you choose which AniList or TMDB entry the title is. Use it + when the app guessed wrong or did not guess at all. A match you pick is never replaced. +- **More on AniList** (or TMDB) opens the title's page there. +- Each season has **Mark season watched** and **Download season**. +- Each episode shows a tick when you have watched it. Right-click an episode and choose **Mark + watched up to here** to tick it and every episode before it. +- **In this franchise** lists the other parts of the series, when posters and descriptions are on. + +An episode counts as watched once you have seen 90% of it. + +## The player + +The picture plays inside the app's window. Move the mouse to show the controls. + +- **Skip opening** and **Skip ending** appear when the file's chapters mark an opening, an ending + or a preview. Files without chapter names get no skip button. Turn on **Skip openings and + credits** in Settings to skip them without asking. +- **Up next** counts down 10 seconds when the credits start, then plays the next episode. Choose + **Play now**, **Cancel** or **Watch till the end**. +- **Subtitles and audio** are chosen from the buttons at the bottom right. The app remembers your + choice for each show. +- **Speed** goes from 0.5× to 3×, and stays for the next episode. + +### Keyboard + +Press **?** anywhere to see this list in the app. + +| Key | Anywhere | +|---|---| +| Ctrl+F or / | Search the library | +| F5 or Ctrl+R | Read every share again | +| Ctrl+1 – 5 | Library, History, Shares, Downloads, Settings | +| Ctrl+, | Settings | +| Ctrl+V | Paste a share link to add it | +| Tab or arrows | Move between titles and episodes | +| Enter | Open or play what is highlighted | +| Alt+← or mouse back | Back | + +| Key | Playing | +|---|---| +| Space or K | Pause and play | +| ← / → | Back 10 s, forward 30 s | +| ↑ / ↓ | Volume | +| M | Mute | +| N / P | Next and previous episode | +| [ / ] | Slower and faster | +| S | Skip the opening or the credits | +| E | The list of episodes | +| F or double-click | Fullscreen | +| Ctrl+S | Save the frame to `Pictures/proton-stream` | +| Esc | Leave fullscreen, then the player | + +Media keys and your desktop's media controls work too. + +### Subtitle files beside the video + +The app picks up `.srt`, `.ass`, `.ssa` and `.vtt` files next to a video: + +```text +Frieren/Season 01/ + S01E01.mkv + S01E01.en.srt English + S01E01.en.forced.ass English, "forced" + Subs/ + S01E01/ + 2_English.srt English +``` + +A `Subs` folder beside the videos works, and so does any subtitle in a folder that holds a single +video. The language comes from the last word in the name that names one. + +### Dialogue, not signs and songs + +Many anime releases have two subtitle tracks in one language: the full dialogue, and a short +*Signs & Songs* track that only translates on-screen text. When a file would start on the short +track, the app switches to the full one in the same language. + +## History + +Everything you played, newest first, grouped by day. An entry says **Watched**, **Stopped at +12:04 of 24:00** or **Played**. Removing an entry marks a watched episode unwatched, or forgets +where you stopped in one you had not finished. + +## Downloads + +A download keeps a full copy of an episode on this device, to play without a connection. Start +one from a title's page: a whole show, a season or a single episode. + +The **Downloads** page lists them with **Pause**, **Resume**, **Cancel**, **Retry**, **Play** and +**Delete**. Three run at a time. Downloads do not count against the streaming cache. + +## Shares + +The **Shares** page lists every share, with what it holds and when it was last read. + +- **Crawl** reads the share again. +- **Rename** changes the name shown in the app. +- **Re-enter link** gives the app the link again, for a keyring that lost it or a password that + changed. The library, history and downloads stay. +- **Remove** takes the share's titles out of the library, deletes its downloads and removes its + link and password from the keyring. + +The **Proton account** section on this page is where you sign in. See +[Account and sync](ACCOUNT_AND_SYNC.md). + +## Settings + +**Appearance** + +- **Palette:** Proton (the near-black default), Catppuccin Latte, Frappé, Macchiato and Mocha, or + Persona 5. +- **Accent:** Mauve, Pink, Sky, Pink → Sky, Lavender, Blue, Teal, Peach or Red. **Gradient + accent** draws it as a gradient. +- **Text size:** 90% to 150%. This one stays on this computer. +- **Library tiles:** posters or stills. **Title names:** as in the share, English or romaji. + +**Playback** + +- **Audio language** and **Subtitles:** the language to start in, or the file's own default. +- **Play the next episode** (on by default) and **Skip openings and credits** (off). +- **Hardware decoding** (on). Turn it off if the picture shows green blocks or stays black. + +**Storage** + +- **Streaming cache:** 1 to 16 GiB, 4 GiB by default. It keeps the parts of videos you streamed, + so watching again needs no network. **Clear the streaming cache** empties it. + +**Posters and descriptions** + +- Off by default. When it is on, the app sends the names of your titles to the provider you pick: + **AniList** (no key needed) or **TMDB** (needs a free API key). See [Privacy](PRIVACY.md). +- **Match the library** looks up new titles now. **Match everything again** looks up every title, + except those you matched by hand. diff --git a/images/android-episodes.png b/images/android-episodes.png new file mode 100644 index 0000000..85b279c Binary files /dev/null and b/images/android-episodes.png differ diff --git a/images/android-home.png b/images/android-home.png new file mode 100644 index 0000000..6c434e2 Binary files /dev/null and b/images/android-home.png differ diff --git a/images/android-library.png b/images/android-library.png new file mode 100644 index 0000000..a5f1e67 Binary files /dev/null and b/images/android-library.png differ diff --git a/images/android-title.png b/images/android-title.png new file mode 100644 index 0000000..02195b3 Binary files /dev/null and b/images/android-title.png differ diff --git a/images/desktop-home.png b/images/desktop-home.png new file mode 100644 index 0000000..4e16a6c Binary files /dev/null and b/images/desktop-home.png differ diff --git a/images/desktop-library.png b/images/desktop-library.png new file mode 100644 index 0000000..10aac08 Binary files /dev/null and b/images/desktop-library.png differ diff --git a/images/logo.svg b/images/logo.svg new file mode 100644 index 0000000..c3b5759 --- /dev/null +++ b/images/logo.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/publish-wiki.sh b/scripts/publish-wiki.sh new file mode 100755 index 0000000..9ae43fc --- /dev/null +++ b/scripts/publish-wiki.sh @@ -0,0 +1,273 @@ +#!/usr/bin/env bash +# Publish the documentation in docs/ to the GitHub wiki. +# +# The pages in docs/ are the source of truth; the wiki is a generated copy for +# people who look there first. Each run checks every link in docs/, clones the +# wiki, replaces all of its pages with the ones listed in SECTIONS below, +# rewrites links between them to wiki page names and every other repository +# link to an absolute GitHub URL, copies the images they show, writes the +# sidebar and footer, and commits. Nothing is pushed without --push. +# +# --check only checks the links and that every page in docs/ is listed or +# excluded, without touching the wiki. CI runs it for every change to docs/, +# and every push to main that passes it publishes. +# +# GitHub creates the wiki repository only when the first page is saved, so +# create any page once in the web UI before the first run. WIKI_REMOTE +# overrides the remote, which is how the workflow pushes with its token. +# +# usage: scripts/publish-wiki.sh [--check | --push] [WORKDIR] +set -euo pipefail + +repo_url="https://github.com/narrrl/proton-stream" +wiki_remote="${WIKI_REMOTE:-git@github.com:narrrl/proton-stream.wiki.git}" + +mode=commit +case "${1:-}" in + --check) mode=check; shift ;; + --push) mode=push; shift ;; +esac + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +workdir="${1:-$(mktemp -d)}" +wiki="$workdir/wiki" + +if [ "$mode" = check ]; then + wiki="$workdir/check" + mkdir -p "$wiki" +elif [ -d "$wiki/.git" ]; then + git -C "$wiki" pull --ff-only --quiet +else + git clone --quiet "$wiki_remote" "$wiki" +fi + +rev="$(git -C "$root" rev-parse --short HEAD)" +version="$(grep -m1 '^version' "$root/Cargo.toml" | cut -d'"' -f2)" + +python3 - "$root" "$wiki" "$repo_url" "$version" "$rev" <<'EOF' +import pathlib +import re +import shutil +import sys + +root, wiki, repo_url, version, rev = sys.argv[1:] +root = pathlib.Path(root) +wiki = pathlib.Path(wiki) +docs = root / "docs" + +HOME = ("README.md", "Home") + +# Sidebar sections: docs/ file -> wiki page name, in order. +SECTIONS = [ + ("Start here", [ + ("GETTING_STARTED.md", "Getting-Started"), + ("INSTALL.md", "Installation"), + ("FAQ.md", "FAQ"), + ]), + ("Using it", [ + ("USER_GUIDE.md", "User-Guide"), + ("ACCOUNT_AND_SYNC.md", "Account-and-Sync"), + ("MOBILE.md", "Android-and-iOS"), + ]), + ("Reference", [ + ("CLI.md", "Command-Line-Reference"), + ("PRIVACY.md", "Privacy"), + ]), + ("When something goes wrong", [ + ("TROUBLESHOOTING.md", "Troubleshooting"), + ]), + ("Contributing", [ + ("DEVELOPMENT.md", "Development"), + ("ARCHITECTURE.md", "Architecture"), + ("TESTING.md", "Testing"), + ("METADATA.md", "Metadata"), + ("DESIGN-TOKENS.md", "Design-Tokens"), + ("ANDROID.md", "Android-Development"), + ("IOS.md", "iOS-Development"), + ]), +] + +# Too long or too internal for the wiki; links to them go to GitHub. +EXCLUDED = {"BUGS.md"} + +# Files outside docs/ whose links are checked but not published. +CHECKED = ["README.md"] + +PAGES = dict([HOME] + [page for _, pages in SECTIONS for page in pages]) +images_url = f"https://raw.githubusercontent.com/wiki/{repo_url.removeprefix('https://github.com/')}" + +LINK = re.compile(r"(\]\(|src=\")([^)\"\s]+)([)\"])") +FENCE = re.compile(r"^\s*(```|~~~)") +INLINE_CODE = re.compile(r"(`[^`\n]*`)") +HEADING = re.compile(r"^(#{1,6})\s+(.*?)\s*#*\s*$") +EXPLICIT_ID = re.compile(r"\n\n" +) +for source, page in PAGES.items(): + (wiki / f"{page}.md").write_text(note + without_title(converted[source])) + +for image in published_images: + (wiki / image).parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(root / image, wiki / image) + +sidebar = ["**[Home](Home)**\n"] +for section, pages in SECTIONS: + sidebar.append(f"\n**{section}**\n\n") + sidebar += [f"- [{title(source)}]({page})\n" for source, page in pages] +sidebar += [ + "\n---\n\n", + f"[Releases]({repo_url}/releases) · [Issues]({repo_url}/issues)\n", +] +(wiki / "_Sidebar.md").write_text(note + "".join(sidebar)) + +(wiki / "_Footer.md").write_text( + note + + f"Documentation of proton-stream {version}, generated from " + + f"[`docs/`]({repo_url}/tree/{rev}/docs) at `{rev}`. " + + f"To change a page, edit it in the [repository]({repo_url}).\n" +) +EOF + +if [ "$mode" = check ]; then + echo "Documentation links are fine." + exit 0 +fi + +cd "$wiki" +git add -A +if git diff --cached --quiet; then + echo "Wiki is already up to date." + exit 0 +fi +git commit --quiet -m "Update from docs at $rev" +git show --stat --format='%s' HEAD + +if [ "$mode" = push ]; then + git push --quiet + echo "Pushed to the wiki." +else + echo "Committed in $wiki. Review it, then push from there or re-run with --push." +fi