コード例 #1
0
def from_search_term(
    query: str,
    output_format: str = None,
    use_youtube: bool = False,
    lyrics_provider: str = None,
) -> List[SongObject]:
    """
    Queries Spotify for a song and returns the best match

    `str` `query` : what you'd type into Spotify's search box
    `str` `output_format` : output format of the song

    returns a `list<SongObject>` containing Url's of each track in the given album
    """

    # get a spotify client
    spotify_client = SpotifyClient()

    # get possible matches from spotify
    result = spotify_client.search(query, type="track")

    # return first result link or if no matches are found, raise Exception
    if result is None or len(result.get("tracks", {}).get("items", [])) == 0:
        raise Exception("No song matches found on Spotify")
    song_url = "http://open.spotify.com/track/" + result["tracks"]["items"][0]["id"]
    try:
        song = from_spotify_url(
            song_url, output_format, use_youtube, lyrics_provider, None
        )
        return [song] if song.youtube_link is not None else []
    except (LookupError, OSError, ValueError):
        return []
コード例 #2
0
def from_saved_tracks(output_format: str = None,
                      use_youtube: bool = False,
                      threads: int = 1) -> List[SongObject]:
    """
    Create and return list containing SongObject for every song that user has saved

    `str` `output_format` : output format of the song

    returns a `list<songObj>` containing Url's of each track in the user's saved tracks
    """

    spotify_client = SpotifyClient()

    saved_tracks_response = spotify_client.current_user_saved_tracks()
    if saved_tracks_response is None:
        raise Exception("Couldn't get saved tracks")

    saved_tracks = saved_tracks_response["items"]
    tracks = []

    # Fetch all saved tracks
    while saved_tracks_response["next"]:
        response = spotify_client.next(saved_tracks_response)
        # response is wrong, break
        if response is None:
            break

        saved_tracks_response = response
        saved_tracks.extend(saved_tracks_response["items"])

    # Remove songs  without id
    saved_tracks = [
        track for track in saved_tracks
        if track is not None and track.get("track") is not None
        and track.get("track", {}).get("id") is not None
    ]

    def get_song(track):
        try:
            return from_spotify_url(
                "https://open.spotify.com/track/" + track["track"]["id"],
                output_format,
                use_youtube,
            )
        except (LookupError, ValueError, OSError):
            return None

    with concurrent.futures.ThreadPoolExecutor(
            max_workers=threads) as executor:
        results = executor.map(get_song, saved_tracks)

    for result in results:
        if result is not None and result.youtube_link is not None:
            tracks.append(result)

    return tracks
コード例 #3
0
def from_url(spotify_url: str):
    if not ("open.spotify.com" in spotify_url and "track" in spotify_url):
        raise Exception(f"passed URL is not that of a track: {spotify_url}")

    # query spotify for song, artist, album details
    spotify_client = SpotifyClient()

    raw_track_meta = spotify_client.track(spotify_url)

    if raw_track_meta is None:
        raise Exception(
            "Couldn't get metadata, check if you have passed correct track id")

    primary_artist_id = raw_track_meta["artists"][0]["id"]
    raw_artist_meta = spotify_client.artist(primary_artist_id)

    albumId = raw_track_meta["album"]["id"]
    raw_album_meta = spotify_client.album(albumId)

    return raw_track_meta, raw_artist_meta, raw_album_meta
