| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- import { NextResponse } from "next/server";
- import { createJob, updateJob } from "@/lib/job-store";
- import { loadConfig } from "@/lib/config";
- import { resolveOutputDir } from "@pipeline/shared/node";
- import { spawn } from "node:child_process";
- import { writeFileSync, mkdirSync, statSync } from "node:fs";
- import { join, resolve } from "node:path";
- import { tmpdir } from "node:os";
- export async function POST(request: Request) {
- const body = await request.json();
- const { input, options } = body as {
- input: {
- text?: string;
- source?: string;
- sourceArgs?: Record<string, string>;
- template: string;
- platform?: string;
- platforms?: string[];
- ttsProvider: string;
- voiceId?: string;
- };
- options?: {
- noTts?: boolean;
- };
- };
- const platforms: string[] = input.platforms
- ?? (input.platform ? input.platform.split(",").map(p => p.trim()) : ["bilibili"]);
- if (!input.text && !input.source) {
- return NextResponse.json({ error: "text or source is required" }, { status: 400 });
- }
- const job = createJob({ ...input, text: input.text ?? "", platforms });
- const jobDir = join(tmpdir(), "pipeline-jobs", job.id);
- mkdirSync(jobDir, { recursive: true });
- // Same unified output directory the CLI resolves (OUTPUT_DIR > config > ./output).
- const config = loadConfig();
- const outputDir = resolveOutputDir(config?.output?.dir);
- const cliArgs = [
- "render",
- "-t", input.template,
- "-p", platforms.join(","),
- "--tts-provider", input.ttsProvider,
- "--output", outputDir,
- ];
- if (input.source) {
- cliArgs.push("--source", input.source);
- if (input.sourceArgs?.owner) cliArgs.push("--source-owner", input.sourceArgs.owner);
- if (input.sourceArgs?.repo) cliArgs.push("--source-repo", input.sourceArgs.repo);
- } else {
- const inputFile = join(jobDir, "input.json");
- writeFileSync(inputFile, input.text!, "utf-8");
- cliArgs.splice(1, 0, inputFile);
- }
- if (input.voiceId) {
- cliArgs.push("--voice", input.voiceId);
- }
- if (options?.noTts) {
- cliArgs.push("--no-tts");
- }
- const cliPath = resolve(process.cwd(), "../cli/dist/index.js");
- const projectRoot = resolve(process.cwd(), "..");
- updateJob(job.id, { status: "running" });
- const child = spawn("node", [cliPath, ...cliArgs], {
- cwd: projectRoot,
- stdio: ["ignore", "pipe", "pipe"],
- env: { ...process.env },
- });
- let stdout = "";
- let stderr = "";
- child.stdout.on("data", (data: Buffer) => {
- stdout += data.toString();
- });
- child.stderr.on("data", (data: Buffer) => {
- stderr += data.toString();
- });
- child.on("close", (code: number) => {
- if (code === 0) {
- const mp4Matches = [...stdout.matchAll(/-> (.+\.mp4)/g)];
- const filePaths = mp4Matches.map(m => m[1].trim());
- const outputFiles = filePaths.map(filePath => {
- let fileSizeBytes = 0;
- try { fileSizeBytes = statSync(filePath).size; } catch {}
- const isPortrait = filePath.includes("douyin") || filePath.includes("9x16");
- return {
- filePath,
- fileSizeBytes,
- width: isPortrait ? 1080 : 1920,
- height: isPortrait ? 1920 : 1080,
- durationSeconds: 0,
- };
- });
- const ossUrls = [...stdout.matchAll(/^[ \t]*oss: (\S+)/gm)].map(m => m[1].trim());
- const publishWarn = stdout.match(/^Publish warning: (.+)$/m);
- const publishError = publishWarn ? publishWarn[1].trim() : undefined;
- updateJob(job.id, {
- status: "completed",
- outputFiles: outputFiles.length > 0 ? outputFiles : undefined,
- ossUrls: ossUrls.length > 0 ? ossUrls : undefined,
- publishError,
- });
- } else {
- updateJob(job.id, {
- status: "failed",
- error: stderr.trim() || `Process exited with code ${code}`,
- });
- }
- });
- child.on("error", (err: Error) => {
- updateJob(job.id, {
- status: "failed",
- error: err.message,
- });
- });
- return NextResponse.json({ jobId: job.id, status: "started" });
- }
|