Sync data between local and remote libraries

In this example, you will:
  • Search for local tracks on a music streaming service and assign unique remote IDs to tags in your local tracks

  • Get tags and images for a track from a music stream service and save it them to your local track file

  • Create remote playlists from your local playlists

Note

This guide will use Spotify, but any supported music streaming service can be used in generally the same way. Just modify the imports and classes as required.

Set up logging

Set up logging to ensure you can see all info reported by the later operations. Libraries log info about loaded objects to the custom STAT level.

import logging
import sys

from musify.logger import STAT

logging.basicConfig(format="%(message)s", level=STAT, stream=sys.stdout)

Sync data

  1. Define a helper function to search for tracks and check the results:

    from collections.abc import Collection
    
    from musify.libraries.core.collection import MusifyCollection
    from musify.libraries.remote.core.factory import RemoteObjectFactory
    from musify.processors.search import RemoteItemSearcher
    from musify.processors.check import RemoteItemChecker
    from musify.processors.match import ItemMatcher
    
    
    async def match_albums_to_remote(albums: Collection[MusifyCollection], factory: RemoteObjectFactory) -> None:
        """Match the items in the given ``albums`` to the remote API's database and assign URIs to them."""
        matcher = ItemMatcher()
    
        searcher = RemoteItemSearcher(matcher=matcher, object_factory=factory)
        async with searcher:
            await searcher(albums)
    
        checker = RemoteItemChecker(matcher=matcher, object_factory=factory)
        async with checker:
            await checker(albums)
    
  2. Define a helper function to load the matched tracks, get tags from the music streaming service, and save the tags to the file:

    Note

    By default, URIs are saved to the comments tag.

    from musify.libraries.local.collection import LocalAlbum
    
    
    async def sync_albums(albums: list[LocalAlbum], factory: RemoteObjectFactory) -> None:
        """Sync the local ``albums`` with tag data from the api in the given ``factory``"""
        async with factory.api:
            for album in albums:
                for local_track in album:
                    remote_track = await factory.track.load(local_track.uri, api=factory.api)
    
                    local_track.title = remote_track.title
                    local_track.artist = remote_track.artist
                    local_track.date = remote_track.date
                    local_track.genres = remote_track.genres
                    local_track.image_links = remote_track.image_links
    
                    # alternatively, just merge all tags
                    local_track |= remote_track
    
                    # save the track here or...
                    await local_track.save(replace=True, dry_run=False)
    
                # ...save all tracks on the album at once here
                await album.save_tracks(replace=True, dry_run=False)
    
  3. Define a helper function to sync the local playlist with a remote playlist once all tracks in a playlist have URIs assigned:

    from musify.libraries.local.library import LocalLibrary
    from musify.libraries.remote.core.library import RemoteLibrary
    
    
    async def sync_local_playlist_with_remote(name: str, local_library: LocalLibrary, remote_library: RemoteLibrary):
        """Sync ``local_library`` playlist with given ``name`` to its matching ``remote_library`` playlist."""
        async with api:
            await remote_library.load_playlists()
    
            local_playlist = local_library.playlists[name]
            remote_playlist = remote_library.playlists[name]
    
            # sync the object with Spotify and pretty print info about the reloaded remote playlist
            await remote_playlist.sync(items=local_playlist, kind="new", reload=True, dry_run=False)
    
        print(remote_playlist)
    
  4. Set up and load a remote API object and local library with a wrangler attached:

    from musify.libraries.remote.spotify.api import SpotifyAPI
    
    api = SpotifyAPI(
        client_id="<YOUR CLIENT ID>",
        client_secret="<YOUR CLIENT SECRET>",
        scope=[
            "user-library-read",
            "user-follow-read",
            "playlist-read-collaborative",
            "playlist-read-private",
            "playlist-modify-public",
            "playlist-modify-private"
        ],
        # providing a `token_file_path` will save the generated token to your system
        # for quicker authorisations in future
        token_file_path="<PATH TO JSON TOKEN>"
    )
    
    from musify.libraries.local.library import LocalLibrary
    
    import asyncio
    
    local_library = LocalLibrary(
        library_folders=["<PATH TO YOUR LIBRARY FOLDER>", ...],
        playlist_folder="<PATH TO YOUR PLAYLIST FOLDER>",
        # this wrangler will be needed to interpret matched URIs as valid
        remote_wrangler=api.wrangler,
    )
    asyncio.run(local_library.load())
    
    albums = local_library.albums
    
  1. Set up the remote library and run the program:

    from musify.libraries.remote.spotify.library import SpotifyLibrary
    
    remote_library = SpotifyLibrary(api=api)
    playlist = "<YOUR PLAYLIST'S NAME>"  # case sensitive
    
    asyncio.run(match_albums_to_remote(albums, remote_library.factory))
    asyncio.run(sync_albums(albums, remote_library.factory))
    asyncio.run(sync_local_playlist_with_remote(playlist, local_library, remote_library))