feature: added dockerfile and docker-entrypoint.sh

This commit is contained in:
unfaiyted 2024-08-02 14:25:11 -05:00
parent 62fc4201ca
commit c0cb7ecbee
23 changed files with 7974 additions and 201 deletions

43
Dockerfile Normal file
View File

@ -0,0 +1,43 @@
# Use an official Python runtime as a parent image
FROM python:3.9-slim
# Set the working directory in the container
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
# Install system dependencies and cron
RUN apt-get update && \
apt-get install -y --no-install-recommends cron #&& \
# rm -rf /var/lib/apt/lists/*
# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir --upgrade pip
RUN pip install --no-cache-dir --trusted-host pypi.org --trusted-host files.pythonhosted.org -r requirements.txt
# Install cron
#RUN apt-get update && apt-get -y install cron
# Copy crontab file to the cron.d directory
COPY app-cron /etc/cron.d/app-cron
# Give execution rights on the cron job
RUN chmod 0644 /etc/cron.d/app-cron
# Apply cron job
RUN crontab /etc/cron.d/app-cron
# Create the log file to be able to run tail
RUN touch /var/log/cron.log
# Run the command on container startup
CMD cron && tail -f /var/log/cron.log
# Copy the entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Set the entrypoint
ENTRYPOINT ["docker-entrypoint.sh"]

1
app-cron Normal file
View File

@ -0,0 +1 @@
0 2 * * * /usr/local/bin/python /app/src/main.py >> /var/log/cron.log 2>&1

10
docker-entrypoint.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/sh
# Replace the cron schedule with the one from .env
#sed -i "s|* * * * *|$CRON_SCHEDULE|g" /etc/cron.d/app-cron
# Apply the updated cron job
#crontab /etc/cron.d/app-cron
# Start cron in the foreground
cron -f

BIN
requirements.txt Normal file

Binary file not shown.

View File

@ -1 +1 @@
{"access_token": "BQCdJ8TcebCvCeo2VM75nwko_tshza2WqpKlhWK_EZufuP4NXOWEtaks1sUfnHlnJt258CbM2Hsx78XUNbu84_4u-uKEHgj51fPRJzAVg91O6MeSd-3ZpIUHbXLaNjnUVBS7ltPClDRHyCuaFaRW8BKlA_F-Nq-BrbPD4vgJnqboI3prb3BH4GTl1tfkInodNAfIog5oeReyHF8vy85KtrSugMZeSptlUInUd4Z-IZLqXg", "token_type": "Bearer", "expires_in": 3600, "scope": "playlist-read-private", "expires_at": 1715037521, "refresh_token": "AQAA4rKcyTFqaVctXWaxmE3d0LqLJu3ms91BNXrlJeYfy_1fYmCyQ3VyX6rO6K0O4iGyRVNWT8lcJB6MGRuC5rLXG85ZFB2XQtpcab6ECyKrIQNcMNU7AJj_eiZgfTwNtFk"} {"access_token": "BQCOqPiTZyLWYouW3TxjCpFO3m15BcmPJXXYXhLZ-k36BaTCh7IVkoFiHh2uXXydEIMabbHuk9mZsImyw-lfZ1DS-_NNCEdR1z1Hy0xzBlM_OJ3ItOHMIW-xodY3czcUJWcQUvcLduUqpGiWEod_lbxN9JaYx35JA9zFozGfwba82UjrMtDsRqYQg0PMhqhqb-VCSBClg8ue0RGR8D3IdmketZRLq7Se46YvWAOBG_td-YI", "token_type": "Bearer", "expires_in": 3600, "scope": "playlist-read-private", "expires_at": 1722629879, "refresh_token": "AQAA4rKcyTFqaVctXWaxmE3d0LqLJu3ms91BNXrlJeYfy_1fYmCyQ3VyX6rO6K0O4iGyRVNWT8lcJB6MGRuC5rLXG85ZFB2XQtpcab6ECyKrIQNcMNU7AJj_eiZgfTwNtFk"}

12
src/README.md Normal file
View File

@ -0,0 +1,12 @@
# Emby Scripts
You can run any number of actions from the action folder directly.
Actions are in `src/actions`
There is a venv that has the variables for everything.
## Actions
- find_unmatched_songs.
-

