Collections¶
The Plex service implements the standard collection interfaces that are common across all services.
Library¶
The PlexLibrarySectionCollection represents content specific to one of your Plex Libraries. (Plex uses different naming: in the frontend they refer to them as “Libraries”, in the backend they are “Sections”.)
from plistsync.services.plex import PlexLibrary
library = PlexLibrary(section_name_or_id="Music")
Iterating Tracks¶
The Plex Library collection implements the TrackStream protocol, so you can simply iterate over its tracks.
# Iterate over tracks
for count, track in enumerate(library.tracks):
print(f"{track.id} -> {track.title} by {track.artists}")
if count == 4:
break
106387 -> If I Could by ['Break, Jack Boston, Kyo']
106388 -> Betamax by ['Break, Total Science']
106389 -> Inside by ['Break, SpectraSoul']
106390 -> Ain't No Turnin' Back by ['Break']
106391 -> Free Your Mind - Break Remix by ['Break, Singing Fats']
This can be a bit slow depending on the number of tracks you want to iterate. To speed things up you can consider preloading all tracks.
# ~ 6sec for 20k tracks
library.preload()
Track Lookup¶
The Plex library collections implements the LocalLookup and GlobalLookup protocols, allowing you to search for tracks across your Plex server. Specifically, plex supports lookups by plex_id and isrc.
Note
plex_id is Plex’s ratingKey. It is only guaranteed to be unique within a single Plex Media Server. If you connect to multiple servers, store it together with a server identifier (e.g. base URL or machine identifier) to avoid collisions.
# By plex id
if plex_track := library.find_by_local_ids({"plex_id": "106390"}):
print(plex_track)
PlexTrack(artist='Break', title="Ain't No Turnin' Back")
Looking up tracks by isrc is currently a bit more involved.
Plex does not expose isrc codes via its API/metadata, so plistsync can’t retrieve them directly from the server. Instead, plistsync falls back to reading the isrc from the audio file’s embedded tags. This requires filesystem access to the underlying media files and can be noticeably slower especially for large libraries or when reading files over a network mount.
In the example below, plistsync is running on a Mac that is not the Plex server. The music library is mounted locally at /Volumes/music, while the Plex server itself stores (and therefore reports) track paths under /media/music. To reconcile these two path “views”, we translate Plex’s remote paths into local mount paths using a {py:class}`PathRewrite <plistsync.core.PathRewrite>.
from pathlib import Path
from plistsync.core import PathRewrite
# We create a path rewrite from `/media/music` to `/Volumes/music`
path_rewrite = PathRewrite.from_str("/media/music/", "/Volumes/music/")
local_path = path_rewrite.apply(
Path("/media/music/clean/Anile/Perspective/01 False Tense [996kbps].flac")
)
print(f"{local_path=}")
print(f"{local_path.is_file()=}")
local_path=PosixPath('/Volumes/music/clean/Anile/Perspective/01 False Tense [996kbps].flac')
local_path.is_file()=True
# by isrc
if plex_track := library.find_by_global_ids(
{"isrc": "GBXJH1000082"},
path_rewrite=path_rewrite,
):
print(plex_track)
PlexTrack(artist='Break, Jack Boston, Kyo', title='If I Could')
Note
The lookup by isrc can take a bit of time this will read the metadata of all your music files until the isrc is found.
It is possible to speedup the operation using a cache but we will leave this here for simplicity.
Playlist¶
The PlexPlaylist represents a playlist that is accessible by the currently authenticated user.
Retrieving playlists¶
You can retrieve all playlists of a user using the library’s PlexLibrarySectionCollection.playlists property.
for count, pl in enumerate(library.playlists):
print(f"{pl.id:7d} {pl.name} ({len(pl)} tracks)")
if count == 4:
break
110044 _42k (4 tracks)
108489 _inbox (67 tracks)
109614 _plistsync (5 tracks)
108528 Abriss (13 tracks)
106822 Bootlegish (34 tracks)
If you just want a specific playlist, you can use the library’s PlexLibraryCollection.get_playlist or PlexLibraryCollection.get_playlist_or_raise method to retrieve a playlist.
# Returns `PlexPlaylist | None`
maybe_pl = library.get_playlist(
name="DnB Classics"
# id = 108530
)
# Raises if not found
pl = library.get_playlist_or_raise(id="108530")
Note
This method supports lookup by various identifiers, currently name= and id=.
Creating playlists¶
By convention, playlist are always created within a library.
To create a new playlist online, use the {py.meth}PlexLibraryCollection.create_playlist <plistsync.services.plex.PlexLibraryCollection.create_playlist> method.
try:
library.get_playlist_or_raise(name="My New Playlist").delete()
except:
print("Playlist did not exist, no need to delete")
pl = library.create_playlist(
name="My New Playlist",
description="Created via plistsync",
)
Playlist did not exist, no need to delete
Updating a playlist¶
For updating a playlist, you should use the playlist’s PlexPlaylist.edit context manager. This ensures that all changes are properly saved back to the server when you exit the context. This also minifies changes and therefore reduces API calls.
# Updating a playlist description/name
with pl.edit():
pl.description = "Updated description"
pl.description
'Updated description'
from plistsync.services.plex import PlexTrack
# find some tracks to play around with
new_tracks: list[PlexTrack] = list(
filter(
None,
library.find_many_by_local_ids(
[
{"plex_id": "111017"},
{"plex_id": "111018"},
{"plex_id": "111023"},
{"plex_id": "111024"},
]
),
)
)
new_tracks
[PlexTrack(artist='gyrofield', title='Fallen In Deep (Trail remix)'),
PlexTrack(artist='Rizzle', title='Mirage (Thread remix)'),
PlexTrack(artist='Rueben & PHI NIX', title='In My Mind (Tom Finster remix)'),
PlexTrack(artist='Skantia', title='2Drill (Calyx remix)')]
# Add the tracks (until now the playlist is empty)
with pl.edit():
pl.tracks = new_tracks
[t.id for t in pl.tracks]
['111017', '111018', '111023', '111024']
# Remove a track
with pl.edit():
pl.tracks.pop()
[t.id for t in pl.tracks]
['111017', '111018', '111023']
# Change order
with pl.edit():
pl.tracks.insert(1, pl.tracks.pop())
[t.id for t in pl.tracks]
['111017', '111023', '111018']
Delete a playlist¶
To remove the playlist from your tidal account, use the PlexPlaylist.delete method.
Note how this returns an instance of OfflinePlaylist, so that you can still work with the local in-memory version of the last online state.
offline_pl = pl.delete()
offline_pl
OfflinePlaylist(name='My New Playlist', tracks=3)
Note: Although you might still have pl as a variable available locally, you should never continue to use it. This will result in undefined behaviour, as the playlist no longer exists online.
try:
library.get_playlist_or_raise(id=pl.id)
except Exception as e:
print(e)
Could not find playlist for {'id': 111295, 'ids': None}