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.
- plexflow/__init__.py +0 -0
- plexflow/__main__.py +15 -0
- plexflow/core/.DS_Store +0 -0
- plexflow/core/__init__.py +0 -0
- plexflow/core/context/__init__.py +0 -0
- plexflow/core/context/metadata/__init__.py +0 -0
- plexflow/core/context/metadata/context.py +32 -0
- plexflow/core/context/metadata/tmdb/__init__.py +0 -0
- plexflow/core/context/metadata/tmdb/context.py +45 -0
- plexflow/core/context/partial_context.py +46 -0
- plexflow/core/context/partials/__init__.py +8 -0
- plexflow/core/context/partials/cache.py +16 -0
- plexflow/core/context/partials/context.py +12 -0
- plexflow/core/context/partials/ids.py +37 -0
- plexflow/core/context/partials/movie.py +115 -0
- plexflow/core/context/partials/tgx_batch.py +33 -0
- plexflow/core/context/partials/tgx_context.py +34 -0
- plexflow/core/context/partials/torrents.py +23 -0
- plexflow/core/context/partials/watchlist.py +35 -0
- plexflow/core/context/plexflow_context.py +29 -0
- plexflow/core/context/plexflow_property.py +36 -0
- plexflow/core/context/root/__init__.py +0 -0
- plexflow/core/context/root/context.py +25 -0
- plexflow/core/context/select/__init__.py +0 -0
- plexflow/core/context/select/context.py +45 -0
- plexflow/core/context/torrent/__init__.py +0 -0
- plexflow/core/context/torrent/context.py +43 -0
- plexflow/core/context/torrent/tpb/__init__.py +0 -0
- plexflow/core/context/torrent/tpb/context.py +45 -0
- plexflow/core/context/torrent/yts/__init__.py +0 -0
- plexflow/core/context/torrent/yts/context.py +45 -0
- plexflow/core/context/watchlist/__init__.py +0 -0
- plexflow/core/context/watchlist/context.py +46 -0
- plexflow/core/downloads/__init__.py +0 -0
- plexflow/core/downloads/candidates/__init__.py +0 -0
- plexflow/core/downloads/candidates/download_candidate.py +210 -0
- plexflow/core/downloads/candidates/filtered.py +51 -0
- plexflow/core/downloads/candidates/utils.py +39 -0
- plexflow/core/env/__init__.py +0 -0
- plexflow/core/env/env.py +31 -0
- plexflow/core/genai/__init__.py +0 -0
- plexflow/core/genai/bot.py +9 -0
- plexflow/core/genai/plexa.py +54 -0
- plexflow/core/genai/torrent/imdb_verify.py +65 -0
- plexflow/core/genai/torrent/movie.py +25 -0
- plexflow/core/genai/utils/__init__.py +0 -0
- plexflow/core/genai/utils/loader.py +5 -0
- plexflow/core/metadata/__init__.py +0 -0
- plexflow/core/metadata/auto/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_meta.py +40 -0
- plexflow/core/metadata/auto/auto_providers/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/auto/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/auto/episode.py +49 -0
- plexflow/core/metadata/auto/auto_providers/auto/item.py +55 -0
- plexflow/core/metadata/auto/auto_providers/auto/movie.py +13 -0
- plexflow/core/metadata/auto/auto_providers/auto/season.py +43 -0
- plexflow/core/metadata/auto/auto_providers/auto/show.py +26 -0
- plexflow/core/metadata/auto/auto_providers/imdb/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/imdb/movie.py +36 -0
- plexflow/core/metadata/auto/auto_providers/imdb/show.py +45 -0
- plexflow/core/metadata/auto/auto_providers/moviemeter/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/moviemeter/movie.py +40 -0
- plexflow/core/metadata/auto/auto_providers/plex/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/plex/movie.py +39 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/episode.py +30 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/movie.py +36 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/season.py +23 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/show.py +41 -0
- plexflow/core/metadata/auto/auto_providers/tmdb.py +92 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/episode.py +28 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/movie.py +36 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/season.py +25 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/show.py +41 -0
- plexflow/core/metadata/providers/__init__.py +0 -0
- plexflow/core/metadata/providers/imdb/__init__.py +0 -0
- plexflow/core/metadata/providers/imdb/datatypes.py +53 -0
- plexflow/core/metadata/providers/imdb/imdb.py +112 -0
- plexflow/core/metadata/providers/moviemeter/__init__.py +0 -0
- plexflow/core/metadata/providers/moviemeter/datatypes.py +111 -0
- plexflow/core/metadata/providers/moviemeter/moviemeter.py +42 -0
- plexflow/core/metadata/providers/plex/__init__.py +0 -0
- plexflow/core/metadata/providers/plex/datatypes.py +693 -0
- plexflow/core/metadata/providers/plex/plex.py +167 -0
- plexflow/core/metadata/providers/tmdb/__init__.py +0 -0
- plexflow/core/metadata/providers/tmdb/datatypes.py +460 -0
- plexflow/core/metadata/providers/tmdb/tmdb.py +85 -0
- plexflow/core/metadata/providers/tvdb/__init__.py +0 -0
- plexflow/core/metadata/providers/tvdb/datatypes.py +257 -0
- plexflow/core/metadata/providers/tvdb/tv_datatypes.py +554 -0
- plexflow/core/metadata/providers/tvdb/tvdb.py +65 -0
- plexflow/core/metadata/providers/universal/__init__.py +0 -0
- plexflow/core/metadata/providers/universal/movie.py +130 -0
- plexflow/core/metadata/providers/universal/old.py +192 -0
- plexflow/core/metadata/providers/universal/show.py +107 -0
- plexflow/core/plex/__init__.py +0 -0
- plexflow/core/plex/api/context/authorized.py +15 -0
- plexflow/core/plex/api/context/discover.py +14 -0
- plexflow/core/plex/api/context/library.py +14 -0
- plexflow/core/plex/discover/__init__.py +0 -0
- plexflow/core/plex/discover/activity.py +448 -0
- plexflow/core/plex/discover/comment.py +89 -0
- plexflow/core/plex/discover/feed.py +11 -0
- plexflow/core/plex/hooks/__init__.py +0 -0
- plexflow/core/plex/hooks/plex_authorized.py +60 -0
- plexflow/core/plex/hooks/plexflow_database.py +6 -0
- plexflow/core/plex/library/__init__.py +0 -0
- plexflow/core/plex/library/library.py +103 -0
- plexflow/core/plex/token/__init__.py +0 -0
- plexflow/core/plex/token/auto_token.py +91 -0
- plexflow/core/plex/utils/__init__.py +0 -0
- plexflow/core/plex/utils/paginated.py +39 -0
- plexflow/core/plex/watchlist/__init__.py +0 -0
- plexflow/core/plex/watchlist/datatypes.py +124 -0
- plexflow/core/plex/watchlist/watchlist.py +23 -0
- plexflow/core/storage/__init__.py +0 -0
- plexflow/core/storage/object/__init__.py +0 -0
- plexflow/core/storage/object/plexflow_storage.py +143 -0
- plexflow/core/storage/object/redis_storage.py +169 -0
- plexflow/core/subtitles/__init__.py +0 -0
- plexflow/core/subtitles/providers/__init__.py +0 -0
- plexflow/core/subtitles/providers/auto_subtitles.py +48 -0
- plexflow/core/subtitles/providers/oss/__init__.py +0 -0
- plexflow/core/subtitles/providers/oss/datatypes.py +104 -0
- plexflow/core/subtitles/providers/oss/download.py +48 -0
- plexflow/core/subtitles/providers/oss/old.py +144 -0
- plexflow/core/subtitles/providers/oss/oss.py +400 -0
- plexflow/core/subtitles/providers/oss/oss_subtitle.py +32 -0
- plexflow/core/subtitles/providers/oss/search.py +52 -0
- plexflow/core/subtitles/providers/oss/unlimited_oss.py +231 -0
- plexflow/core/subtitles/providers/oss/utils/__init__.py +0 -0
- plexflow/core/subtitles/providers/oss/utils/config.py +63 -0
- plexflow/core/subtitles/providers/oss/utils/download_client.py +22 -0
- plexflow/core/subtitles/providers/oss/utils/exceptions.py +35 -0
- plexflow/core/subtitles/providers/oss/utils/file_utils.py +83 -0
- plexflow/core/subtitles/providers/oss/utils/languages.py +78 -0
- plexflow/core/subtitles/providers/oss/utils/response_base.py +221 -0
- plexflow/core/subtitles/providers/oss/utils/responses.py +176 -0
- plexflow/core/subtitles/providers/oss/utils/srt.py +561 -0
- plexflow/core/subtitles/results/__init__.py +0 -0
- plexflow/core/subtitles/results/subtitle.py +170 -0
- plexflow/core/torrents/__init__.py +0 -0
- plexflow/core/torrents/analyzers/analyzed_torrent.py +143 -0
- plexflow/core/torrents/analyzers/analyzer.py +45 -0
- plexflow/core/torrents/analyzers/torrentquest/analyzer.py +47 -0
- plexflow/core/torrents/auto/auto_providers/auto/__init__.py +0 -0
- plexflow/core/torrents/auto/auto_providers/auto/torrent.py +64 -0
- plexflow/core/torrents/auto/auto_providers/tpb/torrent.py +62 -0
- plexflow/core/torrents/auto/auto_torrents.py +29 -0
- plexflow/core/torrents/providers/__init__.py +0 -0
- plexflow/core/torrents/providers/ext/__init__.py +0 -0
- plexflow/core/torrents/providers/ext/ext.py +18 -0
- plexflow/core/torrents/providers/ext/utils.py +64 -0
- plexflow/core/torrents/providers/extratorrent/__init__.py +0 -0
- plexflow/core/torrents/providers/extratorrent/extratorrent.py +21 -0
- plexflow/core/torrents/providers/extratorrent/utils.py +66 -0
- plexflow/core/torrents/providers/eztv/__init__.py +0 -0
- plexflow/core/torrents/providers/eztv/eztv.py +47 -0
- plexflow/core/torrents/providers/eztv/utils.py +83 -0
- plexflow/core/torrents/providers/rarbg2/__init__.py +0 -0
- plexflow/core/torrents/providers/rarbg2/rarbg2.py +19 -0
- plexflow/core/torrents/providers/rarbg2/utils.py +76 -0
- plexflow/core/torrents/providers/snowfl/__init__.py +0 -0
- plexflow/core/torrents/providers/snowfl/snowfl.py +36 -0
- plexflow/core/torrents/providers/snowfl/utils.py +59 -0
- plexflow/core/torrents/providers/tgx/__init__.py +0 -0
- plexflow/core/torrents/providers/tgx/context.py +50 -0
- plexflow/core/torrents/providers/tgx/dump.py +40 -0
- plexflow/core/torrents/providers/tgx/tgx.py +22 -0
- plexflow/core/torrents/providers/tgx/utils.py +61 -0
- plexflow/core/torrents/providers/therarbg/__init__.py +0 -0
- plexflow/core/torrents/providers/therarbg/therarbg.py +17 -0
- plexflow/core/torrents/providers/therarbg/utils.py +61 -0
- plexflow/core/torrents/providers/torrentquest/__init__.py +0 -0
- plexflow/core/torrents/providers/torrentquest/torrentquest.py +20 -0
- plexflow/core/torrents/providers/torrentquest/utils.py +70 -0
- plexflow/core/torrents/providers/tpb/__init__.py +0 -0
- plexflow/core/torrents/providers/tpb/tpb.py +17 -0
- plexflow/core/torrents/providers/tpb/utils.py +139 -0
- plexflow/core/torrents/providers/yts/__init__.py +0 -0
- plexflow/core/torrents/providers/yts/utils.py +57 -0
- plexflow/core/torrents/providers/yts/yts.py +31 -0
- plexflow/core/torrents/results/__init__.py +0 -0
- plexflow/core/torrents/results/torrent.py +165 -0
- plexflow/core/torrents/results/universal.py +220 -0
- plexflow/core/torrents/results/utils.py +15 -0
- plexflow/events/__init__.py +0 -0
- plexflow/events/download/__init__.py +0 -0
- plexflow/events/download/torrent_events.py +96 -0
- plexflow/events/publish/__init__.py +0 -0
- plexflow/events/publish/publish.py +34 -0
- plexflow/logging/__init__.py +0 -0
- plexflow/logging/log_setup.py +8 -0
- plexflow/spiders/quiet_logger.py +9 -0
- plexflow/spiders/tgx/pipelines/dump_json_pipeline.py +30 -0
- plexflow/spiders/tgx/pipelines/meta_pipeline.py +13 -0
- plexflow/spiders/tgx/pipelines/publish_pipeline.py +14 -0
- plexflow/spiders/tgx/pipelines/torrent_info_pipeline.py +12 -0
- plexflow/spiders/tgx/pipelines/validation_pipeline.py +17 -0
- plexflow/spiders/tgx/settings.py +36 -0
- plexflow/spiders/tgx/spider.py +72 -0
- plexflow/utils/__init__.py +0 -0
- plexflow/utils/antibot/human_like_requests.py +122 -0
- plexflow/utils/api/__init__.py +0 -0
- plexflow/utils/api/context/http.py +62 -0
- plexflow/utils/api/rest/__init__.py +0 -0
- plexflow/utils/api/rest/antibot_restful.py +68 -0
- plexflow/utils/api/rest/restful.py +49 -0
- plexflow/utils/captcha/__init__.py +0 -0
- plexflow/utils/captcha/bypass/__init__.py +0 -0
- plexflow/utils/captcha/bypass/decode_audio.py +34 -0
- plexflow/utils/download/__init__.py +0 -0
- plexflow/utils/download/gz.py +26 -0
- plexflow/utils/filesystem/__init__.py +0 -0
- plexflow/utils/filesystem/search.py +129 -0
- plexflow/utils/gmail/__init__.py +0 -0
- plexflow/utils/gmail/mails.py +116 -0
- plexflow/utils/hooks/__init__.py +0 -0
- plexflow/utils/hooks/http.py +84 -0
- plexflow/utils/hooks/postgresql.py +93 -0
- plexflow/utils/hooks/redis.py +112 -0
- plexflow/utils/image/storage.py +36 -0
- plexflow/utils/imdb/__init__.py +0 -0
- plexflow/utils/imdb/imdb_codes.py +107 -0
- plexflow/utils/pubsub/consume.py +82 -0
- plexflow/utils/pubsub/produce.py +25 -0
- plexflow/utils/retry/__init__.py +0 -0
- plexflow/utils/retry/utils.py +38 -0
- plexflow/utils/strings/__init__.py +0 -0
- plexflow/utils/strings/filesize.py +55 -0
- plexflow/utils/strings/language.py +14 -0
- plexflow/utils/subtitle/search.py +76 -0
- plexflow/utils/tasks/decorators.py +78 -0
- plexflow/utils/tasks/k8s/task.py +70 -0
- plexflow/utils/thread_safe/safe_list.py +54 -0
- plexflow/utils/thread_safe/safe_set.py +69 -0
- plexflow/utils/torrent/__init__.py +0 -0
- plexflow/utils/torrent/analyze.py +118 -0
- plexflow/utils/torrent/extract/common.py +37 -0
- plexflow/utils/torrent/extract/ext.py +2391 -0
- plexflow/utils/torrent/extract/extratorrent.py +56 -0
- plexflow/utils/torrent/extract/kat.py +1581 -0
- plexflow/utils/torrent/extract/tgx.py +96 -0
- plexflow/utils/torrent/extract/therarbg.py +170 -0
- plexflow/utils/torrent/extract/torrentquest.py +171 -0
- plexflow/utils/torrent/files.py +36 -0
- plexflow/utils/torrent/hash.py +90 -0
- plexflow/utils/transcribe/__init__.py +0 -0
- plexflow/utils/transcribe/speech2text.py +40 -0
- plexflow/utils/video/__init__.py +0 -0
- plexflow/utils/video/subtitle.py +73 -0
- plexflow-0.0.64.dist-info/METADATA +71 -0
- plexflow-0.0.64.dist-info/RECORD +256 -0
- plexflow-0.0.64.dist-info/WHEEL +4 -0
- 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
|
plexflow/core/env/env.py
ADDED
@@ -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
|
File without changes
|
File without changes
|