Skip to content

Instantly share code, notes, and snippets.

@obar1
Last active April 18, 2025 06:28
Show Gist options
  • Save obar1/e41740fa61f84b5e627deda6c23a212e to your computer and use it in GitHub Desktop.
Save obar1/e41740fa61f84b5e627deda6c23a212e to your computer and use it in GitHub Desktop.
save yt locally in folder and play via ws
from yt_dlp import YoutubeDL
import os
import json
import html
import re
import http.server
import socketserver
import threading
# Directory to store videos
VIDEO_DIR = "videos"
# JSON file to track downloaded videos
VIDEO_DB = "videos.json"
# HTML file for the webpage
INDEX_HTML = "index.html"
def sanitize_filename(filename):
"""Remove or replace invalid characters for filenames."""
return re.sub(r'[<>:"/\\|?*]', '_', filename)
def load_video_db():
"""Load the video database from JSON file."""
if os.path.exists(VIDEO_DB):
with open(VIDEO_DB, 'r') as f:
return json.load(f)
return []
def save_video_db(video_list):
"""Save the video database to JSON file."""
with open(VIDEO_DB, 'w') as f:
json.dump(video_list, f, indent=4)
def download_youtube_video(url, output_path):
"""Download a YouTube video as MP4 and return video info including tags and subtitles."""
try:
# Configure yt-dlp options for browser-compatible MP4 and subtitles
ydl_opts = {
'format': 'bestvideo[vcodec~="^avc1"][ext=mp4]+bestaudio[acodec~="mp4a"]/best[ext=mp4]', # H.264 + AAC
'outtmpl': os.path.join(output_path, '%(title)s.%(ext)s'),
'merge_output_format': 'mp4',
'postprocessors': [{
'key': 'FFmpegVideoConvertor',
'preferedformat': 'mp4', # Ensure MP4 with H.264/AAC
}],
'writesubtitles': True, # Download subtitles if available
'writeautomaticsub': True, # Download automatic subtitles
'subtitleslangs': ['en'], # Prefer English subtitles
'subtitlesformat': 'vtt', # WebVTT format for browser compatibility
}
# Download the video
with YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=True)
video_title = info.get('title', 'Untitled Video')
raw_filename = ydl.prepare_filename(info).split(os.sep)[-1]
video_filename = sanitize_filename(raw_filename)
# Get tags
tags = info.get('tags', []) or []
if not tags:
tags = ['No tags available']
# Get subtitles
subtitles = None
subtitle_file = os.path.join(output_path, f"{sanitize_filename(info.get('title', 'video'))}.en.vtt")
if os.path.exists(subtitle_file):
with open(subtitle_file, 'r', encoding='utf-8') as f:
subtitles = f.read()
else:
subtitles = "No subtitles available"
print(f"Downloading: {video_title}")
print(f"Download completed! Video saved to {output_path}")
return {
'title': video_title,
'filename': video_filename,
'path': os.path.join(output_path, video_filename),
'tags': tags,
'subtitles': subtitles
}
except Exception as e:
print(f"An error occurred during download: {str(e)}")
return None
def generate_index_html(video_list):
"""Generate or update the index.html file with video list, players, tags, and subtitles."""
html_content = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube Video Downloads</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.subtitles {
max-height: 200px;
overflow-y: auto;
background-color: #f8f8f8;
padding: 1rem;
border-radius: 0.5rem;
white-space: pre-wrap;
}
.tag {
display: inline-block;
background-color: #e5e7eb;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
</style>
</head>
<body class="bg-gray-100 font-sans">
<div class="container mx-auto p-4">
<h1 class="text-3xl font-bold mb-6 text-center">Downloaded YouTube Videos</h1>
<div class="grid gap-6">
"""
for video in video_list:
escaped_title = html.escape(video['title'])
video_path = os.path.join('videos', os.path.basename(video['filename'])).replace('\\', '/')
tags = video.get('tags', ['No tags available'])
subtitles = html.escape(video.get('subtitles', 'No subtitles available'))
subtitle_track = os.path.join('videos', f"{sanitize_filename(video['title'])}.en.vtt").replace('\\', '/')
# Generate tags HTML
tags_html = ''.join(f'<span class="tag">{html.escape(tag)}</span>' for tag in tags)
html_content += f"""
<div class="bg-white p-4 rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-2">{escaped_title}</h2>
<video controls class="w-full max-w-2xl mx-auto rounded" style="aspect-ratio: 16/9;">
<source src="{video_path}" type="video/mp4">
<track kind="subtitles" src="{subtitle_track}" srclang="en" label="English" default>
Your browser does not support the video tag, or the video file may be inaccessible. Ensure the file exists at '{video_path}' and is in a compatible MP4 format (H.264/AAC).
</video>
<div class="mt-4">
<h3 class="text-lg font-medium mb-2">Tags</h3>
<div class="mb-4">{tags_html}</div>
<h3 class="text-lg font-medium mb-2">Subtitles</h3>
<div class="subtitles">{subtitles}</div>
</div>
<a href="{video_path}" class="text-blue-500 hover:underline mt-2 inline-block" download>Download Video</a>
</div>
"""
html_content += """
</div>
</div>
</body>
</html>
"""
with open(INDEX_HTML, 'w') as f:
f.write(html_content)
print(f"Updated {INDEX_HTML} with {len(video_list)} videos.")
def start_local_server(port=8123):
"""Start a simple HTTP server to serve the webpage."""
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=os.getcwd(), **kwargs)
with socketserver.TCPServer(("", port), Handler) as httpd:
print(f"Serving at http://localhost:{port}/{INDEX_HTML}")
print("Press Ctrl+C to stop the server.")
httpd.serve_forever()
if __name__ == "__main__":
# Create video directory if it doesn't exist
if not os.path.exists(VIDEO_DIR):
os.makedirs(VIDEO_DIR)
# Load existing video database
video_list = load_video_db()
# Get user input
video_url = input("Enter YouTube video URL: ")
if video_url:
save_path = VIDEO_DIR
# Download the video
video_info = download_youtube_video(video_url, save_path)
if video_info:
# Check if video already exists in the database
if not any(v['filename'] == video_info['filename'] for v in video_list):
video_list.append(video_info)
save_video_db(video_list)
else:
print(f"Video '{video_info['title']}' is already in the database.")
# Generate or update the index.html
generate_index_html(video_list)
# Prompt to start a local server
start_server = input("Would you like to start a local server to view the webpage? (y/n): ").lower() == 'y'
if start_server:
server_thread = threading.Thread(target=start_local_server, daemon=True)
server_thread.start()
input("Press Enter to stop the server...\n")
else:
print("Failed to download the video. Webpage not updated.")
@obar1
Copy link
Author

obar1 commented Apr 18, 2025

done with grok

@obar1
Copy link
Author

obar1 commented Apr 18, 2025

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment