main.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import os
  2. import time
  3. import datetime
  4. import pickle
  5. import threading
  6. from googleapiclient.discovery import build
  7. import subprocess
  8. from google.auth.transport.requests import Request
  9. import sys
  10. # Constants
  11. OUTPUT_DIR = "/media/media/YouTube" # Change this to your Emby folder
  12. DAYS_BACK = 5 # Videos published in the last 5 days
  13. DAYS_TO_KEEP = 10 # Delete videos older than 10 days
  14. TOKEN_FILE = "youtube_token.pickle" # Token file from the OAuth script
  15. EXIT_TIME = datetime.time(6, 0) # Exit the script at 06:00
  16. POPULAR_VIDEOS_MAX_RESULTS = 10 # Number of popular videos to fetch (you can change this)
  17. REGION_CODES = ["US", "GB", "LU"] # Regions to fetch popular videos from
  18. def update_yt_dlp():
  19. """Update yt-dlp to the latest version."""
  20. print("Updating yt-dlp to the latest version...")
  21. subprocess.run(["pip", "install", "--upgrade", "yt-dlp"], check=True)
  22. print("yt-dlp updated successfully!")
  23. def authenticate_with_token():
  24. """Authenticate using the saved token."""
  25. if not os.path.exists(TOKEN_FILE):
  26. raise FileNotFoundError(f"Token file '{TOKEN_FILE}' not found. Please run the token retrieval script first.")
  27. with open(TOKEN_FILE, "rb") as token:
  28. creds = pickle.load(token)
  29. # Refresh the token if needed
  30. if creds and creds.expired and creds.refresh_token:
  31. creds.refresh(Request())
  32. youtube = build("youtube", "v3", credentials=creds)
  33. return youtube
  34. def get_subscribed_channels(youtube):
  35. """Fetch the list of subscribed channels."""
  36. channels = []
  37. request = youtube.subscriptions().list(
  38. part="snippet",
  39. mine=True,
  40. maxResults=50
  41. )
  42. while request:
  43. response = request.execute()
  44. for item in response["items"]:
  45. channels.append(item["snippet"]["resourceId"]["channelId"])
  46. request = youtube.subscriptions().list_next(request, response)
  47. return channels
  48. def get_recent_videos(youtube, channel_id):
  49. """Fetch videos from a channel published in the last `DAYS_BACK` days."""
  50. recent_videos = []
  51. published_after = (datetime.datetime.now() - datetime.timedelta(days=DAYS_BACK)).isoformat("T") + "Z"
  52. request = youtube.search().list(
  53. part="snippet",
  54. channelId=channel_id,
  55. publishedAfter=published_after,
  56. maxResults=50,
  57. type="video",
  58. order="date"
  59. )
  60. response = request.execute()
  61. for item in response["items"]:
  62. recent_videos.append({
  63. "video_id": item["id"]["videoId"],
  64. "title": item["snippet"]["title"],
  65. "description": item["snippet"]["description"],
  66. "published_at": item["snippet"]["publishedAt"],
  67. "channel_title": item["snippet"]["channelTitle"]
  68. })
  69. return recent_videos
  70. def get_popular_videos(youtube, region_code):
  71. """Fetch the most popular videos for a given region."""
  72. popular_videos = []
  73. request = youtube.videos().list(
  74. part="snippet,statistics",
  75. chart="mostPopular",
  76. regionCode=region_code, # Region code passed here
  77. maxResults=POPULAR_VIDEOS_MAX_RESULTS
  78. )
  79. response = request.execute()
  80. for item in response["items"]:
  81. popular_videos.append({
  82. "video_id": item["id"],
  83. "title": item["snippet"]["title"],
  84. "description": item["snippet"]["description"],
  85. "published_at": item["snippet"]["publishedAt"],
  86. "channel_title": item["snippet"]["channelTitle"]
  87. })
  88. return popular_videos
  89. def download_video(video_id, output_path):
  90. """Download a YouTube video in the best quality, handling errors gracefully."""
  91. try:
  92. command = [
  93. "yt-dlp",
  94. "-f", "bestvideo+bestaudio/best", # Best available format
  95. f"https://www.youtube.com/watch?v={video_id}",
  96. "-o", os.path.join(output_path, "%(title)s.%(ext)s")
  97. ]
  98. subprocess.run(command, check=True)
  99. except subprocess.CalledProcessError as e:
  100. print(f"Error downloading video {video_id}: {e}")
  101. return False # Return False to indicate failure
  102. return True # Return True if download is successful
  103. # Temporarily commented out video conversion functionality
  104. # def convert_to_mp4(input_file):
  105. # """Convert a video to mp4 and delete the original file."""
  106. # mp4_file = os.path.splitext(input_file)[0] + ".mp4"
  107. # print(f"Converting {input_file} to {mp4_file}...")
  108. # command = ["ffmpeg", "-i", input_file, "-c:v", "libx264", "-preset", "fast", "-c:a", "aac", mp4_file]
  109. # subprocess.run(command, check=True)
  110. # print(f"Conversion completed: {mp4_file}")
  111. # if os.path.exists(mp4_file):
  112. # os.remove(input_file)
  113. # print(f"Original file deleted: {input_file}")
  114. def create_nfo_file(video_data, file_path):
  115. """Create an Emby-compatible NFO file."""
  116. nfo_content = f"""<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  117. <movie>
  118. <title>{video_data['title']}</title>
  119. <plot>{video_data['description']}</plot>
  120. <year>{video_data['published_at'][:4]}</year>
  121. <premiered>{video_data['published_at']}</premiered>
  122. <studio>{video_data['channel_title']}</studio>
  123. </movie>
  124. """
  125. with open(file_path, "w", encoding="utf-8") as nfo_file:
  126. nfo_file.write(nfo_content)
  127. def delete_old_videos(folder, days=10):
  128. """Delete video files and corresponding NFO files older than `days`."""
  129. now = time.time()
  130. cutoff = now - (days * 86400) # Convert days to seconds
  131. for root, dirs, files in os.walk(folder):
  132. for file in files:
  133. file_path = os.path.join(root, file)
  134. if os.path.isfile(file_path):
  135. # Check the file's last modification time
  136. if os.path.getmtime(file_path) < cutoff:
  137. print(f"Deleting: {file_path}")
  138. os.remove(file_path)
  139. def video_exists(output_path, video_id):
  140. """Check if a video already exists in the directory."""
  141. return os.path.exists(os.path.join(output_path, video_id))
  142. def check_exit_time():
  143. """Check if the current time is past the exit time."""
  144. now = datetime.datetime.now().time()
  145. if now >= EXIT_TIME:
  146. print("Current time exceeds exit time. Terminating script.")
  147. sys.exit(0)
  148. def main():
  149. # Update yt-dlp
  150. update_yt_dlp()
  151. # Authenticate YouTube API
  152. print("Authenticating with token...")
  153. youtube = authenticate_with_token()
  154. if not os.path.exists(OUTPUT_DIR):
  155. os.makedirs(OUTPUT_DIR)
  156. # Clean up old videos
  157. print(f"Deleting videos older than {DAYS_TO_KEEP} days in {OUTPUT_DIR}...")
  158. delete_old_videos(OUTPUT_DIR, days=DAYS_TO_KEEP)
  159. # Fetch subscribed channels
  160. print("Fetching subscribed channels...")
  161. channels = get_subscribed_channels(youtube)
  162. # Fetch and process recent videos from subscribed channels
  163. for channel_id in channels:
  164. print(f"Fetching recent videos for channel: {channel_id}")
  165. videos = get_recent_videos(youtube, channel_id)
  166. for video in videos:
  167. # Check the exit time before processing each video
  168. check_exit_time()
  169. print(f"Processing video: {video['title']}")
  170. video_dir = os.path.join(OUTPUT_DIR, video["video_id"])
  171. # Check if the video already exists
  172. if os.path.exists(video_dir):
  173. print(f"Skipping download, already exists: {video['title']} ({video['video_id']})")
  174. continue
  175. os.makedirs(video_dir, exist_ok=True)
  176. # Download the video
  177. if not download_video(video["video_id"], video_dir):
  178. print(f"Skipping video {video['video_id']} due to download error.")
  179. continue # Skip this video if download failed
  180. # Temporarily comment out conversion, but keep it in the code for future use
  181. # video_file = next(
  182. # (f for f in os.listdir(video_dir) if not f.endswith(".mp4")),
  183. # None
  184. # )
  185. # if video_file:
  186. # input_path = os.path.join(video_dir, video_file)
  187. # threading.Thread(target=convert_to_mp4, args=(input_path,)).start()
  188. # Create the NFO file
  189. nfo_file_path = os.path.join(video_dir, "movie.nfo")
  190. create_nfo_file(video, nfo_file_path)
  191. # Fetch and process most popular videos worldwide (from multiple regions)
  192. for region_code in REGION_CODES:
  193. print(f"Fetching the most popular videos from {region_code}...")
  194. popular_videos = get_popular_videos(youtube, region_code)
  195. for video in popular_videos:
  196. # Check the exit time before processing each video
  197. check_exit_time()
  198. print(f"Processing popular video: {video['title']} ({region_code})")
  199. video_dir = os.path.join(OUTPUT_DIR, video["video_id"])
  200. # Check if the video already exists
  201. if os.path.exists(video_dir):
  202. print(f"Skipping download, already exists: {video['title']} ({video['video_id']})")
  203. continue
  204. os.makedirs(video_dir, exist_ok=True)
  205. # Download the popular video
  206. if not download_video(video["video_id"], video_dir):
  207. print(f"Skipping popular video {video['video_id']} due to download error.")
  208. continue # Skip this video if download failed
  209. # Temporarily comment out conversion, but keep it in the code for future use
  210. # video_file = next(
  211. # (f for f in os.listdir(video_dir) if not f.endswith(".mp4")),
  212. # None
  213. # )
  214. # if video_file:
  215. # input_path = os.path.join(video_dir, video_file)
  216. # threading.Thread(target=convert_to_mp4, args=(input_path,)).start()
  217. # Create the NFO file
  218. nfo_file_path = os.path.join(video_dir, "movie.nfo")
  219. create_nfo_file(video, nfo_file_path)
  220. print("All videos processed successfully!")
  221. if __name__ == "__main__":
  222. main()