plexflow 0.0.64__py3-none-any.whl

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 (256) hide show
  1. plexflow/__init__.py +0 -0
  2. plexflow/__main__.py +15 -0
  3. plexflow/core/.DS_Store +0 -0
  4. plexflow/core/__init__.py +0 -0
  5. plexflow/core/context/__init__.py +0 -0
  6. plexflow/core/context/metadata/__init__.py +0 -0
  7. plexflow/core/context/metadata/context.py +32 -0
  8. plexflow/core/context/metadata/tmdb/__init__.py +0 -0
  9. plexflow/core/context/metadata/tmdb/context.py +45 -0
  10. plexflow/core/context/partial_context.py +46 -0
  11. plexflow/core/context/partials/__init__.py +8 -0
  12. plexflow/core/context/partials/cache.py +16 -0
  13. plexflow/core/context/partials/context.py +12 -0
  14. plexflow/core/context/partials/ids.py +37 -0
  15. plexflow/core/context/partials/movie.py +115 -0
  16. plexflow/core/context/partials/tgx_batch.py +33 -0
  17. plexflow/core/context/partials/tgx_context.py +34 -0
  18. plexflow/core/context/partials/torrents.py +23 -0
  19. plexflow/core/context/partials/watchlist.py +35 -0
  20. plexflow/core/context/plexflow_context.py +29 -0
  21. plexflow/core/context/plexflow_property.py +36 -0
  22. plexflow/core/context/root/__init__.py +0 -0
  23. plexflow/core/context/root/context.py +25 -0
  24. plexflow/core/context/select/__init__.py +0 -0
  25. plexflow/core/context/select/context.py +45 -0
  26. plexflow/core/context/torrent/__init__.py +0 -0
  27. plexflow/core/context/torrent/context.py +43 -0
  28. plexflow/core/context/torrent/tpb/__init__.py +0 -0
  29. plexflow/core/context/torrent/tpb/context.py +45 -0
  30. plexflow/core/context/torrent/yts/__init__.py +0 -0
  31. plexflow/core/context/torrent/yts/context.py +45 -0
  32. plexflow/core/context/watchlist/__init__.py +0 -0
  33. plexflow/core/context/watchlist/context.py +46 -0
  34. plexflow/core/downloads/__init__.py +0 -0
  35. plexflow/core/downloads/candidates/__init__.py +0 -0
  36. plexflow/core/downloads/candidates/download_candidate.py +210 -0
  37. plexflow/core/downloads/candidates/filtered.py +51 -0
  38. plexflow/core/downloads/candidates/utils.py +39 -0
  39. plexflow/core/env/__init__.py +0 -0
  40. plexflow/core/env/env.py +31 -0
  41. plexflow/core/genai/__init__.py +0 -0
  42. plexflow/core/genai/bot.py +9 -0
  43. plexflow/core/genai/plexa.py +54 -0
  44. plexflow/core/genai/torrent/imdb_verify.py +65 -0
  45. plexflow/core/genai/torrent/movie.py +25 -0
  46. plexflow/core/genai/utils/__init__.py +0 -0
  47. plexflow/core/genai/utils/loader.py +5 -0
  48. plexflow/core/metadata/__init__.py +0 -0
  49. plexflow/core/metadata/auto/__init__.py +0 -0
  50. plexflow/core/metadata/auto/auto_meta.py +40 -0
  51. plexflow/core/metadata/auto/auto_providers/__init__.py +0 -0
  52. plexflow/core/metadata/auto/auto_providers/auto/__init__.py +0 -0
  53. plexflow/core/metadata/auto/auto_providers/auto/episode.py +49 -0
  54. plexflow/core/metadata/auto/auto_providers/auto/item.py +55 -0
  55. plexflow/core/metadata/auto/auto_providers/auto/movie.py +13 -0
  56. plexflow/core/metadata/auto/auto_providers/auto/season.py +43 -0
  57. plexflow/core/metadata/auto/auto_providers/auto/show.py +26 -0
  58. plexflow/core/metadata/auto/auto_providers/imdb/__init__.py +0 -0
  59. plexflow/core/metadata/auto/auto_providers/imdb/movie.py +36 -0
  60. plexflow/core/metadata/auto/auto_providers/imdb/show.py +45 -0
  61. plexflow/core/metadata/auto/auto_providers/moviemeter/__init__.py +0 -0
  62. plexflow/core/metadata/auto/auto_providers/moviemeter/movie.py +40 -0
  63. plexflow/core/metadata/auto/auto_providers/plex/__init__.py +0 -0
  64. plexflow/core/metadata/auto/auto_providers/plex/movie.py +39 -0
  65. plexflow/core/metadata/auto/auto_providers/tmdb/__init__.py +0 -0
  66. plexflow/core/metadata/auto/auto_providers/tmdb/episode.py +30 -0
  67. plexflow/core/metadata/auto/auto_providers/tmdb/movie.py +36 -0
  68. plexflow/core/metadata/auto/auto_providers/tmdb/season.py +23 -0
  69. plexflow/core/metadata/auto/auto_providers/tmdb/show.py +41 -0
  70. plexflow/core/metadata/auto/auto_providers/tmdb.py +92 -0
  71. plexflow/core/metadata/auto/auto_providers/tvdb/__init__.py +0 -0
  72. plexflow/core/metadata/auto/auto_providers/tvdb/episode.py +28 -0
  73. plexflow/core/metadata/auto/auto_providers/tvdb/movie.py +36 -0
  74. plexflow/core/metadata/auto/auto_providers/tvdb/season.py +25 -0
  75. plexflow/core/metadata/auto/auto_providers/tvdb/show.py +41 -0
  76. plexflow/core/metadata/providers/__init__.py +0 -0
  77. plexflow/core/metadata/providers/imdb/__init__.py +0 -0
  78. plexflow/core/metadata/providers/imdb/datatypes.py +53 -0
  79. plexflow/core/metadata/providers/imdb/imdb.py +112 -0
  80. plexflow/core/metadata/providers/moviemeter/__init__.py +0 -0
  81. plexflow/core/metadata/providers/moviemeter/datatypes.py +111 -0
  82. plexflow/core/metadata/providers/moviemeter/moviemeter.py +42 -0
  83. plexflow/core/metadata/providers/plex/__init__.py +0 -0
  84. plexflow/core/metadata/providers/plex/datatypes.py +693 -0
  85. plexflow/core/metadata/providers/plex/plex.py +167 -0
  86. plexflow/core/metadata/providers/tmdb/__init__.py +0 -0
  87. plexflow/core/metadata/providers/tmdb/datatypes.py +460 -0
  88. plexflow/core/metadata/providers/tmdb/tmdb.py +85 -0
  89. plexflow/core/metadata/providers/tvdb/__init__.py +0 -0
  90. plexflow/core/metadata/providers/tvdb/datatypes.py +257 -0
  91. plexflow/core/metadata/providers/tvdb/tv_datatypes.py +554 -0
  92. plexflow/core/metadata/providers/tvdb/tvdb.py +65 -0
  93. plexflow/core/metadata/providers/universal/__init__.py +0 -0
  94. plexflow/core/metadata/providers/universal/movie.py +130 -0
  95. plexflow/core/metadata/providers/universal/old.py +192 -0
  96. plexflow/core/metadata/providers/universal/show.py +107 -0
  97. plexflow/core/plex/__init__.py +0 -0
  98. plexflow/core/plex/api/context/authorized.py +15 -0
  99. plexflow/core/plex/api/context/discover.py +14 -0
  100. plexflow/core/plex/api/context/library.py +14 -0
  101. plexflow/core/plex/discover/__init__.py +0 -0
  102. plexflow/core/plex/discover/activity.py +448 -0
  103. plexflow/core/plex/discover/comment.py +89 -0
  104. plexflow/core/plex/discover/feed.py +11 -0
  105. plexflow/core/plex/hooks/__init__.py +0 -0
  106. plexflow/core/plex/hooks/plex_authorized.py +60 -0
  107. plexflow/core/plex/hooks/plexflow_database.py +6 -0
  108. plexflow/core/plex/library/__init__.py +0 -0
  109. plexflow/core/plex/library/library.py +103 -0
  110. plexflow/core/plex/token/__init__.py +0 -0
  111. plexflow/core/plex/token/auto_token.py +91 -0
  112. plexflow/core/plex/utils/__init__.py +0 -0
  113. plexflow/core/plex/utils/paginated.py +39 -0
  114. plexflow/core/plex/watchlist/__init__.py +0 -0
  115. plexflow/core/plex/watchlist/datatypes.py +124 -0
  116. plexflow/core/plex/watchlist/watchlist.py +23 -0
  117. plexflow/core/storage/__init__.py +0 -0
  118. plexflow/core/storage/object/__init__.py +0 -0
  119. plexflow/core/storage/object/plexflow_storage.py +143 -0
  120. plexflow/core/storage/object/redis_storage.py +169 -0
  121. plexflow/core/subtitles/__init__.py +0 -0
  122. plexflow/core/subtitles/providers/__init__.py +0 -0
  123. plexflow/core/subtitles/providers/auto_subtitles.py +48 -0
  124. plexflow/core/subtitles/providers/oss/__init__.py +0 -0
  125. plexflow/core/subtitles/providers/oss/datatypes.py +104 -0
  126. plexflow/core/subtitles/providers/oss/download.py +48 -0
  127. plexflow/core/subtitles/providers/oss/old.py +144 -0
  128. plexflow/core/subtitles/providers/oss/oss.py +400 -0
  129. plexflow/core/subtitles/providers/oss/oss_subtitle.py +32 -0
  130. plexflow/core/subtitles/providers/oss/search.py +52 -0
  131. plexflow/core/subtitles/providers/oss/unlimited_oss.py +231 -0
  132. plexflow/core/subtitles/providers/oss/utils/__init__.py +0 -0
  133. plexflow/core/subtitles/providers/oss/utils/config.py +63 -0
  134. plexflow/core/subtitles/providers/oss/utils/download_client.py +22 -0
  135. plexflow/core/subtitles/providers/oss/utils/exceptions.py +35 -0
  136. plexflow/core/subtitles/providers/oss/utils/file_utils.py +83 -0
  137. plexflow/core/subtitles/providers/oss/utils/languages.py +78 -0
  138. plexflow/core/subtitles/providers/oss/utils/response_base.py +221 -0
  139. plexflow/core/subtitles/providers/oss/utils/responses.py +176 -0
  140. plexflow/core/subtitles/providers/oss/utils/srt.py +561 -0
  141. plexflow/core/subtitles/results/__init__.py +0 -0
  142. plexflow/core/subtitles/results/subtitle.py +170 -0
  143. plexflow/core/torrents/__init__.py +0 -0
  144. plexflow/core/torrents/analyzers/analyzed_torrent.py +143 -0
  145. plexflow/core/torrents/analyzers/analyzer.py +45 -0
  146. plexflow/core/torrents/analyzers/torrentquest/analyzer.py +47 -0
  147. plexflow/core/torrents/auto/auto_providers/auto/__init__.py +0 -0
  148. plexflow/core/torrents/auto/auto_providers/auto/torrent.py +64 -0
  149. plexflow/core/torrents/auto/auto_providers/tpb/torrent.py +62 -0
  150. plexflow/core/torrents/auto/auto_torrents.py +29 -0
  151. plexflow/core/torrents/providers/__init__.py +0 -0
  152. plexflow/core/torrents/providers/ext/__init__.py +0 -0
  153. plexflow/core/torrents/providers/ext/ext.py +18 -0
  154. plexflow/core/torrents/providers/ext/utils.py +64 -0
  155. plexflow/core/torrents/providers/extratorrent/__init__.py +0 -0
  156. plexflow/core/torrents/providers/extratorrent/extratorrent.py +21 -0
  157. plexflow/core/torrents/providers/extratorrent/utils.py +66 -0
  158. plexflow/core/torrents/providers/eztv/__init__.py +0 -0
  159. plexflow/core/torrents/providers/eztv/eztv.py +47 -0
  160. plexflow/core/torrents/providers/eztv/utils.py +83 -0
  161. plexflow/core/torrents/providers/rarbg2/__init__.py +0 -0
  162. plexflow/core/torrents/providers/rarbg2/rarbg2.py +19 -0
  163. plexflow/core/torrents/providers/rarbg2/utils.py +76 -0
  164. plexflow/core/torrents/providers/snowfl/__init__.py +0 -0
  165. plexflow/core/torrents/providers/snowfl/snowfl.py +36 -0
  166. plexflow/core/torrents/providers/snowfl/utils.py +59 -0
  167. plexflow/core/torrents/providers/tgx/__init__.py +0 -0
  168. plexflow/core/torrents/providers/tgx/context.py +50 -0
  169. plexflow/core/torrents/providers/tgx/dump.py +40 -0
  170. plexflow/core/torrents/providers/tgx/tgx.py +22 -0
  171. plexflow/core/torrents/providers/tgx/utils.py +61 -0
  172. plexflow/core/torrents/providers/therarbg/__init__.py +0 -0
  173. plexflow/core/torrents/providers/therarbg/therarbg.py +17 -0
  174. plexflow/core/torrents/providers/therarbg/utils.py +61 -0
  175. plexflow/core/torrents/providers/torrentquest/__init__.py +0 -0
  176. plexflow/core/torrents/providers/torrentquest/torrentquest.py +20 -0
  177. plexflow/core/torrents/providers/torrentquest/utils.py +70 -0
  178. plexflow/core/torrents/providers/tpb/__init__.py +0 -0
  179. plexflow/core/torrents/providers/tpb/tpb.py +17 -0
  180. plexflow/core/torrents/providers/tpb/utils.py +139 -0
  181. plexflow/core/torrents/providers/yts/__init__.py +0 -0
  182. plexflow/core/torrents/providers/yts/utils.py +57 -0
  183. plexflow/core/torrents/providers/yts/yts.py +31 -0
  184. plexflow/core/torrents/results/__init__.py +0 -0
  185. plexflow/core/torrents/results/torrent.py +165 -0
  186. plexflow/core/torrents/results/universal.py +220 -0
  187. plexflow/core/torrents/results/utils.py +15 -0
  188. plexflow/events/__init__.py +0 -0
  189. plexflow/events/download/__init__.py +0 -0
  190. plexflow/events/download/torrent_events.py +96 -0
  191. plexflow/events/publish/__init__.py +0 -0
  192. plexflow/events/publish/publish.py +34 -0
  193. plexflow/logging/__init__.py +0 -0
  194. plexflow/logging/log_setup.py +8 -0
  195. plexflow/spiders/quiet_logger.py +9 -0
  196. plexflow/spiders/tgx/pipelines/dump_json_pipeline.py +30 -0
  197. plexflow/spiders/tgx/pipelines/meta_pipeline.py +13 -0
  198. plexflow/spiders/tgx/pipelines/publish_pipeline.py +14 -0
  199. plexflow/spiders/tgx/pipelines/torrent_info_pipeline.py +12 -0
  200. plexflow/spiders/tgx/pipelines/validation_pipeline.py +17 -0
  201. plexflow/spiders/tgx/settings.py +36 -0
  202. plexflow/spiders/tgx/spider.py +72 -0
  203. plexflow/utils/__init__.py +0 -0
  204. plexflow/utils/antibot/human_like_requests.py +122 -0
  205. plexflow/utils/api/__init__.py +0 -0
  206. plexflow/utils/api/context/http.py +62 -0
  207. plexflow/utils/api/rest/__init__.py +0 -0
  208. plexflow/utils/api/rest/antibot_restful.py +68 -0
  209. plexflow/utils/api/rest/restful.py +49 -0
  210. plexflow/utils/captcha/__init__.py +0 -0
  211. plexflow/utils/captcha/bypass/__init__.py +0 -0
  212. plexflow/utils/captcha/bypass/decode_audio.py +34 -0
  213. plexflow/utils/download/__init__.py +0 -0
  214. plexflow/utils/download/gz.py +26 -0
  215. plexflow/utils/filesystem/__init__.py +0 -0
  216. plexflow/utils/filesystem/search.py +129 -0
  217. plexflow/utils/gmail/__init__.py +0 -0
  218. plexflow/utils/gmail/mails.py +116 -0
  219. plexflow/utils/hooks/__init__.py +0 -0
  220. plexflow/utils/hooks/http.py +84 -0
  221. plexflow/utils/hooks/postgresql.py +93 -0
  222. plexflow/utils/hooks/redis.py +112 -0
  223. plexflow/utils/image/storage.py +36 -0
  224. plexflow/utils/imdb/__init__.py +0 -0
  225. plexflow/utils/imdb/imdb_codes.py +107 -0
  226. plexflow/utils/pubsub/consume.py +82 -0
  227. plexflow/utils/pubsub/produce.py +25 -0
  228. plexflow/utils/retry/__init__.py +0 -0
  229. plexflow/utils/retry/utils.py +38 -0
  230. plexflow/utils/strings/__init__.py +0 -0
  231. plexflow/utils/strings/filesize.py +55 -0
  232. plexflow/utils/strings/language.py +14 -0
  233. plexflow/utils/subtitle/search.py +76 -0
  234. plexflow/utils/tasks/decorators.py +78 -0
  235. plexflow/utils/tasks/k8s/task.py +70 -0
  236. plexflow/utils/thread_safe/safe_list.py +54 -0
  237. plexflow/utils/thread_safe/safe_set.py +69 -0
  238. plexflow/utils/torrent/__init__.py +0 -0
  239. plexflow/utils/torrent/analyze.py +118 -0
  240. plexflow/utils/torrent/extract/common.py +37 -0
  241. plexflow/utils/torrent/extract/ext.py +2391 -0
  242. plexflow/utils/torrent/extract/extratorrent.py +56 -0
  243. plexflow/utils/torrent/extract/kat.py +1581 -0
  244. plexflow/utils/torrent/extract/tgx.py +96 -0
  245. plexflow/utils/torrent/extract/therarbg.py +170 -0
  246. plexflow/utils/torrent/extract/torrentquest.py +171 -0
  247. plexflow/utils/torrent/files.py +36 -0
  248. plexflow/utils/torrent/hash.py +90 -0
  249. plexflow/utils/transcribe/__init__.py +0 -0
  250. plexflow/utils/transcribe/speech2text.py +40 -0
  251. plexflow/utils/video/__init__.py +0 -0
  252. plexflow/utils/video/subtitle.py +73 -0
  253. plexflow-0.0.64.dist-info/METADATA +71 -0
  254. plexflow-0.0.64.dist-info/RECORD +256 -0
  255. plexflow-0.0.64.dist-info/WHEEL +4 -0
  256. plexflow-0.0.64.dist-info/entry_points.txt +24 -0
