plexutil 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. plexutil-1.0.0/LICENSE +21 -0
  2. plexutil-1.0.0/PKG-INFO +36 -0
  3. plexutil-1.0.0/README.md +2 -0
  4. plexutil-1.0.0/pyproject.toml +27 -0
  5. plexutil-1.0.0/setup.cfg +4 -0
  6. plexutil-1.0.0/src/__init__.py +0 -0
  7. plexutil-1.0.0/src/core/__init__.py +0 -0
  8. plexutil-1.0.0/src/core/library.py +224 -0
  9. plexutil-1.0.0/src/core/movie_library.py +68 -0
  10. plexutil-1.0.0/src/core/music_library.py +105 -0
  11. plexutil-1.0.0/src/core/playlist.py +145 -0
  12. plexutil-1.0.0/src/core/prompt.py +191 -0
  13. plexutil-1.0.0/src/core/tv_library.py +92 -0
  14. plexutil-1.0.0/src/dto/__init__.py +0 -0
  15. plexutil-1.0.0/src/dto/library_dto.py +25 -0
  16. plexutil-1.0.0/src/dto/library_preferences_dto.py +25 -0
  17. plexutil-1.0.0/src/dto/music_playlist_dto.py +22 -0
  18. plexutil-1.0.0/src/dto/music_playlist_file_dto.py +27 -0
  19. plexutil-1.0.0/src/dto/plex_config_dto.py +39 -0
  20. plexutil-1.0.0/src/dto/song_dto.py +18 -0
  21. plexutil-1.0.0/src/dto/tv_language_manifest_dto.py +22 -0
  22. plexutil-1.0.0/src/dto/tv_language_manifest_file_dto.py +23 -0
  23. plexutil-1.0.0/src/dto/user_instructions_dto.py +37 -0
  24. plexutil-1.0.0/src/enum/__init__.py +0 -0
  25. plexutil-1.0.0/src/enum/agent.py +7 -0
  26. plexutil-1.0.0/src/enum/file_type.py +35 -0
  27. plexutil-1.0.0/src/enum/language.py +31 -0
  28. plexutil-1.0.0/src/enum/library_name.py +7 -0
  29. plexutil-1.0.0/src/enum/library_type.py +7 -0
  30. plexutil-1.0.0/src/enum/scanner.py +7 -0
  31. plexutil-1.0.0/src/enum/user_request.py +50 -0
  32. plexutil-1.0.0/src/exception/__init__.py +0 -0
  33. plexutil-1.0.0/src/exception/library_op_error.py +28 -0
  34. plexutil-1.0.0/src/exception/library_poll_timeout_error.py +2 -0
  35. plexutil-1.0.0/src/exception/library_unsupported_error.py +13 -0
  36. plexutil-1.0.0/src/exception/plex_util_config_error.py +2 -0
  37. plexutil-1.0.0/src/plex_util_logger.py +54 -0
  38. plexutil-1.0.0/src/plexutil.egg-info/PKG-INFO +36 -0
  39. plexutil-1.0.0/src/plexutil.egg-info/SOURCES.txt +51 -0
  40. plexutil-1.0.0/src/plexutil.egg-info/dependency_links.txt +1 -0
  41. plexutil-1.0.0/src/plexutil.egg-info/top_level.txt +10 -0
  42. plexutil-1.0.0/src/serializer/__init__.py +0 -0
  43. plexutil-1.0.0/src/serializer/music_playlist_file_serializer.py +65 -0
  44. plexutil-1.0.0/src/serializer/plex_config_serializer.py +67 -0
  45. plexutil-1.0.0/src/serializer/serializable.py +3 -0
  46. plexutil-1.0.0/src/serializer/serializer.py +17 -0
  47. plexutil-1.0.0/src/serializer/tv_language_manifest_serializer.py +29 -0
  48. plexutil-1.0.0/src/static.py +7 -0
  49. plexutil-1.0.0/src/util/__init__.py +0 -0
  50. plexutil-1.0.0/src/util/file_importer.py +116 -0
  51. plexutil-1.0.0/src/util/path_ops.py +46 -0
  52. plexutil-1.0.0/src/util/plex_ops.py +16 -0
  53. plexutil-1.0.0/src/util/query_builder.py +67 -0
