Unit3DwebUp 0.0.14__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.
- config/__init__.py +4 -0
- config/api_data.py +28 -0
- config/constants.py +34 -0
- config/host_data.py +47 -0
- config/itt.py +154 -0
- config/logger.py +37 -0
- config/settings.py +212 -0
- config/sis.py +137 -0
- config/tags.py +395 -0
- config/trackers.py +47 -0
- external/__init__.py +0 -0
- external/async_http_client_service.py +48 -0
- external/websocket.py +41 -0
- models/__init__.py +0 -0
- models/interfaces.py +39 -0
- models/keywords.py +13 -0
- models/media.py +597 -0
- models/media_info.py +142 -0
- models/movie.py +287 -0
- models/tv.py +266 -0
- models/tvdb_search.py +102 -0
- models/videos.py +26 -0
- repositories/__init__.py +0 -0
- repositories/db_online.py +82 -0
- repositories/interfaces.py +59 -0
- repositories/job_repos.py +166 -0
- repositories/media_info_factory.py +28 -0
- services/__init__.py +0 -0
- services/auto_async_service.py +237 -0
- services/create_torrent_service.py +94 -0
- services/interfaces.py +72 -0
- services/itt_tracker_helper.py +463 -0
- services/itt_tracker_service.py +85 -0
- services/lifespan_service.py +58 -0
- services/media_service.py +114 -0
- services/tags_service.py +389 -0
- services/tmdb.py +246 -0
- services/torrent_client_service.py +92 -0
- services/torrent_service.py +107 -0
- services/tvdb.py +65 -0
- services/utility.py +433 -0
- services/video_service.py +356 -0
- unit3dwebup-0.0.14.dist-info/METADATA +191 -0
- unit3dwebup-0.0.14.dist-info/RECORD +53 -0
- unit3dwebup-0.0.14.dist-info/WHEEL +5 -0
- unit3dwebup-0.0.14.dist-info/entry_points.txt +2 -0
- unit3dwebup-0.0.14.dist-info/top_level.txt +6 -0
- use_case/__init__.py +0 -0
- use_case/make_torrent_usecase.py +67 -0
- use_case/process_all_usecase.py +43 -0
- use_case/scan_media_usecase.py +133 -0
- use_case/seed_usecase.py +117 -0
- use_case/upload_usecase.py +77 -0
models/media_info.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
# Based on the old code unit3dup 0.8.21
|
|
5
|
+
import re, os
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Optional, List, Dict, Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class MediaFile:
|
|
12
|
+
"""
|
|
13
|
+
Try to create a dataclass for the MediaInfo result
|
|
14
|
+
Many of these data need to be sent to the tracker
|
|
15
|
+
"""
|
|
16
|
+
file_path: str
|
|
17
|
+
media_to_string: str | None = None
|
|
18
|
+
video_tracks: List[Dict] = field(default_factory=list)
|
|
19
|
+
audio_tracks: List[Dict] = field(default_factory=list)
|
|
20
|
+
general_track: Dict = field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def media_description(self) -> str:
|
|
24
|
+
return self.media_to_string
|
|
25
|
+
|
|
26
|
+
# --------- VIDEO ---------
|
|
27
|
+
@property
|
|
28
|
+
def codec_id(self) -> str:
|
|
29
|
+
return self.video_tracks[0].get("codec_id", "Unknown") if self.video_tracks else "Unknown"
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def video_width(self) -> str:
|
|
33
|
+
return self.video_tracks[0].get("width", "Unknown") if self.video_tracks else "Unknown"
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def video_height(self) -> Optional[str]:
|
|
37
|
+
return self.video_tracks[0].get("height") if self.video_tracks else None
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def video_scan_type(self) -> Optional[str]:
|
|
41
|
+
return self.video_tracks[0].get("scan_type") if self.video_tracks else None
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def video_aspect_ratio(self) -> str:
|
|
45
|
+
return self.video_tracks[0].get("display_aspect_ratio", "Unknown") if self.video_tracks else "Unknown"
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def video_frame_rate(self) -> str:
|
|
49
|
+
return self.video_tracks[0].get("frame_rate", "Unknown") if self.video_tracks else "Unknown"
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def video_bit_depth(self) -> str:
|
|
53
|
+
return self.video_tracks[0].get("bit_depth", "Unknown") if self.video_tracks else "Unknown"
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def video_format(self) -> str:
|
|
57
|
+
return self.video_tracks[0].get("format", "Unknown") if self.video_tracks else "Unknown"
|
|
58
|
+
|
|
59
|
+
# --------- AUDIO ---------
|
|
60
|
+
@property
|
|
61
|
+
def audio_codec_id(self) -> str:
|
|
62
|
+
return self.audio_tracks[0].get("codec_id", "Unknown") if self.audio_tracks else "Unknown"
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def audio_bit_rate(self) -> str:
|
|
66
|
+
return self.audio_tracks[0].get("bit_rate", "Unknown") if self.audio_tracks else "Unknown"
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def audio_channels(self) -> str:
|
|
70
|
+
return self.audio_tracks[0].get("channels", "Unknown") if self.audio_tracks else "Unknown"
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def audio_sampling_rate(self) -> str:
|
|
74
|
+
return self.audio_tracks[0].get("sampling_rate", "Unknown") if self.audio_tracks else "Unknown"
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def audio_format(self) -> str:
|
|
78
|
+
return self.audio_tracks[0].get("format", "Unknown") if self.audio_tracks else "Unknown"
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def audio_language(self) -> str:
|
|
82
|
+
return self.audio_tracks[0].get("language", "Unknown") if self.audio_tracks else "Unknown"
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def subtitle_tracks(self) -> List[Dict]:
|
|
86
|
+
return [t for t in self.video_tracks if t.get("track_type") == "Text"]
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def available_languages(self) -> list[Any | None] | list[str]:
|
|
90
|
+
langs = {
|
|
91
|
+
t.get("language")
|
|
92
|
+
for t in (self.audio_tracks + self.subtitle_tracks)
|
|
93
|
+
if t.get("language")
|
|
94
|
+
}
|
|
95
|
+
return list(langs) or ["not found"]
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def file_size(self) -> str:
|
|
99
|
+
return self.general_track.get("file_size", "Unknown")
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def is_interlaced(self) -> Optional[int]:
|
|
103
|
+
encoding = self.video_tracks[0].get("encoding_settings") if self.video_tracks else None
|
|
104
|
+
if not encoding:
|
|
105
|
+
return None
|
|
106
|
+
match = re.search(r"interlaced=(\d)", encoding)
|
|
107
|
+
return int(match.group(1)) if match else None
|
|
108
|
+
|
|
109
|
+
def generate_release_name(self, guess_title: str, resolution: str) -> Optional[str]:
|
|
110
|
+
if not self.video_tracks:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
video_format = self.video_tracks[0].get("format", "")
|
|
114
|
+
audio_format = self.audio_tracks[0].get("format", "")
|
|
115
|
+
_, ext = os.path.splitext(self.file_path)
|
|
116
|
+
|
|
117
|
+
return f"{guess_title}.web-dl.{video_format}.{resolution}.{audio_format}{ext}".lower()
|
|
118
|
+
|
|
119
|
+
def to_dict(self) -> Dict:
|
|
120
|
+
# Converto PosixPath to str
|
|
121
|
+
return {
|
|
122
|
+
"file_path": str(self.file_path),
|
|
123
|
+
"media_to_string": self.media_to_string,
|
|
124
|
+
"video_tracks": self.video_tracks,
|
|
125
|
+
"audio_tracks": self.audio_tracks,
|
|
126
|
+
"general_track": self.general_track,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
# non son più necessarie le virgolette per le classi non ancora completamente costruite ad esempio in "MediaFile"
|
|
131
|
+
# basta importare annotations
|
|
132
|
+
def dict_to_mediafile(cls, data: Dict) -> MediaFile:
|
|
133
|
+
"""
|
|
134
|
+
From dict to Mediafile
|
|
135
|
+
"""
|
|
136
|
+
return cls(
|
|
137
|
+
file_path=data.get("file_path", ""),
|
|
138
|
+
media_to_string=data.get("media_to_string"),
|
|
139
|
+
video_tracks=data.get("video_tracks", []),
|
|
140
|
+
audio_tracks=data.get("audio_tracks", []),
|
|
141
|
+
general_track=data.get("general_track", {}),
|
|
142
|
+
)
|
models/movie.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
# Same as the old code unit3dup 0.8.21. Add annotations
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from config.logger import get_logger
|
|
10
|
+
from models.interfaces import MediaRepoInterface
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(slots=True)
|
|
14
|
+
class Title:
|
|
15
|
+
"""
|
|
16
|
+
Dataclass for manage result from a TMDB search
|
|
17
|
+
"""
|
|
18
|
+
iso_3166_1: str
|
|
19
|
+
title: str
|
|
20
|
+
type: str | None = None
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def from_data(data: dict[str, any]) -> Title | None:
|
|
24
|
+
try:
|
|
25
|
+
return Title(
|
|
26
|
+
iso_3166_1=data["iso_3166_1"],
|
|
27
|
+
title=data["title"],
|
|
28
|
+
type=data.get("type"),
|
|
29
|
+
)
|
|
30
|
+
except KeyError as e:
|
|
31
|
+
print(f"Missing key in Title data: {e}")
|
|
32
|
+
return None
|
|
33
|
+
except TypeError as e:
|
|
34
|
+
print(f"Type error in Title data: {e}")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(slots=True)
|
|
39
|
+
class AltTitle:
|
|
40
|
+
id: int
|
|
41
|
+
titles: list[Title]
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def validate(cls, json_str: str) -> AltTitle:
|
|
45
|
+
data = json.loads(json_str)
|
|
46
|
+
id_ = data.get("id", 0)
|
|
47
|
+
titles_data = data.get("titles", [])
|
|
48
|
+
titles = [
|
|
49
|
+
Title.from_data(title_data)
|
|
50
|
+
for title_data in titles_data
|
|
51
|
+
if Title.from_data(title_data) is not None
|
|
52
|
+
]
|
|
53
|
+
return cls(id=id_, titles=titles)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(slots=True)
|
|
57
|
+
class Genre:
|
|
58
|
+
id: int
|
|
59
|
+
name: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(slots=True)
|
|
63
|
+
class ProductionCompany:
|
|
64
|
+
id: int
|
|
65
|
+
logo_path: str | None
|
|
66
|
+
name: str
|
|
67
|
+
origin_country: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(slots=True)
|
|
71
|
+
class ProductionCountry:
|
|
72
|
+
iso_3166_1: str
|
|
73
|
+
name: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(slots=True)
|
|
77
|
+
class SpokenLanguage:
|
|
78
|
+
english_name: str
|
|
79
|
+
iso_639_1: str
|
|
80
|
+
name: str
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(slots=True)
|
|
84
|
+
class MovieDetails(MediaRepoInterface):
|
|
85
|
+
adult: bool
|
|
86
|
+
backdrop_path: str | None
|
|
87
|
+
belongs_to_collection: str | None
|
|
88
|
+
budget: int
|
|
89
|
+
genres: list[Genre]
|
|
90
|
+
homepage: str
|
|
91
|
+
id: int
|
|
92
|
+
imdb_id: str
|
|
93
|
+
origin_country: list[str]
|
|
94
|
+
original_language: str
|
|
95
|
+
original_title: str
|
|
96
|
+
overview: str
|
|
97
|
+
popularity: float
|
|
98
|
+
poster_path: str | None
|
|
99
|
+
production_companies: list[ProductionCompany]
|
|
100
|
+
production_countries: list[ProductionCountry]
|
|
101
|
+
release_date: str
|
|
102
|
+
revenue: int
|
|
103
|
+
runtime: int
|
|
104
|
+
spoken_languages: list[SpokenLanguage]
|
|
105
|
+
status: str
|
|
106
|
+
tagline: str
|
|
107
|
+
title: str
|
|
108
|
+
video: bool
|
|
109
|
+
vote_average: float
|
|
110
|
+
vote_count: int
|
|
111
|
+
|
|
112
|
+
def get_title(self) -> str:
|
|
113
|
+
return self.title
|
|
114
|
+
|
|
115
|
+
def get_original(self) -> str:
|
|
116
|
+
return self.original_title
|
|
117
|
+
|
|
118
|
+
def get_date(self) -> str:
|
|
119
|
+
return self.release_date
|
|
120
|
+
|
|
121
|
+
def get_id(self) -> int:
|
|
122
|
+
return self.id
|
|
123
|
+
|
|
124
|
+
def get_translations(self) -> dict[str, str] | None:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
def get_imdb(self) -> str | None:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
def get_poster_path(self) -> str:
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass(slots=True)
|
|
135
|
+
class NowPlaying:
|
|
136
|
+
"""
|
|
137
|
+
Represents Nowplaying attributes
|
|
138
|
+
"""
|
|
139
|
+
adult: bool | None = None
|
|
140
|
+
backdrop_path: str | None = None
|
|
141
|
+
genre_ids: list[int] = field(default_factory=list)
|
|
142
|
+
id: int | None = None
|
|
143
|
+
original_language: str | None = None
|
|
144
|
+
original_title: str | None = None
|
|
145
|
+
overview: str | None = None
|
|
146
|
+
popularity: float | None = None
|
|
147
|
+
poster_path: str | None = None
|
|
148
|
+
release_date: str | None = None
|
|
149
|
+
title: str | None = None
|
|
150
|
+
video: bool | None = None
|
|
151
|
+
vote_average: float | None = None
|
|
152
|
+
vote_count: int | None = None
|
|
153
|
+
|
|
154
|
+
def __repr__(self):
|
|
155
|
+
"""Returns a string """
|
|
156
|
+
return f"<Movie title={self.title} id={self.id}>"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass(slots=True)
|
|
160
|
+
class NowPlayingByCountry(NowPlaying):
|
|
161
|
+
"""
|
|
162
|
+
Represents a combined movie object NowPlayIng by Country code
|
|
163
|
+
"""
|
|
164
|
+
iso_3166_1: str | None = None
|
|
165
|
+
release_dates: list[dict[str, str]] = field(default_factory=list)
|
|
166
|
+
|
|
167
|
+
def __post_init__(self):
|
|
168
|
+
"""Validate data """
|
|
169
|
+
if self.iso_3166_1 and (len(self.iso_3166_1) != 2 or not self.iso_3166_1.isalpha()):
|
|
170
|
+
self.iso_3166_1 = None
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def from_data(now_playing: NowPlaying, release_info: MovieReleaseInfo) -> NowPlayingByCountry:
|
|
174
|
+
"""
|
|
175
|
+
Creates a NowPlayingByCountry instance from NowPlaying and MovieReleaseInfo instances
|
|
176
|
+
"""
|
|
177
|
+
return NowPlayingByCountry(
|
|
178
|
+
adult=now_playing.adult,
|
|
179
|
+
backdrop_path=now_playing.backdrop_path,
|
|
180
|
+
genre_ids=now_playing.genre_ids,
|
|
181
|
+
id=now_playing.id,
|
|
182
|
+
original_language=now_playing.original_language,
|
|
183
|
+
original_title=now_playing.original_title,
|
|
184
|
+
overview=now_playing.overview,
|
|
185
|
+
popularity=now_playing.popularity,
|
|
186
|
+
poster_path=now_playing.poster_path,
|
|
187
|
+
release_date=now_playing.release_date,
|
|
188
|
+
title=now_playing.title,
|
|
189
|
+
video=now_playing.video,
|
|
190
|
+
vote_average=now_playing.vote_average,
|
|
191
|
+
vote_count=now_playing.vote_count,
|
|
192
|
+
iso_3166_1=release_info.iso_3166_1,
|
|
193
|
+
release_dates=release_info.release_dates,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass(slots=True)
|
|
198
|
+
class Movie(MediaRepoInterface):
|
|
199
|
+
"""
|
|
200
|
+
A movie object for the search endpoint
|
|
201
|
+
"""
|
|
202
|
+
adult: bool = False
|
|
203
|
+
backdrop_path: str = ''
|
|
204
|
+
genre_ids: list[int] = field(default_factory=list)
|
|
205
|
+
id: int = 0
|
|
206
|
+
original_language: str = ''
|
|
207
|
+
original_title: str = ''
|
|
208
|
+
overview: str = ''
|
|
209
|
+
popularity: float = 0.0
|
|
210
|
+
poster_path: str = ''
|
|
211
|
+
release_date: str = ''
|
|
212
|
+
title: str = ''
|
|
213
|
+
video: bool = False
|
|
214
|
+
vote_average: float = 0.0
|
|
215
|
+
vote_count: int = 0
|
|
216
|
+
softcore: str | None = None
|
|
217
|
+
|
|
218
|
+
def get_title(self) -> str:
|
|
219
|
+
return self.title
|
|
220
|
+
|
|
221
|
+
def get_original(self) -> str:
|
|
222
|
+
return self.original_title
|
|
223
|
+
|
|
224
|
+
def get_date(self) -> str:
|
|
225
|
+
return self.release_date
|
|
226
|
+
|
|
227
|
+
def get_id(self) -> int:
|
|
228
|
+
return self.id
|
|
229
|
+
|
|
230
|
+
def get_poster_path(self) -> str:
|
|
231
|
+
return self.poster_path
|
|
232
|
+
|
|
233
|
+
def get_imdb(self) -> str | None:
|
|
234
|
+
# Get remote ids (IMDB) only from tvdb
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
def get_translations(self) -> list[str] | None:
|
|
238
|
+
# Get translations list only from tvdb
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@dataclass(slots=True)
|
|
243
|
+
class MovieReleaseInfo:
|
|
244
|
+
"""
|
|
245
|
+
Represents release information for a movie in a specific country.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
iso_3166_1: str | None = None
|
|
249
|
+
release_dates: list[dict[str, Any]] = field(default_factory=list)
|
|
250
|
+
|
|
251
|
+
def __repr__(self) -> str:
|
|
252
|
+
"""
|
|
253
|
+
Returns the MovieReleaseInfo string
|
|
254
|
+
"""
|
|
255
|
+
return f"<ReleaseInfo iso_3166_1={self.iso_3166_1}, release_dates={self.release_dates}>"
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def validate(cls, data: dict) -> MovieReleaseInfo | None:
|
|
259
|
+
"""
|
|
260
|
+
Validates the data; return None if it's invalid
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
iso_3166_1 = data.get("iso_3166_1")
|
|
264
|
+
release_dates = data.get("release_dates", {})
|
|
265
|
+
logger = get_logger(cls.__name__)
|
|
266
|
+
|
|
267
|
+
# Validate country code
|
|
268
|
+
if iso_3166_1 is not None:
|
|
269
|
+
if (
|
|
270
|
+
not isinstance(iso_3166_1, str)
|
|
271
|
+
or len(iso_3166_1) != 2
|
|
272
|
+
or not iso_3166_1.isupper()
|
|
273
|
+
):
|
|
274
|
+
logger.error(f"Invalid ISO 3166-1 code: {iso_3166_1}")
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
# Validate release_dates
|
|
278
|
+
if not isinstance(release_dates, list):
|
|
279
|
+
logger.error("release_dates must be a list.")
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
for item in release_dates:
|
|
283
|
+
if not isinstance(item, dict):
|
|
284
|
+
logger.error(f"Invalid item in release_dates list: {item}")
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
return cls(iso_3166_1=iso_3166_1, release_dates=release_dates)
|
models/tv.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Same as the old code unit3dup 0.8.21
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from models.interfaces import MediaRepoInterface
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class Alternative:
|
|
10
|
+
iso_3166_1: str
|
|
11
|
+
title: str
|
|
12
|
+
type: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(slots=True)
|
|
16
|
+
class DataResponse:
|
|
17
|
+
id: int
|
|
18
|
+
results: list[Alternative]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class CreatedBy:
|
|
23
|
+
credit_id: str
|
|
24
|
+
gender: int
|
|
25
|
+
id: int
|
|
26
|
+
name: str
|
|
27
|
+
original_name: str
|
|
28
|
+
profile_path: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(slots=True)
|
|
32
|
+
class Genre:
|
|
33
|
+
id: int
|
|
34
|
+
name: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(slots=True)
|
|
38
|
+
class LastEpisodeToAir:
|
|
39
|
+
air_date: str
|
|
40
|
+
episode_number: int
|
|
41
|
+
episode_type: str
|
|
42
|
+
id: int
|
|
43
|
+
name: str
|
|
44
|
+
overview: str
|
|
45
|
+
production_code: str
|
|
46
|
+
runtime: int
|
|
47
|
+
season_number: int
|
|
48
|
+
show_id: int
|
|
49
|
+
vote_average: float
|
|
50
|
+
vote_count: int
|
|
51
|
+
still_path: str | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(slots=True)
|
|
55
|
+
class Network:
|
|
56
|
+
id: int
|
|
57
|
+
logo_path: str
|
|
58
|
+
name: str
|
|
59
|
+
origin_country: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(slots=True)
|
|
63
|
+
class ProductionCompany:
|
|
64
|
+
id: int
|
|
65
|
+
name: str
|
|
66
|
+
origin_country: str
|
|
67
|
+
logo_path: str | None = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(slots=True)
|
|
71
|
+
class ProductionCountry:
|
|
72
|
+
iso_3166_1: str
|
|
73
|
+
name: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(slots=True)
|
|
77
|
+
class Season:
|
|
78
|
+
episode_count: int
|
|
79
|
+
id: int
|
|
80
|
+
name: str
|
|
81
|
+
overview: str
|
|
82
|
+
season_number: int
|
|
83
|
+
vote_average: float
|
|
84
|
+
air_date: str | None = None
|
|
85
|
+
poster_path: str | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(slots=True)
|
|
89
|
+
class SpokenLanguage:
|
|
90
|
+
english_name: str
|
|
91
|
+
iso_639_1: str
|
|
92
|
+
name: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(slots=True)
|
|
96
|
+
class TVShowDetails(MediaRepoInterface):
|
|
97
|
+
|
|
98
|
+
adult: bool
|
|
99
|
+
first_air_date: str
|
|
100
|
+
homepage: str
|
|
101
|
+
id: int
|
|
102
|
+
in_production: bool
|
|
103
|
+
last_air_date: str
|
|
104
|
+
last_episode_to_air: LastEpisodeToAir
|
|
105
|
+
name: str
|
|
106
|
+
number_of_episodes: int
|
|
107
|
+
number_of_seasons: int
|
|
108
|
+
original_language: str
|
|
109
|
+
original_name: str
|
|
110
|
+
overview: str
|
|
111
|
+
popularity: float
|
|
112
|
+
poster_path: str
|
|
113
|
+
status: str
|
|
114
|
+
tagline: str
|
|
115
|
+
type: str
|
|
116
|
+
vote_average: float
|
|
117
|
+
vote_count: int
|
|
118
|
+
languages: list[str] = field(default_factory=list)
|
|
119
|
+
genres: list[Genre] = field(default_factory=list)
|
|
120
|
+
backdrop_path: str | None = None
|
|
121
|
+
created_by: list[CreatedBy] = field(default_factory=list)
|
|
122
|
+
episode_run_time: list[int] = field(default_factory=list)
|
|
123
|
+
networks: list[Network] = field(default_factory=list)
|
|
124
|
+
next_episode_to_air: LastEpisodeToAir | None = None
|
|
125
|
+
production_companies: list[ProductionCompany] = field(default_factory=list)
|
|
126
|
+
production_countries: list[ProductionCountry] = field(default_factory=list)
|
|
127
|
+
seasons: list[Season] = field(default_factory=list)
|
|
128
|
+
spoken_languages: list[SpokenLanguage] = field(default_factory=list)
|
|
129
|
+
origin_country: list[str] = field(default_factory=list)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_title(self) -> str:
|
|
133
|
+
return self.name
|
|
134
|
+
|
|
135
|
+
def get_original(self) -> str:
|
|
136
|
+
return self.original_name
|
|
137
|
+
|
|
138
|
+
def get_date(self) -> str:
|
|
139
|
+
return self.first_air_date
|
|
140
|
+
|
|
141
|
+
def get_id(self) -> int:
|
|
142
|
+
return self.id
|
|
143
|
+
|
|
144
|
+
def get_translations(self) -> dict[str, str] | None:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
def get_imdb(self) -> str | None:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
def get_poster_path(self) -> str:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass(slots=True)
|
|
155
|
+
class OnTheAir:
|
|
156
|
+
adult: bool | None = None
|
|
157
|
+
backdrop_path: str | None = None
|
|
158
|
+
genre_ids: list[int] | None = None
|
|
159
|
+
id: int | None = None
|
|
160
|
+
original_language: str | None = None
|
|
161
|
+
original_title: str | None = None
|
|
162
|
+
overview: str | None = None
|
|
163
|
+
popularity: float | None = None
|
|
164
|
+
poster_path: str | None = None
|
|
165
|
+
release_date: str | None = None
|
|
166
|
+
title: str | None = None
|
|
167
|
+
video: bool | None = None
|
|
168
|
+
vote_average: float | None = None
|
|
169
|
+
vote_count: int | None = None
|
|
170
|
+
origin_country: list[str] | None = None
|
|
171
|
+
original_name: str | None = None
|
|
172
|
+
first_air_date: str | None = None
|
|
173
|
+
name: str | None = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass(slots=True)
|
|
177
|
+
class Translation:
|
|
178
|
+
"""
|
|
179
|
+
Represents a TV show translation
|
|
180
|
+
"""
|
|
181
|
+
iso_639_1: str
|
|
182
|
+
"""
|
|
183
|
+
Language code of the translation
|
|
184
|
+
"""
|
|
185
|
+
english_name: str
|
|
186
|
+
"""
|
|
187
|
+
English name of the language
|
|
188
|
+
"""
|
|
189
|
+
name: str
|
|
190
|
+
"""
|
|
191
|
+
Name of the translation
|
|
192
|
+
"""
|
|
193
|
+
url: str | None
|
|
194
|
+
"""
|
|
195
|
+
Optional URL associated with the translation
|
|
196
|
+
"""
|
|
197
|
+
tagline: str | None
|
|
198
|
+
"""
|
|
199
|
+
Optional tagline for the translation
|
|
200
|
+
"""
|
|
201
|
+
description: str | None
|
|
202
|
+
"""
|
|
203
|
+
Optional description for the translation
|
|
204
|
+
"""
|
|
205
|
+
country: str | None
|
|
206
|
+
"""
|
|
207
|
+
Optional country code related to the translation
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass(slots=True)
|
|
212
|
+
class TranslationsResponse:
|
|
213
|
+
"""
|
|
214
|
+
Contains a list of TV show translations
|
|
215
|
+
"""
|
|
216
|
+
translations: list[Translation]
|
|
217
|
+
"""
|
|
218
|
+
List of Translation objects
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(slots=True)
|
|
223
|
+
class TvShow(MediaRepoInterface):
|
|
224
|
+
"""
|
|
225
|
+
Dataclass for manage result from a TMDB search
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
id: int
|
|
229
|
+
name: str
|
|
230
|
+
first_air_date: str
|
|
231
|
+
overview: str
|
|
232
|
+
popularity: float
|
|
233
|
+
vote_average: float
|
|
234
|
+
vote_count: int
|
|
235
|
+
genre_ids: list[int] = field(default_factory=list)
|
|
236
|
+
origin_country: list[str] = field(default_factory=list)
|
|
237
|
+
original_language: str = ''
|
|
238
|
+
original_name: str = ''
|
|
239
|
+
backdrop_path: str | None = None
|
|
240
|
+
poster_path: str | None = None
|
|
241
|
+
adult: bool = False
|
|
242
|
+
softcore: str | None = None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def get_title(self) -> str:
|
|
246
|
+
return self.name
|
|
247
|
+
|
|
248
|
+
def get_original(self) -> str:
|
|
249
|
+
return self.original_name
|
|
250
|
+
|
|
251
|
+
def get_date(self) -> str:
|
|
252
|
+
return self.first_air_date
|
|
253
|
+
|
|
254
|
+
def get_id(self) -> int:
|
|
255
|
+
return self.id
|
|
256
|
+
|
|
257
|
+
def get_poster_path(self) -> str:
|
|
258
|
+
return self.poster_path
|
|
259
|
+
|
|
260
|
+
def get_imdb(self) -> str | None:
|
|
261
|
+
# Get remote ids (IMDB) only from tvdb
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
def get_translations(self) -> list[str] | None:
|
|
265
|
+
# Get translations list only from tvdb
|
|
266
|
+
pass
|