Spaces:
Sleeping
Sleeping
Kacper Łukawski commited on
Commit ·
804c481
1
Parent(s): de1cc73
Add download script
Browse files- README.md +22 -0
- pyproject.toml +7 -0
- scripts/download_transcripts.py +174 -0
- uv.lock +102 -0
README.md
CHANGED
|
@@ -28,6 +28,28 @@ uv sync
|
|
| 28 |
uv run main.py
|
| 29 |
```
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
## Running with Docker
|
| 32 |
|
| 33 |
```bash
|
|
|
|
| 28 |
uv run main.py
|
| 29 |
```
|
| 30 |
|
| 31 |
+
## Helper scripts
|
| 32 |
+
|
| 33 |
+
### Download YouTube transcripts
|
| 34 |
+
|
| 35 |
+
Downloads transcripts for one or more YouTube videos and saves each as a `.docx` file named after the video title. Prefers manually-created transcriptions; falls back to auto-generated if unavailable.
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
# Single video
|
| 39 |
+
uv run scripts/download_transcripts.py https://www.youtube.com/watch?v=VIDEO_ID
|
| 40 |
+
|
| 41 |
+
# Multiple videos, custom output directory
|
| 42 |
+
uv run scripts/download_transcripts.py \
|
| 43 |
+
https://youtu.be/VIDEO_ID1 \
|
| 44 |
+
https://youtu.be/VIDEO_ID2 \
|
| 45 |
+
-o ./my-transcripts
|
| 46 |
+
|
| 47 |
+
# Verbose logging
|
| 48 |
+
uv run scripts/download_transcripts.py https://youtu.be/VIDEO_ID -v
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
Output files land in `./transcripts/` by default. Each file is named after the video title (e.g. `My_Talk_Title.docx`). Exit code is non-zero if any video failed.
|
| 52 |
+
|
| 53 |
## Running with Docker
|
| 54 |
|
| 55 |
```bash
|
pyproject.toml
CHANGED
|
@@ -8,3 +8,10 @@ dependencies = [
|
|
| 8 |
"mcp-haystack>=1.3.0",
|
| 9 |
"nvidia-haystack>=1.1.0",
|
| 10 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
"mcp-haystack>=1.3.0",
|
| 9 |
"nvidia-haystack>=1.1.0",
|
| 10 |
]
|
| 11 |
+
|
| 12 |
+
[dependency-groups]
|
| 13 |
+
dev = [
|
| 14 |
+
"python-docx>=1.1.2",
|
| 15 |
+
"youtube-transcript-api>=1.2.4",
|
| 16 |
+
"yt-dlp>=2026.3.17",
|
| 17 |
+
]
|
scripts/download_transcripts.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Download transcripts from YouTube videos and save them as text files."""
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
import logging
|
| 6 |
+
import re
|
| 7 |
+
import sys
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from urllib.parse import parse_qs, urlparse
|
| 10 |
+
|
| 11 |
+
import yt_dlp
|
| 12 |
+
from docx import Document
|
| 13 |
+
from youtube_transcript_api import (
|
| 14 |
+
NoTranscriptFound,
|
| 15 |
+
TranscriptsDisabled,
|
| 16 |
+
VideoUnavailable,
|
| 17 |
+
YouTubeTranscriptApi,
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
| 21 |
+
logger = logging.getLogger(__name__)
|
| 22 |
+
|
| 23 |
+
_ENGLISH_CODES = ["en", "en-US", "en-GB"]
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def parse_args() -> argparse.Namespace:
|
| 27 |
+
parser = argparse.ArgumentParser(description="Download YouTube video transcripts to text files.")
|
| 28 |
+
parser.add_argument("videos", nargs="+", metavar="URL_OR_ID", help="YouTube URLs or video IDs")
|
| 29 |
+
parser.add_argument("-o", "--output", type=Path, default=Path("transcripts"), metavar="DIR")
|
| 30 |
+
parser.add_argument("-v", "--verbose", action="store_true")
|
| 31 |
+
return parser.parse_args()
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def extract_video_id(url_or_id: str) -> str:
|
| 35 |
+
parsed = urlparse(url_or_id)
|
| 36 |
+
if parsed.scheme in ("http", "https"):
|
| 37 |
+
host = parsed.netloc.lower().lstrip("www.")
|
| 38 |
+
if host == "youtu.be":
|
| 39 |
+
vid = parsed.path.lstrip("/").split("/")[0]
|
| 40 |
+
elif host in ("youtube.com", "m.youtube.com"):
|
| 41 |
+
if parsed.path.startswith("/shorts/"):
|
| 42 |
+
vid = parsed.path.split("/shorts/")[1].split("/")[0]
|
| 43 |
+
else:
|
| 44 |
+
qs = parse_qs(parsed.query)
|
| 45 |
+
candidates = qs.get("v", [])
|
| 46 |
+
if not candidates:
|
| 47 |
+
raise ValueError(f"No video ID in URL: {url_or_id}")
|
| 48 |
+
vid = candidates[0]
|
| 49 |
+
else:
|
| 50 |
+
raise ValueError(f"Unrecognised YouTube host: {parsed.netloc}")
|
| 51 |
+
vid = vid.split("&")[0].split("?")[0]
|
| 52 |
+
else:
|
| 53 |
+
vid = url_or_id.strip()
|
| 54 |
+
|
| 55 |
+
if not re.fullmatch(r"[a-zA-Z0-9_-]{11}", vid):
|
| 56 |
+
raise ValueError(f"Does not look like a video ID: {vid!r}")
|
| 57 |
+
return vid
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def fetch_title(video_id: str) -> str:
|
| 61 |
+
opts = {
|
| 62 |
+
"quiet": True,
|
| 63 |
+
"no_warnings": True,
|
| 64 |
+
"extract_flat": True,
|
| 65 |
+
"skip_download": True,
|
| 66 |
+
}
|
| 67 |
+
with yt_dlp.YoutubeDL(opts) as ydl:
|
| 68 |
+
info = ydl.extract_info(f"https://www.youtube.com/watch?v={video_id}", download=False)
|
| 69 |
+
if not info or "title" not in info:
|
| 70 |
+
raise RuntimeError("yt-dlp returned no title")
|
| 71 |
+
return info["title"]
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def fetch_transcript(video_id: str) -> str:
|
| 75 |
+
transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)
|
| 76 |
+
|
| 77 |
+
transcript = None
|
| 78 |
+
try:
|
| 79 |
+
transcript = transcript_list.find_manually_created_transcript(_ENGLISH_CODES)
|
| 80 |
+
logger.debug("Using manually-created English transcript for %s", video_id)
|
| 81 |
+
except NoTranscriptFound:
|
| 82 |
+
pass
|
| 83 |
+
|
| 84 |
+
if transcript is None:
|
| 85 |
+
try:
|
| 86 |
+
transcript = transcript_list.find_generated_transcript(_ENGLISH_CODES)
|
| 87 |
+
logger.debug("Using auto-generated English transcript for %s", video_id)
|
| 88 |
+
except NoTranscriptFound:
|
| 89 |
+
pass
|
| 90 |
+
|
| 91 |
+
if transcript is None:
|
| 92 |
+
available = list(transcript_list)
|
| 93 |
+
if not available:
|
| 94 |
+
raise NoTranscriptFound(video_id, [])
|
| 95 |
+
transcript = available[0]
|
| 96 |
+
logger.warning(
|
| 97 |
+
"No English transcript for %s; using language %s", video_id, transcript.language_code
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
entries = transcript.fetch()
|
| 101 |
+
return " ".join(entry["text"] for entry in entries)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def sanitize_filename(title: str, video_id: str) -> str:
|
| 105 |
+
name = re.sub(r'[<>:"/\\|?*\x00-\x1f]', "_", title)
|
| 106 |
+
name = re.sub(r"\s+", "_", name)
|
| 107 |
+
name = re.sub(r"_+", "_", name)
|
| 108 |
+
name = name.strip("_.")
|
| 109 |
+
name = name[:200]
|
| 110 |
+
return name if name else video_id
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def save_transcript(text: str, stem: str, output_dir: Path) -> Path:
|
| 114 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 115 |
+
path = output_dir / f"{stem}.docx"
|
| 116 |
+
doc = Document()
|
| 117 |
+
doc.add_paragraph(text)
|
| 118 |
+
doc.save(path)
|
| 119 |
+
return path
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def process_video(url_or_id: str, output_dir: Path) -> bool:
|
| 123 |
+
video_id: str | None = None
|
| 124 |
+
try:
|
| 125 |
+
video_id = extract_video_id(url_or_id)
|
| 126 |
+
except ValueError as exc:
|
| 127 |
+
logger.error("Could not parse video ID from %r: %s", url_or_id, exc)
|
| 128 |
+
return False
|
| 129 |
+
|
| 130 |
+
title_stem = video_id
|
| 131 |
+
try:
|
| 132 |
+
title_stem = sanitize_filename(fetch_title(video_id), video_id)
|
| 133 |
+
except Exception as exc:
|
| 134 |
+
logger.warning("Could not fetch title for %s: %s — using video ID as filename", video_id, exc)
|
| 135 |
+
|
| 136 |
+
try:
|
| 137 |
+
text = fetch_transcript(video_id)
|
| 138 |
+
except TranscriptsDisabled:
|
| 139 |
+
logger.error("Transcripts are disabled for %s", video_id)
|
| 140 |
+
return False
|
| 141 |
+
except VideoUnavailable:
|
| 142 |
+
logger.error("Video unavailable: %s", video_id)
|
| 143 |
+
return False
|
| 144 |
+
except NoTranscriptFound:
|
| 145 |
+
logger.error("No transcript available for %s", video_id)
|
| 146 |
+
return False
|
| 147 |
+
except Exception as exc:
|
| 148 |
+
logger.error("Unexpected error fetching transcript for %s: %s", video_id, exc)
|
| 149 |
+
return False
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
path = save_transcript(text, title_stem, output_dir)
|
| 153 |
+
logger.info("Saved: %s", path)
|
| 154 |
+
return True
|
| 155 |
+
except Exception as exc:
|
| 156 |
+
logger.error("Could not write file for %s: %s", video_id, exc)
|
| 157 |
+
return False
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def main() -> None:
|
| 161 |
+
args = parse_args()
|
| 162 |
+
if args.verbose:
|
| 163 |
+
logging.getLogger().setLevel(logging.DEBUG)
|
| 164 |
+
|
| 165 |
+
results = [process_video(v, args.output) for v in args.videos]
|
| 166 |
+
success = sum(results)
|
| 167 |
+
total = len(results)
|
| 168 |
+
logger.info("%d/%d transcripts downloaded", success, total)
|
| 169 |
+
if success < total:
|
| 170 |
+
sys.exit(1)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
if __name__ == "__main__":
|
| 174 |
+
main()
|
uv.lock
CHANGED
|
@@ -207,6 +207,15 @@ wheels = [
|
|
| 207 |
{ url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
|
| 208 |
]
|
| 209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
[[package]]
|
| 211 |
name = "distro"
|
| 212 |
version = "1.9.0"
|
|
@@ -567,6 +576,50 @@ wheels = [
|
|
| 567 |
{ url = "https://files.pythonhosted.org/packages/cd/62/60ed24fa8707f10c1c5aef94791252b820be3dd6bdfc6e2fcdb08bc8912f/lazy_imports-1.2.0-py3-none-any.whl", hash = "sha256:97134d6552e2ba16f1a278e316f05313ab73b360e848e40d593d08a5c2406fdf", size = 18681, upload-time = "2025-12-28T13:51:49.802Z" },
|
| 568 |
]
|
| 569 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
[[package]]
|
| 571 |
name = "markdown-it-py"
|
| 572 |
version = "4.0.0"
|
|
@@ -749,6 +802,13 @@ dependencies = [
|
|
| 749 |
{ name = "nvidia-haystack" },
|
| 750 |
]
|
| 751 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 752 |
[package.metadata]
|
| 753 |
requires-dist = [
|
| 754 |
{ name = "fastapi", extras = ["standard"], specifier = ">=0.134.0" },
|
|
@@ -757,6 +817,13 @@ requires-dist = [
|
|
| 757 |
{ name = "nvidia-haystack", specifier = ">=1.1.0" },
|
| 758 |
]
|
| 759 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 760 |
[[package]]
|
| 761 |
name = "posthog"
|
| 762 |
version = "7.11.1"
|
|
@@ -906,6 +973,19 @@ wheels = [
|
|
| 906 |
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
| 907 |
]
|
| 908 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 909 |
[[package]]
|
| 910 |
name = "python-dotenv"
|
| 911 |
version = "1.2.2"
|
|
@@ -1325,3 +1405,25 @@ wheels = [
|
|
| 1325 |
{ url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" },
|
| 1326 |
{ url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
|
| 1327 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
{ url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
|
| 208 |
]
|
| 209 |
|
| 210 |
+
[[package]]
|
| 211 |
+
name = "defusedxml"
|
| 212 |
+
version = "0.7.1"
|
| 213 |
+
source = { registry = "https://pypi.org/simple" }
|
| 214 |
+
sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" }
|
| 215 |
+
wheels = [
|
| 216 |
+
{ url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
|
| 217 |
+
]
|
| 218 |
+
|
| 219 |
[[package]]
|
| 220 |
name = "distro"
|
| 221 |
version = "1.9.0"
|
|
|
|
| 576 |
{ url = "https://files.pythonhosted.org/packages/cd/62/60ed24fa8707f10c1c5aef94791252b820be3dd6bdfc6e2fcdb08bc8912f/lazy_imports-1.2.0-py3-none-any.whl", hash = "sha256:97134d6552e2ba16f1a278e316f05313ab73b360e848e40d593d08a5c2406fdf", size = 18681, upload-time = "2025-12-28T13:51:49.802Z" },
|
| 577 |
]
|
| 578 |
|
| 579 |
+
[[package]]
|
| 580 |
+
name = "lxml"
|
| 581 |
+
version = "6.1.0"
|
| 582 |
+
source = { registry = "https://pypi.org/simple" }
|
| 583 |
+
sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" }
|
| 584 |
+
wheels = [
|
| 585 |
+
{ url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" },
|
| 586 |
+
{ url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" },
|
| 587 |
+
{ url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" },
|
| 588 |
+
{ url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" },
|
| 589 |
+
{ url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" },
|
| 590 |
+
{ url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" },
|
| 591 |
+
{ url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" },
|
| 592 |
+
{ url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" },
|
| 593 |
+
{ url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" },
|
| 594 |
+
{ url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" },
|
| 595 |
+
{ url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" },
|
| 596 |
+
{ url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" },
|
| 597 |
+
{ url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" },
|
| 598 |
+
{ url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" },
|
| 599 |
+
{ url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" },
|
| 600 |
+
{ url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" },
|
| 601 |
+
{ url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" },
|
| 602 |
+
{ url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" },
|
| 603 |
+
{ url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" },
|
| 604 |
+
{ url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" },
|
| 605 |
+
{ url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" },
|
| 606 |
+
{ url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" },
|
| 607 |
+
{ url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" },
|
| 608 |
+
{ url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" },
|
| 609 |
+
{ url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" },
|
| 610 |
+
{ url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" },
|
| 611 |
+
{ url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" },
|
| 612 |
+
{ url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" },
|
| 613 |
+
{ url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" },
|
| 614 |
+
{ url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" },
|
| 615 |
+
{ url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" },
|
| 616 |
+
{ url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" },
|
| 617 |
+
{ url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" },
|
| 618 |
+
{ url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" },
|
| 619 |
+
{ url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" },
|
| 620 |
+
{ url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" },
|
| 621 |
+
]
|
| 622 |
+
|
| 623 |
[[package]]
|
| 624 |
name = "markdown-it-py"
|
| 625 |
version = "4.0.0"
|
|
|
|
| 802 |
{ name = "nvidia-haystack" },
|
| 803 |
]
|
| 804 |
|
| 805 |
+
[package.dev-dependencies]
|
| 806 |
+
dev = [
|
| 807 |
+
{ name = "python-docx" },
|
| 808 |
+
{ name = "youtube-transcript-api" },
|
| 809 |
+
{ name = "yt-dlp" },
|
| 810 |
+
]
|
| 811 |
+
|
| 812 |
[package.metadata]
|
| 813 |
requires-dist = [
|
| 814 |
{ name = "fastapi", extras = ["standard"], specifier = ">=0.134.0" },
|
|
|
|
| 817 |
{ name = "nvidia-haystack", specifier = ">=1.1.0" },
|
| 818 |
]
|
| 819 |
|
| 820 |
+
[package.metadata.requires-dev]
|
| 821 |
+
dev = [
|
| 822 |
+
{ name = "python-docx", specifier = ">=1.1.2" },
|
| 823 |
+
{ name = "youtube-transcript-api", specifier = ">=1.2.4" },
|
| 824 |
+
{ name = "yt-dlp", specifier = ">=2026.3.17" },
|
| 825 |
+
]
|
| 826 |
+
|
| 827 |
[[package]]
|
| 828 |
name = "posthog"
|
| 829 |
version = "7.11.1"
|
|
|
|
| 973 |
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
| 974 |
]
|
| 975 |
|
| 976 |
+
[[package]]
|
| 977 |
+
name = "python-docx"
|
| 978 |
+
version = "1.2.0"
|
| 979 |
+
source = { registry = "https://pypi.org/simple" }
|
| 980 |
+
dependencies = [
|
| 981 |
+
{ name = "lxml" },
|
| 982 |
+
{ name = "typing-extensions" },
|
| 983 |
+
]
|
| 984 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" }
|
| 985 |
+
wheels = [
|
| 986 |
+
{ url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" },
|
| 987 |
+
]
|
| 988 |
+
|
| 989 |
[[package]]
|
| 990 |
name = "python-dotenv"
|
| 991 |
version = "1.2.2"
|
|
|
|
| 1405 |
{ url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" },
|
| 1406 |
{ url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
|
| 1407 |
]
|
| 1408 |
+
|
| 1409 |
+
[[package]]
|
| 1410 |
+
name = "youtube-transcript-api"
|
| 1411 |
+
version = "1.2.4"
|
| 1412 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1413 |
+
dependencies = [
|
| 1414 |
+
{ name = "defusedxml" },
|
| 1415 |
+
{ name = "requests" },
|
| 1416 |
+
]
|
| 1417 |
+
sdist = { url = "https://files.pythonhosted.org/packages/60/43/4104185a2eaa839daa693b30e15c37e7e58795e8e09ec414f22b3db54bec/youtube_transcript_api-1.2.4.tar.gz", hash = "sha256:b72d0e96a335df599d67cee51d49e143cff4f45b84bcafc202ff51291603ddcd", size = 469839, upload-time = "2026-01-29T09:09:17.088Z" }
|
| 1418 |
+
wheels = [
|
| 1419 |
+
{ url = "https://files.pythonhosted.org/packages/be/95/129ea37efd6cd6ed00f62baae6543345c677810b8a3bf0026756e1d3cf3c/youtube_transcript_api-1.2.4-py3-none-any.whl", hash = "sha256:03878759356da5caf5edac77431780b91448fb3d8c21d4496015bdc8a7bc43ff", size = 485227, upload-time = "2026-01-29T09:09:15.427Z" },
|
| 1420 |
+
]
|
| 1421 |
+
|
| 1422 |
+
[[package]]
|
| 1423 |
+
name = "yt-dlp"
|
| 1424 |
+
version = "2026.3.17"
|
| 1425 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1426 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8b/34/7c6b4e3f89cb6416d2cd7ab6dab141a1df97ab0fb22d15816db2c92148c9/yt_dlp-2026.3.17.tar.gz", hash = "sha256:ba7aa31d533f1ffccfe70e421596d7ca8ff0bf1398dc6bb658b7d9dec057d2c9", size = 3119221, upload-time = "2026-03-17T23:43:00.244Z" }
|
| 1427 |
+
wheels = [
|
| 1428 |
+
{ url = "https://files.pythonhosted.org/packages/cd/13/5093bcb954878e50f7217fd2ab94282b53934022e4e4a03265582da83bf5/yt_dlp-2026.3.17-py3-none-any.whl", hash = "sha256:32992db94303a8a5d211a183f2174834fe7f8c29d83ed2e7a324eae97a8f26d8", size = 3315134, upload-time = "2026-03-17T23:42:57.863Z" },
|
| 1429 |
+
]
|