plexutil-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 CARLOS FLOREZ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,36 @@
1
+ Metadata-Version: 2.1
2
+ Name: plexutil
3
+ Version: 1.0.0
4
+ Author-email: Carlos Florez <carlos@florez.co.uk>
5
+ Maintainer-email: Carlos Florez <carlos@florez.co.uk>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 CARLOS FLOREZ
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/florez-carlos/plexutil
29
+ Classifier: Development Status :: 5 - Production/Stable
30
+ Classifier: Programming Language :: Python :: 3.11
31
+ Requires-Python: >=3.11.6
32
+ Description-Content-Type: text/markdown
33
+ License-File: LICENSE
34
+
35
+ # plexutil
36
+ source init.sh
@@ -0,0 +1,2 @@
1
+ # plexutil
2
+ source init.sh
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=62.6"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "plexutil"
7
+ version = "1.0.0"
8
+ requires-python = ">=3.11.6"
9
+ authors = [
10
+ {name = "Carlos Florez", email = "carlos@florez.co.uk"}
11
+ ]
12
+ maintainers = [
13
+ {name = "Carlos Florez", email = "carlos@florez.co.uk"}
14
+ ]
15
+ description = ""
16
+ readme = "README.md"
17
+ license = {file = "LICENSE"}
18
+ classifiers = [
19
+ "Development Status :: 5 - Production/Stable",
20
+ "Programming Language :: Python :: 3.11",
21
+ ]
22
+
23
+ [tool.setuptools.dynamic]
24
+ dependencies = {file = ["requirements.txt"]}
25
+
26
+ [project.urls]
27
+ Repository = "https://github.com/florez-carlos/plexutil"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
File without changes
@@ -0,0 +1,224 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from abc import ABC, abstractmethod
5
+ from typing import TYPE_CHECKING
6
+
7
+ from src.exception.library_poll_timeout_error import LibraryPollTimeoutError
8
+ from src.plex_util_logger import PlexUtilLogger
9
+
10
+ if TYPE_CHECKING:
11
+ from pathlib import Path
12
+
13
+ from plexapi.audio import Audio
14
+ from plexapi.server import PlexServer
15
+ from plexapi.video import Video
16
+
17
+ from src.dto.library_preferences_dto import LibraryPreferencesDTO
18
+ from src.enum.agent import Agent
19
+ from src.enum.language import Language
20
+ from src.enum.library_name import LibraryName
21
+ from src.enum.scanner import Scanner
22
+
23
+ from alive_progress import alive_bar
24
+ from plexapi.exceptions import NotFound
25
+
26
+ from src.enum.library_type import LibraryType
27
+ from src.exception.library_op_error import LibraryOpError
28
+ from src.exception.library_unsupported_error import LibraryUnsupportedError
29
+
30
+
31
+ class Library(ABC):
32
+ def __init__(
33
+ self,
34
+ plex_server: PlexServer,
35
+ name: LibraryName,
36
+ library_type: LibraryType,
37
+ agent: Agent,
38
+ scanner: Scanner,
39
+ location: Path,
40
+ language: Language,
41
+ preferences: LibraryPreferencesDTO,
42
+ ) -> None:
43
+ self.plex_server = plex_server
44
+ self.name = name
45
+ self.library_type = library_type
46
+ self.agent = agent
47
+ self.scanner = scanner
48
+ self.location = location
49
+ self.language = language
50
+ self.preferences = preferences
51
+
52
+ @abstractmethod
53
+ def create(self) -> None:
54
+ raise NotImplementedError
55
+
56
+ @abstractmethod
57
+ def delete(self) -> None:
58
+ op_type = "DELETE"
59
+
60
+ info = (
61
+ "Deleting library: \n"
62
+ f"Name: {self.name.value}\n"
63
+ f"Type: {self.library_type.value}\n"
64
+ f"Agent: {self.agent.value}\n"
65
+ f"Scanner: {self.scanner.value}\n"
66
+ f"Location: {self.location!s}\n"
67
+ f"Language: {self.language.value}\n"
68
+ f"Preferences: {self.preferences.music}\n"
69
+ )
70
+ PlexUtilLogger.get_logger().info(info)
71
+
72
+ try:
73
+ result = self.plex_server.library.section(self.name.value)
74
+
75
+ if result:
76
+ result.delete()
77
+ else:
78
+ description = "Nothing found"
79
+ raise LibraryOpError(
80
+ op_type=op_type,
81
+ description=description,
82
+ library_type=self.library_type,
83
+ )
84
+
85
+ except NotFound:
86
+ raise LibraryOpError(
87
+ op_type=op_type,
88
+ library_type=self.library_type,
89
+ ) from NotFound
90
+
91
+ @abstractmethod
92
+ def exists(self) -> bool:
93
+ debug = (
94
+ "Checking library exists: \n"
95
+ f"Name: {self.name.value}\n"
96
+ f"Type: {self.library_type.value}\n"
97
+ f"Agent: {self.agent.value}\n"
98
+ f"Scanner: {self.scanner.value}\n"
99
+ f"Location: {self.location!s}\n"
100
+ f"Language: {self.language.value}\n"
101
+ f"Preferences: {self.preferences.movie}\n"
102
+ )
103
+ try:
104
+ result = self.plex_server.library.section(self.name.value)
105
+
106
+ if not result:
107
+ debug = debug + "-Not found-"
108
+ PlexUtilLogger.get_logger().debug(debug)
109
+ return False
110
+
111
+ except NotFound:
112
+ debug = debug + "-Not found-"
113
+ PlexUtilLogger.get_logger().debug(debug)
114
+ return False
115
+
116
+ PlexUtilLogger.get_logger().debug(debug)
117
+ return True
118
+
119
+ def poll(
120
+ self,
121
+ requested_attempts: int = 0,
122
+ expected_count: int = 0,
123
+ interval_seconds: int = 0,
124
+ tvdb_ids: list[int] | None = None,
125
+ ) -> None:
126
+ current_count = len(self.query(tvdb_ids))
127
+ init_offset = abs(expected_count - current_count)
128
+
129
+ debug = (
130
+ f"Requested attempts: {requested_attempts!s}\n"
131
+ f"Interval seconds: {interval_seconds!s}\n"
132
+ f"Current count: {current_count!s}\n"
133
+ f"Expected count: {expected_count!s}\n"
134
+ f"Net change: {init_offset!s}\n"
135
+ )
136
+
137
+ PlexUtilLogger.get_logger().debug(debug)
138
+
139
+ with alive_bar(init_offset) as bar:
140
+ attempts = 0
141
+ display_count = 0
142
+ offset = init_offset
143
+
144
+ while attempts < requested_attempts:
145
+ updated_current_count = len(self.query(tvdb_ids))
146
+ offset = abs(updated_current_count - current_count)
147
+ current_count = updated_current_count
148
+
149
+ if current_count == expected_count:
150
+ for _ in range(abs(current_count - display_count)):
151
+ bar()
152
+
153
+ break
154
+
155
+ for _ in range(offset):
156
+ display_count = display_count + 1
157
+ bar()
158
+
159
+ time.sleep(interval_seconds)
160
+ attempts = attempts + 1
161
+ if attempts >= requested_attempts:
162
+ raise LibraryPollTimeoutError
163
+
164
+ def query(
165
+ self,
166
+ tvdb_ids: list[int] | None = None,
167
+ ) -> list[Audio] | list[Video]:
168
+ op_type = "QUERY"
169
+
170
+ if tvdb_ids is None:
171
+ tvdb_ids = []
172
+
173
+ debug = (
174
+ "Performing query:\n"
175
+ f"Name: {self.name.value}\n"
176
+ f"Library Type: {self.library_type.value}\n"
177
+ f"TVDB Ids: {tvdb_ids}\n"
178
+ )
179
+ PlexUtilLogger.get_logger().debug(debug)
180
+
181
+ try:
182
+ if self.library_type is LibraryType.MUSIC:
183
+ return self.plex_server.library.section(
184
+ self.name.value,
185
+ ).searchTracks()
186
+
187
+ elif self.library_type is LibraryType.TV:
188
+ shows = self.plex_server.library.section(self.name.value).all()
189
+ shows_filtered = []
190
+
191
+ if tvdb_ids:
192
+ for show in shows:
193
+ guids = show.guids
194
+ tvdb_prefix = "tvdb://"
195
+ for guid in guids:
196
+ if tvdb_prefix in guid.id:
197
+ tvdb = guid.id.replace(tvdb_prefix, "")
198
+ if int(tvdb) in tvdb_ids:
199
+ shows_filtered.append(show)
200
+ else:
201
+ description = (
202
+ "Expected ("
203
+ + tvdb_prefix
204
+ + ") but show does not have any: "
205
+ + guid.id
206
+ )
207
+ LibraryOpError(
208
+ op_type=op_type,
209
+ library_type=self.library_type,
210
+ description=description,
211
+ )
212
+
213
+ return shows_filtered
214
+
215
+ else:
216
+ raise LibraryUnsupportedError(
217
+ op_type=op_type,
218
+ library_type=self.library_type,
219
+ )
220
+
221
+ except NotFound:
222
+ debug = "Received Not Found on a Query operation"
223
+ PlexUtilLogger.get_logger().debug(debug)
224
+ return []
@@ -0,0 +1,68 @@
1
+ from pathlib import Path
2
+
3
+ from plexapi.server import PlexServer
4
+
5
+ from src.core.library import Library
6
+ from src.dto.library_preferences_dto import LibraryPreferencesDTO
7
+ from src.enum.agent import Agent
8
+ from src.enum.language import Language
9
+ from src.enum.library_name import LibraryName
10
+ from src.enum.library_type import LibraryType
11
+ from src.enum.scanner import Scanner
12
+ from src.plex_util_logger import PlexUtilLogger
13
+
14
+
15
+ class MovieLibrary(Library):
16
+ def __init__(
17
+ self,
18
+ plex_server: PlexServer,
19
+ location: Path,
20
+ language: Language,
21
+ preferences: LibraryPreferencesDTO,
22
+ ) -> None:
23
+ super().__init__(
24
+ plex_server,
25
+ LibraryName.MOVIE,
26
+ LibraryType.MOVIE,
27
+ Agent.MOVIE,
28
+ Scanner.MOVIE,
29
+ location,
30
+ language,
31
+ preferences,
32
+ )
33
+
34
+ def create(self) -> None:
35
+ info = (
36
+ "Creating movie library: \n"
37
+ f"Name: {self.name.value}\n"
38
+ f"Type: {self.library_type.value}\n"
39
+ f"Agent: {self.agent.value}\n"
40
+ f"Scanner: {self.scanner.value}\n"
41
+ f"Location: {self.location!s}\n"
42
+ f"Language: {self.language.value}\n"
43
+ f"Preferences: {self.preferences.movie}\n"
44
+ )
45
+
46
+ PlexUtilLogger.get_logger().info(info)
47
+
48
+ self.plex_server.library.add(
49
+ name=self.name.value,
50
+ type=self.library_type.value,
51
+ agent=self.agent.value,
52
+ scanner=self.scanner.value,
53
+ location=str(self.location),
54
+ language=self.language.value,
55
+ )
56
+
57
+ # This line triggers a refresh of the library
58
+ self.plex_server.library.sections()
59
+
60
+ self.plex_server.library.section(self.name.value).editAdvanced(
61
+ **self.preferences.movie,
62
+ )
63
+
64
+ def delete(self) -> None:
65
+ return super().delete()
66
+
67
+ def exists(self) -> bool:
68
+ return super().exists()
@@ -0,0 +1,105 @@
1
+ from pathlib import Path
2
+
3
+ from plexapi.server import PlexServer
4
+
5
+ from src.core.library import Library
6
+ from src.dto.library_preferences_dto import LibraryPreferencesDTO
7
+ from src.dto.music_playlist_file_dto import MusicPlaylistFileDTO
8
+ from src.enum.agent import Agent
9
+ from src.enum.language import Language
10
+ from src.enum.library_name import LibraryName
11
+ from src.enum.library_type import LibraryType
12
+ from src.enum.scanner import Scanner
13
+ from src.exception.library_op_error import LibraryOpError
14
+ from src.plex_util_logger import PlexUtilLogger
15
+ from src.util.query_builder import QueryBuilder
16
+
17
+
18
+ class MusicLibrary(Library):
19
+ def __init__(
20
+ self,
21
+ plex_server: PlexServer,
22
+ location: Path,
23
+ language: Language,
24
+ preferences: LibraryPreferencesDTO,
25
+ music_playlist_file_dto: MusicPlaylistFileDTO,
26
+ ) -> None:
27
+ super().__init__(
28
+ plex_server,
29
+ LibraryName.MUSIC,
30
+ LibraryType.MUSIC,
31
+ Agent.MUSIC,
32
+ Scanner.MUSIC,
33
+ location,
34
+ language,
35
+ preferences,
36
+ )
37
+ self.music_playlist_file_dto = music_playlist_file_dto
38
+
39
+ def create(self) -> None:
40
+ op_type = "CREATE"
41
+
42
+ part = ""
43
+
44
+ query_builder = QueryBuilder(
45
+ "/library/sections",
46
+ name=LibraryName.MUSIC.value,
47
+ the_type=LibraryType.MUSIC.value,
48
+ agent=Agent.MUSIC.value,
49
+ scanner=Scanner.MUSIC.value,
50
+ language=Language.ENGLISH_US.value,
51
+ importFromiTunes="",
52
+ enableAutoPhotoTags="",
53
+ location=str(self.location),
54
+ prefs=self.preferences.music,
55
+ )
56
+
57
+ part = query_builder.build()
58
+
59
+ info = (
60
+ "Creating music library: \n"
61
+ f"Name: {self.name.value}\n"
62
+ f"Type: {self.library_type.value}\n"
63
+ f"Agent: {self.agent.value}\n"
64
+ f"Scanner: {self.scanner.value}\n"
65
+ f"Location: {self.location!s}\n"
66
+ f"Language: {self.language.value}\n"
67
+ f"Preferences: {self.preferences.music}\n"
68
+ )
69
+
70
+ debug = f"Query: {part}\n"
71
+
72
+ PlexUtilLogger.get_logger().info(info)
73
+ PlexUtilLogger.get_logger().debug(debug)
74
+
75
+ # This posts a music library
76
+ if part:
77
+ self.plex_server.query(
78
+ part,
79
+ method=self.plex_server._session.post,
80
+ )
81
+ else:
82
+ description = "Query Builder has not built a part!"
83
+ raise LibraryOpError(
84
+ op_type=op_type,
85
+ library_type=self.library_type,
86
+ description=description,
87
+ )
88
+
89
+ # This triggers a refresh of the library
90
+ self.plex_server.library.sections()
91
+
92
+ info = (
93
+ "Checking server music "
94
+ "meets expected "
95
+ f"count: {self.music_playlist_file_dto.track_count!s}\n"
96
+ )
97
+ PlexUtilLogger.get_logger().info(info)
98
+
99
+ self.poll(200, self.music_playlist_file_dto.track_count, 10)
100
+
101
+ def delete(self) -> None:
102
+ return super().delete()
103
+
104
+ def exists(self) -> bool:
105
+ return super().exists()
@@ -0,0 +1,145 @@
1
+ from pathlib import Path
2
+
3
+ from plexapi.server import PlexServer
4
+
5
+ from src.core.library import Library
6
+ from src.dto.library_preferences_dto import LibraryPreferencesDTO
7
+ from src.dto.music_playlist_file_dto import MusicPlaylistFileDTO
8
+ from src.enum.agent import Agent
9
+ from src.enum.language import Language
10
+ from src.enum.library_name import LibraryName
11
+ from src.enum.library_type import LibraryType
12
+ from src.enum.scanner import Scanner
13
+ from src.exception.library_op_error import LibraryOpError
14
+ from src.plex_util_logger import PlexUtilLogger
15
+ from src.util.path_ops import PathOps
16
+
17
+
18
+ class Playlist(Library):
19
+ def __init__(
20
+ self,
21
+ plex_server: PlexServer,
22
+ location: Path,
23
+ language: Language,
24
+ music_playlist_file_dto: MusicPlaylistFileDTO,
25
+ ) -> None:
26
+ super().__init__(
27
+ plex_server,
28
+ LibraryName.MUSIC,
29
+ LibraryType.MUSIC,
30
+ Agent.MUSIC,
31
+ Scanner.MUSIC,
32
+ location,
33
+ language,
34
+ LibraryPreferencesDTO({}, {}, {}, {}),
35
+ )
36
+ self.music_playlist_file_dto = music_playlist_file_dto
37
+
38
+ def create(self) -> None:
39
+ op_type = "CREATE"
40
+ tracks = self.plex_server.library.section(
41
+ self.name.value,
42
+ ).searchTracks()
43
+ plex_track_dict = {}
44
+ plex_playlist = []
45
+
46
+ playlist_names = [
47
+ x.name for x in self.music_playlist_file_dto.playlists
48
+ ]
49
+
50
+ info = "Creating playlist library: \n" f"Playlists: {playlist_names}\n"
51
+
52
+ PlexUtilLogger.get_logger().info(info)
53
+
54
+ info = (
55
+ "Checking server track count "
56
+ f"meets expected "
57
+ f"count: {self.music_playlist_file_dto.track_count!s}\n"
58
+ )
59
+ PlexUtilLogger.get_logger().info(info)
60
+ self.poll(10, self.music_playlist_file_dto.track_count, 10)
61
+
62
+ playlists = self.music_playlist_file_dto.playlists
63
+
64
+ for track in tracks:
65
+ plex_track_absolute_location = track.locations[0]
66
+ plex_track_path = PathOps.get_path_from_str(
67
+ plex_track_absolute_location,
68
+ )
69
+ plex_track_full_name = plex_track_path.name
70
+ plex_track_name = plex_track_full_name.rsplit(".", 1)[0]
71
+ plex_track_dict[plex_track_name] = track
72
+
73
+ for playlist in playlists:
74
+ playlist_name = playlist.name
75
+ songs = playlist.songs
76
+
77
+ for song in songs:
78
+ song_name = song.name
79
+
80
+ if plex_track_dict.get(song_name) is None:
81
+ description = (
82
+ f"File in music playlist: '{song_name}' "
83
+ "does not exist in server"
84
+ )
85
+ raise LibraryOpError(
86
+ op_type=op_type,
87
+ library_type=self.library_type,
88
+ description=description,
89
+ )
90
+
91
+ plex_playlist.append(plex_track_dict.get(song_name))
92
+
93
+ self.plex_server.createPlaylist(
94
+ title=playlist_name,
95
+ items=plex_playlist,
96
+ )
97
+ plex_playlist = []
98
+
99
+ def delete(self) -> None:
100
+ playlist_names = [
101
+ x.name for x in self.music_playlist_file_dto.playlists
102
+ ]
103
+
104
+ info = (
105
+ "Deleting music playlists: \n"
106
+ f"Playlists: {playlist_names}\n"
107
+ f"Location: {self.location!s}\n"
108
+ )
109
+ PlexUtilLogger.get_logger().info(info)
110
+
111
+ server_playlists = self.plex_server.playlists(playlistType="audio")
112
+
113
+ debug = f"Playlists available in server: {server_playlists}"
114
+ PlexUtilLogger.get_logger().debug(debug)
115
+
116
+ for playlist in server_playlists:
117
+ if playlist.title in playlist_names:
118
+ playlist.delete()
119
+
120
+ def exists(self) -> bool:
121
+ playlist_names = [
122
+ x.name for x in self.music_playlist_file_dto.playlists
123
+ ]
124
+ playlists = self.plex_server.playlists(playlistType="audio")
125
+
126
+ debug = (
127
+ f"Checking playlists exist\n"
128
+ f"Requested: {playlist_names}\n"
129
+ f"In server: {playlists}\n"
130
+ )
131
+ PlexUtilLogger.get_logger().debug(debug)
132
+
133
+ if not playlists or not playlist_names:
134
+ return False
135
+
136
+ all_exist = True
137
+ for playlist_name in playlist_names:
138
+ if playlist_name in [x.title for x in playlists]:
139
+ continue
140
+ all_exist = False
141
+
142
+ debug = f"All exist: {all_exist}"
143
+ PlexUtilLogger.get_logger().debug(debug)
144
+
145
+ return all_exist