View File

@ -1 +1 @@
{"access_token": "BQAYOPW8HTsv1vW7-DIAmNRqqXqG7XqddSEB9217h1jxSBqIu5yjvh13FB8sE-aW5k0wtycRdTqiUPcj4fLGPXvE3iNgbO059EqDmOzPWrAdzXBIgwal3SUouI2j2z_8tYTcqFh0VzqgcVSnX5AsEyhQcdJA2QusfItEPTI1jkv69RuAXURYtloPoCPjhkfCoum4FFrvya5xDSN06sgB2ZPA9euT0_9_f8oQbXGnZFsBPA", "token_type": "Bearer", "expires_in": 3600, "scope": "playlist-read-private", "expires_at": 1715107319, "refresh_token": "AQBij3uDeV9G6Z71rKFATWpcHJLhggDPYabbTEuvebdSKEqRY_5VqS98wkx3u037d7nBlaTHIQpeSqkbbOmLdIut56TZ3Yiva56R4t6PV22LSY0wUdTYfwSjP_nWr_jP1wU"} {"access_token": "BQDLUIwaf9wl9YwDlaD-kcAg1imJM99GzbVxIRkdasZ-i1cc0UlxCjkO9-m_dRMPVLjHedkX2mcffSj2XTJDFnrv92DrImZZmOD4AZJMo0FQsXWFyQHSYWdKqgxJg0xIx9a0Xz6-_sFQoPidjYxCS6o34WBOv7agj5V0ARJif90AUxvE9j5O813JRZjLFioCgmicjgcCXylMEm80OMXYCwfEWM3ZUgNVmZ3uqRLlUcHIw5c", "token_type": "Bearer", "expires_in": 3600, "scope": "playlist-read-private", "expires_at": 1722622536, "refresh_token": "AQBij3uDeV9G6Z71rKFATWpcHJLhggDPYabbTEuvebdSKEqRY_5VqS98wkx3u037d7nBlaTHIQpeSqkbbOmLdIut56TZ3Yiva56R4t6PV22LSY0wUdTYfwSjP_nWr_jP1wU"}

Binary file not shown.

View File

@ -0,0 +1,33 @@
from src.clients.emby_client import EmbyClient
from src.config import Config
from src.services.playlist_service import PlaylistService
#
# This is a simple example of copying a playlist to a target user
#
def copy_playlists_to_users(source_emby):
# Create an array of target usernames
target_usernames = ["Alyssa", "Dalton", "Laura", "Zuko", "Azula","Brittany","Adam","Chris","Jason"]
# Get all the playlists for the source user
playlists = source_emby.get_playlists()
# Iterate over each playlist
for playlist in playlists:
# Iterate over the target usernames
for target_username in target_usernames:
# Check if the target username is in the playlist name
if target_username in playlist["Name"]:
try:
# Create an instance of the Emby client for the target user
target_emby = EmbyClient(Config.EMBY_URL, target_username, '')
# Create an instance of the PlaylistService
playlist_service = PlaylistService(source_emby, target_emby)
# Copy the playlist to the target user
new_playlist = playlist_service.copy_playlist_by_usernames(Config.EMBY_USERNAME, target_username, playlist["Id"])
print(f"Playlist '{playlist['Name']}' copied successfully to user '{target_username}'. New playlist ID: {new_playlist['Id']}")
except ValueError as e:
print(f"Error copying playlist '{playlist['Name']}' to user '{target_username}': {str(e)}")

View File

@ -0,0 +1,45 @@
# codebase/emby-scripts/src/actions/delete_duplicate_playlists.py
import logging
from src.config import Config
from src.clients.emby_client import EmbyClient
# Configure logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
def delete_duplicate_playlists(emby):
# Initialize EmbyClient
# Get all playlists from Emby
all_playlists = emby.get_playlists()
# Dictionary to store playlists by name
playlists_by_name = {}
for playlist in all_playlists:
playlist_name = playlist['Name']
playlist_id = playlist['Id']
playlist_item_count = playlist.get('ChildCount', 0)
if playlist_name in playlists_by_name:
existing_playlist = playlists_by_name[playlist_name]
existing_item_count = existing_playlist.get('ChildCount', 0)
if playlist_item_count > existing_item_count:
# Delete the existing playlist with fewer items
emby.delete_playlist(existing_playlist['Id'])
logging.info(f"Deleted duplicate playlist: {existing_playlist['Name']} (ID: {existing_playlist['Id']}) with {existing_item_count} items")
playlists_by_name[playlist_name] = playlist
else:
# Delete the current playlist with fewer items
emby.delete_playlist(playlist_id)
logging.info(f"Deleted duplicate playlist: {playlist_name} (ID: {playlist_id}) with {playlist_item_count} items")
else:
playlists_by_name[playlist_name] = playlist
logging.info("Finished deleting duplicate playlists")
if __name__ == "__main__":
delete_duplicate_playlists()

