diff --git a/.github/workflows/packaging-lint.yml b/.github/workflows/packaging-lint.yml new file mode 100644 index 000000000..74ffd3335 --- /dev/null +++ b/.github/workflows/packaging-lint.yml @@ -0,0 +1,53 @@ +# Quick static checks for the release pipeline (seconds, no Rust build): workflow syntax, +# packaging shell/PowerShell scripts, and the Linux desktop/AppStream/Flatpak metadata. +name: Packaging lint + +on: + push: + branches: [main, release] + paths: [".github/workflows/**", "packaging/**", "assets/app-icon/**"] + pull_request: + paths: [".github/workflows/**", "packaging/**", "assets/app-icon/**"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: packaging-lint-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + env: + ACTIONLINT_VERSION: 1.7.12 + steps: + - uses: actions/checkout@v4 + - name: Install tools + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends shellcheck desktop-file-utils appstream libxml2-utils python3-yaml + curl -fsSL "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \ + | tar -xz -C "$RUNNER_TEMP" actionlint + - name: actionlint + run: | + "$RUNNER_TEMP/actionlint" -color + - name: shellcheck + run: shellcheck packaging/*.sh packaging/*/*.sh + - name: PowerShell + shell: pwsh + run: | + foreach ($f in Get-ChildItem packaging/windows/*.ps1) { + $errors = $null + [System.Management.Automation.Language.Parser]::ParseFile($f.FullName, [ref]$null, [ref]$errors) | Out-Null + if ($errors) { $errors | ForEach-Object { Write-Output "::error file=$($f.Name)::$_" }; exit 1 } + Write-Output "ok $($f.Name)" + } + - name: XML, desktop entry, AppStream, Flatpak manifest + run: | + xmllint --noout packaging/windows/filmcraft.wxs packaging/macos/*.plist* packaging/linux/*.xml* + desktop-file-validate packaging/linux/ai.storyteller.filmcraft.desktop + sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.filmcraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.filmcraft.metainfo.xml" + appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.filmcraft.metainfo.xml" + python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.filmcraft.yml')); assert m['id'] == 'ai.storyteller.filmcraft' and m['command'] == 'filmcraft'" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..0cfad532a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,286 @@ +# Release pipeline: every push to `release` builds signed installers for every platform and +# creates or updates the draft GitHub Release "FilmCraft v". See docs/releasing.md. +# +# All jobs run in the `release` environment (restricted to the `release` branch), which holds +# the signing secrets. Every secret is optional: missing ones produce unsigned artifacts and a +# warning, never a failed build. +name: Release + +on: + push: + branches: [release] + workflow_dispatch: + inputs: + version: + description: "Version override for a test run, e.g. 0.2.0-rc.1 (empty: Cargo.toml)" + type: string + default: "" + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + # Never cancel a run mid-notarization or mid-upload; a newer push waits its turn. + cancel-in-progress: false + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + FILMCRAFT_BUILD_SHA: ${{ github.sha }} + INPUT_VERSION: ${{ inputs.version }} + +jobs: + version: + name: version + runs-on: ubuntu-latest + environment: release + outputs: + version: ${{ steps.v.outputs.version }} + date: ${{ steps.v.outputs.date }} + prerelease: ${{ steps.v.outputs.prerelease }} + steps: + - uses: actions/checkout@v4 + - id: v + name: Resolve version + run: | + . packaging/env.sh + v="${INPUT_VERSION#v}" + v="${v:-$VERSION}" + if ! printf '%s' "$v" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'; then + echo "::error::'$v' is not a version like 1.2.3 or 1.2.3-rc.1" + exit 1 + fi + case "$v" in *-*) pre=true ;; *) pre=false ;; esac + { + echo "version=$v" + echo "date=$(date -u +%Y-%m-%d)" + echo "prerelease=$pre" + } >>"$GITHUB_OUTPUT" + echo "### FilmCraft v$v (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY" + + macos: + name: macOS universal + needs: version + runs-on: macos-15 + environment: release + env: + FILMCRAFT_VERSION: ${{ needs.version.outputs.version }} + FILMCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-apple-darwin, x86_64-apple-darwin + - uses: Swatinem/rust-cache@v2 + with: + key: release-macos-universal + - name: Apply version override + if: inputs.version != '' + run: cargo xtask version set "$FILMCRAFT_VERSION" + - name: Import signing certificate + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: packaging/macos/import-cert.sh + - name: Build, sign, notarize + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: packaging/macos/package.sh --arch universal + - uses: actions/upload-artifact@v4 + with: + name: release-macos + path: dist/release/* + if-no-files-found: error + - name: Remove temporary keychain + if: always() + run: | + if [ -n "${MACOS_KEYCHAIN:-}" ]; then security delete-keychain "$MACOS_KEYCHAIN" || true; fi + + windows: + name: Windows ${{ matrix.arch }} + needs: version + runs-on: windows-latest + environment: release + strategy: + fail-fast: false + matrix: + include: + - { arch: x64, target: x86_64-pc-windows-msvc } + - { arch: x86, target: i686-pc-windows-msvc } + env: + FILMCRAFT_VERSION: ${{ needs.version.outputs.version }} + FILMCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + with: + key: release-windows-${{ matrix.arch }} + - name: Apply version override + if: inputs.version != '' + shell: bash + run: cargo xtask version set "$FILMCRAFT_VERSION" + - name: Install WiX + shell: pwsh + run: dotnet tool install --global wix --version 5.0.2 + - name: Build, sign, package + shell: pwsh + env: + WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} + WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_SIGNING_ENDPOINT: ${{ secrets.AZURE_SIGNING_ENDPOINT }} + AZURE_SIGNING_ACCOUNT: ${{ secrets.AZURE_SIGNING_ACCOUNT }} + AZURE_CERT_PROFILE: ${{ secrets.AZURE_CERT_PROFILE }} + run: ./packaging/windows/package.ps1 -Arch ${{ matrix.arch }} + - uses: actions/upload-artifact@v4 + with: + name: release-windows-${{ matrix.arch }} + path: dist/release/* + if-no-files-found: error + + linux: + name: Linux ${{ matrix.arch }} + needs: version + # The oldest GitHub-hosted image, so the binaries only need glibc >= 2.35 (Ubuntu 22.04+, + # Debian 12+, Fedora 36+, RHEL 10). See docs/releasing.md. + runs-on: ${{ matrix.runner }} + environment: release + strategy: + fail-fast: false + matrix: + include: + - { arch: x86_64, runner: ubuntu-22.04, deb: amd64 } + - { arch: aarch64, runner: ubuntu-22.04-arm, deb: arm64 } + env: + FILMCRAFT_VERSION: ${{ needs.version.outputs.version }} + FILMCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }} + NFPM_VERSION: 2.47.0 + steps: + - uses: actions/checkout@v4 + - name: Install build and packaging tools + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev libasound2-dev \ + desktop-file-utils appstream python3-yaml + curl -fsSL -o "$RUNNER_TEMP/nfpm.deb" \ + "https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_${{ matrix.deb }}.deb" + sudo dpkg -i "$RUNNER_TEMP/nfpm.deb" + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + key: release-linux-${{ matrix.arch }} + - name: Apply version override + if: inputs.version != '' + run: cargo xtask version set "$FILMCRAFT_VERSION" + - name: Build and package (AppImage, deb, rpm, tar.gz) + run: packaging/linux/package.sh + - name: Check the packages + run: | + dpkg-deb --info dist/release/*.deb + dpkg-deb --contents dist/release/*.deb | grep -E 'usr/bin/filmcraft$|metainfo|applications' + ldd target/release/filmcraft + python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.filmcraft.yml')); assert m['id'] == 'ai.storyteller.filmcraft'" + for appimage in dist/release/*.AppImage; do + chmod +x "$appimage" + APPIMAGE_EXTRACT_AND_RUN=1 "$appimage" --version + done + - uses: actions/upload-artifact@v4 + with: + name: release-linux-${{ matrix.arch }} + path: dist/release/* + if-no-files-found: error + + web: + name: Web (wasm) + needs: version + runs-on: ubuntu-latest + environment: release + env: + FILMCRAFT_VERSION: ${{ needs.version.outputs.version }} + FILMCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }} + WASM_BINDGEN_VERSION: 0.2.129 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + with: + key: release-web + - name: Install wasm-bindgen CLI + run: | + # Must match the wasm-bindgen crate version exactly (WASM_BINDGEN in xtask/src/main.rs). + command -v wasm-bindgen >/dev/null && wasm-bindgen --version | grep -q "$WASM_BINDGEN_VERSION" \ + || cargo install wasm-bindgen-cli --version "$WASM_BINDGEN_VERSION" --locked + wasm-bindgen --version + - name: Apply version override + if: inputs.version != '' + run: cargo xtask version set "$FILMCRAFT_VERSION" + - name: Build and zip + run: packaging/web/package.sh + - uses: actions/upload-artifact@v4 + with: + name: release-web + path: dist/release/* + if-no-files-found: error + + release: + name: Draft GitHub Release + needs: [version, macos, windows, linux, web] + runs-on: ubuntu-latest + environment: release + permissions: + contents: write + env: + VERSION: ${{ needs.version.outputs.version }} + PRERELEASE: ${{ needs.version.outputs.prerelease }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + steps: + - uses: actions/download-artifact@v4 + with: + pattern: release-* + path: artifacts + merge-multiple: true + - name: Checksums + working-directory: artifacts + run: | + sha256sum -- * | tee SHA256SUMS.txt + - name: Create or update the draft release + working-directory: artifacts + run: | + tag="v$VERSION" + title="FilmCraft v$VERSION" + pre=() + if [ "$PRERELEASE" = true ]; then pre=(--prerelease); fi + if draft="$(gh release view "$tag" --json isDraft --jq .isDraft 2>/dev/null)"; then + if [ "$draft" != "true" ]; then + echo "::error::Release $tag is already published. Bump the version (cargo xtask version set) before pushing to release." + exit 1 + fi + echo "Updating draft $tag: replacing its assets" + gh release view "$tag" --json assets --jq '.assets[].name' | while read -r asset; do + gh release delete-asset "$tag" "$asset" --yes + done + gh release edit "$tag" --draft --title "$title" --target "$GITHUB_SHA" --prerelease="$PRERELEASE" + gh release upload "$tag" --clobber -- * + else + echo "Creating draft $tag" + gh release create "$tag" --draft --title "$title" --target "$GITHUB_SHA" --generate-notes "${pre[@]}" -- * + fi + { + echo "Draft release **$title** at \`${GITHUB_SHA::9}\`:" + echo + sed 's/^/ /' SHA256SUMS.txt + } >>"$GITHUB_STEP_SUMMARY" diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 3fc73c92e..4c68087af 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -25,7 +25,22 @@ iconography, images or other Adobe assets; open licences only; every asset attri | `docs/images/filmcraft-scopes.png` | FilmCraft contributors | Original work: FilmCraft UI crop (scopes of a Charade (1963) frame, public domain) | MIT OR Apache-2.0 | | `docs/images/filmcraft-effects.png` | FilmCraft contributors | Original work: FilmCraft screenshot crop; NASA Earth Views (US Government work, public domain) | MIT OR Apache-2.0; footage public domain | | `docs/images/filmcraft-timeline.png` | FilmCraft contributors | Original work: FilmCraft UI crop; thumbnails from Night of the Living Dead (1968) and Carnival of Souls (1962), CC0 Chopin waveform | MIT OR Apache-2.0; film frames public domain | -| `apps/filmcraft-web/web/favicon.svg` | FilmCraft contributors | Original work: web app icon (disc, sprocket ring, play triangle) drawn from basic SVG shapes | MIT OR Apache-2.0 | +| `assets/app-icon/filmcraft.svg` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; canonical vector | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/filmcraft-small.svg` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; lighter vector | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/filmcraft-1024.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; 1024 px render | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/filmcraft-macos-512.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; macOS runtime Dock icon (512 px) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/filmcraft.icns` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; macOS .icns | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/filmcraft.ico` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Windows .ico (16-256 px) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 16 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 24 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 32 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 48 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 64 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 128 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 256 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 512 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.filmcraft.svg` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme scalable | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | +| `apps/filmcraft-web/web/favicon.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; 128 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | ## Golden test images @@ -52,6 +67,12 @@ procedural demo footage (`crates/media/src/generators.rs`); no external media. R | `crates/golden/goldens/mask_color.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `mask_color`) from procedurally generated media | MIT OR Apache-2.0 | | `crates/golden/goldens/mask_inverted.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `mask_inverted`) from procedurally generated media | MIT OR Apache-2.0 | | `crates/golden/goldens/mask_opacity.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `mask_opacity`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_distort.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_distort`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_ultra_key.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_ultra_key`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_glows.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_glows`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_spacer_stroke_shadow.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_spacer_stroke_shadow`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_vr_rotate.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_vr_rotate`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_stylize.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_stylize`) from procedurally generated media | MIT OR Apache-2.0 | ## Third-party media shown in screenshots diff --git a/Cargo.lock b/Cargo.lock index 1cd532309..95b99cff3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1671,6 +1671,7 @@ dependencies = [ "rfd", "serde_json", "winit", + "winresource", ] [[package]] @@ -5778,10 +5779,12 @@ version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ + "indexmap", "serde_core", "serde_spanned 1.1.1", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", + "toml_writer", "winnow 1.0.4", ] @@ -5848,6 +5851,12 @@ dependencies = [ "winnow 1.0.4", ] +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tracing" version = "0.1.44" @@ -6968,6 +6977,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winresource" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +dependencies = [ + "toml 1.1.6+spec-1.1.0", + "version_check", +] + [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/Cargo.toml b/Cargo.toml index fa73ed112..aa6515074 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,10 @@ opt-level = 3 [profile.dev.package.filmcraft-aac] opt-level = 3 +# Audio effects: realtime-factor tests and long convolution / spectral runs. +[profile.dev.package.filmcraft-audio-dsp] +opt-level = 3 + [profile.dev.package.filmcraft-opus] opt-level = 3 diff --git a/README.md b/README.md index 60f0871cb..2f1b9679f 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,11 @@

