Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ jobs:
run: ./scripts/test-release-media.ps1

- name: Build Tauri NSIS installer
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: pnpm tauri:build

- name: Verify packaged Windows executable metadata
Expand All @@ -100,12 +103,33 @@ jobs:
$checksum = (Get-FileHash -LiteralPath $installer -Algorithm SHA256).Hash.ToLowerInvariant()
$checksumPath = "$installer.sha256"
"$checksum $fileName" | Set-Content -LiteralPath $checksumPath -Encoding ascii
$signatures = @(Get-ChildItem -LiteralPath "src-tauri/target/release/bundle/nsis" -File -Filter "*.sig")
if ($signatures.Count -ne 1) {
throw "Expected exactly one Tauri updater signature; found $($signatures.Count)."
}
$signaturePath = "$installer.sig"
Copy-Item -LiteralPath $signatures[0].FullName -Destination $signaturePath
$releaseUrl = "https://github.com/$env:GITHUB_REPOSITORY/releases/download/$env:GITHUB_REF_NAME/$fileName"
$latestPath = Join-Path $releaseDirectory "latest.json"
@{
version = $version
notes = (Get-Content -LiteralPath "docs/releases/$env:GITHUB_REF_NAME.md" -Raw)
pub_date = (Get-Date).ToUniversalTime().ToString("o")
platforms = @{
"windows-x86_64" = @{
signature = (Get-Content -LiteralPath $signaturePath -Raw).Trim()
url = $releaseUrl
}
}
} | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $latestPath -Encoding utf8
$notes = "docs/releases/$($env:GITHUB_REF_NAME).md"
if (-not (Test-Path -LiteralPath $notes -PathType Leaf)) {
throw "Release notes are missing: $notes"
}
"installer=$installer" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"checksum=$checksumPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"signature=$signaturePath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"latest=$latestPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"notes=$notes" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"prerelease=$($version.Contains('-').ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8

Expand All @@ -115,18 +139,39 @@ jobs:
GH_TOKEN: ${{ github.token }}
INSTALLER: ${{ steps.package.outputs.installer }}
CHECKSUM: ${{ steps.package.outputs.checksum }}
SIGNATURE: ${{ steps.package.outputs.signature }}
LATEST: ${{ steps.package.outputs.latest }}
NOTES: ${{ steps.package.outputs.notes }}
IS_PRERELEASE: ${{ steps.package.outputs.prerelease }}
run: |
$title = "Reelspace $($env:GITHUB_REF_NAME.Substring(1))"
if (gh release view $env:GITHUB_REF_NAME --repo $env:GITHUB_REPOSITORY 2>$null) {
gh release edit $env:GITHUB_REF_NAME --repo $env:GITHUB_REPOSITORY --title $title --notes-file $env:NOTES
gh release upload $env:GITHUB_REF_NAME $env:INSTALLER $env:CHECKSUM --repo $env:GITHUB_REPOSITORY --clobber
gh release upload $env:GITHUB_REF_NAME $env:INSTALLER $env:CHECKSUM $env:SIGNATURE $env:LATEST --repo $env:GITHUB_REPOSITORY --clobber
} else {
$arguments = @("release", "create", $env:GITHUB_REF_NAME, $env:INSTALLER, $env:CHECKSUM, "--repo", $env:GITHUB_REPOSITORY, "--verify-tag", "--title", $title, "--notes-file", $env:NOTES)
$arguments = @("release", "create", $env:GITHUB_REF_NAME, $env:INSTALLER, $env:CHECKSUM, $env:SIGNATURE, $env:LATEST, "--repo", $env:GITHUB_REPOSITORY, "--verify-tag", "--title", $title, "--notes-file", $env:NOTES)
if ($env:IS_PRERELEASE -eq "true") {
$arguments += "--prerelease"
}
& gh @arguments
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

- name: Update signed updater channel
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
LATEST: ${{ steps.package.outputs.latest }}
IS_PRERELEASE: ${{ steps.package.outputs.prerelease }}
run: |
$channel = if ($env:IS_PRERELEASE -eq "true") { "updater-alpha" } else { "updater-stable" }
$title = if ($env:IS_PRERELEASE -eq "true") { "Reelspace Alpha Update Channel" } else { "Reelspace Stable Update Channel" }
if (gh release view $channel --repo $env:GITHUB_REPOSITORY 2>$null) {
gh release edit $channel --repo $env:GITHUB_REPOSITORY --title $title --notes "Machine-readable Reelspace update channel. Installers remain attached to their versioned releases."
gh release upload $channel $env:LATEST --repo $env:GITHUB_REPOSITORY --clobber
} else {
$arguments = @("release", "create", $channel, $env:LATEST, "--repo", $env:GITHUB_REPOSITORY, "--target", "master", "--title", $title, "--notes", "Machine-readable Reelspace update channel. Installers remain attached to their versioned releases.")
if ($env:IS_PRERELEASE -eq "true") { $arguments += "--prerelease" }
& gh @arguments
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Your gameplay recordings, organized locally.

Reelspace is a Windows desktop library for browsing large capture folders, playing recordings, adding your own organization, and turning useful moments into clips—without uploading your media to a service.
Reelspace is a Windows desktop library for browsing large capture folders, playing recordings, adding your own organization, and turning useful moments into clips—without requiring an online service.

> [!WARNING]
> Reelspace is early alpha software. Back up important recordings and exported library data. The installer is not code-signed, so Windows SmartScreen may show an unknown-publisher warning.
Expand All @@ -12,22 +12,25 @@ Reelspace is a Windows desktop library for browsing large capture folders, playi
- Incremental scanning for large recording folders, including persistent probe caching and background thumbnail recovery
- A responsive gallery with in-window playback and Windows Explorer integration
- Custom tags, notes, collections, and collection membership
- Automatic and manual game classification with aliases, renaming, merging, and collection storage defaults
- Trim definitions and FFmpeg-powered clip generation that retain a reference to the source recording
- Optional private YouTube archival with a persistent, resumable queue and explicit local/remote availability states
- Storage usage warnings and a confirmed raw-recording cleanup flow
- Metadata-only export and import for tags, notes, collections, memberships, and clip references
- Metadata-only export and import for tags, notes, games, collections, storage preferences, archive references, and clip provenance
- Signed in-app update checks backed by public GitHub Releases (the Windows installer itself remains unsigned)
- Local diagnostics, unclean-shutdown detection, bounded job retries, and conservative database recovery

## Install on Windows

Reelspace currently supports Windows 10/11 x64.

1. Open the [latest Reelspace release](https://github.com/canikou/Reelspace/releases/latest).
2. Download `Reelspace_0.1.1-alpha.1_windows_x64_setup.exe` and its `.sha256` file.
2. Download the `Reelspace_*_windows_x64_setup.exe` installer and its matching `.sha256` file.
3. Optionally verify the download in PowerShell:

```powershell
(Get-FileHash .\Reelspace_0.1.1-alpha.1_windows_x64_setup.exe -Algorithm SHA256).Hash
Get-Content .\Reelspace_0.1.1-alpha.1_windows_x64_setup.exe.sha256
(Get-FileHash .\Reelspace_*_windows_x64_setup.exe -Algorithm SHA256).Hash
Get-Content .\Reelspace_*_windows_x64_setup.exe.sha256
```

The two hashes should match.
Expand All @@ -38,20 +41,22 @@ Reelspace currently supports Windows 10/11 x64.

Open **Settings**, add one or more folders containing recordings, and save. Reelspace indexes supported local media and fills thumbnails in the background. Configure a working folder and clip folder before exporting clips.

Use **Export Library Data** in Settings to create a portable JSON archive of your organization metadata. The archive contains file references, not video files. After moving to another computer or drive, index the local recordings first and then import the archive; Reelspace restores data only for safely matched local files. See [portable library data](docs/library-data.md) for the exact matching and conflict rules.
Use **Export Library Data** in Settings to create a portable JSON archive of your organization metadata. The archive contains file and verified remote references, not video files. After moving to another computer or drive, index any available local recordings and then import the archive. See [portable library data](docs/library-data.md) for the exact matching and conflict rules.

## Privacy and local data

Reelspace is local-first. It has no account, cloud sync, advertising, analytics service, or hosted crash-report collector. Your database, thumbnails, previews, recovery reports, and rotated diagnostics remain in your Windows application-data directory. Routine logs avoid full media paths, but review any diagnostic file before sharing it publicly.
Reelspace is local-first. It has no Reelspace account, cloud sync, advertising, analytics service, or hosted crash-report collector. Your database, thumbnails, previews, recovery reports, and rotated diagnostics remain in your Windows application-data directory. Routine logs avoid full media paths, but review any diagnostic file before sharing it publicly.

Private YouTube archival is optional and off by default. When enabled, click **Connect YouTube** and complete Google's browser sign-in; no developer credentials are required. OAuth tokens are kept in Windows Credential Manager and selected recordings are uploaded as private videos. Update checks contact public GitHub Releases; automatic downloads remain off by default.

Exported library-data archives may contain filenames, folder references, tags, notes, and collection names. Treat them as personal data even though they contain no video or audio.

## Known limits

- Windows x64 only; the installer is currently unsigned.
- Recordings must remain available on a local or Windows-accessible filesystem for playback, scanning, and clip creation.
- There is no cloud backup, automatic updater, or online video archival.
- Import carries organization metadata and file references, not media or application settings.
- Local files must remain available for playback and clip creation; YouTube archive copies open in the browser and are not a general cloud-sync filesystem.
- Import carries organization metadata and file/archive references, not media files, OAuth credentials, or general application settings.
- In-app updates are cryptographically signed, but the installer has no paid Authenticode signature and may trigger SmartScreen.
- This alpha may still contain rough edges. Keep backups and verify generated clips before deleting any source recording.

## Roadmap
Expand All @@ -75,6 +80,10 @@ cargo test --manifest-path src-tauri/Cargo.toml --all-targets
pnpm tauri:build
```

Updater-enabled packaging requires `TAURI_SIGNING_PRIVATE_KEY` and `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`. The repository's `build-setup.bat` loads the maintainer's DPAPI-protected local key automatically; other source builders should provide their own Tauri updater key when producing an installer.

Official builds contain Reelspace's public Google Desktop OAuth client ID. Forks can replace it at compile time with `REELSPACE_YOUTUBE_CLIENT_ID`; a Google Desktop client secret is intentionally neither required nor accepted by the app. See [YouTube archive authentication](docs/youtube-archive.md).

The FFmpeg preparation script downloads one pinned BtbN Windows build, verifies its archive and executable hashes, and stages only FFmpeg, FFprobe, and their license for packaging. See [third-party notices](THIRD_PARTY_NOTICES.md).

Release candidates also run `scripts/test-release-media.ps1`. With no arguments it creates and removes its own sanitized fixtures; a custom source folder may contain one valid recording and at least one malformed `.mp4`. The harness exercises scanning, failure isolation, thumbnailing, accurate clipping, persistence, and metadata export/import without writing into the source folder.
2 changes: 2 additions & 0 deletions build-setup.bat
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ echo.
if "%RUN_EXIT%"=="0" (
echo Setup generated successfully.
echo Setup: %CD%\dist\setup.exe
echo Sig: %CD%\dist\setup.exe.sig
echo SHA: %CD%\dist\setup.exe.sha256
) else (
echo Setup generation failed with exit code %RUN_EXIT%.
)
Expand Down
12 changes: 7 additions & 5 deletions docs/library-data.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# Portable Library Data

Reelspace Settings can export and import a versioned `.json` archive for user-created library organization. The archive contains media references, tags, notes, collection names and membership, and clip provenance/trim metadata. It never contains video, audio, thumbnails, previews, FFmpeg binaries, application settings, or diagnostics.
Reelspace Settings can export and import a versioned `.json` archive for user-created library organization. The archive contains media references, tags, notes, game classifications and aliases, collection names and membership, storage preferences, verified private archive references, and clip provenance/trim metadata. It never contains video, audio, thumbnails, previews, FFmpeg binaries, OAuth credentials, application settings, or diagnostics.

## Matching order

1. Exact original path, with kind, size, and duration verification.
2. The exported path relative to any currently configured source, working, clip, or managed clip root.
3. A unique kind, filename, size, and duration match in the current index.
4. Restore a database record only when exactly one original/remapped path exists locally, has the exported size, and remains inside a currently configured source/output root.
4. Restore a database record when exactly one original/remapped path exists locally, has the exported size, and remains inside a currently configured source/output root.
5. If no local copy exists, restore a remote-only record only when the archive contains a validated, verified, private YouTube reference. The placeholder path stays inside Reelspace application data and no video is downloaded.

Ambiguous, changed, and missing files remain unmatched. Import never creates a record pointing at a missing file.
Ambiguous and changed local files remain unmatched. A missing file without a verified remote reference is never restored.

## Merge behavior

- Tags are merged case-insensitively. Existing tags take priority if the 100-tag or 4 KB application limit is reached.
- An imported note fills an empty local note. A different non-empty local note is preserved and reported as a conflict.
- Collections are matched case-insensitively by name, created when absent, and memberships are added without removing local memberships.
- Media and collection changes commit in one SQLite transaction. Any database failure rolls back the entire import.
- Game classifications, aliases, Keep Local choices, collection defaults, and verified private archive references are restored for safely matched records.
- Media, organization, preference, and archive-reference changes commit in one SQLite transaction. Any database failure rolls back the entire import.

The current archive format identifier is `reelspace-library-data`, version `1`. Files are limited to 64 MB, 250,000 media references, 50,000 collections, and 2,000,000 collection memberships. Unknown fields and unsupported versions are rejected so a typo or future incompatible archive cannot be partially interpreted.
The current archive format identifier is `reelspace-library-data`, version `2`. Reelspace also accepts version 1 archives. Files are limited to 64 MB, 250,000 media references, 50,000 collections, and 2,000,000 collection memberships. Unknown fields and unsupported versions are rejected so a typo or future incompatible archive cannot be partially interpreted.
20 changes: 20 additions & 0 deletions docs/releases/v0.2.0-alpha.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Reelspace v0.2.0-alpha.1

This alpha turns Reelspace into a clearer local-first recording workspace and establishes the signed GitHub Releases update path for future versions.

## Highlights

- New Frame Stack application icon and a unified, accessible icon/status system across dark and light themes.
- Games workspace with automatic classification, manual assignment, aliases, renaming, and safe merging.
- Collection-level Keep Local and Archive defaults for newly added media.
- Optional private YouTube archival with desktop OAuth, Windows Credential Manager storage, resumable uploads, persisted progress, quota-aware retries, and explicit verification before local cleanup is allowed.
- Dedicated Archive queue with pause, resume, retry, cancel, progress, and local/remote availability states.
- Reactive source-folder watching, periodic reconciliation, recorder discovery/launch shortcuts, repair tools, and sanitized diagnostics export.
- Portable library-data format v2 carries games, collection policies, Keep Local preferences, and verified private archive references without carrying video files or credentials.
- Signed in-app updater artifacts and GitHub release-channel metadata. The installer remains unsigned and may show a SmartScreen warning.

## Safety notes

Private archival and automatic downloads are off by default. Reelspace never deletes a local file merely because an upload completed; removing a local copy remains an explicit action and requires a verified remote copy with Keep Local disabled. Generated clips default to Keep Local.

Back up important recordings and exported library data while the project remains in alpha.
24 changes: 24 additions & 0 deletions docs/youtube-archive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# YouTube archive authentication

Reelspace uses Google's installed-desktop OAuth flow. A user clicks **Connect YouTube**, signs in through their normal browser, approves the requested YouTube permissions, and returns automatically through a temporary loopback callback. Reelspace uses PKCE for the authorization exchange and keeps refresh/access tokens in Windows Credential Manager.

Users never enter a client ID or client secret. The public desktop client ID identifies Reelspace to Google and is bundled into official builds. Like all installed-app identifiers, it can be extracted from the distributed executable and is not treated as a secret. Reelspace does not bundle or use the Google-generated desktop client secret.

## Maintainer setup

The Google Cloud project behind an official build must have:

1. YouTube Data API v3 enabled.
2. Google Auth Platform branding configured for Reelspace.
3. An External audience. While publishing status is **Testing**, every permitted Google account must be listed under **Audience → Test users**.
4. A Desktop app OAuth client.
5. The `youtube.upload` and `youtube.readonly` scopes declared on the Data Access page.

The official Reelspace client ID is the default in `src-tauri/src/youtube.rs`. A fork or private build can override it without editing source:

```powershell
$env:REELSPACE_YOUTUBE_CLIENT_ID = "your-client-id.apps.googleusercontent.com"
pnpm tauri:build
```

Do not put OAuth access tokens, refresh tokens, account passwords, or unrelated API credentials in source control. No Reelspace server or web redirect URI is required for this flow.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "reelspace",
"version": "0.1.1-alpha.1",
"version": "0.2.0-alpha.1",
"private": true,
"packageManager": "pnpm@11.9.0",
"type": "module",
Expand All @@ -16,6 +16,8 @@
"dependencies": {
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-dialog": "^2.4.1",
"@tauri-apps/plugin-process": "2.3.0",
"@tauri-apps/plugin-updater": "2.10.1",
"lucide-react": "^0.468.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
Expand Down
Loading
Loading