Skip to content

Instantly share code, notes, and snippets.

@obar1
Last active April 18, 2025 06:28
Show Gist options
  • Select an option

  • Save obar1/e41740fa61f84b5e627deda6c23a212e to your computer and use it in GitHub Desktop.

Select an option

Save obar1/e41740fa61f84b5e627deda6c23a212e to your computer and use it in GitHub Desktop.

Revisions

  1. obar1 revised this gist Apr 18, 2025. 1 changed file with 57 additions and 5 deletions.
    62 changes: 57 additions & 5 deletions simple_yt_dowloader.py
    Original file line number Diff line number Diff line change
    @@ -31,9 +31,9 @@ def save_video_db(video_list):
    json.dump(video_list, f, indent=4)

    def download_youtube_video(url, output_path):
    """Download a YouTube video as MP4 and return video info."""
    """Download a YouTube video as MP4 and return video info including tags and subtitles."""
    try:
    # Configure yt-dlp options for browser-compatible MP4
    # 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'),
    @@ -42,6 +42,10 @@ def download_youtube_video(url, output_path):
    '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
    @@ -50,20 +54,37 @@ def download_youtube_video(url, output_path):
    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)
    '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 and players."""
    """Generate or update the index.html file with video list, players, tags, and subtitles."""
    html_content = """
    <!DOCTYPE html>
    <html lang="en">
    @@ -72,6 +93,24 @@ def generate_index_html(video_list):
    <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">
    @@ -81,15 +120,28 @@ def generate_index_html(video_list):

    for video in video_list:
    escaped_title = html.escape(video['title'])
    # Use relative path for HTML (videos/ is relative to index.html)
    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>
    """
  2. obar1 created this gist Apr 18, 2025.
    153 changes: 153 additions & 0 deletions simple_yt_dowloader.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,153 @@
    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."""
    try:
    # Configure yt-dlp options for browser-compatible MP4
    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
    }],
    }

    # 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)
    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)
    }

    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 and players."""
    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>
    </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'])
    # Use relative path for HTML (videos/ is relative to index.html)
    video_path = os.path.join('videos', os.path.basename(video['filename'])).replace('\\', '/')
    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">
    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>
    <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.")