+

FilmCraft

- Professional video editing, rebuilt from scratch in pure Rust. + Video editing, color and sound; an open-source, clean-room reimplementation of Adobe Premiere Pro, rebuilt in pure Rust.

@@ -44,9 +45,8 @@

Apollo 11 - Tranquility: a three-minute documentary edit of NASA's 1969 launch, landing and moonwalk film, with subtitles from the mission transcript. Every frame in these screenshots comes from public-domain footage, decoded, composited and graded by FilmCraft's own code.

> [!NOTE] -> **ArtCraft is a community of artists from all walks of life.** Painters, photographers, -> filmmakers, illustrators, designers, animators, hobbyists, and people who picked up a pencil -> last week. If you make things, you're one of us. **[Come say hi on Discord](https://discord.gg/artcraft).** +> **ArtCraft is a community of artists from all walks of life.** Digital, generative, music, +> games — if you make things, you're one of us. **[Come say hi on Discord](https://discord.gg/artcraft).**

Edit · @@ -134,7 +134,7 @@ FilmCraft is a non-linear editor for people who know Premiere: the same panels,

NASA's Earth Views from the ISS with a look applied, and the Effects browser open on the dissolves.

-- **Around 55 video effects and 30 transitions.** Blurs, keys, distortions, stylize and colour effects. Cross dissolve, dip to black or white, film dissolve, wipes, irises, pushes, slides, zooms, page peel, cube spin and more. +- **Premiere 26's full Video Effects bin (93 effects in 16 folders), the Legacy bin and the obsolete effects old projects use, plus around 30 transitions.** Blurs (Bokeh, Focus, Compound), keys (Ultra Key, Track Matte), distortions (Corner Pin, Turbulent Displace, Warp Stabilizer), Lights & Glows, Immersive Video (VR) effects on equirectangular footage, Posterize Time, Echo and more. Cross dissolve, dip to black or white, film dissolve, wipes, irises, pushes, slides, zooms, page peel, cube spin and more. - **Motion and opacity on every clip:** position, scale, rotation, anchor point and anti-flicker, plus 26 blend modes. - **Keyframes like Premiere's:** linear, Bezier, auto and continuous Bezier, hold, ease in and ease out. Effect Controls shows a keyframe lane for every parameter, and each animated parameter opens into **value and velocity graphs** with draggable influence handles. - **A GPU compositor** built on wgpu (Metal, Vulkan, DirectX 12, WebGPU). It samples YUV straight from the decoder with footprint supersampling and blends in linear light. A CPU path renders the same frames, and the two are tested against each other. @@ -292,15 +292,15 @@ FilmCraft is one of the **Crafting Apps**: free, open-source creative tools from [ArtCraft](https://getartcraft.com/) team, each written from scratch in Rust and each able to stand on its own. -| App | What it's for | Code | Learn more | -|---|---|---|---| -| PhotoCraft | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [getartcraft.com](https://getartcraft.com/apps/photocraft) | -| VectorCraft | Vector illustration (formerly DrawCraft) | [GitHub](https://github.com/storytold/vectorcraft) | [getartcraft.com](https://getartcraft.com/apps/drawcraft) | -| FilmCraft | **Video editing, color and sound · you are here** | [GitHub](https://github.com/storytold/filmcraft) | [getartcraft.com](https://getartcraft.com/apps/filmcraft) | -| LightCraft | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [getartcraft.com](https://getartcraft.com/apps/lightcraft) | -| PrintCraft | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/printcraft) | [getartcraft.com](https://getartcraft.com/apps/printcraft) | -| EffectCraft | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [getartcraft.com](https://getartcraft.com/apps/effectcraft) | -| DesignCraft | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [getartcraft.com](https://getartcraft.com/apps/designcraft) | +| | App | What it's for | Code | Learn more | +|:-:|---|---|---|---| +| | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) | +| | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) | +| | **FilmCraft** | **Video editing, color and sound · you are here** | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) | +| | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) | +| | **PrintCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/printcraft) | [Website](https://getartcraft.com/apps/printcraft) | +| | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) | +| | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) | And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control. @@ -332,7 +332,9 @@ And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video stud **Footage and music in the screenshots:** NASA's Apollo 11 film and television footage from [images.nasa.gov](https://images.nasa.gov) (launch, Launch Control Center, lunar surface and recovery) and the Apollo 11 air-to-ground voice transcript, all US Government works in the public domain (NASA does not endorse this project); *Night of the Living Dead* (1968), *Carnival of Souls* (1962) and *Charade* (1963), all in the US public domain; *Earth Views from the ISS* by NASA; Chopin's Nocturne Op. 48 No. 1 and Ballade No. 1, performed for Musopen and released under CC0. The media itself is not in this repository. Sources and details for every asset are in [ATTRIBUTION.md](ATTRIBUTION.md). -FilmCraft is an independent implementation. It contains no Adobe code, icons, images, presets or LUTs, and no GPL or LGPL code; every icon is drawn in code and every asset is openly licensed and attributed ([AGENTS.md](AGENTS.md)), apart from the ArtCraft name and logo, which are trademarks of the ArtCraft team used with permission. ffmpeg is used only as an external test oracle. Adobe and Premiere Pro are trademarks of Adobe Inc.; FilmCraft is not affiliated with Adobe. +FilmCraft is an independent implementation. It contains no Adobe code, icons, images, presets or LUTs, and no GPL or LGPL code; every icon is drawn in code and every asset is openly licensed and attributed ([AGENTS.md](AGENTS.md)), apart from the ArtCraft name and logo, which are trademarks of the ArtCraft team used with permission. ffmpeg is used only as an external test oracle. + +Adobe, Photoshop, Illustrator, Premiere Pro, Lightroom, Acrobat, After Effects and InDesign are trademarks or registered trademarks of Adobe Inc. in the United States and/or other countries. FilmCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Adobe Inc.; these names are used only to describe the workflows it is compatible with. ## License diff --git a/ROADMAP.md b/ROADMAP.md index d2fce8bce..60d209b54 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,22 +2,51 @@ Progress toward feature parity with Adobe Premiere Pro, with estimates. Updated as milestones land. -**Last updated:** 2026-10-02 · **Overall parity:** ~58% · **Code:** 34 crates, 1095 tests +**Last updated:** 2026-10-02 · **Overall parity:** ~72% (measured scorecard below) · **Code:** 34 crates, 1258 tests + +## Parity scorecard + +Measured against Premiere Pro 26.5 on this machine. Menu items: the native menu bar dump, minus +Adobe-cloud-only items (Team Projects, Productions, Firefly, Stock, Dynamic Link, account/help pages), +matched against `filmcraft-cli commands` and the UI command table. Effects: the Effects panel tree. + +| Area | Weight | Measured | Coverage | +|---|---|---|---| +| Editing, timeline, trimming, multicam | 20% | core edit algebra, trim modes, sync, multicam, menu long tail, Scene Edit Detection, Automate to Sequence | ~80% | +| Menus / commands | (cross-check) | ~325 of 344 in-scope menu items | ~95% | +| Effects and transitions | 12% | video effects 93/93 (+Legacy, Obsolete), audio effects 53/53, video transitions 84/84 (+21 Legacy), audio transitions 3/3; some approximations (Warp Stabilizer 2-D, Morph Cut, Auto Reframe) | ~92% | +| Media I/O and codecs | 12% | H.264, HEVC, VP9, AV1, ProRes, DNx, MJPEG, Opus, AAC, MP4/MOV/MKV/WebM; no MXF, image sequences, HW decode | ~70% | +| Panels and UI fidelity | 12% | all main panels, audio effect editor windows; Metadata, Media Browser, scopes (parade/histogram), Timecode, Events panels thin or missing | ~65% | +| Audio | 10% | mixer, automation, Essential Sound, meters, all 53 effects; no 5.1 buses, voice-over record | ~70% | +| Colour | 8% | Lumetri complete, LUTs, colour management, HDR | ~80% | +| Graphics and captions | 8% | text engine, shapes, captions, transcripts; no MOGRT, rolls/crawls, responsive design | ~60% | +| Export | 8% | own H.264/AAC, ProRes, DNxHR, PNG/GIF/WAV; no preset library/queue UI, AAF/OMF, MXF | ~60% | +| Preferences and project management | 5% | Settings dialog with 16 categories (most settings wired), project settings, scratch disks, search bins, templates | ~80% | +| Performance | 5% | 1080p real-time, 3×1080p; 4K/8K and AV1 real-time not yet | ~50% | +| **Weighted total** | | | **~72%** | ## Estimate to parity -Throughput observed so far: ~25% parity in the first ~5 wall-clock hours (Sep 30, 05:22 → 10:00), with -3–4 coding agents running in parallel and one integrator. The early percentage points were the cheap -ones; the remaining work is broader, with a long tail of commands, dialogs and edge cases. +Measured throughput in the last work block (2026-10-01 night → 10-02): five Opus 5.5 agents in +parallel for ~4.5 wall-clock hours (~18 agent-hours including integration) moved parity by ~4 points +(≈4–5 agent-hours per point), on a machine that was heavily overloaded (load 150–300 on 14 cores) and +once ran out of disk. The remaining points are a long tail (each effect, dialog and preference page is +small, but there are many), so the estimate applies a 1.3–1.5× tail factor. -| | Agent-hours | Wall-clock (4–6 parallel agents, 24/7) | +| | Opus 5.5 agent-hours | Wall-clock (4–5 parallel agents + integrator) | |---|---|---| -| Feature parity by checklist | ~350–500 | **~100–150 h (4–6 days)** | -| Robust on real-world material (codec edge cases, 4K/8K performance, pro workflows) | +150–300 | **+1–2 weeks** | +| Feature parity by checklist (~28 points left) | ~130–180 | **~30–45 h** | +| Robust on real-world material (codec edge cases, 4K/8K performance, pro workflows) | +120–200 | **+30–50 h** | +| **Total to "100% and better"** | **~250–380** | **~60–95 h (≈2.5–4 days, 24/7)** | -Limits on speed: machine load (builds and benchmarks slow down under many agents), disk space, and a -single integrator merging and checking each agent's work. With one agent and no parallelism, multiply -wall-clock by ~3–4. +The 2026-10-02 block moved parity ~62% → ~72% with six agents in ~6 wall-clock hours, despite the +disk filling twice; the remaining work is mostly panels (scopes, Metadata, Media Browser), export +presets/queue, codecs (MXF, image sequences, hardware decode), performance and graphics templates. + +Limits on speed: CPU and disk on one machine (more than ~5 agents slows everyone down: each worktree +build is 6–7 GB and a full `cargo xtask ci` takes 20–60 min under load), and a single integrator +merging, resolving conflicts (e.g. two agents bumping the project schema) and re-running CI. With one +agent and no parallelism, multiply wall-clock by ~3–4. Not reachable clean-room and locally: **Generative Extend** (needs a large video-generation model). **Enhance Speech** and **Auto Reframe** are feasible only with openly licensed models we can ship. @@ -31,7 +60,7 @@ Status: ✅ done · 🟡 in progress · ⬜ not started. Estimates are remaining | M0 | Skeleton + visual shell | ✅ | Workspace, 20 crates, dock/workspaces, Premiere 26 look, native menus, control channel, MCP, xtask gates (layers, wasm) | — | — | | M1 | Media I/O | ✅ | MP4/MOV demux+mux, WAV, stills, MJPEG, symphonia audio (MP3/FLAC/ALAC/Vorbis), GOP seek + frame cache, import | Media Browser polish | 2 | | M2 | H.264 decoder | ✅ | Own decoder, bit-exact on 37+ streams, 500–600 fps 1080p | — | — | -| M3 | Editing core | 🟡 | Edit algebra (insert/overwrite/razor/lift/extract/ripple/roll/slip/slide/rate-stretch/nest/paste), tools, markers, trim mode + Trim Monitor + dynamic J/K/L trimming, Keyboard Shortcuts editor with FilmCraft/Premiere/FCP/Avid presets; Edit/Clip/File menu commands (Label colours and Select Label Group, Paste/Remove Attributes, Select All Matching, Remove Unused, Consolidate Duplicates, Sequence From Clip, Bin From Selection, Offline File, Close Project, Make/Edit Subclip, Modify Audio Channels/Timecode, Frame Hold Options/Add Frame Hold/Insert Frame Hold Segment, Time Interpolation with frame blending, Fit/Fill frame, Breakout to Mono, Extract Audio, Replace With Clip); multicam (Create Multi-Camera Source Sequence, Multi-Camera view with live switching on 1–9, angle switching, Enable/Flatten, Edit Cameras) and sync (Synchronize, Merge Clips; In/Out/timecode/marker/audio — GCC-PHAT, sample-accurate) | Multicam paging >16 angles and grid thumbnails, optical flow (renders as frame blending), ~200 Premiere default shortcuts whose commands don't exist yet | 6–10 | +| M3 | Editing core | 🟡 | Edit algebra (insert/overwrite/razor/lift/extract/ripple/roll/slip/slide/rate-stretch/nest/paste), tools, markers, trim mode + Trim Monitor + dynamic J/K/L trimming, Keyboard Shortcuts editor with FilmCraft/Premiere/FCP/Avid presets; Edit/Clip/File menu commands (Label colours and Select Label Group, Paste/Remove Attributes, Select All Matching, Remove Unused, Consolidate Duplicates, Sequence From Clip, Bin From Selection, Offline File, Close Project, Make/Edit Subclip, Modify Audio Channels/Timecode, Frame Hold Options/Add Frame Hold/Insert Frame Hold Segment, Time Interpolation with frame blending, Fit/Fill frame, Breakout to Mono, Extract Audio, Replace With Clip; M3.11: Scene Edit Detection (pure-Rust cut detection, background job), Normalize Mix Track, Simplify Sequence, Transcribe Sequence, Find/Find Next and search bins, Automate to Sequence, Edit Original, Edit Offline, Source Settings, Update Metadata (XMP), Generate Audio Waveform, Project Settings General/Scratch Disks, Get Media File Properties, Save as Template, Selection as FilmCraft Project, Avid Log Exchange export, Flash Cue markers, Dynamic Audio Waveforms, Reveal Log Files, System Compatibility Report); multicam (Create Multi-Camera Source Sequence, Multi-Camera view with live switching on 1–9, angle switching, Enable/Flatten, Edit Cameras) and sync (Synchronize, Merge Clips; In/Out/timecode/marker/audio — GCC-PHAT, sample-accurate) | Multicam paging >16 angles and grid thumbnails, optical flow (renders as frame blending), ~200 Premiere default shortcuts whose commands don't exist yet | 6–10 | | M4 | Playback | 🟡 | Audio-clock master, prefetch with cancellation, J/K/L, correct dropped-frame stats, playback resolution, render bar + content-hashed render previews, App Nap opt-out; 1080p H.264 and 3 stacked 1080p streams play with 0 dropped frames | 4K under load, 8K, frame-threaded AV1 decode, reduced-resolution decode for multicam grids | 6–10 | | M5 | Effects, keyframes, GPU | 🟡 | ~60 CPU effects, 30 transitions, keyframes + value/velocity graphs, wgpu compositor, effect + opacity masks (ellipse/polygon/pen, feather, expansion, modes; CPU/WGSL parity), mask tracking (Lucas–Kanade + RANSAC), adjustment layers, effect presets (built-in + user, JSON import/export) | Full ~150-effect catalogue, WGSL parity for all effects and masks in the live GPU path, Warp Stabilizer, Morph Cut | 20–30 | | M6 | Export | ✅ | Own H.264 encoder (High/Main/Baseline, B-frames, VBR/CBR/2-pass) → MP4 + own AAC; ProRes, MJPEG, PNG, GIF, WAV; background jobs | Preset library, queue UI, smart render | 6–8 | @@ -48,6 +77,8 @@ Status: ✅ done · 🟡 in progress · ⬜ not started. Estimates are remaining ## Log +- **2026-10-02 (later):** all 93 video effects (+Legacy/Obsolete bins), all 53 audio effects with Parametric/Graphic EQ, Multiband Compressor and Dynamics editor windows, all 84 video transitions (+21 Legacy), Settings dialog (16 categories, most wired), remaining menu items (Scene Edit Detection, Find, Normalize Mix Track, Simplify Sequence, Automate to Sequence, search bins, templates, Project Settings with scratch disks, ALE and selection-as-project export, system report); project schema v11. 1258 tests. + - **2026-10-02:** Premiere menu long tail: Sequence/Markers (gaps, split edits, through edits, subsequence, Delete Tracks, range/chapter markers, ripple sequence markers), Clip/Edit/File (Paste/Remove Attributes, subclips, Frame Hold Options, Time Interpolation with frame blending, Audio Channels, Breakout to Mono, Extract Audio, Replace With Clip, Remove Unused, Consolidate Duplicates), View/monitors (paused resolution, alpha/RGB display modes, comparison view, magnification, rulers, guides + templates, snapping), Graphics and Titles (vertical text, shape layers, align/distribute/arrange). Transcripts and text-based editing (Text panel, optional local Whisper). AV1 tile/frame/post-filter threading. Agent-friendly CLI (`exec`, `inspect`, `describe`, `import`, `export`, `run -`, `--save`, `--bridge`). Project schema v10. - **2026-10-01 (night):** new README hero (Apollo 11 documentary edit, NASA public domain); agents never steal keyboard focus; masks + tracking, adjustment layers, effect presets; multicam + audio sync; DNxHD/DNxHR decode/encode; AV1 decoder bit-exact against libdav1d (all stages, film grain, superres, SVC). diff --git a/apps/filmcraft-cli/src/main.rs b/apps/filmcraft-cli/src/main.rs index 2ab7c24b9..df8b76c5a 100644 --- a/apps/filmcraft-cli/src/main.rs +++ b/apps/filmcraft-cli/src/main.rs @@ -30,6 +30,7 @@ SUBCOMMANDS bench-decode [--frames N] mcp MCP server on stdio (headless, or --bridge to the live app) help this text + --version print the version OPTIONS --project open this project first (headless) @@ -130,10 +131,15 @@ fn format_for(path: &str) -> Option<&'static str> { #[tokio::main] async fn main() { + if matches!(std::env::args().nth(1).as_deref(), Some("--version" | "-V")) { + println!("filmcraft-cli {}", env!("CARGO_PKG_VERSION")); + return; + } let a = Args::parse(std::env::args().skip(1)); let Some(cmd) = a.pos(0) else { usage("missing subcommand") }; match cmd { "help" | "--help" | "-h" => print!("{HELP}"), + "version" => println!("filmcraft-cli {}", env!("CARGO_PKG_VERSION")), "probe" => { let path = a.pos(1).unwrap_or_else(|| usage("probe ")); let bytes = std::fs::read(path).unwrap_or_else(|e| fail(format!("{path}: {e}"))); diff --git a/apps/filmcraft-web/web/favicon.png b/apps/filmcraft-web/web/favicon.png new file mode 100644 index 000000000..3c217317b Binary files /dev/null and b/apps/filmcraft-web/web/favicon.png differ diff --git a/apps/filmcraft-web/web/favicon.png.attribution b/apps/filmcraft-web/web/favicon.png.attribution new file mode 100644 index 000000000..e1a4f05e5 --- /dev/null +++ b/apps/filmcraft-web/web/favicon.png.attribution @@ -0,0 +1,8 @@ +asset: favicon.png +title: FilmCraft web app icon (favicon and loading image), 128 px +author: Brandon Thomas (FilmCraft owner), for the FilmCraft project +source: original work (drawn by the owner in ArtCraft, then vectorised) +license: MIT OR Apache-2.0 +license-file: assets/app-icon/LICENSE.txt +added: 2026-10-02 by Claude (agent) for Brandon Thomas +notes: Rendered from assets/app-icon/filmcraft.svg with resvg by packaging/icons.sh. Replaces the earlier hand-drawn favicon.svg. diff --git a/apps/filmcraft-web/web/favicon.svg b/apps/filmcraft-web/web/favicon.svg deleted file mode 100644 index 6a5e31982..000000000 --- a/apps/filmcraft-web/web/favicon.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/apps/filmcraft-web/web/favicon.svg.attribution b/apps/filmcraft-web/web/favicon.svg.attribution deleted file mode 100644 index 68ec81355..000000000 --- a/apps/filmcraft-web/web/favicon.svg.attribution +++ /dev/null @@ -1,9 +0,0 @@ -asset: favicon.svg -title: FilmCraft web app icon (favicon / install icon) -author: FilmCraft contributors -source: original work -license: MIT OR Apache-2.0 -added: 2026-10-01 by FilmCraft contributors (Claude agent, M13 web app) -notes: Hand-written SVG: a dark teal disc, an orange ring with eight sprocket dots and a play - triangle, drawn from scratch from basic shapes. Not derived from any other product's - icon. diff --git a/apps/filmcraft-web/web/index.html b/apps/filmcraft-web/web/index.html index 72ddb88f5..73307f441 100644 --- a/apps/filmcraft-web/web/index.html +++ b/apps/filmcraft-web/web/index.html @@ -5,7 +5,7 @@ FilmCraft - +