iimport spotipy
from spotipy.oauth2 import SpotifyClientCredentials
from dash import dcc, html, Input, Output, State, ALL, _dash_renderer
import dash
import dash_mantine_components as dmc
from dash_iconify import DashIconify
import yt_dlp
from youtubesearchpython import VideosSearch
import os
# Set React to version 18 for compatibility with Mantine components
_dash_renderer._set_react_version("18.2.0")
# Initialize Dash app with Mantine styles
app = dash.Dash(
__name__,
suppress_callback_exceptions=True,
serve_locally=True,
external_stylesheets=dmc.styles.ALL # Add Mantine stylesheets
)
# Define Spotify API credentials
client_id = '5bcf46c42c774499830a12778ce058c7'
client_secret = '81c341f58ce341c5baa51412f697c30f'
# Authenticate with Spotify API
auth_manager = SpotifyClientCredentials(client_id=client_id, client_secret=client_secret)
sp = spotipy.Spotify(auth_manager=auth_manager)
# Define the download directory
download_dir = os.path.join(os.path.expanduser("~"), "SpotDownloads")
if not os.path.exists(download_dir):
os.makedirs(download_dir)
# Function to get playlist tracks
def get_playlist_tracks(playlist_url):
playlist_id = playlist_url.split("/")[-1].split("?")[0]
results = sp.playlist_tracks(playlist_id)
tracks = []
for item in results['items']:
track = item['track']
track_info = {
'name': track['name'],
'artist': track['artists'][0]['name'],
'album': track['album']['name'],
'album_cover': track['album']['images'][0]['url']
}
tracks.append(track_info)
return tracks
# Function to search and download a song from YouTube
def download_song_from_youtube(track_name):
try:
search = VideosSearch(track_name, limit=1)
video_result = search.result()['result'][0]
video_url = video_result['link']
ydl_opts = {
'format': 'bestaudio/best',
'outtmpl': os.path.join(download_dir, f'{track_name}.mp3'),
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
'ffmpeg_location': '/opt/homebrew/bin/ffmpeg',
'quiet': True
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([video_url])
return f"'{track_name}' downloaded successfully to SpotDownloads!"
except Exception as e:
return f"Error downloading '{track_name}': {str(e)}"
# Define the layout with Mantine components
app.layout = dmc.MantineProvider(
children=[
html.Div([
html.H1("Spotify Playlist Viewer", style={"text-align": "center"}),
html.Div([
html.Label("Enter Spotify Playlist URL:"),
dcc.Input(id="playlist-url", type="text", value="", style={'width': '100%'}),
html.Button('Fetch Playlist', id='submit', n_clicks=0),
], style={"margin-top": "20px"}),
html.Div(id="playlist-content"),
html.Button('Download All Tracks', id='download-all', n_clicks=0, style={'margin-top': '20px'}),
html.Div(id="download-status", style={'margin-top': '20px', 'font-weight': 'bold', 'color': '#007BFF'})
])
],
forceColorScheme='dark'
)
# Callback to fetch and display playlist tracks
@app.callback(
Output('playlist-content', 'children'),
[Input('submit', 'n_clicks')],
[State('playlist-url', 'value')]
)
def update_output(n_clicks, url):
if n_clicks > 0 and url:
tracks = get_playlist_tracks(url)
cards = [
dmc.Card(
children=[
dmc.Image(src=track['album_cover'], h=160, style={"object-fit": "cover"}),
dmc.Text(track['name'], fw=500, size="lg", style={"margin-top": "10px"}),
dmc.Text(f"Artist: {track['artist']}", c="dimmed", size="sm"),
dmc.Text(f"Album: {track['album']}", c="dimmed", size="sm"),
dmc.Button(
"Download", # Button now just says "Download"
id={'type': 'download-button', 'index': i},
variant="outline",
color="blue",
style={"margin-top": "10px"},
n_clicks=0
)
],
withBorder=True,
shadow="sm",
radius="md",
style={"width": "300px", "margin": "20px", "display": "inline-block", "vertical-align": "top"}
)
for i, track in enumerate(tracks)
]
return dmc.SimpleGrid(
children=cards,
cols=3, # Number of columns in the grid (adjust as needed)
spacing="xs", # Set the gap between grid items
)
return ""
# Callback to handle downloads
@app.callback(
Output('download-status', 'children'),
[Input({'type': 'download-button', 'index': ALL}, 'n_clicks'),
Input('download-all', 'n_clicks')],
[State('playlist-url', 'value')]
)
def handle_download(n_clicks_list, download_all_clicks, url):
if not url:
return ""
tracks = get_playlist_tracks(url)
ctx = dash.callback_context
if not ctx.triggered:
return "No download initiated."
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
if 'download-button' in triggered_id:
for i, n_clicks in enumerate(n_clicks_list):
if n_clicks and n_clicks > 0:
track_name = f"{tracks[i]['name']} by {tracks[i]['artist']}"
result_message = download_song_from_youtube(track_name)
return result_message
elif 'download-all' in triggered_id and download_all_clicks > 0:
messages = []
for track in tracks:
track_name = f"{track['name']} by {track['artist']}"
result_message = download_song_from_youtube(track_name)
messages.append(result_message)
return " | ".join(messages)
return "No download initiated."
# Run the app
if __name__ == "__main__":
app.run(debug=True, port=8265)