diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9528ff7..426318d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,8 +1,17 @@ -name: Deploy to GitHub Pages +name: Deploy GitHub Pages redirect + +# The web app itself is deployed to app.getrescript.com by Vercel (see +# vercel.json), which can serve the cross-origin isolation headers GitHub Pages +# cannot. This workflow keeps the old wassgha.github.io/rescript URL alive as a +# redirect so existing links, README badges, and shared URLs still land in the +# editor. on: push: branches: [main] + paths: + - "redirect/**" + - ".github/workflows/deploy.yml" workflow_dispatch: permissions: @@ -19,22 +28,20 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - uses: actions/configure-pages@v5 with: enablement: true - - run: npm ci - - name: Build static export - run: npm run build - env: - STATIC_EXPORT: "1" - NEXT_PUBLIC_BASE_PATH: /${{ github.event.repository.name }} + - name: Assemble redirect site + run: | + mkdir -p site + cp redirect/index.html site/index.html + # Deep links (/rescript/editor) have no file on Pages, so 404.html is + # served for those — it runs the same path-preserving redirect. + cp redirect/index.html site/404.html + touch site/.nojekyll - uses: actions/upload-pages-artifact@v3 with: - path: out + path: site deploy: needs: build diff --git a/.gitignore b/.gitignore index 984463f..10f0dc8 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,3 @@ next-env.d.ts # runtime assets copied from node_modules on postinstall /public/vendor/ -/public/coi-serviceworker.js diff --git a/README.md b/README.md index 8c1ceb4..b685778 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ [![Join the Discord](https://img.shields.io/badge/Discord-Join%20the%20server-5865F2?logo=discord&logoColor=white)](https://discord.gg/qJAhYFydat) [![Follow @wassgha on X](https://img.shields.io/badge/Follow%20@wassgha-000000?logo=x&logoColor=white)](https://x.com/wassgha) -**✨ Try it in the browser [wassgha.github.io/rescript](https://wassgha.github.io/rescript/)** or download the [Desktop App](#download) +**✨ Try it in the browser [app.getrescript.com](https://app.getrescript.com/)** or download the [Desktop App](#download) -[![Rescript Demo](./screenshots/rescript.png)](https://wassgha.github.io/rescript/) +[![Rescript Demo](./screenshots/rescript.png)](https://app.getrescript.com/) Rescript is an open-source, transcript-based media editor. Drop in a video or audio file and it is transcribed locally with per-word timestamps and speaker @@ -26,9 +26,9 @@ the media. Export the final cut — without your file ever leaving your device. See the [Releases](https://github.com/wassgha/rescript/releases) page. Desktop builds auto-update from GitHub Releases. Prefer the browser? Use the -[web app](https://wassgha.github.io/rescript/) — same editor, no install. +[web app](https://app.getrescript.com/) — same editor, no install. -- 🔒 **Private by design** — no server, no auth, no uploads; all media processing happens on-device +- 🔒 **Private by design** — no auth, no uploads; all media processing happens on-device - 📝 **Word-level editing** — select words, press ⌫, the cut follows the text - 📥 **Import your own transcript** — skip Whisper and edit with an SRT, VTT, or JSON caption file - 📤 **Export hub** — video (MP4/WebM, 720p–4K), audio (M4A/MP3/WAV), transcript (TXT/MD), or subtitles (SRT/VTT/JSON) @@ -78,12 +78,13 @@ audio track. For desktop packaging, signing, and cutting releases, see > *first* time you transcribe, then cached in browser / app storage. After that, > everything — transcription, editing, export — works with the network fully > disconnected. Your media and transcript never leave the device; the only -> third-party request the app makes is anonymous page analytics (Google -> Analytics), which fails silently when offline. +> third-party requests the app makes are anonymous page analytics (Google +> Analytics and Vercel Web Analytics — page views and app opens, nothing about +> your media), which fail silently when offline. ## How it works -[![Rescript Promo](./screenshots/rescript-2.png)](https://wassgha.github.io/rescript/) +[![Rescript Promo](./screenshots/rescript-2.png)](https://app.getrescript.com/) 1. **Extract** — ffmpeg.wasm decodes the audio track to mono 16 kHz PCM. 2. **Transcribe** — Whisper runs in a Web Worker with `return_timestamps: "word"`, diff --git a/ROADMAP.md b/ROADMAP.md index 0b9845f..895dedd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,7 +6,7 @@ User-facing progress for Rescript. Items are marked done when they shipped to ## Done ### Core editor -- [x] Offline transcript-based editor in the browser (no server, no auth, no uploads) +- [x] Offline transcript-based editor in the browser (no auth, no uploads) - [x] Upload → local Whisper transcription with per-word timestamps - [x] Speaker diarization (pyannote) with speaker-grouped transcript - [x] Cut / restore by selecting words (⌫, floating toolbar); undo / redo diff --git a/app/layout.tsx b/app/layout.tsx index 4e4e866..4bb0655 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,7 +3,6 @@ import { Geist, Geist_Mono } from "next/font/google"; import Script from "next/script"; import { GoogleAnalytics } from "@next/third-parties/google"; import { Analytics } from "@vercel/analytics/next"; -import { isElectron } from "@/lib/platform"; import "./globals.css"; const geistSans = Geist({ @@ -16,13 +15,12 @@ const geistMono = Geist_Mono({ subsets: ["latin"], }); -const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; - -// Vercel Web Analytics is collected by the deployment at getrescript.com. -// Neither GitHub Pages nor the Electron app:// shell can serve the default -// /_vercel/insights paths, so point the SDK at absolute URLs there. Left unset -// in development so the SDK loads its debug script and logs to the console -// instead of reporting local traffic. +// Vercel Web Analytics for both the web app and the desktop shell is collected +// by the deployment at getrescript.com, so the two show up in one dashboard. +// The Electron app:// shell cannot serve the default /_vercel/insights paths at +// all, so the SDK is pointed at absolute URLs through that project's CORS +// proxy. Left unset in development so the SDK loads its debug script and logs +// to the console instead of reporting local traffic. const analyticsHost = process.env.NEXT_PUBLIC_VERCEL_ANALYTICS_HOST ?? "https://www.getrescript.com"; const analyticsProps = @@ -38,18 +36,18 @@ const description = "A fully offline, open-source transcript-based video editor. Transcribe with Whisper, cut by deleting words, export with ffmpeg — on your device."; export const metadata: Metadata = { - metadataBase: new URL("https://wassgha.github.io"), + metadataBase: new URL("https://app.getrescript.com"), title, description, openGraph: { type: "website", siteName: "Rescript", - url: `${basePath}/`, + url: "/", title, description, images: [ { - url: `${basePath}/og.png`, + url: "/og.png", width: 1200, height: 630, alt: "Rescript — a transcript-based video editor running in the browser", @@ -60,7 +58,7 @@ export const metadata: Metadata = { card: "summary_large_image", title, description, - images: [`${basePath}/og.png`], + images: ["/og.png"], }, }; @@ -84,17 +82,6 @@ export default function RootLayout({ strategy="beforeInteractive" dangerouslySetInnerHTML={{ __html: appearanceBootScript }} /> - {/* Provides COOP/COEP via a service worker on static hosts (GitHub - Pages) that can't send headers; no-op when the server already - sends them. Required for SharedArrayBuffer / multi-threading. - Skipped in the Electron shell — the app:// protocol sets the - headers directly. */} - {!isElectron && ( - + + + +
+

