132 lines
3.9 KiB
Python
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.") |