コード例 #4
0
def from_artist(
    artist_url: str,
    output_format: str = None,
    use_youtube: bool = False,
    lyrics_provider: str = None,
    threads: int = 1,
) -> List[SongObject]:
    """
    `str` `artist_url` : Spotify Url of the artist whose tracks are to be
    retrieved
    returns a `list<songObj>` containing Url's of each track in the artist profile
    """

    spotify_client = SpotifyClient()

    artist_tracks = []

    artist_response = spotify_client.artist_albums(
        artist_url, album_type="album,single"
    )
    if artist_response is None:
        raise ValueError("Wrong artist id")

    albums_list = artist_response["items"]
    albums_object: Dict[str, str] = {}

    # Fetch all artist albums
    while artist_response and artist_response["next"]:
        response = spotify_client.next(artist_response)
        if response is None:
            break

        artist_response = response
        albums_list.extend(artist_response["items"])

    # Remove duplicate albums
    for album in albums_list:
        # return an iterable containing the string's alphanumeric characters
        alpha_numeric_filter = filter(str.isalnum, album["name"].lower())

        # join all characters into one string
        album_name = "".join(alpha_numeric_filter)

        if albums_object.get(album_name) is None:
            albums_object[album_name] = album["uri"]

    tracks_list = []
    tracks_object: Dict[str, str] = {}

    # Fetch all tracks from all albums
    for album_uri in albums_object.values():
        response = spotify_client.album_tracks(album_uri)
        if response is None:
            continue

        album_response = response
        album_tracks = album_response["items"]

        while album_response["next"]:
            album_response = spotify_client.next(album_response)
            if album_response is None:
                break

            album_tracks.extend(album_response["items"])

        tracks_list.extend(album_tracks)

    # Filter tracks to remove duplicates and songs without our artist
    for track in tracks_list:
        # ignore None tracks or tracks without uri
        if track is not None and track.get("uri") is None:
            continue

        # return an iterable containing the string's alphanumeric characters
        alphaNumericFilter = filter(str.isalnum, track["name"].lower())

        # join all characters into one string
        trackName = "".join(alphaNumericFilter)

        if tracks_object.get(trackName) is None:
            for artist in track["artists"]:
                # get artist id from url
                # https://api.spotify.com/v1/artists/1fZAAHNWdSM5gqbi9o5iEA/albums
                #
                # split string
                #  ['https:', '', 'api.spotify.com', 'v1', 'artists',
                #  '1fZAAHNWdSM5gqbi9o5iEA', 'albums']
                #
                # get second element from the end
                # '1fZAAHNWdSM5gqbi9o5iEA'
                artistId = artist_response["href"].split("/")[-2]

                # ignore tracks that are not from our artist by checking
                # the id
                if artist["id"] == artistId:
                    tracks_object[trackName] = track["uri"]

    # Create song objects from track ids
    def get_song(track_uri):
        try:
            return from_spotify_url(
                f"https://open.spotify.com/track/{track_uri.split(':')[-1]}",
                output_format,
                use_youtube,
                lyrics_provider,
                None,
            )
        except (LookupError, ValueError, OSError):
            return None

    with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
        results = executor.map(get_song, tracks_object.values())

    for result in results:
        if result is not None and result.youtube_link is not None:
            artist_tracks.append(result)

    return artist_tracks
