chore: Update everything
This commit is contained in:
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "youtube-transcript-api",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
from youtube_transcript_api import YouTubeTranscriptApi
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")
|
||||
|
||||
|
||||
def extract_video_id(url: str) -> str:
|
||||
parsed = urlparse(url)
|
||||
|
||||
if parsed.hostname in ("youtu.be", "www.youtu.be"):
|
||||
return parsed.path.lstrip("/")
|
||||
|
||||
if parsed.hostname in ("youtube.com", "www.youtube.com", "m.youtube.com"):
|
||||
if parsed.path == "/watch":
|
||||
return parse_qs(parsed.query)["v"][0]
|
||||
elif parsed.path.startswith("/embed/"):
|
||||
return parsed.path.split("/")[2]
|
||||
elif parsed.path.startswith("/v/"):
|
||||
return parsed.path.split("/")[2]
|
||||
|
||||
raise ValueError(f"Could not extract video ID from URL: {url}")
|
||||
|
||||
|
||||
def format_timestamp(seconds: float) -> str:
|
||||
hours = int(seconds // 3600)
|
||||
minutes = int((seconds % 3600) // 60)
|
||||
secs = int(seconds % 60)
|
||||
millis = int((seconds % 1) * 1000)
|
||||
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
|
||||
|
||||
|
||||
def transcript_to_srt(transcript: list[dict]) -> str:
|
||||
srt_lines = []
|
||||
for i, entry in enumerate(transcript, start=1):
|
||||
start_time = format_timestamp(entry["start"])
|
||||
end_time = format_timestamp(entry["start"] + entry["duration"])
|
||||
text = entry["text"]
|
||||
|
||||
srt_lines.append(f"{i}")
|
||||
srt_lines.append(f"{start_time} --> {end_time}")
|
||||
srt_lines.append(text)
|
||||
srt_lines.append("")
|
||||
|
||||
return "\n".join(srt_lines)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Fetch YouTube video transcripts",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument("url", help="YouTube video URL")
|
||||
parser.add_argument(
|
||||
"--save-srt",
|
||||
type=Path,
|
||||
metavar="FILE_PATH",
|
||||
help="Save transcript as SRT file to the specified path",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
|
||||
try:
|
||||
video_id = extract_video_id(args.url)
|
||||
logging.info(f"Fetching transcript for video ID: {video_id}")
|
||||
|
||||
ytt_api = YouTubeTranscriptApi()
|
||||
transcript = ytt_api.fetch(video_id)
|
||||
|
||||
if args.save_srt:
|
||||
srt_content = transcript_to_srt(transcript.to_raw_data())
|
||||
args.save_srt.write_text(srt_content)
|
||||
logging.info(f"SRT file saved to: {args.save_srt}")
|
||||
else:
|
||||
text = "\n".join(snippet.text for snippet in transcript)
|
||||
print(text)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to fetch transcript: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user