View File

@ -2,11 +2,22 @@ import shlex
import sqlite3 import sqlite3
import logging import logging
import subprocess import subprocess
import unicodedata
import re import re
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
# Configure logging # Configure logging
logging.basicConfig( logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler("unmatched_songs.log", encoding="utf-8"),
logging.StreamHandler(sys.stdout)
]
) )
# Set the path to the spotdl.exe file # Set the path to the spotdl.exe file
@ -15,16 +26,37 @@ spotdl_path = "C:\\Users\\Dane Miller\\Downloads\\spotdl-4.2.5-win32.exe"
# Set the output directory where the songs will be saved # Set the output directory where the songs will be saved
output_dir = "C:\\Users\\Dane Miller\\Music\\spotdl_downloads" output_dir = "C:\\Users\\Dane Miller\\Music\\spotdl_downloads"
def remove_special_characters(string, skip_non_ascii=False):
if skip_non_ascii and not all(ord(char) < 128 for char in string):
return None # Skip strings with non-ASCII characters
# Normalize Unicode characters
normalized = unicodedata.normalize('NFKD', string)
# Remove diacritics (accent marks)
without_diacritics = ''.join([c for c in normalized if not unicodedata.combining(c)])
# Replace spaces and other special characters with underscores
cleaned_string = re.sub(r'[^\w\s-]', '_', without_diacritics)
# Replace multiple consecutive underscores with a single underscore
cleaned_string = re.sub(r'_+', '_', cleaned_string)
# Remove leading and trailing underscores
cleaned_string = cleaned_string.strip('_')
# Remove non-ASCII characters
cleaned_string = cleaned_string.encode('ascii', 'ignore').decode('ascii')
if cleaned_string == '':
return None
return cleaned_string
def find_unmatched_songs():
# Connect to the SQLite database # Connect to the SQLite database
conn = sqlite3.connect('unmatched_songs.db') conn = sqlite3.connect('unmatched_songs.db')
c = conn.cursor() c = conn.cursor()
def remove_special_characters(string):
# Remove single quotes, double quotes, slashes, and other special characters
cleaned_string = re.sub(r'[\'"\\/]', '', string)
return cleaned_string
# Create the table to store downloaded songs if it doesn't exist # Create the table to store downloaded songs if it doesn't exist
c.execute('''CREATE TABLE IF NOT EXISTS downloaded_songs c.execute('''CREATE TABLE IF NOT EXISTS downloaded_songs
(track_name TEXT, artist_name TEXT, album_name TEXT)''') (track_name TEXT, artist_name TEXT, album_name TEXT)''')
@ -49,6 +81,9 @@ total_unmatched_songs = len(unmatched_songs)
for index, song in enumerate(unmatched_songs, start=1): for index, song in enumerate(unmatched_songs, start=1):
track_name, artist_name, album_name = song track_name, artist_name, album_name = song
cleaned_track = remove_special_characters(track_name)
cleaned_artist = remove_special_characters(artist_name)
# Check if the song has already been downloaded # Check if the song has already been downloaded
c.execute('SELECT COUNT(*) FROM downloaded_songs WHERE track_name = ? AND artist_name = ? AND album_name = ?', c.execute('SELECT COUNT(*) FROM downloaded_songs WHERE track_name = ? AND artist_name = ? AND album_name = ?',
(track_name, artist_name, album_name)) (track_name, artist_name, album_name))
@ -62,7 +97,7 @@ for index, song in enumerate(unmatched_songs, start=1):
# Use spotdl to find and download the song # Use spotdl to find and download the song
try: try:
command = f'"{spotdl_path}" download "{remove_special_characters(artist_name)} - {remove_special_characters(track_name)}" --output "{output_dir}"' command = f'"{spotdl_path}" download "{cleaned_artist} - {cleaned_track}" --output "{output_dir}"'
logging.info(f"Running command: {command}") logging.info(f"Running command: {command}")
subprocess.run(command, shell=True, check=True) subprocess.run(command, shell=True, check=True)
logging.info(f"Successfully downloaded '{track_name}' by {artist_name}") logging.info(f"Successfully downloaded '{track_name}' by {artist_name}")
@ -80,3 +115,7 @@ for index, song in enumerate(unmatched_songs, start=1):
# Close the database connection # Close the database connection
conn.close() conn.close()
if __name__ == '__main__':
find_unmatched_songs()

