Requirements
Install the required Python libraries before running the notebooks:
# bash
pip install "yt-dlp[default]" openai-whisper pandas
# bash
pip install "yt-dlp[default]" openai-whisper pandas
Install ffmpeg:
#bash
sudo apt install ffmp
#bash
sudo apt install ffmp
- yt-dlp — downloads videos and audio from YouTube and other platforms; `[default]` includes the EJS scripts required for YouTube's JavaScript challenge solving
- openai-whisper — transcribes audio files using OpenAI's Whisper model; requires ffmpeg for audio processing
- pandas — handles metadata extraction and CSV export
- ffmpeg — processes and converts audio/video fil
yt-dlp YouTube Setup Guide
[yt-dlp](https://github.com/yt-dlp/yt-dlp) is a tool for downloading videos and audio from YouTube and hundreds of other platforms. It is a feature-rich fork of youtube-dl with active development and broader format support.
Step 1: Get cookies
YouTube restricts anonymous downloads and requires authentication to verify you are not a bot.
Cookies allow yt-dlp to use your existing YouTube session without logging in directly.
1. Install **Get cookies.txt LOCALLY** extension in Chrome
2. Allow the extension in incognito mode: `chrome://extensions/` → Details → Allow in incognito
3. Open an incognito window and log into YouTube
4. Navigate to `youtube.com/robots.txt` — do not close this tab
5. Click the extension icon → **Export As** → **Netscape**
6. Close the incognito window
7. Place `cookies.txt` next to your notebook
Step 2: Install Deno
yt-dlp requires a JavaScript runtime to solve YouTube's n-challenge — a mechanism YouTube uses to prevent automated downloads. Deno is recommended and enabled by default, but Node.js, Bun, and QuickJS are also supported. For more details, refer to the [official yt-dlp EJS guide](https://github.com/yt-dlp/yt-dlp/wiki/EJS).
Import all the libraries
import json
import yt_dlp
import os
import whisper
import pandas as pd
from pathlib import Path
# add system binaries to PATH so yt-dlp can find ffmpeg and deno
os.environ['PATH'] += ':/usr/bin'
import json
import yt_dlp
import os
import whisper
import pandas as pd
from pathlib import Path
# add system binaries to PATH so yt-dlp can find ffmpeg and deno
os.environ['PATH'] += ':/usr/bin'
Download single video
Download audio file
# Replace with your YouTube video URL.
VIDEO_URL = 'https://www.youtube.com/watch?v=k5xjIiINCv8'
# Get video metadata without downloading to extract channel name.
with yt_dlp.YoutubeDL({'cookiefile': 'cookies.txt', 'quiet': True}) as ydl:
info = ydl.extract_info(VIDEO_URL, download=False)
CHANNEL_DIR = Path('data') / info['uploader'] # Root folder for channel.
AUDIO_DIR = CHANNEL_DIR / 'audio' # Folder for audio files.
AUDIO_DIR.mkdir(parents=True, exist_ok=True) # Create folders if not exists.
ydl_opts = {
'quiet': False,
'no_warnings': False,
'cookiefile': 'cookies.txt', # Use cookies for authentication.
'format': 'bestaudio/best', # Download best available audio.
'outtmpl': str(AUDIO_DIR / '%(title)s [%(id)s].%(ext)s'), # Output: data/channel/audio/title [id].ext
'postprocessors': [{
'key': 'FFmpegExtractAudio', # Extract audio with ffmpeg.
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
}
# Download audio and extract metadata.
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(VIDEO_URL)
# Replace with your YouTube video URL.
VIDEO_URL = 'https://www.youtube.com/watch?v=k5xjIiINCv8'
# Get video metadata without downloading to extract channel name.
with yt_dlp.YoutubeDL({'cookiefile': 'cookies.txt', 'quiet': True}) as ydl:
info = ydl.extract_info(VIDEO_URL, download=False)
CHANNEL_DIR = Path('data') / info['uploader'] # Root folder for channel.
AUDIO_DIR = CHANNEL_DIR / 'audio' # Folder for audio files.
AUDIO_DIR.mkdir(parents=True, exist_ok=True) # Create folders if not exists.
ydl_opts = {
'quiet': False,
'no_warnings': False,
'cookiefile': 'cookies.txt', # Use cookies for authentication.
'format': 'bestaudio/best', # Download best available audio.
'outtmpl': str(AUDIO_DIR / '%(title)s [%(id)s].%(ext)s'), # Output: data/channel/audio/title [id].ext
'postprocessors': [{
'key': 'FFmpegExtractAudio', # Extract audio with ffmpeg.
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
}
# Download audio and extract metadata.
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(VIDEO_URL)
# Extract relevant metadata fields.
fields = [
'id',
'title',
'description',
'uploader',
'upload_date',
'duration',
'view_count',
'like_count',
'comment_count',
'tags',
'categories',
'webpage_url'
]
meta = {k: info.get(k) for k in fields}
print(json.dumps(
meta,
ensure_ascii=False,
indent=2
))# Extract relevant metadata fields.
fields = [
'id',
'title',
'description',
'uploader',
'upload_date',
'duration',
'view_count',
'like_count',
'comment_count',
'tags',
'categories',
'webpage_url'
]
meta = {k: info.get(k) for k in fields}
print(json.dumps(
meta,
ensure_ascii=False,
indent=2
))Download playlist
PLAYLIST_URL = 'https://www.youtube.com/watch?v=ykjFMOcamd0&list=PLr0vtm6PM7z2VKhJ4U3BoDd6NxwTdsF5H'
# Replace with your YouTube playlist URL.
# Get playlist metadata without downloading to extract channel name.
with yt_dlp.YoutubeDL({'cookiefile': 'cookies.txt', 'quiet': True}) as ydl:
info = ydl.extract_info(PLAYLIST_URL, download=False)
CHANNEL_DIR = Path('data') / info['uploader'] # Root folder for channel.
# Define relevant metadata fields.
FIELDS = [
'id',
'title',
'description',
'uploader',
'upload_date',
'duration',
'view_count',
'like_count',
'comment_count',
'tags',
'categories',
'webpage_url'
]PLAYLIST_URL = 'https://www.youtube.com/watch?v=ykjFMOcamd0&list=PLr0vtm6PM7z2VKhJ4U3BoDd6NxwTdsF5H'
# Replace with your YouTube playlist URL.
# Get playlist metadata without downloading to extract channel name.
with yt_dlp.YoutubeDL({'cookiefile': 'cookies.txt', 'quiet': True}) as ydl:
info = ydl.extract_info(PLAYLIST_URL, download=False)
CHANNEL_DIR = Path('data') / info['uploader'] # Root folder for channel.
# Define relevant metadata fields.
FIELDS = [
'id',
'title',
'description',
'uploader',
'upload_date',
'duration',
'view_count',
'like_count',
'comment_count',
'tags',
'categories',
'webpage_url'
] Download audio
AUDIO_DIR = CHANNEL_DIR / 'audio' # Folder for audio files.
AUDIO_DIR.mkdir(parents=True, exist_ok=True) # Create folders if not exists.
ydl_opts = {
'quiet': False,
'no_warnings': False,
'cookiefile': 'cookies.txt', # Use cookies for authentication.
'outtmpl': str(AUDIO_DIR / '%(title)s [%(id)s].%(ext)s'), # Output: data/channel/audio/title [id].ext.
'postprocessors': [{
'key': 'FFmpegExtractAudio', # Extract audio with ffmpeg.
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
'ignoreerrors': True, # Skip unavailable videos.
}
# Download playlist and extract metadata.
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
playlist_info = ydl.extract_info(PLAYLIST_URL, download=True)
# Extract metadata for each video.
rows = []
for entry in playlist_info['entries']:
if entry: # Skip unavailable videos.
row = {field: entry.get(field) for field in FIELDS} # Extract selected fields.
row['file_path'] = str(AUDIO_DIR / f"{entry.get('title', '')} [{entry.get('id', '')}].mp3") # Path to downloaded file.
rows.append(row)
# Save metadata to csv next to the files
metadata_df = pd.DataFrame(rows, columns=FIELDS + ['file_path'])
metadata_df.to_csv(AUDIO_DIR / 'metadata.csv', index=False)AUDIO_DIR = CHANNEL_DIR / 'audio' # Folder for audio files.
AUDIO_DIR.mkdir(parents=True, exist_ok=True) # Create folders if not exists.
ydl_opts = {
'quiet': False,
'no_warnings': False,
'cookiefile': 'cookies.txt', # Use cookies for authentication.
'outtmpl': str(AUDIO_DIR / '%(title)s [%(id)s].%(ext)s'), # Output: data/channel/audio/title [id].ext.
'postprocessors': [{
'key': 'FFmpegExtractAudio', # Extract audio with ffmpeg.
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
'ignoreerrors': True, # Skip unavailable videos.
}
# Download playlist and extract metadata.
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
playlist_info = ydl.extract_info(PLAYLIST_URL, download=True)
# Extract metadata for each video.
rows = []
for entry in playlist_info['entries']:
if entry: # Skip unavailable videos.
row = {field: entry.get(field) for field in FIELDS} # Extract selected fields.
row['file_path'] = str(AUDIO_DIR / f"{entry.get('title', '')} [{entry.get('id', '')}].mp3") # Path to downloaded file.
rows.append(row)
# Save metadata to csv next to the files
metadata_df = pd.DataFrame(rows, columns=FIELDS + ['file_path'])
metadata_df.to_csv(AUDIO_DIR / 'metadata.csv', index=False)Transcribe downloaded files
audio_files = [
{'path': p, 'id': p.stem.split('[')[-1].rstrip(']'), 'title': p.stem.rsplit('[', 1)[0].strip()}
for p in sorted(AUDIO_DIR.iterdir()) # Iterate over audio folder.
if p.suffix == '.mp4' # Filter mp4 files only
audio_files = [
{'path': p, 'id': p.stem.split('[')[-1].rstrip(']'), 'title': p.stem.rsplit('[', 1)[0].strip()}
for p in sorted(AUDIO_DIR.iterdir()) # Iterate over audio folder.
if p.suffix == '.mp4' # Filter mp4 files only
WHISPER_MODEL = 'medium' # Whisper model size.
LANGUAGE = 'ru' # Transcription language.
TRANSCRIPTS_DIR = CHANNEL_DIR / 'transcripts' # Folder for transcripts next to audio and video.
TRANSCRIPTS_DIR.mkdir(parents=True, exist_ok=True) # Create folder if not exists.
model = whisper.load_model(WHISPER_MODEL) # Load whisper model.
for item in audio_files:
result = model.transcribe(str(item['path']), language=LANGUAGE, verbose=False, fp16=False) # Transcribe audio.
out = TRANSCRIPTS_DIR / f"{item['path'].stem}_transcript.txt" # Same name as audio file + _transcript.
with open(out, 'w', encoding='utf-8') as f:
f.write(f"# {item['title']}\n\n") # Write title.
for seg in result['segments']:
s, e = int(seg['start']), int(seg['end'])
ts = lambda x: f'{x // 60:02d}:{x % 60:02d}' # Format timestamp as mm:ss.
f.write(f"[{ts(s)} -> {ts(e)}] {seg['text'].strip()}\n") # Write segment with timestamps.WHISPER_MODEL = 'medium' # Whisper model size.
LANGUAGE = 'ru' # Transcription language.
TRANSCRIPTS_DIR = CHANNEL_DIR / 'transcripts' # Folder for transcripts next to audio and video.
TRANSCRIPTS_DIR.mkdir(parents=True, exist_ok=True) # Create folder if not exists.
model = whisper.load_model(WHISPER_MODEL) # Load whisper model.
for item in audio_files:
result = model.transcribe(str(item['path']), language=LANGUAGE, verbose=False, fp16=False) # Transcribe audio.
out = TRANSCRIPTS_DIR / f"{item['path'].stem}_transcript.txt" # Same name as audio file + _transcript.
with open(out, 'w', encoding='utf-8') as f:
f.write(f"# {item['title']}\n\n") # Write title.
for seg in result['segments']:
s, e = int(seg['start']), int(seg['end'])
ts = lambda x: f'{x // 60:02d}:{x % 60:02d}' # Format timestamp as mm:ss.
f.write(f"[{ts(s)} -> {ts(e)}] {seg['text'].strip()}\n") # Write segment with timestamps.