Skip to content

Commit d9c84d5

Browse files
committed
Added example that shows how to sync subscriptions (playlists that are updated
regularly on spotify) to tidal
1 parent ba9d1ab commit d9c84d5

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""Clone a few Drum and Bass Playlists from Spotify to Tidal.
2+
3+
These playlists are regular updated by awesome curators, but I dont pay Spotify premium.
4+
So I am running this script once a week, to stay updated on Tidal.
5+
6+
Two Options:
7+
- Get the the tracklist into Tidal (clone)
8+
- Accumulate into larger playlists to get everything covered over the whole year (sink)
9+
10+
Currently does not reorder tracks, just checks we include everything thats available
11+
on Tidal.
12+
"""
13+
14+
import html
15+
import re
16+
from datetime import datetime
17+
18+
import typer
19+
from rich.console import Console
20+
from rich.table import Table, box
21+
22+
from plistsync.logger import log
23+
from plistsync.services.spotify import SpotifyLibrary
24+
from plistsync.services.tidal import (
25+
TidalLibrary,
26+
TidalPlaylistTrack,
27+
)
28+
29+
spotify_library = SpotifyLibrary()
30+
tidal_library = TidalLibrary()
31+
32+
# Updated from spotify to tidal, with deletions and reordering
33+
# https://www.reddit.com/r/DnB/comments/1c7al1q/what_are_the_best_spotify_playlists_for_new_dnb/
34+
lists_to_clone = {
35+
"https://open.spotify.com/playlist/3HSccBIzwpC5QOaUtifSqQ": #
36+
"H2L New DnB",
37+
#
38+
"https://open.spotify.com/playlist/4AOoXhcCyDS26rrnPccDwi": #
39+
"1MoreThing The Freshest GUESTlist!",
40+
#
41+
"https://open.spotify.com/playlist/4yfYTwDYPXn9GuhZxeEg53": #
42+
"Lennart Hoffmann Brand New DnB 🦀",
43+
#
44+
"https://open.spotify.com/playlist/7MGauBXssMrJ7JMa2RTp2w": #
45+
"Sub Focus DnB Selects",
46+
#
47+
"https://open.spotify.com/playlist/0wl7MGZyYsdWfZRhUdNenj": #
48+
"Lenzman Soulful DnB Essentials",
49+
#
50+
"https://open.spotify.com/playlist/1coTr2tQFEfsrX6PFNumay": #
51+
"LQ Fresh DnB Picks",
52+
#
53+
"https://open.spotify.com/playlist/6OYoyDsHzOoH8vJETyprlk": #
54+
"Emily Makis DnB Vocals That Bang",
55+
#
56+
"https://open.spotify.com/playlist/0Zarq4BVkFkZOWkmqsfrjA": #
57+
"UKF DnB Top 100",
58+
#
59+
}
60+
61+
# Only insertions. I use these to collect by year
62+
lists_to_sink = {
63+
"https://open.spotify.com/playlist/0Zarq4BVkFkZOWkmqsfrjA": #
64+
f"UKF DnB Top 100 {datetime.now().year}",
65+
}
66+
67+
68+
def main():
69+
log.info(f"Will clone {len(lists_to_clone)} playlists")
70+
for spotify_url, tidal_target_name in lists_to_clone.items():
71+
transfer_playlist(spotify_url, tidal_target_name)
72+
73+
log.info(f"Will sink {len(lists_to_sink)} playlists")
74+
for spotify_url, tidal_target_name in lists_to_sink.items():
75+
transfer_playlist(spotify_url, tidal_target_name, insert_only=True)
76+
77+
78+
def transfer_playlist(
79+
spotify_url: str,
80+
tidal_target_name: str,
81+
insert_only: bool = False,
82+
):
83+
pass
84+
85+
log.info(f"Transferring {spotify_url}")
86+
spotify_plist = spotify_library.get_playlist_or_raise(url=spotify_url)
87+
log.info(f"Found {len(spotify_plist)} tracks in source: {spotify_plist.name}")
88+
89+
tidal_plist = tidal_library.get_playlist(name=tidal_target_name)
90+
if tidal_plist is None:
91+
log.debug(f"Creating new plist on tidal: {tidal_target_name}")
92+
tidal_plist = tidal_library.create_playlist(
93+
name=tidal_target_name,
94+
)
95+
96+
log.info(
97+
f"Target playlist '{tidal_target_name}' currently has {len(tidal_plist)} tracks"
98+
)
99+
tidal_description = html.unescape(
100+
re.sub(r"<[^>]+>", "", spotify_plist.description or "")
101+
)
102+
with tidal_plist.edit():
103+
tidal_plist.name = tidal_target_name
104+
tidal_plist.description = (
105+
f"{tidal_description}"
106+
f"\n\n| Last synced with ❤️ using plistsync on "
107+
f"{datetime.today().strftime('%Y-%m-%d')}"
108+
f"\n\n| Original playlist: {spotify_url}"
109+
)
110+
111+
# for now, the comparison is mostly isrc based
112+
tidal_isrcs = [t.isrc for t in tidal_plist.tracks if t.isrc is not None]
113+
spotify_isrcs = [t.isrc for t in tidal_plist.tracks if t.isrc is not None]
114+
115+
if not insert_only:
116+
log.info("Removing old tracks from Tidal playlist")
117+
with tidal_plist.edit():
118+
for t in reversed(tidal_plist.tracks):
119+
if t.isrc not in spotify_isrcs:
120+
tidal_plist.tracks.remove(t)
121+
122+
log.info("Finding tracks to add on Tidal")
123+
ids_to_lookup = [
124+
t.global_ids for t in spotify_plist.tracks if t.isrc not in tidal_isrcs
125+
]
126+
missing_tracks = [
127+
TidalPlaylistTrack(t)
128+
for t in tidal_library.find_many_by_global_ids(ids_to_lookup)
129+
if t is not None
130+
]
131+
132+
log.info(f"Adding {len(missing_tracks)} tracks to Tidal")
133+
with tidal_plist.edit():
134+
tidal_plist.tracks.extend(missing_tracks)
135+
136+
# log what we have synced
137+
console = Console()
138+
table = Table(box=box.MINIMAL_DOUBLE_HEAD)
139+
table.add_column("Artist", style="cyan")
140+
table.add_column("Title", style="cyan")
141+
table.add_column("Spotify ID", style="green")
142+
table.add_column("ISRC", style="magenta")
143+
table.add_column("Tidal ID", style="white")
144+
145+
for spotify_track in spotify_plist.tracks:
146+
row = [
147+
spotify_track.artists[0],
148+
spotify_track.name,
149+
spotify_track.id,
150+
spotify_track.isrc,
151+
]
152+
try:
153+
tidal_track = [
154+
t for t in tidal_plist.tracks if t.isrc == spotify_track.isrc
155+
][0]
156+
row.append(tidal_track.id)
157+
except IndexError:
158+
row.append("[red]Not found[/red]")
159+
160+
table.add_row(*row)
161+
162+
# print instead of log, since logging adds whitespace
163+
console.print(table)
164+
165+
166+
main.__doc__ = __doc__ # use module docstring as help
167+
if __name__ == "__main__":
168+
typer.run(main)

0 commit comments

Comments
 (0)