Rescript has moved

+

+ The editor is now at + app.getrescript.com. +

+
+ + diff --git a/scripts/copy-assets.mjs b/scripts/copy-assets.mjs index 732388f..7a000cf 100644 --- a/scripts/copy-assets.mjs +++ b/scripts/copy-assets.mjs @@ -102,24 +102,4 @@ function patchParakeetWasmPaths() { patchParakeetWasmPaths(); -// coi-serviceworker provides COOP/COEP headers on static hosts (GitHub Pages) -// that can't send them, keeping cross-origin isolation for SharedArrayBuffer. -// A config prelude is prepended: always use COEP "credentialless" (needed for -// Google Analytics) and skip registration when real headers already isolated -// the page (local dev / self-hosting with a proper server). -const coiSrc = join(root, "node_modules/coi-serviceworker/coi-serviceworker.js"); -const coiPrelude = - 'if (typeof window !== "undefined") {\n' + - " window.coi = {\n" + - " coepCredentialless: () => true,\n" + - " shouldRegister: () => !window.crossOriginIsolated,\n" + - " };\n" + - "}\n"; -writeFileSync( - join(root, "public/coi-serviceworker.js"), - coiPrelude + readFileSync(coiSrc, "utf8") -); - -console.log( - "[copy-assets] ffmpeg core + onnxruntime wasm + coi-serviceworker copied to public/" -); +console.log("[copy-assets] ffmpeg core + onnxruntime wasm copied to public/"); diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..649af40 --- /dev/null +++ b/vercel.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": "nextjs", + "buildCommand": "npm run export:web", + "headers": [ + { + "source": "/(.*)", + "headers": [ + { + "key": "Cross-Origin-Opener-Policy", + "value": "same-origin" + }, + { + "key": "Cross-Origin-Embedder-Policy", + "value": "credentialless" + } + ] + } + ] +} diff --git a/workers/transcription.worker.ts b/workers/transcription.worker.ts index bd7eac5..5402d11 100644 --- a/workers/transcription.worker.ts +++ b/workers/transcription.worker.ts @@ -49,9 +49,9 @@ import { import { isWebGpuDeviceLostError } from "@/lib/webgpu"; env.allowLocalModels = false; -const ORT_WASM_PATHS = `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/vendor/ort/`; +const ORT_WASM_PATHS = "/vendor/ort/"; /** Parakeet.js pins onnxruntime-web@1.24.1 — keep its WASM on a separate path. */ -const PARAKEET_ORT_WASM_PATHS = `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/vendor/ort-parakeet/`; +const PARAKEET_ORT_WASM_PATHS = "/vendor/ort-parakeet/"; // Serve onnxruntime-web WASM from our own origin (offline friendly). if (env.backends?.onnx?.wasm) { env.backends.onnx.wasm.wasmPaths = ORT_WASM_PATHS;