whisper.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. function registerWhisperRoutes(app, deps) {
  2. const {
  3. exec,
  4. fs,
  5. path,
  6. projectRoot,
  7. whisperDir,
  8. readManifest,
  9. writeManifest,
  10. resolveVideoPath,
  11. } = deps;
  12. app.post('/api/whisper/transcribe', async (req, res) => {
  13. const { videoId, language = 'Chinese', model = 'base' } = req.body;
  14. if (!videoId) {
  15. return res.status(400).json({ error: '缺少 videoId 参数' });
  16. }
  17. const manifest = readManifest();
  18. const video = manifest.find(v => v.id === videoId);
  19. if (!video) {
  20. return res.status(404).json({ error: `未找到视频: ${videoId}` });
  21. }
  22. const videoPath = resolveVideoPath(video.filename);
  23. if (!fs.existsSync(videoPath)) {
  24. return res.status(404).json({ error: `视频文件不存在: ${video.filename}` });
  25. }
  26. const baseName = video.filename.replace(/\.[^.]+$/, '');
  27. const outputDir = path.join(whisperDir, baseName);
  28. if (!fs.existsSync(outputDir)) {
  29. fs.mkdirSync(outputDir, { recursive: true });
  30. }
  31. console.log(`🎙️ 开始 Whisper 转录: ${video.filename}`);
  32. console.log(` 模型: ${model}, 语言: ${language}`);
  33. console.log(` 输出目录: ${outputDir}`);
  34. const cmd = `whisper "${videoPath}" --model ${model} --language ${language} --output_dir "${outputDir}"`;
  35. try {
  36. await new Promise((resolve, reject) => {
  37. const process = exec(cmd, {
  38. cwd: projectRoot,
  39. timeout: 10 * 60 * 1000,
  40. maxBuffer: 10 * 1024 * 1024,
  41. });
  42. let stdout = '';
  43. let stderr = '';
  44. process.stdout.on('data', (data) => {
  45. stdout += data;
  46. console.log(` [whisper] ${data.toString().trim()}`);
  47. });
  48. process.stderr.on('data', (data) => {
  49. stderr += data;
  50. });
  51. process.on('close', (code) => {
  52. if (code === 0) {
  53. resolve({ stdout, stderr });
  54. } else {
  55. reject(new Error(`Whisper 退出码: ${code}\n${stderr}`));
  56. }
  57. });
  58. process.on('error', (err) => {
  59. reject(new Error(`无法启动 Whisper: ${err.message}`));
  60. });
  61. });
  62. const txtFile = path.join(outputDir, `${baseName}.txt`);
  63. const srtFile = path.join(outputDir, `${baseName}.srt`);
  64. const transcript = fs.existsSync(txtFile) ? fs.readFileSync(txtFile, 'utf-8') : '';
  65. const srt = fs.existsSync(srtFile) ? fs.readFileSync(srtFile, 'utf-8') : '';
  66. if (!transcript) {
  67. return res.status(500).json({ error: 'Whisper 执行完成但未生成文字稿' });
  68. }
  69. const whisperPaths = {
  70. transcript: `Whisper/${baseName}/${baseName}.txt`,
  71. srt: `Whisper/${baseName}/${baseName}.srt`,
  72. };
  73. const jsonFile = path.join(outputDir, `${baseName}.json`);
  74. if (fs.existsSync(jsonFile)) whisperPaths.segments = `Whisper/${baseName}/${baseName}.json`;
  75. video.whisper = whisperPaths;
  76. writeManifest(manifest);
  77. console.log(`✅ Whisper 转录完成: ${baseName}`);
  78. res.json({
  79. success: true,
  80. videoId: video.id,
  81. transcript,
  82. srt,
  83. whisper: whisperPaths,
  84. outputDir: `Whisper/${baseName}`,
  85. });
  86. } catch (err) {
  87. console.error('❌ Whisper 转录失败:', err.message);
  88. res.status(500).json({
  89. error: `Whisper 转录失败: ${err.message}`,
  90. hint: '请确保已安装 Whisper: pip install openai-whisper',
  91. });
  92. }
  93. });
  94. app.get('/api/whisper/status', (req, res) => {
  95. exec('whisper --help', { timeout: 5000 }, (err) => {
  96. if (err) {
  97. res.json({ available: false, message: '未检测到 Whisper,请执行: pip install openai-whisper' });
  98. } else {
  99. res.json({ available: true, message: 'Whisper 已安装' });
  100. }
  101. });
  102. });
  103. }
  104. module.exports = { registerWhisperRoutes };