コード例 #5
0
def from_playlist(
    playlist_url: str,
    output_format: str = None,
    use_youtube: bool = False,
    lyrics_provider: str = None,
    generate_m3u: bool = False,
    threads: int = 1,
    path_template: str = None,
) -> List[SongObject]:
    """
    Create and return list containing SongObject for every song in the playlist

    `str` `album_url` : Spotify Url of the album whose tracks are to be retrieved
    `str` `output_format` : output format of the song

    returns a `list<SongObject>` containing Url's of each track in the given album
    """

    spotify_client = SpotifyClient()
    tracks = []

    playlist_response = spotify_client.playlist_tracks(playlist_url)
    playlist = spotify_client.playlist(playlist_url)
    if playlist_response is None:
        raise ValueError("Wrong playlist id")

    playlist_tracks = [
        track
        for track in playlist_response["items"]
        # check if track has id
        if track is not None
        and track.get("track") is not None
        and track["track"].get("id") is not None
    ]

    # Get all tracks from playlist
    while playlist_response["next"]:
        playlist_response = spotify_client.next(playlist_response)

        # Failed to get response, break the loop
        if playlist_response is None:
            break

        playlist_tracks.extend(playlist_response["items"])

    # Remove songs  without id
    playlist_tracks = [
        track
        for track in playlist_tracks
        if track is not None
        and track.get("track") is not None
        and track["track"].get("id") is not None
    ]

    def get_song(track):
        try:
            song = from_spotify_url(
                "https://open.spotify.com/track/" + track["track"]["id"],
                output_format,
                use_youtube,
                lyrics_provider,
                playlist,
            )

            if generate_m3u:
                if path_template:
                    file_path = _parse_path_template(path_template, song, output_format)
                else:
                    file_path = _get_converted_file_path(song, output_format)

                return song, f"{file_path}\n"

            return song, None
        except (LookupError, ValueError):
            return None, None
        except OSError:
            if generate_m3u:
                song_obj = SongObject(
                    track["track"], {}, {}, None, "", playlist_response
                )
                if path_template:
                    file_path = _parse_path_template(
                        path_template, song_obj, output_format
                    )
                else:
                    file_path = _get_converted_file_path(song_obj, output_format)

                return None, f"{file_path}\n"

            return None, None

    with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
        results = executor.map(get_song, playlist_tracks)

    playlist_text = ""
    for result in results:
        if result[1] is not None:
            playlist_text += result[1]

        if result[0] is not None and result[0].youtube_link is not None:
            tracks.append(result[0])

    if playlist_response and generate_m3u is True:
        playlist_data = spotify_client.playlist(playlist_url)

        if playlist_data is not None:
            playlist_name = playlist_data["name"]
        else:
            playlist_name = playlist_tracks[0]["track"]["name"]

        playlist_name = format_name(playlist_name)

        playlist_file = Path(f"{playlist_name}.m3u")

        with open(playlist_file, "w", encoding="utf-8") as file:
            file.write(playlist_text)

    return tracks
コード例 #6
0
def from_album(
    album_url: str,
    output_format: str = None,
    use_youtube: bool = False,
    lyrics_provider: str = None,
    generate_m3u: bool = False,
    threads: int = 1,
) -> List[SongObject]:
    """
    Create and return list containing SongObject for every song in the album

    `str` `album_url` : Spotify Url of the album whose tracks are to be retrieved
    `str` `output_format` : output format of the song

    returns a `list<SongObject>` containing Url's of each track in the given album
    """

    spotify_client = SpotifyClient()
    tracks = []

    album_response = spotify_client.album_tracks(album_url)
    if album_response is None:
        raise ValueError("Wrong album id")

    album_tracks = album_response["items"]

    # Get all tracks from album
    while album_response["next"]:
        album_response = spotify_client.next(album_response)

        # Failed to get response, break the loop
        if album_response is None:
            break

        album_tracks.extend(album_response["items"])

    # Remove songs  without id
    album_tracks = [
        track for track in album_tracks
        if track is not None and track.get("id") is not None
    ]

    def get_tracks(track):
        try:
            song = from_spotify_url(
                "https://open.spotify.com/track/" + track["id"],
                output_format,
                use_youtube,
                lyrics_provider,
                None,
            )

            if generate_m3u:
                file_path = _get_converted_file_path(song, output_format)

                return song, f"{file_path}\n"

            return song, None
        except (LookupError, ValueError):
            return None, None
        except OSError:
            if generate_m3u:
                file_path = (str(
                    provider_utils._create_song_title(
                        track["name"],
                        [artist["name"] for artist in track["artists"]],
                    )) + "." + output_format
                             if output_format is not None else "mp3")

                if len(file_path) > 256:
                    file_path = (str(
                        provider_utils._create_song_title(
                            track["name"], [track["artists"][0]["name"]])) +
                                 "." + output_format
                                 if output_format is not None else "mp3")

                return None, f"{file_path}\n"

            return None, None

    with concurrent.futures.ThreadPoolExecutor(
            max_workers=threads) as executor:
        results = executor.map(get_tracks, album_tracks)

    album_text = ""
    for result in results:
        if result[1] is not None:
            album_text += "".join(char for char in result[1]
                                  if char not in "/?\\*|<>")

        if result[0] is not None and result[0].youtube_link is not None:
            tracks.append(result[0])

    if album_response and generate_m3u is True:
        album_data = spotify_client.album(album_url)

        if album_data is not None:
            album_name = album_data["name"]
        else:
            album_name = album_tracks[0]["name"]

        album_name = "".join(char for char in album_name
                             if char not in "/?\\*|<>")

        album_file = Path(f"{album_name}.m3u")

        with open(album_file, "w", encoding="utf-8") as file:
            file.write(album_text)

    return tracks
