#!/usr/bin/env python3 """ M3U Playlist URL Validator Tests video URLs with retry logic and timeout handling similar to VLC player """ import sys import urllib.request import urllib.error import socket from typing import List, Tuple def test_url(url: str, timeout: int = 10, max_retries: int = 3) -> bool: """ Test if a video URL is accessible by attempting to read the first 1KB. Uses retry logic and follows redirects similar to VLC player. Args: url: The video URL to test timeout: Connection timeout in seconds max_retries: Maximum number of retry attempts Returns: True if URL is accessible, False otherwise """ for attempt in range(max_retries): try: # Create request with headers similar to VLC req = urllib.request.Request(url) req.add_header('User-Agent', 'VLCPlayer/3.0') req.add_header('Range', 'bytes=0-1023') # Request first 1KB only # Attempt to open URL with timeout with urllib.request.urlopen(req, timeout=timeout) as response: # Try to read first 1KB data = response.read(1024) # If we got data, URL is valid if len(data) > 0: return True except urllib.error.HTTPError as e: # HTTP 520 or other server errors - retry if attempt < max_retries - 1: continue return False except urllib.error.URLError as e: # Network error - retry if attempt < max_retries - 1: continue return False except socket.timeout: # Timeout - retry if attempt < max_retries - 1: continue return False except Exception as e: # Other errors - retry if attempt < max_retries - 1: continue return False return False def parse_m3u(filepath: str) -> List[Tuple[str, str]]: """ Parse M3U playlist file and extract entries with their URLs. Args: filepath: Path to M3U file Returns: List of tuples containing (entry_info, url) """ entries = [] current_info = None with open(filepath, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if line.startswith('#EXTINF:'): current_info = line elif line and not line.startswith('#'): # This is a URL line if current_info: entries.append((current_info, line)) current_info = None return entries def validate_playlist(input_file: str, output_file: str = None, verbose: bool = True): """ Validate all URLs in an M3U playlist and optionally create filtered version. Args: input_file: Path to input M3U file output_file: Path to output filtered M3U file (optional) verbose: Print validation progress """ print(f"šŸ“‹ Reading playlist: {input_file}") entries = parse_m3u(input_file) print(f"šŸ“Š Found {len(entries)} entries to validate\n") valid_entries = [] invalid_entries = [] for i, (info, url) in enumerate(entries, 1): # Extract name from info line name = "Unknown" if 'tvg-name="' in info: start = info.index('tvg-name="') + 10 end = info.index('"', start) name = info[start:end] if verbose: print(f"[{i}/{len(entries)}] Testing: {name}") print(f" URL: {url}") # Test URL is_valid = test_url(url) if is_valid: if verbose: print(f" āœ… VALID\n") valid_entries.append((info, url)) else: if verbose: print(f" āŒ INVALID\n") invalid_entries.append((info, url)) # Print summary print("=" * 70) print(f"šŸ“Š Validation Summary:") print(f" Total entries: {len(entries)}") print(f" Valid entries: {len(valid_entries)} ({len(valid_entries)/len(entries)*100:.1f}%)") print(f" Invalid entries: {len(invalid_entries)} ({len(invalid_entries)/len(entries)*100:.1f}%)") print("=" * 70) # Write filtered playlist if output file specified if output_file: print(f"\nšŸ’¾ Writing filtered playlist to: {output_file}") with open(output_file, 'w', encoding='utf-8') as f: f.write("#EXTM3U\n\n") for info, url in valid_entries: f.write(f"{info}\n") f.write(f"{url}\n\n") print(f"āœ… Filtered playlist created successfully") # List invalid entries if invalid_entries and verbose: print(f"\nāŒ Invalid entries:") for info, url in invalid_entries: name = "Unknown" if 'tvg-name="' in info: start = info.index('tvg-name="') + 10 end = info.index('"', start) name = info[start:end] print(f" - {name}") print(f" {url}") if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python3 validate_playlist_urls.py [output.m3u]") print("\nExamples:") print(" python3 validate_playlist_urls.py sample_playlist.m3u") print(" python3 validate_playlist_urls.py sample_playlist.m3u filtered_playlist.m3u") sys.exit(1) input_file = sys.argv[1] output_file = sys.argv[2] if len(sys.argv) > 2 else None validate_playlist(input_file, output_file, verbose=True)