feat(baoyu-youtube-transcript): add title heading, description summary, and cover image to markdown output
This commit is contained in:
parent
5071a1d0d0
commit
bb78aab095
|
|
@ -88,7 +88,7 @@ Accepts any of these as video input:
|
||||||
|
|
||||||
| Format | Extension | Description |
|
| Format | Extension | Description |
|
||||||
|--------|-----------|-------------|
|
|--------|-----------|-------------|
|
||||||
| `text` | `.md` | Markdown with frontmatter, natural paragraphs, optional timestamps/chapters/speakers |
|
| `text` | `.md` | Markdown with frontmatter (incl. `description`), title heading, summary, optional TOC/cover/timestamps/chapters/speakers |
|
||||||
| `srt` | `.srt` | SubRip subtitle format for video players |
|
| `srt` | `.srt` | SubRip subtitle format for video players |
|
||||||
|
|
||||||
## Output Directory
|
## Output Directory
|
||||||
|
|
@ -147,7 +147,7 @@ If no chapter timestamps exist in the description, the transcript is output as g
|
||||||
### Speaker Identification (`--speakers`)
|
### Speaker Identification (`--speakers`)
|
||||||
|
|
||||||
Speaker identification requires AI processing. The script outputs a raw `.md` file containing:
|
Speaker identification requires AI processing. The script outputs a raw `.md` file containing:
|
||||||
- YAML frontmatter with video metadata (title, channel, date, cover, language)
|
- YAML frontmatter with video metadata (title, channel, date, cover, description, language)
|
||||||
- Video description (for speaker name extraction)
|
- Video description (for speaker name extraction)
|
||||||
- Chapter list from description (if available)
|
- Chapter list from description (if available)
|
||||||
- Raw transcript in SRT format (pre-computed start/end timestamps, token-efficient)
|
- Raw transcript in SRT format (pre-computed start/end timestamps, token-efficient)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,12 @@ You are an expert transcript specialist. Process the raw transcript file (with Y
|
||||||
## Output Structure
|
## Output Structure
|
||||||
|
|
||||||
Produce a single cohesive markdown file containing:
|
Produce a single cohesive markdown file containing:
|
||||||
1. YAML frontmatter (keep the original frontmatter from the raw file)
|
1. YAML frontmatter (keep the original frontmatter from the raw file, which includes `description`)
|
||||||
2. Table of Contents
|
2. `# Title` heading (from frontmatter title)
|
||||||
3. Full chapter-segmented transcript with speaker labels
|
3. Description/summary paragraph (from frontmatter `description`)
|
||||||
|
4. Table of Contents
|
||||||
|
5. Cover image (if `cover` exists in frontmatter): `` — right after the ToC
|
||||||
|
6. Full chapter-segmented transcript with speaker labels
|
||||||
|
|
||||||
Use the same language as the transcription for the title and ToC.
|
Use the same language as the transcription for the title and ToC.
|
||||||
|
|
||||||
|
|
@ -79,13 +82,20 @@ channel: "The Show"
|
||||||
date: 2024-04-15
|
date: 2024-04-15
|
||||||
url: "https://www.youtube.com/watch?v=xxx"
|
url: "https://www.youtube.com/watch?v=xxx"
|
||||||
cover: imgs/cover.jpg
|
cover: imgs/cover.jpg
|
||||||
|
description: "Jane Doe discusses her groundbreaking five-year study on the long-term effects of dietary changes."
|
||||||
language: en
|
language: en
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Example Interview
|
||||||
|
|
||||||
|
Jane Doe discusses her groundbreaking five-year study on the long-term effects of dietary changes.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
* [00:00:00] Introduction and Welcome
|
* [00:00:00] Introduction and Welcome
|
||||||
* [00:00:12] Overview of the New Research
|
* [00:00:12] Overview of the New Research
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## Introduction and Welcome [00:00:00]
|
## Introduction and Welcome [00:00:00]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -505,17 +505,28 @@ function yamlEscape(s: string): string {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractSummary(description: string): string {
|
||||||
|
if (!description) return "";
|
||||||
|
const firstPara = description.split(/\n\s*\n/)[0].trim();
|
||||||
|
const lines = firstPara.split("\n").filter(l => !/^\s*(https?:\/\/|#|@|\d+:\d+)/.test(l) && l.trim());
|
||||||
|
return lines.join(" ").slice(0, 300).trim();
|
||||||
|
}
|
||||||
|
|
||||||
function formatMarkdown(sentences: Sentence[], meta: VideoMeta, opts: { timestamps: boolean; chapters: boolean; speakers: boolean }, snippets?: Snippet[]): string {
|
function formatMarkdown(sentences: Sentence[], meta: VideoMeta, opts: { timestamps: boolean; chapters: boolean; speakers: boolean }, snippets?: Snippet[]): string {
|
||||||
|
const summary = extractSummary(meta.description);
|
||||||
let md = "---\n";
|
let md = "---\n";
|
||||||
md += `title: ${yamlEscape(meta.title)}\n`;
|
md += `title: ${yamlEscape(meta.title)}\n`;
|
||||||
md += `channel: ${yamlEscape(meta.channel)}\n`;
|
md += `channel: ${yamlEscape(meta.channel)}\n`;
|
||||||
if (meta.publishDate) md += `date: ${meta.publishDate}\n`;
|
if (meta.publishDate) md += `date: ${meta.publishDate}\n`;
|
||||||
md += `url: ${yamlEscape(meta.url)}\n`;
|
md += `url: ${yamlEscape(meta.url)}\n`;
|
||||||
if (meta.coverImage) md += `cover: ${meta.coverImage}\n`;
|
if (meta.coverImage) md += `cover: ${meta.coverImage}\n`;
|
||||||
|
if (summary) md += `description: ${yamlEscape(summary)}\n`;
|
||||||
if (meta.language) md += `language: ${meta.language.code}\n`;
|
if (meta.language) md += `language: ${meta.language.code}\n`;
|
||||||
md += "---\n\n";
|
md += "---\n\n";
|
||||||
|
|
||||||
if (opts.speakers) {
|
if (opts.speakers) {
|
||||||
|
md += `# ${meta.title}\n\n`;
|
||||||
|
if (summary) md += `${summary}\n\n`;
|
||||||
if (meta.description) md += "# Description\n\n" + meta.description.trim() + "\n\n";
|
if (meta.description) md += "# Description\n\n" + meta.description.trim() + "\n\n";
|
||||||
if (meta.chapters.length) {
|
if (meta.chapters.length) {
|
||||||
md += "# Chapters\n\n";
|
md += "# Chapters\n\n";
|
||||||
|
|
@ -527,12 +538,17 @@ function formatMarkdown(sentences: Sentence[], meta: VideoMeta, opts: { timestam
|
||||||
return md;
|
return md;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
md += `# ${meta.title}\n\n`;
|
||||||
|
if (summary) md += `${summary}\n\n`;
|
||||||
|
|
||||||
const chapters = opts.chapters ? meta.chapters : [];
|
const chapters = opts.chapters ? meta.chapters : [];
|
||||||
|
|
||||||
if (chapters.length) {
|
if (chapters.length) {
|
||||||
md += "## Table of Contents\n\n";
|
md += "## Table of Contents\n\n";
|
||||||
for (const ch of chapters) md += opts.timestamps ? `* [${ts(ch.start)}] ${ch.title}\n` : `* ${ch.title}\n`;
|
for (const ch of chapters) md += opts.timestamps ? `* [${ts(ch.start)}] ${ch.title}\n` : `* ${ch.title}\n`;
|
||||||
md += "\n\n";
|
md += "\n";
|
||||||
|
if (meta.coverImage) md += `\n\n`;
|
||||||
|
md += "\n";
|
||||||
for (let i = 0; i < chapters.length; i++) {
|
for (let i = 0; i < chapters.length; i++) {
|
||||||
const nextStart = i < chapters.length - 1 ? chapters[i + 1].start : Infinity;
|
const nextStart = i < chapters.length - 1 ? chapters[i + 1].start : Infinity;
|
||||||
const chSentences = sentences.filter(s => parseTs(s.start) >= chapters[i].start && parseTs(s.start) < nextStart);
|
const chSentences = sentences.filter(s => parseTs(s.start) >= chapters[i].start && parseTs(s.start) < nextStart);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue