fixed ci, release, docs

This commit is contained in:
nvrl committed 2026-08-03 09:38:01 +02:00
1 parent 14c48428a3
commit 4c842402a6
9 files changed
+315 -44

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
name: libmpv (Windows)
description: >
Provide libmpv's Windows development files. `libmpv2-sys` emits
`cargo:rustc-link-lib=mpv`, and mpv's Windows *player* build ships neither
mpv.lib nor libmpv-2.dll — they come from the separate mpv-dev archive. Sets
LIB for the linker, PATH so binaries find the DLL at run time, and
MPV_DEV_DIR for `scripts/build.ps1`.
outputs:
dir:
description: Directory holding libmpv-2.dll and mpv.lib.
value: ${{ steps.mpv.outputs.dir }}
runs:
using: composite
steps:
# lib.exe, for turning mpv.def into an import library.
- uses: ilammy/msvc-dev-cmd@v1
- id: mpv
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$feed = [xml](Invoke-WebRequest -UseBasicParsing `
'https://sourceforge.net/projects/mpv-player-windows/rss?path=/libmpv').Content
# The "v3" variants are x86-64-v3 builds; take the baseline one.
$item = $feed.rss.channel.item |
Where-Object { $_.link -match 'mpv-dev-x86_64-2[^/]*\.7z/download$' } |
Select-Object -First 1
if (-not $item) { throw 'no mpv-dev build in the SourceForge feed' }
$archive = Join-Path $env:RUNNER_TEMP 'mpv-dev.7z'
$dir = Join-Path $env:RUNNER_TEMP 'mpv-dev'
Invoke-WebRequest -UseBasicParsing $item.link -OutFile $archive
7z x $archive "-o$dir" -y | Out-Null
$dll = Get-ChildItem -Path $dir -Filter 'libmpv-2.dll' -Recurse |
Select-Object -First 1
if (-not $dll) { throw "libmpv-2.dll not found under $dir" }
$libDir = $dll.Directory.FullName
# /NAME matters: without it the import library references mpv.dll, and
# the file that actually ships is libmpv-2.dll.
lib.exe /def:"$libDir\mpv.def" /name:libmpv-2.dll `
/out:"$libDir\mpv.lib" /machine:x64 | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'lib.exe failed to build mpv.lib' }
"dir=$libDir" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8
"MPV_DEV_DIR=$libDir" | Out-File $env:GITHUB_ENV -Append -Encoding utf8
"LIB=$libDir;$env:LIB" | Out-File $env:GITHUB_ENV -Append -Encoding utf8
$libDir | Out-File $env:GITHUB_PATH -Append -Encoding utf8
+6
View File
@@ -34,6 +34,12 @@ jobs:
sudo apt-get update
sudo apt-get install -y libmpv-dev
# Without this the test binaries do not link: there is no mpv.lib on the
# runner. See .github/actions/libmpv-windows.
- name: Install libmpv (Windows)
if: runner.os == 'Windows'
uses: ./.github/actions/libmpv-windows
- run: cargo fmt --all -- --check
- run: cargo clippy --workspace --all-targets -- -D warnings
+153
View File
@@ -0,0 +1,153 @@
name: Release
# A release is a `v<version>` tag on main. The tag is the trigger and the
# version of record: nothing here reads the branch, so the first job checks that
# the commit really is on main and that the tag, `Cargo.toml` and the PKGBUILD
# all say the same version — a tag that disagrees would otherwise publish
# artifacts named after the manifest, not after the tag.
#
# `workflow_dispatch` builds everything and publishes nothing, which is how to
# rehearse a release without burning a tag.
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
verify:
name: Tag and versions
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: The tag is on main
if: startsWith(github.ref, 'refs/tags/')
run: |
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
git merge-base --is-ancestor "$GITHUB_SHA" origin/main ||
{ echo "::error::$GITHUB_REF_NAME points at a commit that is not on main"; exit 1; }
- name: The tag, Cargo.toml and the PKGBUILD agree
if: startsWith(github.ref, 'refs/tags/')
run: |
tag="${GITHUB_REF_NAME#v}"
manifest="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -n1)"
pkgbuild="$(sed -n 's/^pkgver=//p' packaging/PKGBUILD | head -n1)"
echo "tag=$tag manifest=$manifest pkgbuild=$pkgbuild"
[ "$tag" = "$manifest" ] ||
{ echo "::error::tag $tag != Cargo.toml $manifest"; exit 1; }
[ "$tag" = "$pkgbuild" ] ||
{ echo "::error::tag $tag != PKGBUILD $pkgbuild"; exit 1; }
# A release must resolve the SDK from crates.io. The build scripts refuse
# to run with a patch pointing at a missing sibling checkout, but here the
# patch is wrong even when the path exists.
- name: No path patch on the SDK
run: |
! grep -q '^\[patch\.crates-io\]' Cargo.toml ||
{ echo "::error::Cargo.toml carries [patch.crates-io]; releases build against the registry"; exit 1; }
linux:
name: Linux artifacts
needs: verify
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
# Pinned: the MSRV the workspace declares.
toolchain: "1.96"
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
# rpm for rpmbuild; the .deb goes through dpkg-deb, which is preinstalled.
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y libmpv-dev rpm
- name: Gate
run: |
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace --locked
- name: Build tarball, .deb and .rpm
run: scripts/build.sh tarball deb rpm
- uses: actions/upload-artifact@v4
with:
name: linux
path: |
dist/*.tar.gz
dist/*.deb
dist/*.rpm
if-no-files-found: error
windows:
name: Windows artifacts
needs: verify
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.96"
- uses: Swatinem/rust-cache@v2
# Sets MPV_DEV_DIR, which is what build.ps1 reads when -MpvDev is absent.
- name: Install libmpv
uses: ./.github/actions/libmpv-windows
# build.ps1 installs the WiX toolset itself if `wix` is missing, and the
# runner has the .NET SDK it needs for that.
- name: Build the portable .zip and the .msi
shell: pwsh
run: scripts\build.ps1 -Artifacts zip,msi
- uses: actions/upload-artifact@v4
with:
name: windows
path: |
dist/*.zip
dist/*.msi
if-no-files-found: error
publish:
name: Publish
if: startsWith(github.ref, 'refs/tags/')
needs: [linux, windows]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Checksums
run: |
cd artifacts
sha256sum * > SHA256SUMS
cat SHA256SUMS
- uses: softprops/action-gh-release@v2
with:
files: artifacts/*
generate_release_notes: true
fail_on_unmatched_files: true
Generated
+4
View File
@@ -4037,6 +4037,8 @@ checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
[[package]]
name = "proton-drive-rs"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b162681255e296b48ef7261cad899ffb384756e866a1c6d7b7c791cbbc8af3ac"
dependencies = [
"async-trait",
"base64 0.22.1",
@@ -4062,6 +4064,8 @@ dependencies = [
[[package]]
name = "proton-sdk"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d9b94ec901edb0e849238ea833121f2fb173308b4927b8aa6b157643fa0a73"
dependencies = [
"aes-gcm 0.11.0",
"async-trait",
+8 -6
View File
@@ -91,9 +91,11 @@ lru = "0.12"
# be tested against a fake without a network or an account.
async-trait = "0.1"
# During development the SDK work for this app lives in the sibling repo and is
# not published yet. Remove once 0.3.3 is on crates.io — releases must build
# against the registry.
[patch.crates-io]
proton-sdk = { path = "../proton-sdk-rs/crates/proton-sdk" }
proton-drive-rs = { path = "../proton-sdk-rs/crates/proton-drive-rs" }
# The SDK builds against the registry — 0.4.0 is published. To test an
# unpublished `../proton-sdk-rs` change against this app, add locally and do
# **not** commit it (CI has no sibling checkout, and the build scripts refuse to
# run with a patch pointing at a missing path):
#
# [patch.crates-io]
# proton-sdk = { path = "../proton-sdk-rs/crates/proton-sdk" }
# proton-drive-rs = { path = "../proton-sdk-rs/crates/proton-drive-rs" }
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Nils Pukropp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+24 -12
View File
@@ -18,23 +18,28 @@ is playable without downloading 2 GB of it.
## Status
Early, but it plays, and there is a window now. The SDK streaming surface, the
catalog, the streaming layer and the player are done and verified against a real
share; the app has a library wall, a page per title with seasons and episodes,
resume-where-you-left-off, a transport bar and share management. The picture
still lands in mpv's own window — embedding it in the app's surface is next. See
`docs/DEVELOPMENT.md`.
Early, but it is an app now: the picture lands **inside the window**, not in an
mpv window of its own. The SDK streaming surface, the catalog, the streaming
layer and the player are done and verified against a real share, and the app has
a library wall, a page per title with seasons and episodes,
resume-where-you-left-off, an overlay with a chapter-marked seek bar, a skip
button for openings and endings, next/previous episode with an "up next"
countdown, optional metadata, themes and share management. What is left is mostly sidecar subtitle files
(`.srt`/`.ass` beside the video are invisible to the crawl today) and running the
macOS packaging on a real Mac. See `docs/DEVELOPMENT.md`.
```bash
cargo run --release -p pstr-app # the app
```
Working today, via the `pstr` CLI:
The `pstr` CLI does the same work headlessly, and is what tells you which layer
is broken:
```bash
cargo run -p pstr-cli -- add --name anime 'https://drive.proton.me/urls/TOKEN#fragment'
cargo run -p pstr-cli -- crawl
cargo run -p pstr-cli -- list
cargo run -p pstr-cli -- metadata # opt-in; off unless configured
cargo run --release -p pstr-cli -- play --file Frieren --disk-cache
cargo run --release -p pstr-cli -- bench --file Frieren --verify
```
@@ -54,12 +59,12 @@ against the SDK's own range download.
|---|---|
| Topology | Fat client. No server; each viewer's app talks to Proton directly. |
| Auth | Public links only — but several, merged into one library. |
| Player | Embedded libmpv: HEVC 10-bit, HDR and ASS subtitles with hardware decode. Volume, audio-track, subtitle and chapter pickers; skip-the-opening; next/previous episode and autoplay. The language picked is preferred for the next file too. |
| Player | Embedded libmpv, drawing into the app's own GL surface: HEVC 10-bit, HDR and ASS subtitles with hardware decode. Volume, audio-track, subtitle and chapter pickers; skip-the-opening; next/previous episode and autoplay. The language picked is preferred for the next file too. |
| Transcoding | None. mpv demuxes and decodes natively. |
| UI | egui/eframe on glow; mpv renders into the same GL context. |
| Themes | The shipped near-black palette plus Catppuccin Latte, Frappé, Macchiato and Mocha, each with a choice of eight accents — pink and sky among them — drawn as a gradient unless you turn that off. Picked on the Shares page, applied without a restart. |
| Metadata | Filename parsing, with optional AniList/TMDB enrichment — **off by default**, because enabling it sends your library's titles to a third party. |
| Platforms | Linux and Windows. |
| Metadata | Filename parsing, with optional AniList/TMDB enrichment — **off by default**, because enabling it sends your library's titles to a third party. It brings posters, synopses and per-episode titles; a title the scorer refuses to guess about can be pinned by hand, and a pinned match is never overwritten. |
| Platforms | Linux and Windows. The macOS packaging is written but has not been run on a Mac. |
## Building
@@ -69,8 +74,15 @@ Rust 2024, MSRV 1.96.
cargo build --release
```
`pstr-player` needs libmpv development headers (`mpv` ≥ 2.0). On Windows,
`libmpv-2.dll` is bundled next to the executable.
`pstr-player` needs libmpv development headers (`mpv` ≥ 2.0). On Windows those
come from the separate **mpv-dev** archive rather than mpv's player build, and
`libmpv-2.dll` ships next to the executable. The Proton SDK crates come from
crates.io; nothing here builds against a sibling checkout.
Installable artifacts — tarball, `.deb`, `.rpm`, Arch package, `.app`/`.dmg`,
and a portable `.zip` plus an `.msi` on Windows — are produced by
`scripts/build.sh` and `scripts\build.ps1`; `packaging/README.md` has the
per-platform detail.
## Privacy
+23 -18
View File
@@ -4,34 +4,39 @@
| Layer | State |
|---|---|
| SDK streaming surface (`../proton-sdk-rs` 0.3.3) | **done, live-verified** — `open_revision` / `download_range` / thumbnails / `refresh_session` on the visitor path; node-key cache; chunked `enumerate_nodes`; paged `download_file_to` |
| `pstr-core` — shares, config, catalog, naming, library grouping | **done** — 67 offline tests |
| `pstr-cli` — add / crawl / list | **done** — verified against a real 354-node share: 310 playable files, 22 titles, correct seasons and episodes |
| SDK streaming surface (`../proton-sdk-rs` 0.4.0, from crates.io) | **done, live-verified** — `open_revision` / `download_range` / thumbnails / `refresh_session` on the visitor path; node-key cache; chunked `enumerate_nodes`; paged `download_file_to` |
| `pstr-core` — shares, config, catalog, naming, library grouping | **done** — 106 offline tests |
| `pstr-cli` — add / shares / remove / crawl / list / metadata / play / bench | **done** — verified against a real 354-node share: 310 playable files, 22 titles, correct seasons and episodes |
| `pstr-stream` — block cache, read-ahead | **done, live-verified** — 68 offline tests; benchmarked against a real 761 MiB episode (see `docs/TESTING.md`) |
| `pstr-player` — libmpv `stream_cb` | **done, live-verified** — 16 offline tests; plays a real 1080p HEVC episode over a public link, seeks, resumes from the disk cache. The render API (embedding) is not started |
| `pstr-app` — egui UI, poster grid | **done, offline-tested** — library wall, title pages, transport, share management; mpv still opens its own window |
| Metadata providers (AniList / TMDB) | not started — and this is what the wall is waiting on: see the note on thumbnails below |
| Packaging | **done, locally verified** — `scripts/build.sh` (tarball, `.deb`, `.rpm`, `.app`, `.dmg`) and `scripts/build.ps1` (portable `.zip`, WiX `.msi`), plus `packaging/PKGBUILD`. See `packaging/README.md`. The Windows and macOS halves are written but only the Linux ones have been run on this machine |
| `pstr-player` — libmpv `stream_cb` + render API | **done, live-verified** — 33 offline tests; plays a real 1080p HEVC episode over a public link, seeks, resumes from the disk cache, and renders into the app's GL context |
| `pstr-app` — egui UI, poster grid, embedded video | **done, offline-tested** — 15 offline tests; library wall, title pages, player page with overlay controls, themes, share management |
| Metadata providers (AniList / TMDB) | **done** — 32 offline tests; posters, synopses, per-episode titles, hand-pinned matches. Opt-in, off by default |
| Packaging | **done, locally verified** — `scripts/build.sh` (tarball, `.deb`, `.rpm`, `.app`, `.dmg`) and `scripts/build.ps1` (portable `.zip`, WiX `.msi`), plus `packaging/PKGBUILD`. See `packaging/README.md`. The macOS half is written but has not been run on a Mac |
## Build order from here
1. **`pstr-player`, step two.** `mpv_render_context_create` with
`MPV_RENDER_API_TYPE_OPENGL` against eframe's glow context, rendered inside an
`egui::PaintCallback`; overlay controls. If this proves troublesome, the
standalone-window mode already built remains shippable. Nothing in
`pstr-player` except `PlayerConfig`'s window options is about where pixels
land, so this does not reach the streaming path.
2. Metadata providers, docs. Packaging is in place (`packaging/README.md`); what
is left there is running `scripts/build.ps1` and the macOS half on real
hosts, and a release workflow that drives both.
1. **Sidecar subtitle files** — see the "not done" note at the end of this
section. The work is in `pstr-core`: crawl them, and pair them to a video by
name.
2. Packaging is in place (`packaging/README.md`); what is left there is running
the macOS half on a real host, and a release workflow that drives every
platform.
Done: **`pstr-player` step two** — `mpv_render_context_create` with
`MPV_RENDER_API_TYPE_OPENGL` against eframe's glow context
(`pstr-player/src/render.rs`), painted from a framebuffer this app owns
(`pstr-app/src/video.rs`) so the picture can sit in a rectangle rather than
filling the window. `VideoOutput::Embedded` is what the app asks for; the
standalone-window mode the CLI uses is unchanged, and nothing about it reaches
the streaming path.
Done: `pstr-player` step one — `mpv_stream_cb_add_ro`, mpv in its own window.
`pstr play --file <text>` plays a catalogued episode over a public link.
Done: `pstr-app` step one — `cargo run -p pstr-app` opens a window with the
library wall, a title page per series, a transport bar and share management. It
plays through the same `Player` the CLI uses, so mpv still owns its own window;
the app's transport drives it from a command channel. Two pieces of that are
plays through the same `Player` the CLI uses — at that point still in mpv's own
window; the app's transport drives it from a command channel. Two pieces of that are
worth knowing before touching it:
- **Drawing never mutates.** Pages are handed `&`-state and push `Action`s onto
+25 -8
View File
@@ -128,12 +128,29 @@ The `.icns` is rasterized from `io.narl.proton-stream.svg` when `rsvg-convert` o
The Debian `control` file has no template — `scripts/build.sh` writes it, because
almost every field in it is computed.
## Two things to know before releasing
## Releasing
- **The `[patch.crates-io]` block in `Cargo.toml` is development only.** It points
at `../proton-sdk-rs`, so both build scripts refuse to start when it is present
and that checkout is missing. A release must build against the published
crates — drop the block once the pinned proton-sdk is on crates.io.
- **There is no `LICENSE` file in the repository root** even though `Cargo.toml`
declares MIT. Every packager warns and ships without the license text; the MSI
falls back to `windows/license.rtf`. Adding the file fixes all of them at once.
A release is a `v<version>` tag on `main`; `.github/workflows/release.yml` does
the rest. It runs the CI gate and `scripts/build.sh tarball deb rpm` on Linux,
`scripts\build.ps1 -Artifacts zip,msi` on Windows, and publishes both sets plus
a `SHA256SUMS` to a GitHub release. So the steps are:
```bash
# 1. bump Cargo.toml's workspace version *and* packaging/PKGBUILD's pkgver
# 2. commit on main, then
git tag -a v0.1.0 -m 'proton-stream 0.1.0'
git push origin main --follow-tags
```
The workflow refuses to publish a tag whose commit is not an ancestor of `main`,
or whose version disagrees with `Cargo.toml` or the PKGBUILD — the artifacts are
named from the manifest, so a drifted tag would ship a file called something
other than the tag. It also refuses a `Cargo.toml` carrying `[patch.crates-io]`:
a release builds against the published SDK crates, never a sibling checkout.
`workflow_dispatch` runs everything except the publish step, which is how to
rehearse without burning a tag.
Not covered by the workflow: the Arch package (`makepkg` compiles in place, so
it wants an Arch host) and the macOS `.app`/`.dmg` (no runner for it here yet).
Both are still `scripts/build.sh arch` and `scripts/build.sh dmg` by hand.