fix(audio): remove speed from TTS/cache/MinIO keys; fix presigned URL host rewrite

- Speed is no longer part of Kokoro generation, in-memory cache keys, or
  MinIO object keys — audio is always generated at 1.0 and playback speed
  is applied client-side via audioEl.playbackRate
- presignAudio() now calls rewriteHost() so chapter audio URLs use the
  public MinIO endpoint (same as presignVoiceSample already did)
- docker-compose.yml: rename MINIO_PUBLIC_ENDPOINT → PUBLIC_MINIO_PUBLIC_URL
  for the ui service so SvelteKit's $env/dynamic/public picks it up
This commit is contained in:
Admin
2026-03-04 19:30:46 +05:00
parent c8e0cf2813
commit acbfafb8cd
9 changed files with 45 additions and 72 deletions

View File

@@ -10,9 +10,9 @@ const SCRAPER_URL = env.SCRAPER_API_URL ?? 'http://localhost:8080';
* Proxies the audio generation request to the scraper's /api/audio endpoint.
* Keeps the scraper URL server-side — the browser never needs to know it.
*
* Body: { voice?: string, speed?: number }
* Body: { voice?: string }
* Response: { url: string, filename: string }
* where `url` is a relative path to GET /api/audio/[slug]/[n]?voice=...&speed=...
* where `url` is a relative path to GET /api/audio/[slug]/[n]?voice=...
*/
export const POST: RequestHandler = async ({ params, request }) => {
const { slug, n } = params;
@@ -21,7 +21,7 @@ export const POST: RequestHandler = async ({ params, request }) => {
error(400, 'Invalid slug or chapter number');
}
let body: { voice?: string; speed?: number } = {};
let body: { voice?: string } = {};
try {
body = await request.json();
} catch {
@@ -45,10 +45,8 @@ export const POST: RequestHandler = async ({ params, request }) => {
// The scraper returns a proxy URL pointing to /api/audio-proxy/... — we rewrite
// it to our own /api/audio/[slug]/[n]?... so the browser never calls the scraper directly.
const voice = body.voice ?? '';
const speed = body.speed ?? 1.0;
const qs = new URLSearchParams();
if (voice) qs.set('voice', voice);
qs.set('speed', String(speed));
return new Response(
JSON.stringify({
@@ -60,7 +58,7 @@ export const POST: RequestHandler = async ({ params, request }) => {
};
/**
* GET /api/audio/[slug]/[n]?voice=...&speed=...
* GET /api/audio/[slug]/[n]?voice=...
* Proxies the audio stream from the scraper's /api/audio-proxy endpoint.
* This is the URL the browser's <audio> element uses as its src.
*/
@@ -72,10 +70,8 @@ export const GET: RequestHandler = async ({ params, url }) => {
}
const voice = url.searchParams.get('voice') ?? '';
const speed = url.searchParams.get('speed') ?? '1';
const qs = new URLSearchParams();
if (voice) qs.set('voice', voice);
qs.set('speed', speed);
const scraperRes = await fetch(`${SCRAPER_URL}/api/audio-proxy/${slug}/${chapter}?${qs.toString()}`);

View File

@@ -4,7 +4,7 @@ import { presignAudio } from '$lib/server/minio';
import { log } from '$lib/server/logger';
/**
* GET /api/presign/audio?slug=...&n=...&voice=...&speed=...
* GET /api/presign/audio?slug=...&n=...&voice=...
* Returns a presigned MinIO URL for the audio file so the browser
* can stream it directly without going through the server.
* Returns 404 when the audio has not been generated yet.
@@ -13,14 +13,13 @@ export const GET: RequestHandler = async ({ url }) => {
const slug = url.searchParams.get('slug');
const n = parseInt(url.searchParams.get('n') ?? '', 10);
const voice = url.searchParams.get('voice') ?? undefined;
const speed = parseFloat(url.searchParams.get('speed') ?? '1') || 1;
if (!slug || !n || n < 1) {
error(400, 'Missing slug or n');
}
try {
const presignedUrl = await presignAudio(slug, n, voice, speed);
const presignedUrl = await presignAudio(slug, n, voice);
return json({ url: presignedUrl });
} catch (e) {
const status = (e as { status?: number }).status;