知识库首页 seo-llm 资料 re-extract-transcripts.ts.txt

re extract transcripts.ts

本地来源:seo-llm/raw/seo知识库/TXT整理/seo方法论/seo-knowledge-base/Scripts-for-Youtube/re-extract-transcripts.ts.txt

/**
 * 重新提取94个 SeedVR2 视频的字幕
 * 使用新的 youtube-caption-extractor 库
 *
 * 运行方式: pnpm tsx scripts/re-extract-transcripts.ts
 */

import fs from 'node:fs';
import path from 'node:path';
import { getSubtitles } from 'youtube-caption-extractor';

// 延迟函数
function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// 提取单个视频的字幕
async function extractTranscript(
  videoId: string,
  index: number,
  total: number
) {
  console.log(`\n[${index}/${total}] 提取视频: ${videoId}`);

  try {
    const captions = await getSubtitles({ videoID: videoId });

    if (captions && captions.length > 0) {
      const script = captions
        .map((item) => item.text)
        .join(' ')
        .replace(/\s+/g, ' ')
        .trim();

      console.log(`  ✅ 成功! 字幕长度: ${script.length} 字符`);

      return {
        videoId,
        success: true,
        hasTranscript: true,
        script,
        scriptLength: script.length,
        extractedAt: new Date().toISOString(),
        error: null,
      };
    }
    console.log('  ⚠️  返回空结果');
    return {
      videoId,
      success: true,
      hasTranscript: false,
      script: '',
      scriptLength: 0,
      extractedAt: new Date().toISOString(),
      error: 'No captions returned',
    };
  } catch (error: any) {
    console.log(`  ❌ 失败: ${error.message}`);
    return {
      videoId,
      success: false,
      hasTranscript: false,
      script: '',
      scriptLength: 0,
      extractedAt: new Date().toISOString(),
      error: error.message,
    };
  }
}

// 主函数
async function main() {
  console.log('🚀 开始重新提取 SeedVR2 视频字幕\n');
  console.log('使用库: youtube-caption-extractor (最新版)\n');
  console.log('='.repeat(80));

  // 读取现有的 JSON 文件
  const jsonPath = path.join(process.cwd(), 'SEO/Videos/seedvr2.json');

  if (!fs.existsSync(jsonPath)) {
    console.error(`❌ 文件不存在: ${jsonPath}`);
    process.exit(1);
  }

  console.log(`\n📖 读取文件: ${jsonPath}`);
  const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));

  console.log(`📊 视频总数: ${data.videos.length}`);
  console.log('\n开始提取字幕...\n');

  // 提取所有视频的字幕
  const results: Array<{
    videoId: string;
    success: boolean;
    hasTranscript: boolean;
    script: string;
    scriptLength: number;
    extractedAt: string;
    error: string | null;
  }> = [];

  for (let i = 0; i < data.videos.length; i++) {
    const video = data.videos[i];
    const result = await extractTranscript(video.id, i + 1, data.videos.length);
    results.push(result);

    // 延迟避免限流
    if (i < data.videos.length - 1) {
      await delay(1500);
    }
  }

  // 统计结果
  const successCount = results.filter((r) => r.success).length;
  const hasTranscriptCount = results.filter((r) => r.hasTranscript).length;
  const failedCount = results.filter((r) => !r.success).length;

  console.log(`\n${'='.repeat(80)}`);
  console.log('\n📊 提取结果统计:\n');
  console.log(`总视频数: ${results.length}`);
  console.log(
    `成功访问: ${successCount} (${((successCount / results.length) * 100).toFixed(1)}%)`
  );
  console.log(
    `有字幕: ${hasTranscriptCount} (${((hasTranscriptCount / results.length) * 100).toFixed(1)}%)`
  );
  console.log(
    `失败: ${failedCount} (${((failedCount / results.length) * 100).toFixed(1)}%)`
  );

  // 更新 JSON 数据
  const updatedVideos = data.videos.map((video: any) => {
    const result = results.find((r) => r.videoId === video.id);

    if (!result) {
      return video;
    }

    return {
      ...video,
      script: result.script,
      hasTranscript: result.hasTranscript,
      scriptLength: result.scriptLength,
      extractedAt: result.extractedAt,
      extractError: result.error,
    };
  });

  const updatedData = {
    ...data,
    videos: updatedVideos,
    statistics: {
      totalVideos: updatedVideos.length,
      successfulExtracts: successCount,
      videosWithTranscript: hasTranscriptCount,
      failedExtracts: failedCount,
      lastUpdated: new Date().toISOString(),
    },
    metadata: {
      ...data.metadata,
      totalVideos: updatedVideos.length,
      successfulExtracts: successCount,
      failedExtracts: failedCount,
      videosWithTranscript: hasTranscriptCount,
      lastUpdated: new Date().toISOString(),
      extractionLibrary: 'youtube-caption-extractor',
      extractionVersion: '1.9.1',
    },
  };

  // 创建备份
  const backupPath = jsonPath.replace('.json', `-backup-${Date.now()}.json`);
  console.log(`\n💾 创建备份: ${backupPath}`);
  fs.writeFileSync(backupPath, JSON.stringify(data, null, 2));

  // 保存更新后的数据
  console.log(`\n✍️  保存更新后的数据: ${jsonPath}`);
  fs.writeFileSync(jsonPath, JSON.stringify(updatedData, null, 2));

  // 同时更新 data/seo 目录
  const dataSeoPath = path.join(
    process.cwd(),
    'data/seo',
    `seedvr2-complete-${Date.now()}.json`
  );
  console.log(`\n💾 保存副本到: ${dataSeoPath}`);
  fs.writeFileSync(dataSeoPath, JSON.stringify(updatedData, null, 2));

  // 显示有字幕的视频列表
  if (hasTranscriptCount > 0) {
    console.log('\n✅ 有字幕的视频 (前10个):\n');
    updatedVideos
      .filter((v: any) => v.hasTranscript)
      .slice(0, 10)
      .forEach((v: any, i: number) => {
        console.log(
          `${i + 1}. [${v.scriptLength.toLocaleString()} 字符] ${v.title}`
        );
      });

    if (hasTranscriptCount > 10) {
      console.log(`\n... 还有 ${hasTranscriptCount - 10} 个视频有字幕`);
    }
  }

  // 显示失败的视频
  if (failedCount > 0) {
    console.log('\n❌ 提取失败的视频:\n');
    updatedVideos
      .filter(
        (v: any) => v.extractError && v.extractError !== 'No captions returned'
      )
      .forEach((v: any) => {
        console.log(`  - ${v.title}`);
        console.log(`    错误: ${v.extractError}\n`);
      });
  }

  console.log('\n✨ 全部完成!');
  console.log('\n📊 最终统计:');
  console.log(`  总视频数: ${updatedData.statistics.totalVideos}`);
  console.log(
    `  有字幕: ${updatedData.statistics.videosWithTranscript} (${((hasTranscriptCount / results.length) * 100).toFixed(1)}%)`
  );
  console.log(
    `  无字幕: ${updatedData.statistics.failedExtracts} (${((failedCount / results.length) * 100).toFixed(1)}%)`
  );
}

// 运行
main().catch((error) => {
  console.error('\n❌ 错误:', error);
  process.exit(1);
});

本文档为站内渲染。原始文件本地路径:saas/source/seo-llm/raw-seo知识库-TXT整理-seo方法论-seo-knowledge-base-Scripts-for-Youtu-3e5d8e.txt(仅本地保留,不入库不部署)