import os import time import datetime import pickle import threading from googleapiclient.discovery import build import subprocess from google.auth.transport.requests import Request import sys # Constants OUTPUT_DIR = "/media/media/YouTube" # Change this to your Emby folder DAYS_BACK = 5 # Videos published in the last 5 days DAYS_TO_KEEP = 10 # Delete videos older than 10 days TOKEN_FILE = "youtube_token.pickle" # Token file from the OAuth script EXIT_TIME = datetime.time(6, 0) # Exit the script at 06:00 POPULAR_VIDEOS_MAX_RESULTS = 10 # Number of popular videos to fetch (you can change this) REGION_CODES = ["US", "GB", "LU"] # Regions to fetch popular videos from def update_yt_dlp(): """Update yt-dlp to the latest version.""" print("Updating yt-dlp to the latest version...") subprocess.run(["pip", "install", "--upgrade", "yt-dlp"], check=True) print("yt-dlp updated successfully!") def authenticate_with_token(): """Authenticate using the saved token, with automatic refresh.""" if not os.path.exists(TOKEN_FILE): raise FileNotFoundError(f"Token file '{TOKEN_FILE}' not found. Please run the token retrieval script first.") # Load credentials from the token file with open(TOKEN_FILE, "rb") as token: creds = pickle.load(token) # Refresh the token if it has expired if creds and creds.expired and creds.refresh_token: print("Access token expired. Refreshing...") creds.refresh(Request()) # Save the refreshed credentials back to the file with open(TOKEN_FILE, "wb") as token: pickle.dump(creds, token) print("Token refreshed successfully!") # Build the YouTube API client youtube = build("youtube", "v3", credentials=creds) return youtube def get_subscribed_channels(youtube): """Fetch the list of subscribed channels.""" channels = [] request = youtube.subscriptions().list( part="snippet", mine=True, maxResults=50 ) while request: response = request.execute() for item in response["items"]: channels.append(item["snippet"]["resourceId"]["channelId"]) request = youtube.subscriptions().list_next(request, response) return channels def get_recent_videos(youtube, channel_id): """Fetch videos from a channel published in the last `DAYS_BACK` days.""" recent_videos = [] published_after = (datetime.datetime.now() - datetime.timedelta(days=DAYS_BACK)).isoformat("T") + "Z" request = youtube.search().list( part="snippet", channelId=channel_id, publishedAfter=published_after, maxResults=50, type="video", order="date" ) response = request.execute() for item in response["items"]: recent_videos.append({ "video_id": item["id"]["videoId"], "title": item["snippet"]["title"], "description": item["snippet"]["description"], "published_at": item["snippet"]["publishedAt"], "channel_title": item["snippet"]["channelTitle"] }) return recent_videos def get_popular_videos(youtube, region_code): """Fetch the most popular videos for a given region.""" popular_videos = [] request = youtube.videos().list( part="snippet,statistics", chart="mostPopular", regionCode=region_code, # Region code passed here maxResults=POPULAR_VIDEOS_MAX_RESULTS ) response = request.execute() for item in response["items"]: popular_videos.append({ "video_id": item["id"], "title": item["snippet"]["title"], "description": item["snippet"]["description"], "published_at": item["snippet"]["publishedAt"], "channel_title": item["snippet"]["channelTitle"] }) return popular_videos def download_video(video_id, output_path): """Download a YouTube video in the best quality, handling errors gracefully.""" try: command = [ "yt-dlp", "-f", "bestvideo+bestaudio/best", # Best available format f"https://www.youtube.com/watch?v={video_id}", "-o", os.path.join(output_path, "%(title)s.%(ext)s") ] subprocess.run(command, check=True) except subprocess.CalledProcessError as e: print(f"Error downloading video {video_id}: {e}") return False # Return False to indicate failure return True # Return True if download is successful # Temporarily commented out video conversion functionality # def convert_to_mp4(input_file): # """Convert a video to mp4 and delete the original file.""" # mp4_file = os.path.splitext(input_file)[0] + ".mp4" # print(f"Converting {input_file} to {mp4_file}...") # command = ["ffmpeg", "-i", input_file, "-c:v", "libx264", "-preset", "fast", "-c:a", "aac", mp4_file] # subprocess.run(command, check=True) # print(f"Conversion completed: {mp4_file}") # if os.path.exists(mp4_file): # os.remove(input_file) # print(f"Original file deleted: {input_file}") def create_nfo_file(video_data, file_path): """Create an Emby-compatible NFO file.""" nfo_content = f""" {video_data['title']} {video_data['description']} {video_data['published_at'][:4]} {video_data['published_at']} {video_data['channel_title']} """ with open(file_path, "w", encoding="utf-8") as nfo_file: nfo_file.write(nfo_content) def delete_old_videos(folder, days=10): """Delete video files and corresponding NFO files older than `days`.""" now = time.time() cutoff = now - (days * 86400) # Convert days to seconds for root, dirs, files in os.walk(folder): for file in files: file_path = os.path.join(root, file) if os.path.isfile(file_path): # Check the file's last modification time if os.path.getmtime(file_path) < cutoff: print(f"Deleting: {file_path}") os.remove(file_path) def video_exists(output_path, video_id): """Check if a video already exists in the directory.""" return os.path.exists(os.path.join(output_path, video_id)) def check_exit_time(): """Check if the current time is past the exit time.""" now = datetime.datetime.now().time() if now >= EXIT_TIME: print("Current time exceeds exit time. Terminating script.") sys.exit(0) def main(): # Update yt-dlp update_yt_dlp() # Authenticate YouTube API print("Authenticating with token...") youtube = authenticate_with_token() if not os.path.exists(OUTPUT_DIR): os.makedirs(OUTPUT_DIR) # Clean up old videos print(f"Deleting videos older than {DAYS_TO_KEEP} days in {OUTPUT_DIR}...") delete_old_videos(OUTPUT_DIR, days=DAYS_TO_KEEP) # Fetch subscribed channels print("Fetching subscribed channels...") channels = get_subscribed_channels(youtube) # Fetch and process recent videos from subscribed channels for channel_id in channels: print(f"Fetching recent videos for channel: {channel_id}") videos = get_recent_videos(youtube, channel_id) for video in videos: # Check the exit time before processing each video check_exit_time() print(f"Processing video: {video['title']}") video_dir = os.path.join(OUTPUT_DIR, video["video_id"]) # Check if the video already exists if os.path.exists(video_dir): print(f"Skipping download, already exists: {video['title']} ({video['video_id']})") continue os.makedirs(video_dir, exist_ok=True) # Download the video if not download_video(video["video_id"], video_dir): print(f"Skipping video {video['video_id']} due to download error.") continue # Skip this video if download failed # Temporarily comment out conversion, but keep it in the code for future use # video_file = next( # (f for f in os.listdir(video_dir) if not f.endswith(".mp4")), # None # ) # if video_file: # input_path = os.path.join(video_dir, video_file) # threading.Thread(target=convert_to_mp4, args=(input_path,)).start() # Create the NFO file nfo_file_path = os.path.join(video_dir, "movie.nfo") create_nfo_file(video, nfo_file_path) # Fetch and process most popular videos worldwide (from multiple regions) for region_code in REGION_CODES: print(f"Fetching the most popular videos from {region_code}...") popular_videos = get_popular_videos(youtube, region_code) for video in popular_videos: # Check the exit time before processing each video check_exit_time() print(f"Processing popular video: {video['title']} ({region_code})") video_dir = os.path.join(OUTPUT_DIR, video["video_id"]) # Check if the video already exists if os.path.exists(video_dir): print(f"Skipping download, already exists: {video['title']} ({video['video_id']})") continue os.makedirs(video_dir, exist_ok=True) # Download the popular video if not download_video(video["video_id"], video_dir): print(f"Skipping popular video {video['video_id']} due to download error.") continue # Skip this video if download failed # Temporarily comment out conversion, but keep it in the code for future use # video_file = next( # (f for f in os.listdir(video_dir) if not f.endswith(".mp4")), # None # ) # if video_file: # input_path = os.path.join(video_dir, video_file) # threading.Thread(target=convert_to_mp4, args=(input_path,)).start() # Create the NFO file nfo_file_path = os.path.join(video_dir, "movie.nfo") create_nfo_file(video, nfo_file_path) print("All videos processed successfully!") if __name__ == "__main__": main()