105 lines
4.3 KiB
JavaScript
105 lines
4.3 KiB
JavaScript
const response = require('../response');
|
||
const path = require('path');
|
||
|
||
function routes(db, log, cfg) {
|
||
function getStoragePath() {
|
||
const loadConfig = require('../config').loadConfig;
|
||
const c = (cfg && cfg.storage) ? cfg : loadConfig();
|
||
return path.isAbsolute(c.storage?.local_path)
|
||
? c.storage.local_path
|
||
: path.join(process.cwd(), c.storage?.local_path || './data/storage');
|
||
}
|
||
|
||
return {
|
||
/** 为单条分镜生成 TTS:对白 → audio_local_path;旁白 → narration_audio_local_path(body.tts_kind === 'narration') */
|
||
extract: async (req, res) => {
|
||
const { storyboard_id, text, tts_kind } = req.body || {};
|
||
if (!text && !storyboard_id) return response.badRequest(res, '请提供 storyboard_id 或 text');
|
||
const kind = String(tts_kind || 'dialogue').toLowerCase() === 'narration' ? 'narration' : 'dialogue';
|
||
let ttsText = text;
|
||
if (kind === 'narration') {
|
||
if ((!ttsText || !String(ttsText).trim()) && storyboard_id) {
|
||
const row = db.prepare('SELECT narration FROM storyboards WHERE id = ? AND deleted_at IS NULL').get(Number(storyboard_id));
|
||
ttsText = row?.narration;
|
||
}
|
||
if (!ttsText || !String(ttsText).trim()) {
|
||
return response.badRequest(res, '分镜解说旁白为空,无法合成语音');
|
||
}
|
||
} else {
|
||
if ((!ttsText || !String(ttsText).trim()) && storyboard_id) {
|
||
const row = db.prepare('SELECT dialogue FROM storyboards WHERE id = ? AND deleted_at IS NULL').get(Number(storyboard_id));
|
||
ttsText = row?.dialogue;
|
||
}
|
||
if (!ttsText || !String(ttsText).trim()) {
|
||
return response.badRequest(res, '分镜对白为空,无法合成语音');
|
||
}
|
||
}
|
||
try {
|
||
const ttsService = require('../services/ttsService');
|
||
const result = await ttsService.synthesize(db, log, {
|
||
text: ttsText,
|
||
storyboard_id: storyboard_id || null,
|
||
storage_base: getStoragePath(),
|
||
});
|
||
if (storyboard_id && result.local_path) {
|
||
const now = new Date().toISOString();
|
||
try {
|
||
if (kind === 'narration') {
|
||
db.prepare('UPDATE storyboards SET narration_audio_local_path = ?, updated_at = ? WHERE id = ?').run(
|
||
result.local_path, now, Number(storyboard_id)
|
||
);
|
||
} else {
|
||
db.prepare('UPDATE storyboards SET audio_local_path = ?, updated_at = ? WHERE id = ?').run(
|
||
result.local_path, now, Number(storyboard_id)
|
||
);
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
response.success(res, { local_path: result.local_path, url: result.local_path ? '/static/' + result.local_path : '', tts_kind: kind });
|
||
} catch (err) {
|
||
log.error('audio extract', { error: err.message });
|
||
response.internalError(res, err.message);
|
||
}
|
||
},
|
||
|
||
/** 批量为多条分镜生成 TTS */
|
||
extractBatch: async (req, res) => {
|
||
const { storyboard_ids } = req.body || {};
|
||
if (!Array.isArray(storyboard_ids) || storyboard_ids.length === 0) {
|
||
return response.badRequest(res, 'storyboard_ids 不能为空');
|
||
}
|
||
const results = [];
|
||
const storagePath = getStoragePath();
|
||
for (const sbId of storyboard_ids) {
|
||
const row = db.prepare('SELECT id, dialogue FROM storyboards WHERE id = ? AND deleted_at IS NULL').get(Number(sbId));
|
||
if (!row || !row.dialogue?.trim()) {
|
||
results.push({ storyboard_id: sbId, error: '对白为空' });
|
||
continue;
|
||
}
|
||
try {
|
||
const ttsService = require('../services/ttsService');
|
||
const result = await ttsService.synthesize(db, log, {
|
||
text: row.dialogue,
|
||
storyboard_id: row.id,
|
||
storage_base: storagePath,
|
||
});
|
||
if (result.local_path) {
|
||
const now = new Date().toISOString();
|
||
try {
|
||
db.prepare('UPDATE storyboards SET audio_local_path = ?, updated_at = ? WHERE id = ?').run(
|
||
result.local_path, now, row.id
|
||
);
|
||
} catch (_) {}
|
||
}
|
||
results.push({ storyboard_id: sbId, local_path: result.local_path });
|
||
} catch (err) {
|
||
results.push({ storyboard_id: sbId, error: err.message });
|
||
}
|
||
}
|
||
response.success(res, results);
|
||
},
|
||
};
|
||
}
|
||
|
||
module.exports = routes;
|