#!/usr/bin/env python3 """ Jamie YouTube Video Info Fetcher Fetches detailed information for specific YouTube videos from URLs. """ import os import sys import re import argparse from pathlib import Path from datetime import datetime from dotenv import load_dotenv from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from googleapiclient.discovery import build import pickle # Load environment variables load_dotenv(Path(__file__).parent / '.env') SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl'] TOKEN_FILE = Path(__file__).parent / 'jamie_youtube_token.pickle' CLIENT_SECRETS_FILE = Path(os.getenv('GOOGLE_CLIENT_SECRETS_FILE', '/Users/ourdigital/.config/gcloud/keys/jamie-youtube-manager.json')) # Jamie Clinic YouTube Channel (Default) JAMIE_CHANNEL_ID = "UCtjR6NnlaX1dPPER7wHbGpw" JAMIE_CHANNEL_NAME = "제이미 성형외과" def extract_video_id(url_or_id): """Extract video ID from various YouTube URL formats.""" # Already a video ID if re.match(r'^[\w-]{11}$', url_or_id): return url_or_id # Standard YouTube URLs patterns = [ r'(?:youtube\.com/watch\?v=|youtu\.be/|youtube\.com/embed/|youtube\.com/v/)([^&\n?#]+)', r'youtube\.com/shorts/([^&\n?#]+)', ] for pattern in patterns: match = re.search(pattern, url_or_id) if match: return match.group(1) return None def get_authenticated_service(): """Authenticate and return YouTube API service.""" creds = None if TOKEN_FILE.exists(): with open(TOKEN_FILE, 'rb') as token: creds = pickle.load(token) if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES) creds = flow.run_local_server(port=8080) with open(TOKEN_FILE, 'wb') as token: pickle.dump(creds, token) return build('youtube', 'v3', credentials=creds) def format_duration(duration): """Convert ISO 8601 duration to readable format.""" match = re.match(r'PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?', duration) if match: hours, minutes, seconds = match.groups() parts = [] if hours: parts.append(f"{hours}h") if minutes: parts.append(f"{int(minutes)}m") if seconds: parts.append(f"{int(seconds)}s") return " ".join(parts) if parts else "0s" return duration def format_number(num_str): """Format number with commas.""" try: return f"{int(num_str):,}" except: return num_str def get_video_info(youtube, video_id, verbose=False): """Fetch detailed video information.""" try: response = youtube.videos().list( part="snippet,status,statistics,contentDetails,topicDetails", id=video_id ).execute() if not response.get('items'): return None video = response['items'][0] snippet = video['snippet'] status = video['status'] stats = video.get('statistics', {}) content = video['contentDetails'] # Check if Jamie video is_jamie = snippet['channelId'] == JAMIE_CHANNEL_ID info = { 'id': video_id, 'title': snippet['title'], 'description': snippet['description'], 'channel': snippet['channelTitle'], 'channel_id': snippet['channelId'], 'is_jamie': is_jamie, 'published_at': snippet['publishedAt'], 'privacy_status': status['privacyStatus'], 'duration': format_duration(content['duration']), 'duration_raw': content['duration'], 'views': format_number(stats.get('viewCount', '0')), 'likes': format_number(stats.get('likeCount', '0')), 'comments': format_number(stats.get('commentCount', '0')), 'tags': snippet.get('tags', []), 'category_id': snippet.get('categoryId', ''), 'thumbnail': snippet.get('thumbnails', {}).get('maxres', {}).get('url') or snippet.get('thumbnails', {}).get('high', {}).get('url', ''), } return info except Exception as e: print(f"Error fetching video info: {e}") return None def print_video_info(info, verbose=False): """Print video information in formatted output.""" if not info: print("❌ Video not found or not accessible") return jamie_badge = "🏥 Jamie" if info['is_jamie'] else "External" status_icon = {'public': '🟢', 'unlisted': '🟡', 'private': '🔴'}.get(info['privacy_status'], '⚪') print("\n" + "="*70) print(f"📹 Video Information") print("="*70) print(f"\n🎬 Title: {info['title']}") print(f"🔗 URL: https://www.youtube.com/watch?v={info['id']}") print(f"📺 Channel: {info['channel']} [{jamie_badge}]") print(f"📅 Published: {info['published_at'][:10]}") print(f"{status_icon} Status: {info['privacy_status']}") print(f"⏱️ Duration: {info['duration']}") print(f"\n📊 Statistics:") print(f" Views: {info['views']}") print(f" Likes: {info['likes']}") print(f" Comments: {info['comments']}") if verbose: print(f"\n📝 Description:") print("-"*70) desc_lines = info['description'].split('\n')[:15] for line in desc_lines: print(f" {line[:65]}") if len(info['description'].split('\n')) > 15: print(" ...") print("-"*70) if info['tags']: print(f"\n🏷️ Tags ({len(info['tags'])}):") print(f" {', '.join(info['tags'][:10])}") if len(info['tags']) > 10: print(f" ... and {len(info['tags']) - 10} more") print(f"\n🖼️ Thumbnail: {info['thumbnail']}") print("\n" + "="*70) def main(): parser = argparse.ArgumentParser( description='Fetch YouTube video information', epilog='Examples:\n' ' python jamie_video_info.py https://youtu.be/P-ovr-aaD1E\n' ' python jamie_video_info.py P-ovr-aaD1E -v\n' ' python jamie_video_info.py URL1 URL2 URL3', formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument('urls', nargs='+', help='YouTube URL(s) or video ID(s)') parser.add_argument('-v', '--verbose', action='store_true', help='Show detailed info including description and tags') parser.add_argument('--json', action='store_true', help='Output as JSON') args = parser.parse_args() youtube = get_authenticated_service() if not youtube: sys.exit(1) results = [] for url in args.urls: video_id = extract_video_id(url) if not video_id: print(f"\n❌ Invalid URL or video ID: {url}") continue info = get_video_info(youtube, video_id, args.verbose) if args.json: results.append(info) else: print_video_info(info, args.verbose) if args.json: import json print(json.dumps(results, indent=2, ensure_ascii=False)) if __name__ == "__main__": main()