コード例 #7
0
def console_entry_point():
    """
    This is where all the console processing magic happens.
    Its super simple, rudimentary even but, it's dead simple & it works.
    """

    # If -v parameter is specified print version and exit
    if len(sys.argv) >= 2 and sys.argv[1] in ["-v", "--version"]:
        version = pkg_resources.require("spotdl")[0].version
        print(version)
        sys.exit(0)

    # Parser arguments
    arguments = parse_arguments()

    # Convert arguments to dict
    args_dict = vars(arguments)

    # Check if ffmpeg has correct version, if not exit
    if (ffmpeg.has_correct_version(arguments.ignore_ffmpeg_version,
                                   arguments.ffmpeg or "ffmpeg") is False):
        sys.exit(1)

    if "saved" in arguments.query and not arguments.user_auth:
        arguments.user_auth = True
        print(
            "Detected 'saved' in command line, but no --user-auth flag. Enabling Anyways."
        )
        print("Please Log In...")

    # Initialize spotify client
    SpotifyClient.init(
        client_id="5f573c9620494bae87890c0f08a60293",
        client_secret="212476d9b0f3472eaa762d90b19b0ba8",
        user_auth=arguments.user_auth,
    )

    # Change directory if user specified correct output path
    if arguments.output:
        if not os.path.isdir(arguments.output):
            sys.exit("The output directory doesn't exist.")
        print(f"Will download to: {os.path.abspath(arguments.output)}")
        os.chdir(arguments.output)

    # Start download manager
    with DownloadManager(args_dict) as downloader:
        if not arguments.debug_termination:

            def graceful_exit(signal, frame):
                downloader.display_manager.close()
                sys.exit(0)

            signal.signal(signal.SIGINT, graceful_exit)
            signal.signal(signal.SIGTERM, graceful_exit)

        # Find tracking files in queries
        tracking_files = [
            query for query in arguments.query
            if query.endswith(".spotdlTrackingFile")
        ]

        # Restart downloads
        for tracking_file in tracking_files:
            print("Preparing to resume download...")
            downloader.resume_download_from_tracking_file(tracking_file)

        # Get songs
        song_list = parse_query(
            arguments.query,
            arguments.output_format,
            arguments.use_youtube,
            arguments.generate_m3u,
            arguments.search_threads,
        )

        # Start downloading
        if len(song_list) > 0:
            downloader.download_multiple_songs(song_list)
コード例 #8
0
def new_initialize(client_id, client_secret, user_auth):
    """This function allows calling `initialize()` multiple times"""
    try:
        return SpotifyClient()
    except:
        return ORIGINAL_INITIALIZE(client_id, client_secret, user_auth)
コード例 #9
0
import os
import platform
import re
from functools import partial
from pathlib import Path
from pprint import pprint
from typing import Callable, List, NamedTuple

import spotdl.download as spotdl
from spotdl.search import SpotifyClient, from_spotify_url

PLAYLIST_RE = re.compile("https?://open\.spotify\.com/playlist/*")

SpotifyClient.init(
    client_id="9fec59cd741a4692b5ae2ccb84a725af",
    client_secret="4e347cc3d2b74c93b20b57b6a5f75951",
    user_auth=False,
)

CLIENT = SpotifyClient()


class Song(NamedTuple):
    title: str
    artists: List[str]
    album: str
    url: str


class Diff(NamedTuple):
    to_delete: List[Path]