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
services/utility.py
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Same as old code unit3dup 0.9.31
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import unicodedata
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
from config.tags import additions
|
|
11
|
+
from thefuzz import fuzz
|
|
12
|
+
import guessit
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ManageTitles:
|
|
16
|
+
"""
|
|
17
|
+
Manages file title operations like removing punctuation,
|
|
18
|
+
removing accented letters, and handling file types
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
marks = [
|
|
22
|
+
".", ",", ";", ":", "!", "?", '"', "(", ")", "[", "]", "{", "}", "/",
|
|
23
|
+
"\\", "&", "*", "$", "%", "#", "@", "_", "+", "|"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
iso_3166_alpha3 = [
|
|
28
|
+
"ENG", "USA", "GBR", "ITA", "DEU", "GER", "FRA", "ESP", "SPA",
|
|
29
|
+
"BRA", "JPN", "CHN", "RUS", "PER", "NOR",
|
|
30
|
+
"SWE", "DAN", "POL", "HIN", "TUR", "ARA", "KOR", "VIE", "IND"
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
iso_3166_alpha2_to_alpha3 = {
|
|
34
|
+
"EN": "ENG", "US": "USA", "GB": "GBR", "EN-US": "ENG", "EN-GB": "ENG", "EN-AU": "ENG",
|
|
35
|
+
"IT": "ITA",
|
|
36
|
+
"DE": "GER",
|
|
37
|
+
"FR": "FRA",
|
|
38
|
+
"ES": "SPA", "ES-ES": "SPA", "ES-MX": "SPA",
|
|
39
|
+
"BR": "BRA",
|
|
40
|
+
"JP": "JPN",
|
|
41
|
+
"CN": "CHN",
|
|
42
|
+
"RU": "RUS",
|
|
43
|
+
"FA": "PER", "IR": "PER",
|
|
44
|
+
"NO": "NOR",
|
|
45
|
+
"SE": "SWE",
|
|
46
|
+
"DK": "DAN",
|
|
47
|
+
"PL": "POL",
|
|
48
|
+
"HI": "HIN",
|
|
49
|
+
"TR": "TUR",
|
|
50
|
+
"AR": "ARA",
|
|
51
|
+
"KO": "KOR",
|
|
52
|
+
"VI": "VIE",
|
|
53
|
+
"ID": "IND"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
long_name = {
|
|
57
|
+
"ENGLISH": "ENG",
|
|
58
|
+
"AMERICAN": "ENG",
|
|
59
|
+
"BRITISH": "ENG",
|
|
60
|
+
"ITALIAN": "ITA",
|
|
61
|
+
"GERMAN": "GER",
|
|
62
|
+
"FRENCH": "FRA",
|
|
63
|
+
"SPANISH": "SPA",
|
|
64
|
+
"BRAZILIAN": "BRA",
|
|
65
|
+
"JAPANESE": "JPN",
|
|
66
|
+
"CHINESE": "CHN",
|
|
67
|
+
"RUSSIAN": "RUS",
|
|
68
|
+
"PERSIAN": "PER",
|
|
69
|
+
"FARSI": "PER",
|
|
70
|
+
"NORWEGIAN": "NOR",
|
|
71
|
+
"SWEDISH": "SWE",
|
|
72
|
+
"DANISH": "DAN",
|
|
73
|
+
"POLISH": "POL",
|
|
74
|
+
"HINDI": "HIN",
|
|
75
|
+
"TURKISH": "TUR",
|
|
76
|
+
"ARABIC": "ARA",
|
|
77
|
+
"KOREAN": "KOR",
|
|
78
|
+
"VIETNAMESE": "VIE",
|
|
79
|
+
"INDONESIAN": "IND"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def convert_iso(code) -> list[str] | str:
|
|
84
|
+
""" Convert iso 2 to 3 """
|
|
85
|
+
code = code.upper()
|
|
86
|
+
# if it's 'multilang'
|
|
87
|
+
if '-' in code:
|
|
88
|
+
codes = code.split('-')
|
|
89
|
+
else:
|
|
90
|
+
codes = [code]
|
|
91
|
+
|
|
92
|
+
if code in ManageTitles.long_name:
|
|
93
|
+
codes = [ManageTitles.long_name[code]]
|
|
94
|
+
|
|
95
|
+
result = []
|
|
96
|
+
for part in codes:
|
|
97
|
+
# Capture the 2 or 3 letter code followed by '-' or end string
|
|
98
|
+
match = re.match(r'([A-Za-z]{2,3})(?:-|$)', part)
|
|
99
|
+
if match:
|
|
100
|
+
iso_code = match.group(1)
|
|
101
|
+
if len(iso_code) == 2: # // alpha-2
|
|
102
|
+
return ManageTitles.iso_3166_alpha2_to_alpha3.get(code, "")
|
|
103
|
+
elif len(iso_code) == 3: # // alpha3
|
|
104
|
+
# return the same code provided it is an alpha3
|
|
105
|
+
if iso_code in ManageTitles.iso_3166_alpha3:
|
|
106
|
+
if iso_code:
|
|
107
|
+
result.append(iso_code)
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def clean(filename: str) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Removes special characters and replaces them with spaces
|
|
115
|
+
Returns a cleaned string without punctuation
|
|
116
|
+
"""
|
|
117
|
+
for punct in ManageTitles.marks:
|
|
118
|
+
filename = filename.replace(punct, " ")
|
|
119
|
+
return " ".join(filename.split())
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def remove_accent(my_string: str) -> str:
|
|
123
|
+
"""
|
|
124
|
+
Removes accented characters from a string
|
|
125
|
+
"""
|
|
126
|
+
str_tmp = unicodedata.normalize('NFD', my_string)
|
|
127
|
+
return ''.join(char for char in str_tmp if unicodedata.category(char) != 'Mn')
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def filter_ext(file: str) -> bool:
|
|
131
|
+
"""
|
|
132
|
+
Filters files by their extensions
|
|
133
|
+
Returns True if the file is a video
|
|
134
|
+
"""
|
|
135
|
+
video_ext = [
|
|
136
|
+
".mp4", ".mkv", ".avi", ".mov", ".wmv", ".flv", ".webm", ".3gp", ".ogg",
|
|
137
|
+
".mpg", ".mpeg", ".m4v", ".rm", ".rmvb", ".vob", ".ts", ".m2ts", ".divx",
|
|
138
|
+
".asf", ".swf", ".ogv", ".drc", ".m3u8", ".pdf", ".epub", ".rar"
|
|
139
|
+
]
|
|
140
|
+
return os.path.splitext(file)[1].lower() in video_ext
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def replace(subdir: str) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Replaces hyphens with dots and removes video resolutions
|
|
146
|
+
"""
|
|
147
|
+
# resolutions = ["4320", "2160", "1080", "720", "576", "480"]
|
|
148
|
+
resolutions = ["4320p", "2160p", "1080p", "720p", "576p", "480p"]
|
|
149
|
+
|
|
150
|
+
subdir = subdir.replace("-", ".")
|
|
151
|
+
for res in resolutions:
|
|
152
|
+
subdir = subdir.replace(res, " ")
|
|
153
|
+
return subdir
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def media_docu_type(file_name: str) -> str | None:
|
|
157
|
+
"""
|
|
158
|
+
Returns document type based on file extension
|
|
159
|
+
"""
|
|
160
|
+
ext = os.path.splitext(file_name)[1].lower()
|
|
161
|
+
type_ = {
|
|
162
|
+
".pdf": "edicola",
|
|
163
|
+
".epub": "edicola",
|
|
164
|
+
}
|
|
165
|
+
return type_.get(ext, None)
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def fuzzyit(str1: str, str2: str) -> int:
|
|
169
|
+
"""
|
|
170
|
+
Returns similarity score between two strings
|
|
171
|
+
"""
|
|
172
|
+
str2 = str2.lower().replace("-", "")
|
|
173
|
+
str2 = ManageTitles.clean(str2)
|
|
174
|
+
return fuzz.ratio(ManageTitles.remove_accent(str1.lower()), ManageTitles.remove_accent(str2.lower()))
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def normalize_filename(filename) -> str:
|
|
178
|
+
# Remove spaces
|
|
179
|
+
filename = filename.strip()
|
|
180
|
+
|
|
181
|
+
# Remove invalid characters
|
|
182
|
+
filename = re.sub(r'[<>:"/\\|?*]', '_', filename)
|
|
183
|
+
|
|
184
|
+
# Normalize
|
|
185
|
+
filename = unicodedata.normalize('NFD', filename).encode('ascii', 'ignore').decode('ascii')
|
|
186
|
+
|
|
187
|
+
# Replace spaces with underscores
|
|
188
|
+
filename = filename.replace(' ', '_')
|
|
189
|
+
|
|
190
|
+
# Set file name length to 100 characters
|
|
191
|
+
filename = filename[:100]
|
|
192
|
+
|
|
193
|
+
# Remove periods or spaces at the end
|
|
194
|
+
filename = filename.rstrip('. ')
|
|
195
|
+
|
|
196
|
+
return filename
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def clean_text(filename: str) -> str:
|
|
200
|
+
|
|
201
|
+
# Remove each addition from the string
|
|
202
|
+
filename_sanitized = filename
|
|
203
|
+
for addition in additions:
|
|
204
|
+
filename_sanitized = re.sub(rf"\b{addition}\b", "", filename_sanitized)
|
|
205
|
+
|
|
206
|
+
# Remove v version
|
|
207
|
+
filename_sanitized = re.sub(r"v\d+(?:[ .]\d+)*", "", filename_sanitized).strip()
|
|
208
|
+
|
|
209
|
+
# Remove dots, extra spaces
|
|
210
|
+
filename_sanitized = re.sub(r"[._]", " ", filename_sanitized)
|
|
211
|
+
|
|
212
|
+
# remove spaces, tab, newline
|
|
213
|
+
filename_sanitized = re.sub(r"\s+", " ", filename_sanitized)
|
|
214
|
+
|
|
215
|
+
# Recover tag
|
|
216
|
+
filename_sanitized = ManageTitles.recover_tag(filename_sanitized)
|
|
217
|
+
|
|
218
|
+
# remove double space
|
|
219
|
+
filename_sanitized = re.sub(r"\s+", " ", filename_sanitized).strip()
|
|
220
|
+
|
|
221
|
+
return filename_sanitized
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def recover_tag(filename_sanitized: str) -> str:
|
|
225
|
+
|
|
226
|
+
# Add the tag
|
|
227
|
+
replacements = [
|
|
228
|
+
(r'\b7 \b1\b', '7.1'),
|
|
229
|
+
(r'\b5 \b1\b', '5.1'),
|
|
230
|
+
(r'\bDDP5 \b1\b', 'DDP5.1'),
|
|
231
|
+
(r'\bDDP2 \b0\b', 'DDP2.0'),
|
|
232
|
+
(r'\bDD5 \b1\b', 'DD5.1'),
|
|
233
|
+
(r'\bDD2 \b0\b', 'DD2.0'),
|
|
234
|
+
(r'\b2 \b0\b', '2.0'),
|
|
235
|
+
(r'\bWEB \bDL\b', 'WEB-DL'),
|
|
236
|
+
(r'\bWEB \bDLMUX\b', 'WEB-DLMUX'),
|
|
237
|
+
(r'\bBD \bUNTOUCHED\b', 'BD-UNTOUCHED'),
|
|
238
|
+
(r'\bCINEMA \bMD\b', 'CINEMA-MD'),
|
|
239
|
+
(r'\bHEVC \bFHC\b', 'HEVC-FHC'),
|
|
240
|
+
(r'\bCBR \bCBZ\b', 'CBR-CBZ'),
|
|
241
|
+
(r'\bH \b264\b', 'H.264'),
|
|
242
|
+
(r'\bH \b265\b', 'H.265'),
|
|
243
|
+
(r'\bAAC2 \b0\b', 'AAC2.0'),
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
for tag, replacement in replacements:
|
|
247
|
+
filename_sanitized = re.sub(tag, replacement, filename_sanitized, flags=re.IGNORECASE)
|
|
248
|
+
return filename_sanitized
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class MyString:
|
|
252
|
+
"""
|
|
253
|
+
Handles string operations like date parsing
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def parse_date(line_str: str) -> datetime | None:
|
|
258
|
+
"""
|
|
259
|
+
Parses a string with or without time
|
|
260
|
+
Returns a datetime object if the string is valid
|
|
261
|
+
"""
|
|
262
|
+
match_time = re.search(r"(\w{3})\s+(\d{1,2})\s+(\d{2}:\d{2})", line_str)
|
|
263
|
+
match_no_time = re.search(r"(\w{3})\s+(\d{1,2})\s+(\d{4})", line_str)
|
|
264
|
+
|
|
265
|
+
if match_time:
|
|
266
|
+
# Case with time
|
|
267
|
+
month, day, time = match_time.groups()
|
|
268
|
+
year = datetime.now().year
|
|
269
|
+
return datetime.strptime(f"{month} {day} {time} {year}", "%b %d %H:%M %Y")
|
|
270
|
+
elif match_no_time:
|
|
271
|
+
# Case without time
|
|
272
|
+
month, day, year = match_no_time.groups()
|
|
273
|
+
return datetime.strptime(f"{month} {day} {year}", "%b %d %Y")
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class System:
|
|
278
|
+
"""
|
|
279
|
+
Manages system-related operations like file and folder size handling
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
# // Category neutral value before being translated into tracker values
|
|
283
|
+
DOCUMENTARY = 4
|
|
284
|
+
TV_SHOW = 2
|
|
285
|
+
MOVIE = 1
|
|
286
|
+
GAME = 3
|
|
287
|
+
|
|
288
|
+
category_list = {MOVIE: 'movie', TV_SHOW: 'series', GAME: 'game', DOCUMENTARY: 'edicola'}
|
|
289
|
+
|
|
290
|
+
RESOLUTIONS = ["8640", "4320", "2160", "1080", "720", "576", "480"]
|
|
291
|
+
RESOLUTION_labels = ["8640p", "4320p", "2160p", "1080p", "1080i", "720p", "720i", "576p", "576i", "480p", "480i"]
|
|
292
|
+
NO_RESOLUTION = 'altro'
|
|
293
|
+
|
|
294
|
+
@staticmethod
|
|
295
|
+
def get_size(folder_path: str) -> tuple[float, str]:
|
|
296
|
+
"""
|
|
297
|
+
Returns the size of a folder or file in GB or MB
|
|
298
|
+
"""
|
|
299
|
+
if os.path.isfile(folder_path):
|
|
300
|
+
total_size = os.path.getsize(folder_path)
|
|
301
|
+
else:
|
|
302
|
+
total_size = 0
|
|
303
|
+
for dir_path, _, filenames in os.walk(folder_path):
|
|
304
|
+
for file in filenames:
|
|
305
|
+
file_path = Path(dir_path) / file
|
|
306
|
+
if not os.path.islink(file_path):
|
|
307
|
+
total_size += os.path.getsize(file_path)
|
|
308
|
+
|
|
309
|
+
return (round(total_size / (1024 ** 3), 2), 'GB') if total_size > 1024 ** 3 \
|
|
310
|
+
else (round(total_size / (1024 ** 2), 2), 'MB')
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class Guessit:
|
|
314
|
+
|
|
315
|
+
def __init__(self, filename: str):
|
|
316
|
+
temp_name = ManageTitles.replace(filename)
|
|
317
|
+
self.guessit = guessit.guessit(temp_name)
|
|
318
|
+
self.filename = filename
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def guessit_title(self) -> str:
|
|
322
|
+
"""
|
|
323
|
+
Estrae la stringa con il titolo dal nome del file film_title o title(serie ?)
|
|
324
|
+
:return:
|
|
325
|
+
"""
|
|
326
|
+
#
|
|
327
|
+
# Se fallisce guessit ad esempio in questo caso :
|
|
328
|
+
# titolo : 1923 ( nessun altra informazione nel titolo)
|
|
329
|
+
# MatchesDict([('year', 1923), ('type', 'movie')])
|
|
330
|
+
# dove non trova ne title e film_title e erroneamente lo credo un movie..
|
|
331
|
+
# bypass guessit e ritorna filename alla ricerca di tmdb
|
|
332
|
+
return self.guessit.get("film_title", self.guessit.get("title", self.filename))
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def guessit_alternative(self) -> str:
|
|
336
|
+
"""
|
|
337
|
+
Estrae la stringa con il titolo dal nome del file film_title o title(serie ?)
|
|
338
|
+
:return:
|
|
339
|
+
"""
|
|
340
|
+
return self.guessit.get(
|
|
341
|
+
"alternative_title", self.guessit.get("title", self.filename)
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def guessit_year(self) -> str | None:
|
|
346
|
+
"""
|
|
347
|
+
Estrae l'anno di pubblicazione dal titolo
|
|
348
|
+
:return:
|
|
349
|
+
"""
|
|
350
|
+
return self.guessit["year"] if "year" in self.guessit else None
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def guessit_episode(self) -> str | None:
|
|
354
|
+
"""
|
|
355
|
+
Estrae il numero di episodio dal titolo
|
|
356
|
+
:return:
|
|
357
|
+
"""
|
|
358
|
+
return self.guessit["episode"] if "episode" in self.guessit else None
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def guessit_season(self) -> str | None:
|
|
362
|
+
"""
|
|
363
|
+
Estrae il numero di stagione dal titolo
|
|
364
|
+
:return:
|
|
365
|
+
"""
|
|
366
|
+
# return int(self.guessit['season']) if 'season' in self.guessit else None
|
|
367
|
+
return self.guessit["season"] if "season" in self.guessit else None
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def guessit_episode_title(self) -> str:
|
|
371
|
+
"""
|
|
372
|
+
Get the episode title
|
|
373
|
+
:return:
|
|
374
|
+
"""
|
|
375
|
+
return guessit.guessit(self.filename, {"excludes": "part"}).get("episode_title", "")
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def type(self) -> str | None:
|
|
379
|
+
"""
|
|
380
|
+
Determina se è una serie verificando la presenza di un numero di stagione
|
|
381
|
+
:return:
|
|
382
|
+
"""
|
|
383
|
+
return self.guessit["type"] if "type" in self.guessit else None
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def source(self) -> str | None:
|
|
387
|
+
"""
|
|
388
|
+
Grab the source
|
|
389
|
+
:return:
|
|
390
|
+
"""
|
|
391
|
+
return self.guessit["source"] if "source" in self.guessit else None
|
|
392
|
+
|
|
393
|
+
@property
|
|
394
|
+
def other(self) -> str | None:
|
|
395
|
+
"""
|
|
396
|
+
Grab the 'other' info
|
|
397
|
+
:return:
|
|
398
|
+
"""
|
|
399
|
+
return self.guessit["other"] if "other" in self.guessit else None
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def audio_codec(self) -> str | None:
|
|
403
|
+
"""
|
|
404
|
+
Grab the 'other' info
|
|
405
|
+
:return:
|
|
406
|
+
"""
|
|
407
|
+
return self.guessit["audio_codec"] if "audio_codec" in self.guessit else None
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def subtitle(self) -> str | None:
|
|
411
|
+
"""
|
|
412
|
+
Grab the 'other' subtitle
|
|
413
|
+
:return:
|
|
414
|
+
"""
|
|
415
|
+
return self.guessit["subtitle"] if "subtitle" in self.guessit else None
|
|
416
|
+
|
|
417
|
+
@property
|
|
418
|
+
def release_group(self) -> str | None:
|
|
419
|
+
"""
|
|
420
|
+
Grab the 'release_group'
|
|
421
|
+
:return:
|
|
422
|
+
"""
|
|
423
|
+
return (
|
|
424
|
+
self.guessit["release_group"] if "release_group" in self.guessit else None
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def screen_size(self) -> str | None:
|
|
429
|
+
"""
|
|
430
|
+
Grab the 'screen_size'
|
|
431
|
+
:return:
|
|
432
|
+
"""
|
|
433
|
+
return self.guessit["screen_size"] if "screen_size" in self.guessit else None
|