View File

View File

@ -2,12 +2,13 @@ import spotipy
from spotipy.oauth2 import SpotifyOAuth from spotipy.oauth2 import SpotifyOAuth
import requests import requests
import logging import logging
import json
from fuzzywuzzy import fuzz from fuzzywuzzy import fuzz
import re import re
from src.config import Config from src.config import Config
from src.utils.string import StringUtils from src.utils.string import StringUtils
from src.clients.emby_client import EmbyClient from src.clients.emby_client import EmbyClient
from src.clients.spotify_client import SpotifyClient from src.clients.spotify_client import SpotifyClient, clean_title
import sqlite3 import sqlite3
# Configure logging # Configure logging
@ -16,22 +17,47 @@ logging.basicConfig(
) )
# Get the user's playlists from Spotify # Get the user's playlists from Spotify
spot = SpotifyClient(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET, Config.SPOTIFY_REDIRECT_URI, # spot = SpotifyClient(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET, Config.SPOTIFY_REDIRECT_URI,
Config.SPOTIFY_SCOPE) # Config.SPOTIFY_SCOPE)
emby = EmbyClient(Config.EMBY_URL, Config.EMBY_USERNAME, Config.EMBY_PASSWORD) # emby = EmbyClient(Config.EMBY_URL, Config.EMBY_USERNAME, Config.EMBY_PASSWORD)
# Connect to the SQLite database (it will be created if it doesn't exist) # Connect to the SQLite database (it will be created if it doesn't exist)
def try_match_and_add(track_name, artist_name, emby_playlist, emby, spot):
emby_search_results = emby.search_for_track(track_name, artist_name)
if emby_search_results:
for result in emby_search_results:
emby_item_id = result["Id"]
logging.debug(f'Matching {track_name} with {result["Name"]}')
if spot.match_song({"name": track_name, "artists": [{"name": artist_name}]}, result):
try:
emby.add_item_to_playlist(emby_playlist['Id'], emby_item_id)
# logging.info(f"Added '{track_name}' by {artist_name} to Emby playlist")
return True
except requests.exceptions.RequestException as e:
logging.warning(f"Error adding track to Emby playlist: {track_name}")
logging.warning(f"Error message: {str(e)}")
return False
def sync_spotify_playlists(spot, emby):
conn = sqlite3.connect('unmatched_songs.db') conn = sqlite3.connect('unmatched_songs.db')
c = conn.cursor() c = conn.cursor()
# Create the table to store unmatched songs if it doesn't exist # Create the table to store unmatched songs if it doesn't exist
c.execute('''CREATE TABLE IF NOT EXISTS unmatched_songs c.execute('''CREATE TABLE IF NOT EXISTS unmatched_songs
(playlist_name TEXT, track_name TEXT, artist_name TEXT, album_name TEXT)''') (playlist_name TEXT, track_name TEXT, artist_name TEXT, album_name TEXT)''')
playlists = spot.get_playlists() playlists = spot.get_playlists()
featured_playlists = spot.get_featured_playlists()
# print(json.dumps(featured_playlists, indent=4))
all_playlists = playlists["items"] + featured_playlists["items"]
# Iterate over each Spotify playlist # Iterate over each Spotify playlist
for playlist in playlists["items"]: for playlist in all_playlists:
playlist_name = playlist["name"] playlist_name = playlist["name"]
playlist_id = playlist["id"] playlist_id = playlist["id"]
playlist_owner = playlist["owner"]["display_name"] playlist_owner = playlist["owner"]["display_name"]
@ -92,49 +118,22 @@ for playlist in playlists["items"]:
track_name = track["track"]["name"] track_name = track["track"]["name"]
artist_name = track["track"]["artists"][0]["name"] artist_name = track["track"]["artists"][0]["name"]
album_name = track["track"]["album"]["name"] album_name = track["track"]["album"]["name"]
clean_track_name = StringUtils.remove_special_characters(track_name)
clean_artist_name = StringUtils.remove_special_characters(artist_name)
emby_search_results = emby.search_for_track(track_name, artist_name) if try_match_and_add(track_name, artist_name, emby_playlist, emby, spot):
emby_search_results_cleaned = emby.search_for_track(
clean_track_name, clean_artist_name
)
if emby_search_results:
found_match = False
for result in emby_search_results:
if found_match:
break
emby_item_id = result["Id"]
logging.debug(f'Matching {track["track"]["name"]} with {result["Name"]}')
if spot.match_song(track["track"], result):
logging.debug(f"Matched track: {track_name}")
try:
emby.add_item_to_playlist(emby_playlist['Id'], emby_item_id)
logging.info(
f"Added '{track_name}' by {artist_name} to Emby playlist"
)
found_match = True
added_tracks += 1 added_tracks += 1
except requests.exceptions.RequestException as e: continue
logging.warning(
f"Error adding track to Emby playlist: {track_name}" clean_track_name = clean_title(track_name)
) clean_artist_name = StringUtils.remove_special_characters(artist_name)
logging.warning(f"Error message: {str(e)}") if clean_track_name != track_name or clean_artist_name != artist_name:
else: if try_match_and_add(clean_track_name, clean_artist_name, emby_playlist, emby, spot):
logging.warning( added_tracks += 1
f"No match found for '{track_name}' by {artist_name} in Emby, failed match_song" continue
)
logging.warning(f"SPOTIFY: {track_name} by {artist_name}") logging.warning(f"No match found for '{track_name}' by {artist_name} in Emby")
logging.warning( logging.warning(f"Cleaned track name: {clean_track_name}")
f"EMBY: {result['Name']} by {result.get('Artists', [])}" logging.warning(f"Cleaned artist name: {clean_artist_name}")
)
else:
logging.warning(
f"No match found for '{track_name}' by {artist_name} in Emby"
)
unmatched_tracks.append((playlist_name, track_name, artist_name, album_name)) unmatched_tracks.append((playlist_name, track_name, artist_name, album_name))
# Insert the unmatched songs into the database # Insert the unmatched songs into the database
@ -150,3 +149,13 @@ for playlist in playlists["items"]:
# Close the database connection # Close the database connection
conn.close() conn.close()
if __name__ == "__main__":
spot = SpotifyClient(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET, Config.SPOTIFY_REDIRECT_URI,
Config.SPOTIFY_SCOPE)
emby = EmbyClient(Config.EMBY_URL, Config.EMBY_USERNAME, Config.EMBY_PASSWORD)
sync_spotify_playlists(spot, emby)

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,10 @@
import logging import logging
import re
import requests import requests
import spotipy import spotipy
from fuzzywuzzy import fuzz from fuzzywuzzy import fuzz
from spotipy import SpotifyOAuth from spotipy import SpotifyOAuth
from src.utils.string import StringUtils from src.utils.string import StringUtils
@ -24,9 +25,13 @@ class SpotifyClient:
Match Spotify artists with Emby artists using fuzzy string matching. Match Spotify artists with Emby artists using fuzzy string matching.
Returns the maximum artist similarity score found. Returns the maximum artist similarity score found.
""" """
# print("MATCH ARTISTS")
# print(spotify_artists)
# print(emby_artists)
max_artist_score = 0 max_artist_score = 0
for spotify_artist in spotify_artists: for spotify_artist in spotify_artists:
spotify_artist_name = StringUtils.clean_string(spotify_artist["name"]) spotify_artist_name = StringUtils.clean_string(spotify_artist)
for emby_artist in emby_artists: for emby_artist in emby_artists:
emby_artist_name = StringUtils.clean_string(emby_artist) emby_artist_name = StringUtils.clean_string(emby_artist)
artist_score = fuzz.ratio(spotify_artist_name, emby_artist_name) artist_score = fuzz.ratio(spotify_artist_name, emby_artist_name)
@ -37,27 +42,49 @@ class SpotifyClient:
""" """
Match a Spotify song with an Emby song using fuzzy string matching. Match a Spotify song with an Emby song using fuzzy string matching.
""" """
# print(spotify_song)
# print(emby_song)
try: try:
spotify_title = StringUtils.clean_string(spotify_song["name"]) spotify_title = StringUtils.clean_string(spotify_song.get("name", "").lower())
spotify_artists = spotify_song["artists"] spotify_artists = [artist.get("name", "").lower() for artist in spotify_song["artists"]]
emby_title = StringUtils.clean_string(emby_song["Name"]) emby_title = StringUtils.clean_string(emby_song.get("Name", "").lower())
emby_artists = emby_song["Artists"] emby_artists = [artist.lower() for artist in emby_song.get("Artists", [])]
spotify_title_no_parentheses = remove_parentheses(spotify_title)
emby_title_no_parentheses = remove_parentheses(emby_title)
spotify_title_cleaned = clean_title(spotify_title)
emby_title_cleaned = clean_title(emby_title)
# print("SPOTIFY TITLE NO PARENTHESES", spotify_title_no_parentheses)
# print("EMBY TITLE NO PARENTHESES", emby_title_no_parentheses)
title_ratio = max(
fuzz.ratio(spotify_title, emby_title),
fuzz.ratio(spotify_title_no_parentheses, emby_title_no_parentheses),
fuzz.ratio(spotify_title_cleaned, emby_title_cleaned)
)
# print("TITLE RATIO", title_ratio)
title_ratio = fuzz.ratio(spotify_title, emby_title)
artist_ratio = self.match_artists(spotify_artists, emby_artists) artist_ratio = self.match_artists(spotify_artists, emby_artists)
# print("ARTIST RATIO", artist_ratio)
# Adjust the thresholds as needed # Adjust the thresholds as needed
title_threshold = 90 title_threshold = 85
artist_threshold = 80 artist_threshold = 75
combined_threshold = 170 # Sum of title and artist thresholds combined_threshold = 165 # Sum of title and artist thresholds
if title_ratio >= title_threshold and artist_ratio >= artist_threshold: if title_ratio >= title_threshold and artist_ratio >= artist_threshold:
combined_score = title_ratio + artist_ratio combined_score = title_ratio + artist_ratio
if combined_score >= combined_threshold: if combined_score >= combined_threshold:
print(f"MATCH: {spotify_title} by {spotify_artists} vs {emby_title} by {emby_artists}")
return True return True
return False return False
except Exception as e: except Exception as e:
logging.error(f"Error matching song: {str(e)}") logging.error(f"Error matching song: {str(e)}")
return False return False
@ -85,6 +112,7 @@ class SpotifyClient:
return image_data return image_data
return None return None
def get_liked_songs(self): def get_liked_songs(self):
liked_songs = [] liked_songs = []
results = self.sp.current_user_saved_tracks() results = self.sp.current_user_saved_tracks()
@ -92,3 +120,75 @@ class SpotifyClient:
liked_songs.extend(results["items"]) liked_songs.extend(results["items"])
results = self.sp.next(results) results = self.sp.next(results)
return liked_songs return liked_songs
def get_featured_playlists(self):
return self.sp.featured_playlists()["playlists"]
pass
def remove_parentheses(text):
if text:
return re.sub(r'\([^)]*\)', '', text).strip()
else:
return text
def clean_title(title):
# Convert to lowercase
title = title.lower()
logging.info(f"Current title: {title}")
# Remove text after featuring, feat, ft, etc.
patterns = [
r'\(feat\..*?\)',
r'\(ft\..*?\)',
r'\(featuring.*?\)',
r'feat\..*',
r'ft\..*',
r'featuring.*',
r'\(with.*?\)',
r'\(prod\..*?\)',
r'\(produced by.*?\)',
r'- radio edit',
r'- single version',
r'- album version',
r'\(remaster(ed)?\)',
r'- remaster(ed)?',
r'\(remix\)',
r'- remix',
r'\(live\)',
r'- live',
r'\(acoustic\)',
r'- acoustic',
r'\(deluxe\)',
r'- deluxe',
r'\(extended\)',
r'- extended',
r'\(original mix\)',
r'- original mix',
r'\(album version\)',
r'- album version',
r'\(explicit\)',
r'- explicit',
r'\(clean\)',
r'- clean',
r'\d{4} (remaster|version)',
r'\d{4} digital (remaster|version)',
]
for pattern in patterns:
title = re.sub(pattern, '', title, flags=re.IGNORECASE)
# Remove any remaining parentheses and their contents
title = re.sub(r'\([^)]*\)', '', title)
# Remove common filler words
# filler_words = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'by']
title_words = title.split()
# title_words = [word for word in title_words if word.lower() not in filler_words]
title = ' '.join(title_words)
# Remove any extra whitespace
title = ' '.join(title.split())
logging.info(f"Cleaned Title: {title}")
return title.strip()

