main.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import os
  2. import time
  3. import datetime
  4. import pickle
  5. from googleapiclient.discovery import build
  6. import subprocess
  7. from google.auth.transport.requests import Request
  8. import signal
  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. def authenticate_with_token():
  17. """Authenticate using the saved token."""
  18. if not os.path.exists(TOKEN_FILE):
  19. raise FileNotFoundError(f"Token file '{TOKEN_FILE}' not found. Please run the token retrieval script first.")
  20. with open(TOKEN_FILE, "rb") as token:
  21. creds = pickle.load(token)
  22. # Refresh the token if needed
  23. if creds and creds.expired and creds.refresh_token:
  24. creds.refresh(Request())
  25. youtube = build("youtube", "v3", credentials=creds)
  26. return youtube
  27. def get_subscribed_channels(youtube):
  28. """Fetch the list of subscribed channels."""
  29. channels = []
  30. request = youtube.subscriptions().list(
  31. part="snippet",
  32. mine=True,
  33. maxResults=50
  34. )
  35. while request:
  36. response = request.execute()
  37. for item in response["items"]:
  38. channels.append(item["snippet"]["resourceId"]["channelId"])
  39. request = youtube.subscriptions().list_next(request, response)
  40. return channels
  41. def get_recent_videos(youtube, channel_id):
  42. """Fetch videos from a channel published in the last `DAYS_BACK` days."""
  43. recent_videos = []
  44. published_after = (datetime.datetime.now() - datetime.timedelta(days=DAYS_BACK)).isoformat("T") + "Z"
  45. request = youtube.search().list(
  46. part="snippet",
  47. channelId=channel_id,
  48. publishedAfter=published_after,
  49. maxResults=50,
  50. type="video",
  51. order="date"
  52. )
  53. response = request.execute()
  54. for item in response["items"]:
  55. recent_videos.append({
  56. "video_id": item["id"]["videoId"],
  57. "title": item["snippet"]["title"],
  58. "description": item["snippet"]["description"],
  59. "published_at": item["snippet"]["publishedAt"],
  60. "channel_title": item["snippet"]["channelTitle"]
  61. })
  62. return recent_videos
  63. def download_video(video_id, output_path):
  64. """Download a YouTube video as mp4 using yt-dlp."""
  65. command = [
  66. "yt-dlp",
  67. "-f", "bestvideo+bestaudio[ext=mp4]/best[ext=mp4]/best", # Ensure highest quality as MP4
  68. f"https://www.youtube.com/watch?v={video_id}",
  69. "-o", os.path.join(output_path, "%(title)s.%(ext)s")
  70. ]
  71. subprocess.run(command, check=True)
  72. def create_nfo_file(video_data, file_path):
  73. """Create an Emby-compatible NFO file."""
  74. nfo_content = f"""<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  75. <movie>
  76. <title>{video_data['title']}</title>
  77. <plot>{video_data['description']}</plot>
  78. <year>{video_data['published_at'][:4]}</year>
  79. <premiered>{video_data['published_at']}</premiered>
  80. <studio>{video_data['channel_title']}</studio>
  81. </movie>
  82. """
  83. with open(file_path, "w", encoding="utf-8") as nfo_file:
  84. nfo_file.write(nfo_content)
  85. def delete_old_videos(folder, days=10):
  86. """Delete video files and corresponding NFO files older than `days`."""
  87. now = time.time()
  88. cutoff = now - (days * 86400) # Convert days to seconds
  89. for root, dirs, files in os.walk(folder):
  90. for file in files:
  91. file_path = os.path.join(root, file)
  92. if os.path.isfile(file_path):
  93. # Check the file's last modification time
  94. if os.path.getmtime(file_path) < cutoff:
  95. print(f"Deleting: {file_path}")
  96. os.remove(file_path)
  97. def video_exists(output_path, video_id):
  98. """Check if a video already exists in the directory."""
  99. return os.path.exists(os.path.join(output_path, video_id))
  100. def check_exit_time():
  101. """Check if the current time is past the exit time."""
  102. now = datetime.datetime.now().time()
  103. if now >= EXIT_TIME:
  104. print("Current time exceeds exit time. Terminating script.")
  105. sys.exit(0)
  106. def main():
  107. # Authenticate YouTube API
  108. print("Authenticating with token...")
  109. youtube = authenticate_with_token()
  110. if not os.path.exists(OUTPUT_DIR):
  111. os.makedirs(OUTPUT_DIR)
  112. # Clean up old videos
  113. print(f"Deleting videos older than {DAYS_TO_KEEP} days in {OUTPUT_DIR}...")
  114. delete_old_videos(OUTPUT_DIR, days=DAYS_TO_KEEP)
  115. # Fetch subscribed channels
  116. print("Fetching subscribed channels...")
  117. channels = get_subscribed_channels(youtube)
  118. # Fetch and process recent videos
  119. for channel_id in channels:
  120. print(f"Fetching recent videos for channel: {channel_id}")
  121. videos = get_recent_videos(youtube, channel_id)
  122. for video in videos:
  123. # Check the exit time before processing each video
  124. check_exit_time()
  125. print(f"Processing video: {video['title']}")
  126. video_dir = os.path.join(OUTPUT_DIR, video["video_id"])
  127. # Check if the video already exists
  128. if os.path.exists(video_dir):
  129. print(f"Skipping download, already exists: {video['title']} ({video['video_id']})")
  130. continue
  131. os.makedirs(video_dir, exist_ok=True)
  132. # Download the video
  133. download_video(video["video_id"], video_dir)
  134. # Create the NFO file
  135. nfo_file_path = os.path.join(video_dir, "movie.nfo")
  136. create_nfo_file(video, nfo_file_path)
  137. print("All videos processed successfully!")
  138. if __name__ == "__main__":
  139. main()