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.
Files changed (53) hide show
  1. config/__init__.py +4 -0
  2. config/api_data.py +28 -0
  3. config/constants.py +34 -0
  4. config/host_data.py +47 -0
  5. config/itt.py +154 -0
  6. config/logger.py +37 -0
  7. config/settings.py +212 -0
  8. config/sis.py +137 -0
  9. config/tags.py +395 -0
  10. config/trackers.py +47 -0
  11. external/__init__.py +0 -0
  12. external/async_http_client_service.py +48 -0
  13. external/websocket.py +41 -0
  14. models/__init__.py +0 -0
  15. models/interfaces.py +39 -0
  16. models/keywords.py +13 -0
  17. models/media.py +597 -0
  18. models/media_info.py +142 -0
  19. models/movie.py +287 -0
  20. models/tv.py +266 -0
  21. models/tvdb_search.py +102 -0
  22. models/videos.py +26 -0
  23. repositories/__init__.py +0 -0
  24. repositories/db_online.py +82 -0
  25. repositories/interfaces.py +59 -0
  26. repositories/job_repos.py +166 -0
  27. repositories/media_info_factory.py +28 -0
  28. services/__init__.py +0 -0
  29. services/auto_async_service.py +237 -0
  30. services/create_torrent_service.py +94 -0
  31. services/interfaces.py +72 -0
  32. services/itt_tracker_helper.py +463 -0
  33. services/itt_tracker_service.py +85 -0
  34. services/lifespan_service.py +58 -0
  35. services/media_service.py +114 -0
  36. services/tags_service.py +389 -0
  37. services/tmdb.py +246 -0
  38. services/torrent_client_service.py +92 -0
  39. services/torrent_service.py +107 -0
  40. services/tvdb.py +65 -0
  41. services/utility.py +433 -0
  42. services/video_service.py +356 -0
  43. unit3dwebup-0.0.14.dist-info/METADATA +191 -0
  44. unit3dwebup-0.0.14.dist-info/RECORD +53 -0
  45. unit3dwebup-0.0.14.dist-info/WHEEL +5 -0
  46. unit3dwebup-0.0.14.dist-info/entry_points.txt +2 -0
  47. unit3dwebup-0.0.14.dist-info/top_level.txt +6 -0
  48. use_case/__init__.py +0 -0
  49. use_case/make_torrent_usecase.py +67 -0
  50. use_case/process_all_usecase.py +43 -0
  51. use_case/scan_media_usecase.py +133 -0
  52. use_case/seed_usecase.py +117 -0
  53. 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