| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715 |
- "use strict";
- /**
- * 书籍生成服务 - 核心业务逻辑
- * 使用 Prisma 数据库存储
- */
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- var desc = Object.getOwnPropertyDescriptor(m, k);
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
- desc = { enumerable: true, get: function() { return m[k]; } };
- }
- Object.defineProperty(o, k2, desc);
- }) : (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- o[k2] = m[k];
- }));
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
- Object.defineProperty(o, "default", { enumerable: true, value: v });
- }) : function(o, v) {
- o["default"] = v;
- });
- var __importStar = (this && this.__importStar) || (function () {
- var ownKeys = function(o) {
- ownKeys = Object.getOwnPropertyNames || function (o) {
- var ar = [];
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
- return ar;
- };
- return ownKeys(o);
- };
- return function (mod) {
- if (mod && mod.__esModule) return mod;
- var result = {};
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
- __setModuleDefault(result, mod);
- return result;
- };
- })();
- var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
- };
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.bookGeneratorService = exports.BookGeneratorService = void 0;
- const axios_1 = __importDefault(require("axios"));
- const config_1 = require("../../config");
- const models_1 = require("../../models");
- const TtsService = __importStar(require("../tts/tts.service"));
- const VideoService = __importStar(require("../video-generator/video-generator.service"));
- const book_generator_store_1 = require("./book-generator.store");
- // ============ 默认配置 ============
- const DEFAULT_CONFIG = {
- model: 'qwen3.6-plus',
- temperature: 0.7,
- maxTokens: 4000,
- chapterWordRange: {
- min: 800,
- max: 2000,
- },
- retryPolicy: {
- maxRetries: 3,
- retryDelay: 2000,
- },
- };
- // ============ 提示词模板 ============
- const PROMPT_TEMPLATES = {
- systemPrompt: `你是专业的书籍作者,擅长撰写结构严谨、内容丰富、通俗易懂的作品。`,
- outlinePrompt: `
- 请为以下书籍生成详细的章节大纲。
- ## 书籍信息
- - 书名:{title}
- - 副标题:{subtitle}
- - 主题:{description}
- - 目标受众:{targetAudience}
- - 风格:{style}
- - 章节数:{totalChapters}章
- ## 输出要求
- 请生成JSON格式的大纲,包含:
- 1. mainTheme: 本书的核心主题
- 2. structureLogic: 整体结构逻辑
- 3. chapters: 章节大纲数组,每个章节包含:
- - number: 章节序号
- - title: 章节标题
- - summary: 章节概述(1-2句话)
- - keyPoints: 核心知识点(3-5个)
- - estimatedWords: 预估字数
- 请直接输出JSON,不要其他内容:
- `,
- chapterPrompt: (chapter, context) => `
- 请撰写书籍《{bookTitle}》第${chapter.number}章的完整内容。
- ## 章节信息
- - 章节标题:${chapter.title}
- - 章节概述:${chapter.summary}
- - 核心知识点:
- ${chapter.keyPoints.map((p, i) => ` ${i + 1}. ${p}`).join('\n')}
- - 预估字数:${chapter.estimatedWords}字
- ## 全书上下文
- - 主题主线:${context.mainTheme}
- - 结构逻辑:${context.structureLogic}
- ## 写作要求
- 1. 语言通俗易懂,适合目标受众
- 2. 包含引入、正文、总结三个部分
- 3. 适当使用小标题划分内容
- 4. 长度控制在${chapter.estimatedWords}字左右
- 5. 使用markdown格式输出
- 请直接输出正文内容:
- `,
- forewordPrompt: (book) => `
- 请为书籍《${book.title}》撰写前言。
- 主题:${book.description}
- 目标受众:${book.targetAudience}
- 写作风格:${book.style}
- 长度:300-500字
- 请直接输出前言内容:
- `,
- afterwordPrompt: (book) => `
- 请为书籍《${book.title}》撰写后记。
- 主题:${book.description}
- 写作风格:${book.style}
- 长度:300-500字
- 请直接输出后记内容:
- `,
- };
- // ============ 核心服务类 ============
- class BookGeneratorService {
- apiKey = '';
- config;
- constructor(config) {
- this.config = { ...DEFAULT_CONFIG, ...config };
- }
- setApiKey(apiKey) {
- this.apiKey = apiKey;
- }
- // ============ 书籍 CRUD ============
- async createBook(request) {
- return book_generator_store_1.bookStore.create({
- title: request.title,
- subtitle: request.subtitle,
- description: request.description,
- targetAudience: request.targetAudience,
- style: request.style,
- totalChapters: request.totalChapters,
- });
- }
- async getBook(id) {
- return book_generator_store_1.bookStore.getById(id);
- }
- async getAllBooks(userId) {
- return book_generator_store_1.bookStore.getAllByUser(userId);
- }
- async deleteBook(id) {
- return book_generator_store_1.bookStore.delete(id);
- }
- // ============ 生成大纲 ============
- async generateOutline(bookId) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- await book_generator_store_1.bookStore.update(bookId, { status: 'planning' });
- try {
- const outlinePrompt = this.buildOutlinePrompt(book);
- const outlineJson = await this.callLLM(outlinePrompt);
- const outline = this.parseOutline(outlineJson);
- await book_generator_store_1.bookStore.update(bookId, {
- outlineJson: JSON.stringify(outline),
- estimatedWords: outline.chapters.reduce((sum, c) => sum + c.estimatedWords, 0),
- });
- // 创建章节记录
- await book_generator_store_1.bookStore.createChapters(bookId, outline.chapters.map(c => ({
- number: c.number,
- title: c.title,
- summary: c.summary,
- keyPoints: c.keyPoints,
- estimatedWords: c.estimatedWords,
- })));
- return outline;
- }
- catch (error) {
- await book_generator_store_1.bookStore.update(bookId, { status: 'failed', errorMsg: error instanceof Error ? error.message : '生成失败' });
- throw error;
- }
- }
- // ============ 生成章节 ============
- async generateChapter(bookId, chapterNumber) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- if (!book.outline)
- throw new Error('请先生成大纲');
- const outlineChapter = book.outline.chapters.find((c) => c.number === chapterNumber);
- if (!outlineChapter)
- throw new Error(`第${chapterNumber}章不存在`);
- await book_generator_store_1.bookStore.update(bookId, { status: 'generating' });
- try {
- const chapterPrompt = this.buildChapterPrompt(book, outlineChapter);
- const content = await this.callLLM(chapterPrompt);
- const wordCount = this.countWords(content);
- const chapter = await book_generator_store_1.bookStore.updateChapter(bookId, chapterNumber, {
- content,
- wordCount,
- status: 'completed',
- });
- // 更新进度
- const completedCount = await book_generator_store_1.bookStore.countCompletedChapters(bookId);
- const progress = Math.round((completedCount / book.totalChapters) * 100);
- await book_generator_store_1.bookStore.update(bookId, {
- progress,
- status: progress >= 100 ? 'completed' : 'generating',
- });
- return chapter;
- }
- catch (error) {
- await book_generator_store_1.bookStore.updateChapter(bookId, chapterNumber, {
- status: 'failed',
- errorMsg: error instanceof Error ? error.message : '生成失败',
- });
- throw error;
- }
- }
- async generateAllChapters(bookId) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- if (!book.outline)
- throw new Error('请先生成大纲');
- const results = [];
- for (const outlineChapter of book.outline.chapters) {
- const existingChapter = book.chapters.find((c) => c.number === outlineChapter.number);
- if (existingChapter?.status === 'completed') {
- results.push(existingChapter);
- continue;
- }
- try {
- const chapter = await this.generateChapter(bookId, outlineChapter.number);
- results.push(chapter);
- }
- catch (error) {
- console.error(`生成第${outlineChapter.number}章失败:`, error);
- }
- }
- return results;
- }
- // ============ 前言/后记 ============
- async generateForeword(bookId) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- try {
- const prompt = PROMPT_TEMPLATES.forewordPrompt(book);
- const foreword = await this.callLLM(prompt);
- await book_generator_store_1.bookStore.update(bookId, { foreword });
- return foreword;
- }
- catch (error) {
- throw new Error(`生成前言失败: ${error instanceof Error ? error.message : error}`);
- }
- }
- async generateAfterword(bookId) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- try {
- const prompt = PROMPT_TEMPLATES.afterwordPrompt(book);
- const afterword = await this.callLLM(prompt);
- await book_generator_store_1.bookStore.update(bookId, { afterword });
- return afterword;
- }
- catch (error) {
- throw new Error(`生成后记失败: ${error instanceof Error ? error.message : error}`);
- }
- }
- // ============ 内容获取 ============
- async getFullContent(bookId) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- const parts = [];
- parts.push(`# ${book.title}`);
- if (book.subtitle)
- parts.push(`## ${book.subtitle}`);
- if (book.metadata?.foreword) {
- parts.push('\n## 前言\n');
- parts.push(book.metadata.foreword);
- }
- if (book.outline) {
- parts.push('\n## 目录\n');
- book.outline.chapters.forEach((ch) => {
- parts.push(`${ch.number}. ${ch.title}`);
- });
- }
- book.chapters.forEach((ch) => {
- parts.push(`\n## 第${ch.number}章 ${ch.title}\n`);
- parts.push(ch.content);
- });
- if (book.metadata?.afterword) {
- parts.push('\n## 后记\n');
- parts.push(book.metadata.afterword);
- }
- return parts.join('\n');
- }
- async getProgress(bookId) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- return null;
- const completedChapters = book.chapters.filter((c) => c.status === 'completed').length;
- return {
- bookId,
- status: book.status,
- progress: book.progress,
- completedChapters,
- totalChapters: book.totalChapters,
- };
- }
- // ============ 一键生成(同步阻塞) ============
- async generateBook(bookId, options) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- return { success: false, completedChapters: 0, failedChapters: 0, errors: ['书籍不存在'] };
- const errors = [];
- try {
- if (!book.outline) {
- await this.generateOutline(bookId);
- }
- }
- catch (error) {
- return { success: false, completedChapters: 0, failedChapters: 0, errors: [`大纲: ${error instanceof Error ? error.message : error}`] };
- }
- const results = [];
- let failedCount = 0;
- for (const outlineChapter of book.outline?.chapters || []) {
- try {
- const chapter = await this.generateChapter(bookId, outlineChapter.number);
- results.push(chapter);
- options?.onProgress?.(Math.round((results.length / book.totalChapters) * 100), outlineChapter.number);
- }
- catch (error) {
- failedCount++;
- errors.push(`第${outlineChapter.number}章: ${error instanceof Error ? error.message : error}`);
- }
- }
- if (options?.generateForeword) {
- try {
- await this.generateForeword(bookId);
- }
- catch (error) {
- errors.push(`前言: ${error instanceof Error ? error.message : error}`);
- }
- }
- if (options?.generateAfterword) {
- try {
- await this.generateAfterword(bookId);
- }
- catch (error) {
- errors.push(`后记: ${error instanceof Error ? error.message : error}`);
- }
- }
- const updatedBook = await book_generator_store_1.bookStore.getById(bookId);
- return {
- success: failedCount === 0 && errors.length === 0,
- book: updatedBook || undefined,
- completedChapters: results.length,
- failedChapters: failedCount,
- errors,
- };
- }
- // ============ 一键生成(异步不阻塞) ============
- async generateBookAsync(bookId, options) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- return;
- try {
- await book_generator_store_1.bookStore.update(bookId, { status: 'generating', progress: 0 });
- if (!book.outline) {
- await this.generateOutline(bookId);
- }
- // 生成章节
- for (const outlineChapter of book.outline?.chapters || []) {
- try {
- await this.generateChapter(bookId, outlineChapter.number);
- const updatedBook = await book_generator_store_1.bookStore.getById(bookId);
- await book_generator_store_1.bookStore.update(bookId, { progress: updatedBook.progress });
- }
- catch (error) {
- console.error(`生成第${outlineChapter.number}章失败:`, error);
- }
- }
- if (options?.generateForeword) {
- await this.generateForeword(bookId);
- }
- if (options?.generateAfterword) {
- await this.generateAfterword(bookId);
- }
- await book_generator_store_1.bookStore.update(bookId, { status: 'completed', progress: 100 });
- }
- catch (error) {
- await book_generator_store_1.bookStore.update(bookId, { status: 'failed', errorMsg: error instanceof Error ? error.message : '生成失败' });
- }
- }
- // ============ 私有方法 ============
- buildOutlinePrompt(book) {
- return PROMPT_TEMPLATES.outlinePrompt
- .replace('{title}', book.title)
- .replace('{subtitle}', book.subtitle || '无')
- .replace('{description}', book.description)
- .replace('{targetAudience}', book.targetAudience)
- .replace('{style}', book.style)
- .replace('{totalChapters}', String(book.totalChapters));
- }
- buildChapterPrompt(book, chapter) {
- if (!book.outline)
- throw new Error('书籍大纲不存在');
- return PROMPT_TEMPLATES.chapterPrompt(chapter, book.outline).replace('{bookTitle}', book.title);
- }
- parseOutline(jsonStr) {
- try {
- const jsonMatch = jsonStr.match(/\{[\s\S]*\}/);
- if (jsonMatch) {
- return this.validateOutline(JSON.parse(jsonMatch[0]));
- }
- }
- catch {
- console.error('解析大纲 JSON 失败');
- }
- return this.createDefaultOutline();
- }
- validateOutline(data) {
- if (!data.chapters || !Array.isArray(data.chapters)) {
- throw new Error('大纲格式不正确');
- }
- return {
- mainTheme: data.mainTheme || '主题待定',
- structureLogic: data.structureLogic || '由浅入深',
- chapters: data.chapters.map((c, i) => ({
- number: c.number || i + 1,
- title: c.title || `第${i + 1}章`,
- summary: c.summary || '',
- keyPoints: c.keyPoints || [],
- estimatedWords: c.estimatedWords || 1000,
- stories: c.stories || [],
- })),
- };
- }
- createDefaultOutline() {
- return {
- mainTheme: '核心主题',
- structureLogic: '由浅入深',
- chapters: [{ number: 1, title: '概述', summary: '介绍', keyPoints: ['基础概念'], estimatedWords: 1000 }],
- };
- }
- countWords(text) {
- return (text.match(/[\u4e00-\u9fa5]/g) || []).length;
- }
- async callLLM(prompt, retryCount = 0, currentModel) {
- const modelId = currentModel || this.config.model;
- // 从模型配置获取 API Key 和 URL
- const modelConfig = config_1.config.models.getModel(modelId);
- if (!modelConfig?.apiKey || !modelConfig?.baseUrl) {
- throw new Error(`模型 ${modelId} 缺少 API 配置`);
- }
- const { apiKey, baseUrl } = modelConfig;
- try {
- const response = await axios_1.default.post(`${baseUrl}/chat/completions`, {
- model: modelId,
- messages: [{ role: 'user', content: prompt }],
- }, {
- headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
- timeout: 120000,
- });
- const result = response.data?.choices?.[0]?.message?.content || '';
- if (!result)
- throw new Error('API 返回内容为空');
- return result;
- }
- catch (error) {
- const errorMessage = error.response?.data?.message || error.message || 'API 调用失败';
- console.log(`[LLM] 模型 ${modelId} 调用失败: ${errorMessage}`);
- // 检查是否需要切换模型
- if (config_1.config.models.shouldSwitchModel(errorMessage)) {
- const nextModel = config_1.config.models.getNextModel(modelId, 'text');
- if (nextModel) {
- console.log(`[LLM] 自动切换到模型: ${nextModel}`);
- return this.callLLM(prompt, 0, nextModel); // 重置 retryCount
- }
- }
- // 重试当前模型
- if (retryCount < this.config.retryPolicy.maxRetries) {
- await this.delay(this.config.retryPolicy.retryDelay);
- return this.callLLM(prompt, retryCount + 1, currentModel);
- }
- throw new Error(errorMessage);
- }
- }
- delay(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
- // ============ 音频生成 ============
- /**
- * 生成单个章节音频
- */
- async generateChapterAudio(bookId, chapterNumber, voiceId) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- const chapter = book.chapters.find((c) => c.number === chapterNumber);
- if (!chapter)
- throw new Error(`第${chapterNumber}章不存在`);
- if (!chapter.content || chapter.content.trim().length === 0) {
- throw new Error('章节内容为空,请先生成章节内容');
- }
- // 从数据库获取章节记录
- const dbChapter = await models_1.prisma.bookChapter.findFirst({
- where: {
- bookId: parseInt(bookId),
- number: chapterNumber,
- },
- });
- if (!dbChapter)
- throw new Error('章节数据库记录不存在');
- const taskId = `audio_${bookId}_${chapterNumber}_${Date.now()}`;
- // 异步生成音频
- this.processChapterAudio(dbChapter.id, chapter.content, voiceId).catch((err) => {
- console.error(`章节 ${chapterNumber} 音频生成失败:`, err);
- });
- return { taskId, chapterId: String(dbChapter.id) };
- }
- /**
- * 批量生成书籍所有章节音频
- */
- async generateAllChaptersAudio(bookId, voiceId) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- const completedChapters = book.chapters.filter((c) => c.status === 'completed' && c.content && c.content.trim().length > 0);
- if (completedChapters.length === 0) {
- throw new Error('没有可生成音频的章节,请先生成章节内容');
- }
- const taskId = `audio_book_${bookId}_${Date.now()}`;
- // 异步批量生成音频
- this.processAllChaptersAudio(bookId, completedChapters, voiceId).catch((err) => {
- console.error(`书籍 ${bookId} 批量音频生成失败:`, err);
- });
- return { taskId, totalChapters: completedChapters.length };
- }
- /**
- * 处理单个章节音频生成
- */
- async processChapterAudio(chapterId, content, voiceId) {
- try {
- console.log(`🎵 开始生成章节 ${chapterId} 音频...`);
- // 清理内容(移除 markdown 格式)
- const cleanContent = content
- .replace(/^#{1,6}\s+/gm, '') // 移除标题标记
- .replace(/\*\*(.*?)\*\*/g, '$1') // 移除粗体
- .replace(/\*(.*?)\*/g, '$1') // 移除斜体
- .replace(/`(.*?)`/g, '$1') // 移除行内代码
- .replace(/^\s*[-*+]\s+/gm, '') // 移除列表标记
- .replace(/^\s*\d+\.\s+/gm, '') // 移除数字列表标记
- .trim();
- // 调用 TTS 服务生成音频
- const result = await TtsService.generateAudio('system', cleanContent, voiceId, { speed: 1.0, pitch: 0, volume: 50 }, async (audioUrl, duration) => {
- // 音频生成完成后更新数据库
- await models_1.prisma.bookChapter.update({
- where: { id: chapterId },
- data: {
- audioUrl,
- audioDuration: duration,
- },
- });
- console.log(`✅ 章节 ${chapterId} 音频生成完成: ${audioUrl}`);
- });
- console.log(`🎵 章节 ${chapterId} 音频任务已启动: ${result.audioId}`);
- }
- catch (error) {
- console.error(`❌ 章节 ${chapterId} 音频生成失败:`, error);
- throw error;
- }
- }
- /**
- * 处理批量章节音频生成
- */
- async processAllChaptersAudio(bookId, chapters, voiceId) {
- console.log(`🎵 开始批量生成书籍 ${bookId} 的 ${chapters.length} 个章节音频...`);
- // 从数据库获取所有章节记录
- const dbChapters = await models_1.prisma.bookChapter.findMany({
- where: { bookId: parseInt(bookId) },
- });
- for (const chapter of chapters) {
- const dbChapter = dbChapters.find((c) => c.number === chapter.number);
- if (dbChapter && chapter.content) {
- try {
- await this.processChapterAudio(dbChapter.id, chapter.content, voiceId);
- // 每个章节之间稍作延迟,避免请求过于密集
- await this.delay(1000);
- }
- catch (error) {
- console.error(`第${chapter.number}章音频生成失败,继续下一个:`, error);
- }
- }
- }
- console.log(`✅ 书籍 ${bookId} 批量音频生成任务完成`);
- }
- // ============ 视频生成 ============
- /**
- * 生成单个章节视频(从音频转视频)
- */
- async generateChapterVideo(bookId, chapterNumber) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- const chapter = book.chapters.find((c) => c.number === chapterNumber);
- if (!chapter)
- throw new Error(`第${chapterNumber}章不存在`);
- if (!chapter.audioUrl) {
- throw new Error('章节音频不存在,请先生成音频');
- }
- // 获取数据库中的章节记录
- const dbChapter = await models_1.prisma.bookChapter.findFirst({
- where: {
- bookId: parseInt(bookId),
- number: chapterNumber,
- },
- });
- if (!dbChapter)
- throw new Error('章节数据库记录不存在');
- // 创建视频项目并生成
- const videoProject = await VideoService.createVideoProjectFromBook(parseInt(bookId), dbChapter.id);
- if (!videoProject) {
- throw new Error('创建视频项目失败');
- }
- // 异步生成视频
- this.processChapterVideo(videoProject.id).catch((err) => {
- console.error(`章节 ${chapterNumber} 视频生成失败:`, err);
- });
- return { projectId: videoProject.id, chapterId: String(dbChapter.id) };
- }
- /**
- * 批量生成书籍所有章节视频
- */
- async generateAllChaptersVideo(bookId) {
- const book = await book_generator_store_1.bookStore.getById(bookId);
- if (!book)
- throw new Error('书籍不存在');
- const chaptersWithAudio = book.chapters.filter((c) => c.status === 'completed' && c.audioUrl);
- if (chaptersWithAudio.length === 0) {
- throw new Error('没有可生成视频的章节(需要先有音频)');
- }
- const taskId = `video_book_${bookId}_${Date.now()}`;
- // 异步批量生成视频
- this.processAllChaptersVideo(bookId, chaptersWithAudio).catch((err) => {
- console.error(`书籍 ${bookId} 批量视频生成失败:`, err);
- });
- return { taskId, totalChapters: chaptersWithAudio.length };
- }
- /**
- * 处理单个章节视频生成
- */
- async processChapterVideo(projectId) {
- try {
- console.log(`🎬 开始生成视频项目 ${projectId}...`);
- const result = await VideoService.generateVideoForProject(projectId);
- if (result.success) {
- console.log(`✅ 视频项目 ${projectId} 生成完成: ${result.outputUrl}`);
- }
- else {
- console.error(`❌ 视频项目 ${projectId} 生成失败: ${result.error}`);
- }
- }
- catch (error) {
- console.error(`❌ 视频项目 ${projectId} 生成失败:`, error);
- throw error;
- }
- }
- /**
- * 处理批量章节视频生成
- */
- async processAllChaptersVideo(bookId, chapters) {
- console.log(`🎬 开始批量生成书籍 ${bookId} 的 ${chapters.length} 个章节视频...`);
- for (const chapter of chapters) {
- try {
- const dbChapter = await models_1.prisma.bookChapter.findFirst({
- where: {
- bookId: parseInt(bookId),
- number: chapter.number,
- },
- });
- if (!dbChapter || !chapter.audioUrl) {
- console.warn(`第${chapter.number}章跳过:缺少音频或数据库记录`);
- continue;
- }
- // 创建视频项目
- const videoProject = await VideoService.createVideoProjectFromBook(parseInt(bookId), dbChapter.id);
- if (videoProject) {
- // 生成视频
- await this.processChapterVideo(videoProject.id);
- }
- // 每个视频之间稍作延迟
- await this.delay(2000);
- }
- catch (error) {
- console.error(`第${chapter.number}章视频生成失败,继续下一个:`, error);
- }
- }
- console.log(`✅ 书籍 ${bookId} 批量视频生成任务完成`);
- }
- }
- exports.BookGeneratorService = BookGeneratorService;
- // 导出单例
- exports.bookGeneratorService = new BookGeneratorService();
- exports.bookGeneratorService.setApiKey(config_1.config.dashscope?.apiKey || '');
|