Fix duplicated text in Markdown from YouTube auto-captions
Rolling auto-caption cues repeat the previous line as plain text and carry the new text in lines with inline word-timing tags. Keep only the tagged lines in such cues (JS module and Python CLI); skip writing Markdown files whose content is identical to one already written. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
361c88be65
commit
03eec79b4c
@ -9,6 +9,13 @@ def strip_html(text):
|
|||||||
return re.sub(r'<[^>]+>', '', text)
|
return re.sub(r'<[^>]+>', '', text)
|
||||||
|
|
||||||
|
|
||||||
|
# YouTube auto-captions use rolling cues: the previous line is repeated as
|
||||||
|
# plain text while the new line carries inline word timings (<00:00:00.200>,
|
||||||
|
# <c>…</c>). Only the tagged lines contain new text.
|
||||||
|
def has_inline_timing_tags(line):
|
||||||
|
return re.search(r'<\d{2}:\d{2}:|<c[.\w]*>', line) is not None
|
||||||
|
|
||||||
|
|
||||||
def parse_vtt(content):
|
def parse_vtt(content):
|
||||||
blocks = re.split(r'\n{2,}', content.strip())
|
blocks = re.split(r'\n{2,}', content.strip())
|
||||||
cues = []
|
cues = []
|
||||||
@ -23,9 +30,12 @@ def parse_vtt(content):
|
|||||||
ts_idx = next((i for i, l in enumerate(lines) if '-->' in l), None)
|
ts_idx = next((i for i, l in enumerate(lines) if '-->' in l), None)
|
||||||
if ts_idx is None:
|
if ts_idx is None:
|
||||||
continue
|
continue
|
||||||
|
text_lines = lines[ts_idx + 1:]
|
||||||
|
if any(has_inline_timing_tags(l) for l in text_lines):
|
||||||
|
text_lines = [l for l in text_lines if has_inline_timing_tags(l)]
|
||||||
text = ' '.join(
|
text = ' '.join(
|
||||||
strip_html(l).strip()
|
strip_html(l).strip()
|
||||||
for l in lines[ts_idx + 1:]
|
for l in text_lines
|
||||||
if strip_html(l).strip()
|
if strip_html(l).strip()
|
||||||
)
|
)
|
||||||
if text:
|
if text:
|
||||||
|
|||||||
@ -5,6 +5,13 @@ function stripHtml(text) {
|
|||||||
return text.replace(/<[^>]+>/g, '');
|
return text.replace(/<[^>]+>/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YouTube auto-captions use rolling cues: the previous line is repeated as
|
||||||
|
// plain text while the new line carries inline word timings (<00:00:00.200>,
|
||||||
|
// <c>…</c>). Only the tagged lines contain new text.
|
||||||
|
function hasInlineTimingTags(line) {
|
||||||
|
return /<\d{2}:\d{2}:|<c[.\w]*>/.test(line);
|
||||||
|
}
|
||||||
|
|
||||||
function parseVtt(content) {
|
function parseVtt(content) {
|
||||||
const blocks = content.split(/\n{2,}/);
|
const blocks = content.split(/\n{2,}/);
|
||||||
const cues = [];
|
const cues = [];
|
||||||
@ -15,8 +22,11 @@ function parseVtt(content) {
|
|||||||
if (lines[0].startsWith('NOTE') || lines[0].startsWith('STYLE')) continue;
|
if (lines[0].startsWith('NOTE') || lines[0].startsWith('STYLE')) continue;
|
||||||
const tsIdx = lines.findIndex(l => l.includes('-->'));
|
const tsIdx = lines.findIndex(l => l.includes('-->'));
|
||||||
if (tsIdx === -1) continue;
|
if (tsIdx === -1) continue;
|
||||||
const text = lines
|
let textLines = lines.slice(tsIdx + 1);
|
||||||
.slice(tsIdx + 1)
|
if (textLines.some(hasInlineTimingTags)) {
|
||||||
|
textLines = textLines.filter(hasInlineTimingTags);
|
||||||
|
}
|
||||||
|
const text = textLines
|
||||||
.map(l => stripHtml(l).trim())
|
.map(l => stripHtml(l).trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ');
|
.join(' ');
|
||||||
|
|||||||
@ -105,12 +105,18 @@ async function runDownload(videoUrl, audioOnly, send, signal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const writtenMd = new Set();
|
||||||
for (const subPath of subtitlePaths) {
|
for (const subPath of subtitlePaths) {
|
||||||
if (!existsSync(subPath)) continue;
|
if (!existsSync(subPath)) continue;
|
||||||
send('info', `Converting: ${basename(subPath)}`);
|
send('info', `Converting: ${basename(subPath)}`);
|
||||||
try {
|
try {
|
||||||
const md = convertSubtitleToMarkdown(subPath);
|
const md = convertSubtitleToMarkdown(subPath);
|
||||||
const mdPath = subPath.replace(/\.(vtt|srt)$/, '.md');
|
const mdPath = subPath.replace(/\.(vtt|srt)$/, '.md');
|
||||||
|
if (writtenMd.has(md)) {
|
||||||
|
send('info', `Skipped (identical): ${basename(mdPath)}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
writtenMd.add(md);
|
||||||
writeFileSync(mdPath, md, 'utf-8');
|
writeFileSync(mdPath, md, 'utf-8');
|
||||||
send('info', `Saved: ${basename(mdPath)}`);
|
send('info', `Saved: ${basename(mdPath)}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user