Files
openmaic/OpenMAIC/app/api/export-video/render/[jobId]/download/route.ts
T

67 lines
2.7 KiB
TypeScript

import { NextResponse, type NextRequest } from 'next/server';
import { apiError } from '@/lib/server/api-response';
import { proxyFetch } from '@/lib/server/proxy-fetch';
import { resolveRenderServiceUrl } from '@/lib/server/render-service';
import { createLogger } from '@/lib/logger';
const log = createLogger('ExportVideo Download API');
export const dynamic = 'force-dynamic';
/**
* Stream the rendered MP4 back to the browser.
*
* Uses `redirect: 'manual'` so that if the render service's artifact store
* returns a `302` (a presigned object-storage URL, in a demo-scale deployment)
* we pass that redirect straight through and the browser downloads directly
* from storage — bypassing this proxy. The OSS default (local-disk artifacts)
* streams the bytes through here.
*/
export async function GET(req: NextRequest, context: { params: Promise<{ jobId: string }> }) {
const { jobId } = await context.params;
const resolved = resolveRenderServiceUrl();
if ('error' in resolved) {
return apiError('PROVIDER_DISABLED', 501, 'Render service is not configured');
}
// Bound only the time to obtain the response headers — NOT the body stream.
// A total-duration timeout would truncate a large MP4 over a slow connection,
// so we abort just the initial fetch and clear the timer once headers arrive.
const controller = new AbortController();
const headerTimeout = setTimeout(() => controller.abort(), 30_000);
try {
const upstream = await proxyFetch(
`${resolved.url}/render/${encodeURIComponent(jobId)}/download`,
{ method: 'GET', redirect: 'manual', signal: controller.signal },
);
clearTimeout(headerTimeout);
// Presigned-URL artifact store: hand the redirect to the browser.
if (upstream.status === 302 || upstream.status === 301) {
const location = upstream.headers.get('location');
if (location) return NextResponse.redirect(location, 302);
}
if (!upstream.ok || !upstream.body) {
const status = upstream.status === 404 || upstream.status === 409 ? upstream.status : 502;
return apiError('UPSTREAM_ERROR', status, 'Render output not available');
}
return new NextResponse(upstream.body, {
status: 200,
headers: {
'Content-Type': 'video/mp4',
...(upstream.headers.get('content-length')
? { 'Content-Length': upstream.headers.get('content-length')! }
: {}),
'Content-Disposition': `attachment; filename="${jobId}.mp4"`,
'Cache-Control': 'private, no-store',
},
});
} catch (error) {
clearTimeout(headerTimeout);
log.error(`Failed to download render output ${jobId}:`, error);
return apiError('UPSTREAM_ERROR', 502, 'Failed to reach render service');
}
}