diff --git a/.env.example b/.env.example index 706aaf5..df58460 100644 --- a/.env.example +++ b/.env.example @@ -72,6 +72,5 @@ IMPORT_LIBRARY_DIRS= # (Required without Docker) Path to your local whisper.cpp CLI binary for STT timestamp generation WHISPER_CPP_BIN=/whisper.cpp/build/bin/whisper-cli -# (Optional) Override ffmpeg/ffprobe binary paths used for audiobook processing/probing +# (Optional) Override ffmpeg binary path used for audiobook processing FFMPEG_BIN= -FFPROBE_BIN= diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d33a290..2564e12 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -10,7 +10,6 @@ jobs: runs-on: ubuntu-latest env: FFMPEG_BIN: /usr/bin/ffmpeg - FFPROBE_BIN: /usr/bin/ffprobe USE_EMBEDDED_WEED_MINI: true BASE_URL: http://127.0.0.1:3003 S3_ENDPOINT: http://127.0.0.1:8333 diff --git a/Dockerfile b/Dockerfile index d985b1a..e279dce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,7 +45,7 @@ FROM node:lts-alpine AS runner # Add runtime OS dependencies: # - libreoffice-writer: required for DOCX → PDF conversion -# ffmpeg/ffprobe are provided by ffmpeg-static/ffprobe-static from node_modules. +# ffmpeg is provided by ffmpeg-static from node_modules. RUN apk add --no-cache ca-certificates libreoffice-writer # Install pnpm globally for running the app diff --git a/docs-site/docs/deploy/vercel-deployment.md b/docs-site/docs/deploy/vercel-deployment.md index a77ea2f..00a61bd 100644 --- a/docs-site/docs/deploy/vercel-deployment.md +++ b/docs-site/docs/deploy/vercel-deployment.md @@ -7,7 +7,7 @@ This guide covers deploying OpenReader WebUI to Vercel with external Postgres an ## What works on Vercel - Documents (PDF/EPUB/TXT/MD) work with `POSTGRES_URL` + external S3 storage. -- Audiobook routes work on Node.js serverless functions using `ffmpeg-static`/`ffprobe-static`. +- Audiobook routes work on Node.js serverless functions using `ffmpeg-static`. :::warning DOCX Conversion Limitation `docx` conversion requires `soffice` (LibreOffice), which is not available in a standard Vercel runtime. @@ -65,9 +65,9 @@ For internet-exposed Vercel deployments, set both `BASE_URL` and `AUTH_SECRET`. For all variables and defaults, see [Environment Variables](../reference/environment-variables). ::: -## 2. FFmpeg/ffprobe packaging in Vercel functions +## 2. FFmpeg packaging in Vercel functions -`ffmpeg-static` and `ffprobe-static` binaries must be included in function traces. This repo already does that in `next.config.ts` via `outputFileTracingIncludes` for: +`ffmpeg-static` binaries must be included in function traces. This repo already does that in `next.config.ts` via `outputFileTracingIncludes` for: - `/api/audiobook` - `/api/audiobook/chapter` @@ -75,7 +75,7 @@ For all variables and defaults, see [Environment Variables](../reference/environ - `/api/whisper` :::info -`serverExternalPackages` should include `ffmpeg-static` and `ffprobe-static` so package paths resolve at runtime instead of being bundled into route output. +`serverExternalPackages` should include `ffmpeg-static` so package paths resolve at runtime instead of being bundled into route output. ::: If you change route paths or split handlers, update `outputFileTracingIncludes` accordingly. diff --git a/docs-site/docs/reference/environment-variables.md b/docs-site/docs/reference/environment-variables.md index 5504bc1..59044bb 100644 --- a/docs-site/docs/reference/environment-variables.md +++ b/docs-site/docs/reference/environment-variables.md @@ -49,7 +49,6 @@ This is the single reference page for OpenReader WebUI environment variables. | `IMPORT_LIBRARY_DIRS` | Library import | unset | Set multiple roots (comma/colon/semicolon separated) | | `WHISPER_CPP_BIN` | Word timing | unset | Set to enable `whisper.cpp` timestamps | | `FFMPEG_BIN` | Audio runtime | auto-detected (`ffmpeg-static`) | Override ffmpeg binary path | -| `FFPROBE_BIN` | Audio runtime | auto-detected (`ffprobe-static`) | Override ffprobe binary path | ## Client Runtime and Feature Flags @@ -369,10 +368,3 @@ Absolute path or executable name for the ffmpeg binary used by audiobook/process - Resolution order: `FFMPEG_BIN` -> `ffmpeg-static` - Example: `/var/task/node_modules/ffmpeg-static/ffmpeg` - -### FFPROBE_BIN - -Absolute path or executable name for the ffprobe binary used for audio probing. - -- Resolution order: `FFPROBE_BIN` -> `ffprobe-static` -- Example: `/var/task/node_modules/ffprobe-static/bin/linux/x64/ffprobe` diff --git a/package.json b/package.json index 4d17007..3c84549 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "epubjs": "^0.3.93", "fast-xml-parser": "^5.3.5", "ffmpeg-static": "^5.3.0", - "ffprobe-static": "^3.1.0", "howler": "^2.2.4", "jszip": "^3.10.1", "lru-cache": "^11.2.6", @@ -65,7 +64,6 @@ "@playwright/test": "^1.58.2", "@tailwindcss/typography": "^0.5.19", "@types/better-sqlite3": "^7.6.13", - "@types/ffprobe-static": "^2.0.3", "@types/howler": "^2.2.12", "@types/node": "^20.19.33", "@types/pg": "^8.16.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09bc895..a5b09ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,9 +67,6 @@ importers: ffmpeg-static: specifier: ^5.3.0 version: 5.3.0 - ffprobe-static: - specifier: ^3.1.0 - version: 3.1.0 howler: specifier: ^2.2.4 version: 2.2.4 @@ -137,9 +134,6 @@ importers: '@types/better-sqlite3': specifier: ^7.6.13 version: 7.6.13 - '@types/ffprobe-static': - specifier: ^2.0.3 - version: 2.0.3 '@types/howler': specifier: ^2.2.12 version: 2.2.12 @@ -1395,9 +1389,6 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/ffprobe-static@2.0.3': - resolution: {integrity: sha512-86Q8dIzhjknxA6935ZcVLkVU3t6bP+F08mdriWZk2+3UqjxIbT3QEADjbO5Yq8826cVs/aZBREVZ7jUoSTgHiw==} - '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -2406,9 +2397,6 @@ packages: resolution: {integrity: sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==} engines: {node: '>=16'} - ffprobe-static@3.1.0: - resolution: {integrity: sha512-Dvpa9uhVMOYivhHKWLGDoa512J751qN1WZAIO+Xw4L/mrUSPxS4DApzSUDbCFE/LUq2+xYnznEahTd63AqBSpA==} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -5306,8 +5294,6 @@ snapshots: '@types/estree@1.0.8': {} - '@types/ffprobe-static@2.0.3': {} - '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -6397,8 +6383,6 @@ snapshots: transitivePeerDependencies: - supports-color - ffprobe-static@3.1.0: {} - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 diff --git a/scripts/migrate-fs-v2.mjs b/scripts/migrate-fs-v2.mjs index 5196252..d59752e 100644 --- a/scripts/migrate-fs-v2.mjs +++ b/scripts/migrate-fs-v2.mjs @@ -15,7 +15,7 @@ import { const require = createRequire(import.meta.url); const { Pool } = require('pg'); const BetterSqlite3 = require('better-sqlite3'); -const ffprobeStatic = require('ffprobe-static'); +const ffmpegStatic = require('ffmpeg-static'); const DOCSTORE_DIR = path.join(process.cwd(), 'docstore'); const DOCUMENTS_V1_DIR = path.join(DOCSTORE_DIR, 'documents_v1'); @@ -321,34 +321,29 @@ function chooseBinary(preferred, bundled, envVarName, packageName) { return bundledValue; } -function getFFprobePath() { - return chooseBinary(process.env.FFPROBE_BIN || null, ffprobeStatic?.path || null, 'FFPROBE_BIN', 'ffprobe-static'); +function getFFmpegPath() { + return chooseBinary(process.env.FFMPEG_BIN || null, ffmpegStatic || null, 'FFMPEG_BIN', 'ffmpeg-static'); } async function ffprobeTitleTag(filePath) { return new Promise((resolve) => { - const child = spawn(getFFprobePath(), [ - '-v', 'quiet', - '-print_format', 'json', - '-show_entries', 'format_tags=title', - filePath, + const child = spawn(getFFmpegPath(), [ + '-i', filePath, + '-f', 'ffmetadata', + '-', ]); - let output = ''; - child.stdout.on('data', (data) => { output += data.toString(); }); + let stdout = ''; + child.stdout.on('data', (data) => { stdout += data.toString(); }); child.on('error', () => resolve(null)); - child.on('close', (code) => { - if (code !== 0) { + child.on('close', () => { + const line = stdout.split(/\r?\n/).find((l) => l.startsWith('title=')); + if (!line) { resolve(null); return; } - try { - const parsed = JSON.parse(output); - const title = parsed?.format?.tags?.title; - resolve(typeof title === 'string' ? title : null); - } catch { - resolve(null); - } + const raw = line.slice('title='.length).trim(); + resolve(raw.length > 0 ? raw : null); }); }); }