Files
ZonePlayer/yt_sync.py
T
2025-06-28 01:05:35 -05:00

132 lines
3.9 KiB
Python

import os
import json
import yaml
from yt_dlp import YoutubeDL
from Playlist_Updater import update
import re
CONFIG_FILE = "config.yaml"
NOW_PLAYING_FILE = "/home/media/Music/now_playing.json"
MUSIC_BASE = "/home/media/Music"
with open(CONFIG_FILE, "r") as f:
config = yaml.safe_load(f)
sources = config.get("sources", {})
if os.path.exists(NOW_PLAYING_FILE):
with open(NOW_PLAYING_FILE, "r") as f:
now_playing = json.load(f)
else:
now_playing = {}
ydl_opts = {
"quiet": True,
"extract_flat": True,
"dump_single_json": True,
"skip_download": True
}
def sanitize_filename(title):
return title.replace("/", "-")
pending_files = {}
for name, data in sources.items():
url = data.get("youtube_url")
id_cache_file = data.get("id_cache")
if not url or not id_cache_file:
continue
print(f"Processing Playlist: {name}")
try:
with open(id_cache_file, "r") as f:
content = f.read().strip()
old_ids = set(json.loads(content)) if content else set()
except FileNotFoundError:
old_ids = set()
with YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=False)
new_ids = set(entry["id"] for entry in info["entries"])
to_add = new_ids - old_ids
to_remove = old_ids - new_ids
print(f"To download: {len(to_add)}")
print(f"To remove: {len(to_remove)}")
# where new music will be saved
folder_path = os.path.join(MUSIC_BASE, name.capitalize())
os.makedirs(folder_path, exist_ok=True)
# Define download options for yt-dlp
download_opts = {
"format": "bestaudio/best",
"quiet": False,
"postprocessors": [{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
"preferredquality": "192",
}]
}
if to_add:
with YoutubeDL({"quiet": True}) as metadata_ydl:
for vid in to_add:
try:
info = metadata_ydl.extract_info(f"https://youtu.be/{vid}", download=False)
title = info.get("title", vid)
safe_title = sanitize_filename(title)
filename = f"{safe_title} [{vid}]"
full_output_path = os.path.join(folder_path, filename)
if os.path.exists(full_output_path):
print(f"File already downloaded")
continue
print(f"downloading... {filename}")
download_opts["outtmpl"] = full_output_path
with YoutubeDL(download_opts) as downloader:
downloader.download([f"https://youtu.be/{vid}"])
except Exception as e:
print(f"Failed to process {vid}: {e}")
currently_playing = now_playing.get(name, "").strip()
pending_delete = []
for file in os.listdir(folder_path):
if not file.endswith(".mp3"):
continue
full_path = os.path.join(folder_path, file)
# Try to match video ID
match = re.search(r'\[([a-zA-Z0-9_-]{11})\]\.mp3$', file)
if not match:
continue
file_id = match.group(1)
# Only delete if:
# - the ID is in the removal list
# - the file is NOT currently playing
if file_id in to_remove:
if file == currently_playing:
print(f"postponing deletion (still playing): {file}")
pending_delete.append(file_id)
pending_files.setdefault(name, []).append(file)
else:
print(f"Deleting: {file}")
os.remove(full_path)
with open(id_cache_file, "w") as f:
json.dump(sorted(new_ids), f, indent=2)
print(f"Saved updated ID cache: {id_cache_file}")
print("\n🔁 Regenerating .m3u playlists...")
update(skip_files=pending_files)
print("✅ Playlist sync complete.")