diff --git a/plextraktsync/cli.py b/plextraktsync/cli.py index 8f3a832a45..601a5391dc 100644 --- a/plextraktsync/cli.py +++ b/plextraktsync/cli.py @@ -276,6 +276,29 @@ def unmatched(): """ +@command() +@click.option( + "--remove-watched", + type=bool, + default=False, + is_flag=True, + help="Remove watched items from playlist", +) +@click.option( + "--dry-run", + "dry_run", + type=bool, + default=False, + is_flag=True, + help="Dry run: Do not make changes", +) +@click.argument("playlist", nargs=1) +def update_playlist(): + """ + Update playlist: Remove watched items + """ + + @command() @click.option( "--server", @@ -379,5 +402,6 @@ def config(): cli.add_command(sync) cli.add_command(trakt_login) cli.add_command(unmatched) +cli.add_command(update_playlist) cli.add_command(watch) cli.add_command(watched_shows) diff --git a/plextraktsync/commands/update_playlist.py b/plextraktsync/commands/update_playlist.py new file mode 100644 index 0000000000..14ce208557 --- /dev/null +++ b/plextraktsync/commands/update_playlist.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from plextraktsync.decorators.coro import coro +from plextraktsync.factory import factory +from plextraktsync.plex.PlexPlaylist import PlexPlaylist + +if TYPE_CHECKING: + from plextraktsync.plex.types import PlexPlayable + + +def format_title(p: PlexPlayable): + library_title = p._data.attrib.get("librarySectionTitle") + title = f"'{p.title}'" + if p.year: + title += f" ({p.year})" + + if library_title: + title += f" (in '{library_title}')" + if p.sourceURI: + title += f" (on {p.sourceURI})" + + return title + + +@coro +async def update_playlist(playlist: str, remove_watched: bool, dry_run: bool): + print = factory.print + print(f"Update playlist: '{playlist}'") + print(f"Remove watched from playlist: {remove_watched}") + print(f"Dry run: {dry_run}") + + pl = PlexPlaylist(factory.plex_server, playlist) + print(f"Playlist: {pl}") + items = pl.playlist.items().copy() + p: PlexPlayable + for p in items: + if remove_watched and p.isPlayed: + print(f"{'Remove' if not dry_run else 'Would remove'} from playlist: {format_title(p)}") + items.remove(p) + print(f"Update playlist: {len(pl)} -> {len(items)} items") + if not dry_run: + pl.update(items) diff --git a/plextraktsync/plex/PlexPlaylist.py b/plextraktsync/plex/PlexPlaylist.py index 063200f2d6..e792530c41 100644 --- a/plextraktsync/plex/PlexPlaylist.py +++ b/plextraktsync/plex/PlexPlaylist.py @@ -31,6 +31,9 @@ def __len__(self): def __contains__(self, m: Media): return m.plex_key in self.items + def __str__(self): + return f"PlexPlaylist" + @cached_property def playlist(self) -> Playlist | None: try: diff --git a/plextraktsync/plex/types.py b/plextraktsync/plex/types.py index b33f136434..66d598fc93 100644 --- a/plextraktsync/plex/types.py +++ b/plextraktsync/plex/types.py @@ -5,3 +5,4 @@ from plexapi.video import Episode, Movie, Show PlexMedia = Union[Movie, Show, Episode] +PlexPlayable = Union[Movie, Episode]