- Scale volume by 0.25x to prevent earrape (user still sees 0-200%) - Add support for direct audio file URL links (.mp3, .mp4, etc.) - New =playfile command for Discord file uploads - Supports MP3, MP4, WAV, OGG, FLAC, M4A, WEBM, AAC, OPUS formats
210 lines
5.7 KiB
Python
210 lines
5.7 KiB
Python
# Handles translating urls and search terms
|
|
|
|
import yt_dlp as ytdlp
|
|
import spotipy
|
|
|
|
# Updated yt-dlp options to handle current YouTube changes
|
|
ydl_opts = {
|
|
'format': 'bestaudio/best',
|
|
'quiet': True,
|
|
'no_warnings': False, # Show warnings for debugging
|
|
'default_search': 'ytsearch',
|
|
'ignoreerrors': True,
|
|
'source_address': '0.0.0.0', # Bind to IPv4 to avoid IPv6 issues
|
|
'extract_flat': False,
|
|
'nocheckcertificate': True,
|
|
# Add extractor args to handle YouTube's new requirements
|
|
'extractor_args': {
|
|
'youtube': {
|
|
'player_skip': ['webpage', 'configs'],
|
|
'player_client': ['android', 'web'],
|
|
}
|
|
},
|
|
}
|
|
|
|
async def main(url, sp):
|
|
|
|
#url = url.lower()
|
|
|
|
# Check if link or search
|
|
if not url.startswith("https://") and not url.startswith("http://"):
|
|
return await search_song(url)
|
|
|
|
#TODO add better regex or something
|
|
if 'spotify' in url:
|
|
if 'track' in url:
|
|
return await spotify_song(url, sp)
|
|
elif 'playlist' in url:
|
|
return await spotify_playlist(url, sp)
|
|
|
|
soundcloud_song = 'soundcloud' in url and 'sets' not in url
|
|
# Not implemented yet
|
|
# soundcloud_playlist = 'soundcloud' in url and 'sets' in url
|
|
|
|
youtube_song = 'watch?v=' in url or 'youtu.be/' in url
|
|
youtube_playlist = 'playlist?list=' in url
|
|
|
|
# Check for direct audio/video file URLs
|
|
# Supported formats: mp3, mp4, wav, ogg, flac, m4a, webm, aac, opus
|
|
audio_extensions = ('.mp3', '.mp4', '.wav', '.ogg', '.flac', '.m4a', '.webm', '.aac', '.opus')
|
|
is_direct_file = any(url.lower().endswith(ext) for ext in audio_extensions)
|
|
# Also check for URLs with query parameters (e.g., file.mp3?download=true)
|
|
is_direct_file = is_direct_file or any(ext in url.lower() for ext in audio_extensions)
|
|
|
|
if soundcloud_song or youtube_song or is_direct_file:
|
|
return await song_download(url)
|
|
|
|
if youtube_playlist:
|
|
return await playlist_download(url)
|
|
|
|
return []
|
|
|
|
|
|
async def search_song(search):
|
|
with ytdlp.YoutubeDL(ydl_opts) as ydl:
|
|
try:
|
|
info = ydl.extract_info(f"ytsearch1:{search}", download=False)
|
|
except Exception as e:
|
|
print(f"Error searching for '{search}': {e}")
|
|
return []
|
|
if info is None:
|
|
return []
|
|
|
|
if 'entries' not in info or len(info['entries']) == 0:
|
|
return []
|
|
|
|
info = info['entries'][0] # Get first search result
|
|
|
|
# Get the best audio stream URL
|
|
if 'url' not in info:
|
|
print(f"No URL found for: {search}")
|
|
return []
|
|
|
|
data = {
|
|
'url': info['url'],
|
|
'title': info.get('title', 'Unknown'),
|
|
'thumbnail': info.get('thumbnail', ''),
|
|
'duration': info.get('duration', 0)
|
|
}
|
|
return [data]
|
|
|
|
|
|
async def spotify_song(url, sp):
|
|
track = sp.track(url.split("/")[-1].split("?")[0])
|
|
search = ""
|
|
|
|
for i in track["artists"]:
|
|
# grabs all the artists name's if there's more than one
|
|
search = search + (i['name'] + ", ")
|
|
|
|
# remove last comma
|
|
search = search[:-2]
|
|
|
|
# set search to name
|
|
query = search + " - " + track['name']
|
|
|
|
return await search_song(query)
|
|
|
|
|
|
async def spotify_playlist(url, sp):
|
|
# Get the playlist uri code
|
|
code = url.split("/")[-1].split("?")[0]
|
|
|
|
# Grab the tracks if the playlist is correct
|
|
try:
|
|
results = sp.playlist_tracks(code)['items']
|
|
except spotipy.exceptions.SpotifyException:
|
|
return []
|
|
|
|
# Go through the tracks
|
|
songs = []
|
|
for track in results:
|
|
search = ""
|
|
|
|
# Fetch all artists
|
|
for artist in track['track']['artists']:
|
|
|
|
# Add all artists to search
|
|
search += f"{artist['name']}, "
|
|
|
|
# Remove last column
|
|
search = search[:-2]
|
|
search += f" - {track['track']['name']}"
|
|
songs.append(search)
|
|
|
|
#searched_result = search_song(search)
|
|
#if searched_result == []:
|
|
#continue
|
|
|
|
#songs.append(searched_result[0])
|
|
|
|
while True:
|
|
search_result = await search_song(songs[0])
|
|
if search_result == []:
|
|
songs.pop(0)
|
|
continue
|
|
else:
|
|
songs[0] = search_result[0]
|
|
break
|
|
|
|
return songs
|
|
|
|
|
|
async def song_download(url):
|
|
with ytdlp.YoutubeDL(ydl_opts) as ydl:
|
|
try:
|
|
info = ydl.extract_info(url, download=False)
|
|
except Exception as e:
|
|
print(f"Error downloading '{url}': {e}")
|
|
return []
|
|
|
|
if info is None:
|
|
return []
|
|
|
|
# Handle both direct videos and playlists with single entry
|
|
if 'entries' in info:
|
|
if len(info['entries']) == 0:
|
|
return []
|
|
info = info['entries'][0]
|
|
|
|
if 'url' not in info:
|
|
print(f"No URL found for: {url}")
|
|
return []
|
|
|
|
data = {
|
|
'url': info['url'],
|
|
'title': info.get('title', 'Unknown'),
|
|
'thumbnail': info.get('thumbnail', ''),
|
|
'duration': info.get('duration', 0)
|
|
}
|
|
return [data]
|
|
|
|
|
|
async def playlist_download(url):
|
|
with ytdlp.YoutubeDL(ydl_opts) as ydl:
|
|
try:
|
|
info = ydl.extract_info(url, download=False)
|
|
except Exception as e:
|
|
print(f"Error downloading playlist '{url}': {e}")
|
|
return []
|
|
|
|
if info is None:
|
|
return []
|
|
|
|
info = info['entries'] # Grabbing all songs in playlist
|
|
urls = []
|
|
|
|
for song in info:
|
|
if song is None or 'url' not in song:
|
|
continue
|
|
|
|
data = {
|
|
'url': song['url'],
|
|
'title': song.get('title', 'Unknown'),
|
|
'thumbnail': song.get('thumbnail', ''),
|
|
'duration': song.get('duration', 0)
|
|
}
|
|
urls.append(data)
|
|
|
|
return urls
|