@@ -0,0 +1,45 @@
1
+ from plexflow.core.storage.object.plexflow_storage import PlexflowObjectStore
2
+ from plexflow.core.context.plexflow_context import PlexflowContext
3
+ from plexflow.core.context.plexflow_property import PlexflowObjectProperty
4
+ from plexflow.core.torrents.providers.tpb.utils import TPBSearchResult
5
+ from typing import Union, List
6
+
7
+ class ThePirateBayTorrentContext(PlexflowContext):
8
+ """A class used to represent a Select Context in Plexflow.
9
+
10
+ This class extends PlexflowContext and adds a selected item property.
11
+
12
+ Attributes:
13
+ selected_item (PlexflowObjectProperty): The selected item in the context.
14
+ """
15
+
16
+ def __init__(self, store: PlexflowObjectStore, **kwargs):
17
+ """Initializes the SelectContext with the given object store and keyword arguments.
18
+
19
+ Args:
20
+ store (PlexflowObjectStore): The object store to be used.
21
+ **kwargs: Arbitrary keyword arguments.
22
+ """
23
+
24
+ super().__init__(store=store, **kwargs)
25
+ self._torrents = PlexflowObjectProperty(self.object_store, "torrents", local=True)
26
+
27
+ @property
28
+ def items(self) -> Union[List[TPBSearchResult], None]:
29
+ """Gets the value of the selected item.
30
+
31
+ Returns:
32
+ Any: The value of the selected item.
33
+ """
34
+
35
+ return self._torrents.value
36
+
37
+ @items.setter
38
+ def items(self, val: List[TPBSearchResult]):
39
+ """Sets the value of the selected item.
40
+
41
+ Args:
42
+ val (Any): The value to be set.
43
+ """
44
+
45
+ self._torrents.value = val
File without changes
@@ -0,0 +1,45 @@
1
+ from plexflow.core.storage.object.plexflow_storage import PlexflowObjectStore
2
+ from plexflow.core.context.plexflow_context import PlexflowContext
3
+ from plexflow.core.context.plexflow_property import PlexflowObjectProperty
4
+ from plexflow.core.torrents.providers.yts.utils import YTSSearchResult
5
+ from typing import Union, List
6
+
7
+ class YTSTorrentContext(PlexflowContext):
8
+ """A class used to represent a Select Context in Plexflow.
9
+
10
+ This class extends PlexflowContext and adds a selected item property.
11
+
12
+ Attributes:
13
+ selected_item (PlexflowObjectProperty): The selected item in the context.
14
+ """
15
+
16
+ def __init__(self, store: PlexflowObjectStore, **kwargs):
17
+ """Initializes the SelectContext with the given object store and keyword arguments.
18
+
19
+ Args:
20
+ store (PlexflowObjectStore): The object store to be used.
21
+ **kwargs: Arbitrary keyword arguments.
22
+ """
23
+
24
+ super().__init__(store=store, **kwargs)
25
+ self._torrents = PlexflowObjectProperty(self.object_store, "torrents", local=True)
26
+
27
+ @property
28
+ def items(self) -> Union[List[YTSSearchResult], None]:
29
+ """Gets the value of the selected item.
30
+
31
+ Returns:
32
+ Any: The value of the selected item.
33
+ """
34
+
35
+ return self._torrents.value
36
+
37
+ @items.setter
38
+ def items(self, val: List[YTSSearchResult]):
39
+ """Sets the value of the selected item.
40
+
41
+ Args:
42
+ val (Any): The value to be set.
43
+ """
44
+
45
+ self._torrents.value = val
File without changes
@@ -0,0 +1,46 @@
1
+ from plexflow.core.storage.object.plexflow_storage import PlexflowObjectStore
2
+ from plexflow.core.context.plexflow_context import PlexflowContext
3
+ from plexflow.core.context.plexflow_property import PlexflowObjectProperty
4
+ from plexflow.core.plex.watchlist.datatypes import MediaContainer
5
+ from typing import Union
6
+
7
+ class WatchlistContext(PlexflowContext):
8
+ """
9
+ A class used to represent a context for managing a watchlist in Plexflow.
10
+
11
+ This class extends PlexflowContext and adds a property for the watchlist.
12
+
13
+ Attributes:
14
+ watchlist (PlexflowObjectProperty): The watchlist in the context.
15
+ """
16
+
17
+ def __init__(self, store: PlexflowObjectStore, **kwargs):
18
+ """
19
+ Initializes the WatchlistContext with the given object store and optional keyword arguments.
20
+
21
+ Args:
22
+ store (PlexflowObjectStore): The object store to be used.
23
+ **kwargs: Additional keyword arguments.
24
+ """
25
+ super().__init__(store=store, **kwargs)
26
+ self.watchlist = PlexflowObjectProperty(self.object_store, "watchlist", local=True)
27
+
28
+ @property
29
+ def list(self) -> Union[MediaContainer, None]:
30
+ """
31
+ Gets the value of the watchlist.
32
+
33
+ Returns:
34
+ Any: The value of the selected watchlist item.
35
+ """
36
+ return self.watchlist.value
37
+
38
+ @list.setter
39
+ def list(self, val: MediaContainer):
40
+ """
41
+ Sets the value of the watchlist.
42
+
43
+ Args:
44
+ val (Any): The value to be set.
45
+ """
46
+ self.watchlist.value = val
File without changes
File without changes
@@ -0,0 +1,210 @@
1
+ import math
2
+ from typing import List, Optional, Set
3
+ from plexflow.core.torrents.results.universal import UniversalTorrent
4
+ from plexflow.core.subtitles.results.subtitle import Subtitle
5
+ from plexflow.utils.imdb.imdb_codes import IMDbCode
6
+
7
+ class DownloadCandidate:
8
+ """
9
+ Represents a download candidate for a torrent with associated subtitles.
10
+ """
11
+
12
+ def __init__(self, torrent: UniversalTorrent, subtitles: List[Subtitle]):
13
+ """
14
+ Initializes a new instance of the DownloadCandidate class.
15
+
16
+ Args:
17
+ torrent (UniversalTorrent): The torrent associated with the download candidate.
18
+ subtitles (List[Subtitle]): The list of subtitles associated with the download candidate.
19
+ """
20
+ self.torrent = torrent
21
+ self.subtitles = subtitles
22
+
23
+ @property
24
+ def imdb_code(self) -> IMDbCode:
25
+ """
26
+ Get the IMDb code of the torrent.
27
+
28
+ Returns:
29
+ IMDbCode: The IMDb code of the torrent.
30
+ """
31
+ return self.torrent.imdb_code
32
+
33
+ @property
34
+ def is_season_pack(self) -> bool:
35
+ """
36
+ Check if the torrent is a season pack.
37
+
38
+ Returns:
39
+ bool: True if the torrent is a season pack, False otherwise.
40
+ """
41
+ return self.torrent.is_season_pack
42
+
43
+ @property
44
+ def season(self) -> Optional[int]:
45
+ """
46
+ Get the season number of the torrent.
47
+
48
+ Returns:
49
+ Optional[int]: The season number of the torrent, or None if not applicable.
50
+ """
51
+ return self.torrent.season
52
+
53
+ @property
54
+ def episode(self) -> Optional[int]:
55
+ """
56
+ Get the episode number of the torrent.
57
+
58
+ Returns:
59
+ Optional[int]: The episode number of the torrent, or None if not applicable.
60
+ """
61
+ return self.torrent.episode
62
+
63
+ @property
64
+ def max_peers(self) -> int:
65
+ """
66
+ Get the maximum number of peers for the torrent.
67
+
68
+ Returns:
69
+ int: The maximum number of peers for the torrent.
70
+ """
71
+ return self.torrent.max_peers
72
+
73
+ @property
74
+ def max_seeds(self) -> int:
75
+ """
76
+ Get the maximum number of seeds for the torrent.
77
+
78
+ Returns:
79
+ int: The maximum number of seeds for the torrent.
80
+ """
81
+ return self.torrent.max_seeds
82
+
83
+ @property
84
+ def min_seeds(self) -> int:
85
+ """
86
+ Get the minimum number of seeds for the torrent.
87
+
88
+ Returns:
89
+ int: The minimum number of seeds for the torrent.
90
+ """
91
+ return self.torrent.min_seeds
92
+
93
+ @property
94
+ def min_peers(self) -> int:
95
+ """
96
+ Get the minimum number of peers for the torrent.
97
+
98
+ Returns:
99
+ int: The minimum number of peers for the torrent.
100
+ """
101
+ return self.torrent.min_peers
102
+
103
+ @property
104
+ def sources(self) -> Set:
105
+ """
106
+ Get the set of sources for the torrent.
107
+
108
+ Returns:
109
+ Set: The set of sources for the torrent.
110
+ """
111
+ return self.torrent.sources
112
+
113
+ @property
114
+ def max_size_bytes(self) -> int:
115
+ """
116
+ Get the maximum size of the torrent in bytes.
117
+
118
+ Returns:
119
+ int: The maximum size of the torrent in bytes.
120
+ """
121
+ return self.torrent.max_size_bytes
122
+
123
+ @property
124
+ def min_size_bytes(self) -> int:
125
+ """
126
+ Get the minimum size of the torrent in bytes.
127
+
128
+ Returns:
129
+ int: The minimum size of the torrent in bytes.
130
+ """
131
+ return self.torrent.min_size_bytes
132
+
133
+ @property
134
+ def has_native_subtitles(self) -> bool:
135
+ """
136
+ Check if the torrent has native subtitles.
137
+
138
+ Returns:
139
+ bool: True if the torrent has native subtitles, False otherwise.
140
+ """
141
+ return self.torrent.has_native_subtitles
142
+
143
+ @property
144
+ def has_native_dutch_subtitles(self) -> bool:
145
+ """
146
+ Check if the torrent has native Dutch subtitles.
147
+
148
+ Returns:
149
+ bool: True if the torrent has native Dutch subtitles, False otherwise.
150
+ """
151
+ return self.torrent.has_native_dutch_subtitles
152
+
153
+ @property
154
+ def has_native_english_subtitles(self) -> bool:
155
+ """
156
+ Check if the torrent has native English subtitles.
157
+
158
+ Returns:
159
+ bool: True if the torrent has native English subtitles, False otherwise.
160
+ """
161
+ return self.torrent.has_native_english_subtitles
162
+
163
+ @property
164
+ def fitness(self) -> float:
165
+ """
166
+ Calculate the fitness score of the download candidate.
167
+
168
+ Returns:
169
+ float: The fitness score of the download candidate.
170
+ """
171
+ return self.max_seeds / max(math.sqrt(self.min_size_bytes), 1)
172
+
173
+ @property
174
+ def has_subtitles(self) -> bool:
175
+ """
176
+ Check if the download candidate has subtitles.
177
+
178
+ Returns:
179
+ bool: True if the download candidate has subtitles, False otherwise.
180
+ """
181
+ return any([
182
+ self.has_native_subtitles,
183
+ len(self.subtitles) > 0
184
+ ])
185
+
186
+ @property
187
+ def has_dutch_subtitles(self) -> bool:
188
+ """
189
+ Check if the download candidate has Dutch subtitles.
190
+
191
+ Returns:
192
+ bool: True if the download candidate has Dutch subtitles, False otherwise.
193
+ """
194
+ return any([
195
+ self.has_native_dutch_subtitles,
196
+ any(s.language == "nl" for s in self.subtitles),
197
+ ])
198
+
199
+ @property
200
+ def has_english_subtitles(self) -> bool:
201
+ """
202
+ Check if the download candidate has English subtitles.
203
+
204
+ Returns:
205
+ bool: True if the download candidate has English subtitles, False otherwise.
206
+ """
207
+ return any([
208
+ self.has_native_english_subtitles,
209
+ any(s.language == "en" for s in self.subtitles),
210
+ ])
@@ -0,0 +1,51 @@
1
+ from plexflow.core.downloads.candidates.download_candidate import DownloadCandidate
2
+ from typing import List
3
+
4
+ class FilteredCandidates:
5
+ """
6
+ Represents a collection of filtered download candidates based on specific criteria.
7
+
8
+ Args:
9
+ imdb_id (str): The IMDb ID of the movie.
10
+ year (int): The release year of the movie.
11
+ titles (List[str]): A list of movie titles.
12
+ candidates (List[DownloadCandidate]): A list of download candidates.
13
+
14
+ Attributes:
15
+ imdb_id (str): The IMDb ID of the movie.
16
+ year (int): The release year of the movie.
17
+ titles (List[str]): A list of movie titles.
18
+ candidates (List[DownloadCandidate]): A list of download candidates.
19
+ """
20
+
21
+ def __init__(self, imdb_id: str, year: int, titles: List[str], candidates: List[DownloadCandidate]):
22
+ self.imdb_id = imdb_id
23
+ self.year = year
24
+ self.titles = titles
25
+ self.candidates = candidates
26
+
27
+ def __iter__(self):
28
+ """
29
+ Returns an iterator that yields candidates from the `candidates` list
30
+ if they match the criteria specified by the `_is_match` method.
31
+
32
+ Yields:
33
+ candidate: A candidate that matches the criteria.
34
+ """
35
+ return (candidate for candidate in self.candidates if self._is_match(candidate))
36
+
37
+ def _is_match(self, candidate: DownloadCandidate) -> bool:
38
+ """
39
+ Checks if the given candidate is a match for the current filter.
40
+
41
+ Args:
42
+ candidate (DownloadCandidate): The candidate to check.
43
+
44
+ Returns:
45
+ bool: True if the candidate is a match, False otherwise.
46
+ """
47
+ if candidate.imdb_code == self.imdb_id:
48
+ return True
49
+ if candidate.year == self.year and candidate.title in self.titles:
50
+ return True
51
+ return False
@@ -0,0 +1,39 @@
1
+ from typing import Iterable, List
2
+ from plexflow.core.torrents.results.universal import UniversalTorrent
3
+ from plexflow.core.subtitles.results.subtitle import Subtitle
4
+ from plexflow.core.downloads.candidates.download_candidate import DownloadCandidate
5
+
6
+ def create_download_candidates(torrents: Iterable[UniversalTorrent], subtitles: Iterable[Subtitle]) -> List[DownloadCandidate]:
7
+ """
8
+ This function creates a list of DownloadCandidate objects from an iterable of UniversalTorrent and an iterable of Subtitle.
9
+ All subtitles that are compatible with a torrent will become together a DownloadCandidate.
10
+
11
+ Parameters:
12
+ torrents (Iterable[UniversalTorrent]): An iterable of UniversalTorrent objects.
13
+ subtitles (Iterable[Subtitle]): An iterable of Subtitle objects.
14
+
15
+ Returns:
16
+ List[DownloadCandidate]: A list of DownloadCandidate objects.
17
+
18
+ Raises:
19
+ ValueError: If either torrents or subtitles is not an iterable.
20
+ """
21
+
22
+ # Check if inputs are iterables
23
+ if not isinstance(torrents, Iterable):
24
+ raise ValueError("torrents should be an iterable of UniversalTorrent objects")
25
+ if not isinstance(subtitles, Iterable):
26
+ raise ValueError("subtitles should be an iterable of Subtitle objects")
27
+
28
+ download_candidates = []
29
+
30
+ for torrent in torrents:
31
+ # Check if torrent is an instance of UniversalTorrent
32
+ if not isinstance(torrent, UniversalTorrent):
33
+ raise ValueError("Each torrent should be an instance of UniversalTorrent")
34
+
35
+ compatible_subtitles = [subtitle for subtitle in subtitles if torrent.is_compatible_with(subtitle)]
36
+ download_candidate = DownloadCandidate(torrent, compatible_subtitles)
37
+ download_candidates.append(download_candidate)
38
+
39
+ return download_candidates
File without changes
@@ -0,0 +1,31 @@
1
+ import os
2
+ from airflow.models import Variable
3
+ from airflow.exceptions import AirflowException
4
+
5
+ def get_var(var_name: str) -> str:
6
+ """
7
+ Function to retrieve a variable from Airflow or OS environment.
8
+
9
+ This function first attempts to retrieve the variable from Airflow's variables.
10
+ If the variable is not found in Airflow or an error occurs, it then attempts to retrieve it from the OS environment.
11
+
12
+ Args:
13
+ var_name (str): The name of the variable to retrieve.
14
+
15
+ Returns:
16
+ str: The value of the variable. If the variable is not found, returns None.
17
+
18
+ Examples:
19
+ >>> get_var('HOME')
20
+ '/home/user'
21
+ >>> get_var('UNKNOWN_VAR')
22
+ None
23
+ """
24
+ try:
25
+ # Try to get the variable from Airflow
26
+ var_value = Variable.get(var_name)
27
+ except AirflowException:
28
+ # If it fails, get the variable from os environment
29
+ var_value = os.getenv(var_name)
30
+
31
+ return var_value
File without changes
@@ -0,0 +1,9 @@
1
+ import os
2
+ import cohere
3
+ from plexflow.core.genai.utils.loader import load_preamble
4
+
5
+ class CohereBot:
6
+ def __init__(self, preamble_id: str) -> None:
7
+ self.co = cohere.Client(api_key=os.getenv("COHERE_API_KEY"))
8
+ self.chat_history = []
9
+ self.preamble = load_preamble(preamble_id)
@@ -0,0 +1,54 @@
1
+
2
+ import json
3
+ import os
4
+ import cohere
5
+ from plexflow.core.genai.utils.loader import load_preamble
6
+
7
+ class Plexa:
8
+ def __init__(self) -> None:
9
+ self.bot_name = "Plexa"
10
+ self.co = cohere.Client(api_key=os.getenv("COHERE_API_KEY"))
11
+ self.chat_history = []
12
+ self.preamble = load_preamble("plexa_v2")
13
+ self.hint = "BE BRIEF IN YOUR RESPONSE, SPLIT TEXT IN MESSAGES AND FORMAT AS VALID JSON"
14
+
15
+ def add_user_message(self, message: str, context: dict):
16
+ self.chat_history.append({
17
+ "role": "USER", "text":
18
+ json.dumps({
19
+ "movie": context.get("title"),
20
+ "release_date": context.get("release_date"),
21
+ "user_question": message,
22
+ "hint": self.hint,
23
+ })
24
+ })
25
+
26
+ def add_bot_message(self, message: str, context: dict):
27
+ self.chat_history.append({
28
+ "role": "CHATBOT", "text":
29
+ json.dumps({
30
+ "response": message,
31
+ "parts": [
32
+ message
33
+ ]
34
+ })
35
+ })
36
+
37
+ def react(self, question: str, context: dict):
38
+ message = json.dumps({
39
+ "movie": context.get("title"),
40
+ "release_date": context.get("release_date"),
41
+ "user_question": question,
42
+ "hint": self.hint,
43
+ })
44
+
45
+ return self.co.chat(
46
+ message=message,
47
+ temperature=1,
48
+ connectors=[{"id": "web-search"}],
49
+ model="command-r-plus",
50
+ chat_history=self.chat_history[-10:],
51
+ preamble=self.preamble,
52
+ )
53
+
54
+
@@ -0,0 +1,65 @@
1
+ from plexflow.core.genai.bot import CohereBot
2
+ import json
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+
6
+ class TorrentImdbMatcher(CohereBot):
7
+ def __init__(self) -> None:
8
+ super().__init__(preamble_id="torrent_imdb_matcher")
9
+
10
+ def parse(self, content: str):
11
+ response = self.co.chat(
12
+ message=content,
13
+ temperature=1,
14
+ model="command-r-plus",
15
+ preamble=self.preamble,
16
+ )
17
+
18
+ # print(response.json())
19
+ content = response.text
20
+
21
+ if content.startswith('```json'):
22
+ content = content.lstrip("`json")
23
+
24
+ if content.endswith('`'):
25
+ content = content.rstrip("`")
26
+
27
+ return json.loads(content)
28
+
29
+ def get_page_text(url):
30
+ r = requests.get(
31
+ url,
32
+ headers={
33
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
34
+ "Cookie": r"dom3ic8zudi28v8lr6fgphwffqoz0j6c=b7c768c9-98eb-49e8-8ec5-2c9e5c5828c9%3A2%3A1; sb_main_755b5f8e271690d6cb76076f459e9c82=1; PHPSESSID=g2g6grs8ejkoqisi81h1tomtk6; sb_count_755b5f8e271690d6cb76076f459e9c82=4; hu8935j4i9fq3hpuj9q39=true; fencekey=8ad2dbbf2b3ba78729048b3d823574ee; AdskeeperStorage=%7B%220%22%3A%7B%22svspr%22%3A%22%22%2C%22svsds%22%3A9%7D%2C%22C1543068%22%3A%7B%22page%22%3A5%2C%22time%22%3A%221718527871490%22%7D%2C%22C1343686%22%3A%7B%22page%22%3A5%2C%22time%22%3A%221718527871482%22%7D%2C%22C385455%22%3A%7B%22page%22%3A5%2C%22time%22%3A%221718527871553%22%7D%7D"
35
+ }
36
+ )
37
+
38
+ r.raise_for_status()
39
+
40
+ soup = BeautifulSoup(r.text, 'html.parser')
41
+
42
+ return soup.get_text()
43
+
44
+ def matches_with_imdb(imdb_id: str, torrent_url: str):
45
+ parser = TorrentImdbMatcher()
46
+
47
+ imdb_info = get_page_text(f"https://www.imdb.com/title/{imdb_id}")
48
+ torrent_info = get_page_text(torrent_url)
49
+
50
+ prompt = f"""
51
+ IMDB INFO:
52
+ {imdb_info}
53
+
54
+ TORRENT INFO:
55
+ {torrent_info}
56
+ """
57
+
58
+ print("prompt has", len(prompt), "chars")
59
+
60
+ data = parser.parse(prompt)
61
+
62
+ matched = data.get("match")
63
+ reasons = data.get("reasons", [])
64
+
65
+ return matched, reasons
@@ -0,0 +1,25 @@
1
+ from plexflow.core.genai.bot import CohereBot
2
+ import json
3
+
4
+ class MovieTorrentParser(CohereBot):
5
+ def __init__(self) -> None:
6
+ super().__init__(preamble_id="movie_torrent_parser")
7
+
8
+ def parse(self, content: str):
9
+ response = self.co.chat(
10
+ message=content,
11
+ temperature=1,
12
+ model="command-r-plus",
13
+ preamble=self.preamble,
14
+ )
15
+
16
+ # print(response.json())
17
+ content = response.text
18
+
19
+ if content.startswith('```json'):
20
+ content = content.lstrip("`json")
21
+
22
+ if content.endswith('`'):
23
+ content = content.rstrip("`")
24
+
25
+ return json.loads(content)
File without changes
@@ -0,0 +1,5 @@
1
+
2
+
3
+ def load_preamble(name: str):
4
+ with open(f"config/genai/{name}.txt", "r") as file:
5
+ return file.read()
File without changes
File without changes