View File

@ -1,31 +1,52 @@
from src.clients.emby_client import EmbyClient # codebase/emby-scripts/src/main.py
from services.playlist_service import PlaylistService
import logging
from src.config import Config from src.config import Config
from src.clients.emby_client import EmbyClient
from src.clients.spotify_client import SpotifyClient
from src.services.playlist_service import PlaylistService
from src.actions.sync_spotify_playlists import sync_spotify_playlists
from src.actions.find_unmatched_songs import find_unmatched_songs
from src.actions.delete_duplicate_playlists import delete_duplicate_playlists
from src.actions.copy_emby_playlist_to_users import copy_playlists_to_users
from dotenv import load_dotenv
# Create an instance of the Emby client for the source user # Configure logging
source_emby = EmbyClient(Config.EMBY_URL, Config.EMBY_USERNAME, Config.EMBY_PASSWORD) logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Create an array of target usernames # Load environment variables from .env file
target_usernames = ["Alyssa", "Dalton", "Laura", "Zuko", "Azula","Brittany","Adam","Chris"] load_dotenv()
# Get all the playlists for the source user def main():
playlists = source_emby.get_playlists() # Initialize clients
emby = EmbyClient(Config.EMBY_URL, Config.EMBY_USERNAME, Config.EMBY_PASSWORD)
spotify = SpotifyClient(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET, Config.SPOTIFY_REDIRECT_URI, Config.SPOTIFY_SCOPE)
# Iterate over each playlist # 1. Sync Spotify playlists
for playlist in playlists: logger.info("Starting Spotify playlist sync...")
# Iterate over the target usernames sync_spotify_playlists(spotify, emby)
for target_username in target_usernames: logger.info("Spotify playlist sync completed.")
# Check if the target username is in the playlist name
if target_username in playlist["Name"]:
try:
# Create an instance of the Emby client for the target user
target_emby = EmbyClient(Config.EMBY_URL, target_username, '')
# Create an instance of the PlaylistService # 2. Find unmatched songs
playlist_service = PlaylistService(source_emby, target_emby) logger.info("Finding unmatched songs...")
find_unmatched_songs()
logger.info("Unmatched songs search completed.")
# Copy the playlist to the target user # 3. Sync Spotify playlists again
new_playlist = playlist_service.copy_playlist_by_usernames(Config.EMBY_USERNAME, target_username, playlist["Id"]) logger.info("Starting second Spotify playlist sync...")
print(f"Playlist '{playlist['Name']}' copied successfully to user '{target_username}'. New playlist ID: {new_playlist['Id']}") sync_spotify_playlists(spotify, emby)
except ValueError as e: logger.info("Second Spotify playlist sync completed.")
print(f"Error copying playlist '{playlist['Name']}' to user '{target_username}': {str(e)}")
# 4. Delete duplicate playlists
logger.info("Deleting duplicate playlists...")
delete_duplicate_playlists(emby)
logger.info("Duplicate playlists deletion completed.")
# 5. Copy playlists to other users
logger.info("Copying playlists to other users...")
copy_playlists_to_users(emby)
logger.info("Playlist copying completed.")
if __name__ == "__main__":
main()

BIN
src/unmatched_songs.db Normal file

Binary file not shown.

0
src/unmatched_songs.log Normal file
View File