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(仅本地保留,不入库不部署)