Collections¶
The Plex service implements the standard collection interfaces that are common across all services.
Library Collection¶
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 PlexLibrarySectionCollection
library = PlexLibrarySectionCollection(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
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": "14605"}):
print(plex_track)
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/foo/music.mp3"))
print(f"{local_path=}")
print(f"{local_path.is_file()=}")
# by isrc
if plex_track := library.find_by_global_ids(
{"isrc": "GBXJH1000082"},
path_rewrite=path_rewrite,
):
print(plex_track)
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 Collection¶
The PlexPlaylistCollection 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
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 `PlexPlaylistCollection | 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¶
Use {py.class}PlexPlaylistCollection <plistsync.services.plex.PlexPlaylistCollection> to model a playlist locally, then (optionally) create/associate it on Plex (online).
from plistsync.services.plex import PlexPlaylistCollection
try:
library.get_playlist_or_raise(name="My New Playlist").remote_delete()
except:
print("Playlist did not exist, no need to delete")
pl = PlexPlaylistCollection(
library=library,
name="My New Playlist",
description="Created via plistsync",
)
# currently, server does not know of this playlist.
assert pl.remote_associated == False
To remotly create the same playlist use the PlexPlaylistCollection.remote_create method.
pl.remote_create()
assert pl.remote_associated == True
Updating a playlist¶
For updating a playlist, you should use the playlist’s PlexPlaylistCollection.remote_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.remote_edit():
pl.description = "Created via plistsync"
pl.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
# Add the tracks (until now the playlist is empty)
with pl.remote_edit():
pl.tracks = new_tracks # type: ignore # TODO
[t.id for t in pl.tracks]
# Remove a track
with pl.remote_edit():
pl.tracks.pop()
[t.id for t in pl.tracks]
# Change order
with pl.remote_edit():
pl.tracks.insert(1, pl.tracks.pop())
[t.id for t in pl.tracks]