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
models/media.py ADDED
@@ -0,0 +1,597 @@
1
+ # -*- coding: utf-8 -*-
2
+ # based on the old code unit3dup 08.21
3
+ from pathlib import Path
4
+ import hashlib
5
+ import os
6
+ import re
7
+
8
+ from config.tags import crew_patterns, platform_patterns
9
+ from services.utility import ManageTitles, System
10
+ from models.media_info import MediaFile
11
+ from config.constants import MediaStatus
12
+ from config.settings import get_settings
13
+ from config.tags import SIGNS_LIST, TAGS_LIST, BAN_LIST
14
+ from services import utility
15
+
16
+ settings = get_settings()
17
+
18
+
19
+ class Media:
20
+ """
21
+ AsyncMediaManager service uses this class to build for each file an object of type Media
22
+ Each Media has a job_id based on its path and filename. Many job_ids make up a job_list
23
+ As the Media object is processed by multiple classes,
24
+ it is gradually built until it is used to create the payload for the tracker
25
+ """
26
+
27
+ def __init__(self, folder: str, subfolder: str, torrent_archive_path: str) -> None:
28
+ """
29
+ :param folder: the main path
30
+ :param subfolder: file path or subfolder path
31
+ """
32
+ self.folder: Path = Path(folder)
33
+ self.subfolder: str = subfolder
34
+ self.title: str = (Path(self.folder) / self.subfolder).name
35
+ self._torrent_file_path = Path(torrent_archive_path) / "ITT" / f"{self.title}.torrent"
36
+
37
+ # // Assign a job id
38
+ path = Path(self.folder) / self.subfolder
39
+ self.job_id: str = hashlib.sha256(str(path).encode()).hexdigest()
40
+ # // Media
41
+ self.cached: bool = False # non utilizzato
42
+ self._crew_list: list[str] | None = None
43
+ self._game_title: str | None = None
44
+ self._platform_list: list[str] | None = None
45
+ self._title_sanitized: str | None = None
46
+ self._guess_title: str | None = None
47
+ self._guess_filename: utility.Guessit | None = None
48
+ self._guess_season: int | None = None
49
+ self._episode: int | None = None
50
+ self._source: str | None = None
51
+ self._screen_size: str | None = None
52
+ self._audio_codec: str | None = None
53
+ self._subtitle: str | None = None
54
+ self._torrent_path: Path | None = None
55
+ self._media_to_string: str | None = None
56
+ self.signs_list: dict[str, str] = SIGNS_LIST
57
+ self.tags_list: dict[str, str] = TAGS_LIST
58
+ self.ban_list: dict[str, str] = BAN_LIST
59
+ self.releaser_sign: str = settings.prefs.RELEASER_SIGN
60
+ self._tag_position: list[str] = []
61
+
62
+ # // Contents
63
+ self._file_name: str | None = None
64
+ self._display_name: str | None = None
65
+ self._category: str | None = None
66
+ self._audio_languages: list[str] = []
67
+ self._media_file: MediaFile | None = None
68
+ self._languages: list[str] | None = None
69
+ self._resolution: str | None = None
70
+ self._tracker_name: str | None = None
71
+
72
+ # // Contents dall'esterno
73
+ self._torrent_name: str | None = None
74
+ self._size: int = 0
75
+ self._metainfo: str | None = None
76
+ self._torrent_pack: bool = False
77
+ self._doc_description: str | None = None
78
+ self._game_nfo: str | None = None
79
+ self._tmdb_id: int | None = None
80
+ self._imdb_id: int | None = None
81
+ self._tvdb_id: int | None = None
82
+ self._igdb_id: int | None = None
83
+ self._generate_title: str | None = None
84
+ self._keyword: str | None = None
85
+ self._trailer: str | None = None
86
+ self._description: str | None = None
87
+ self._is_hd: int = 0
88
+ self._is_folder: bool = False
89
+ self._extracted_frames: list[bytes] | None = None
90
+ self._backdrop_path: str | None = None
91
+ self._status: int | None = MediaStatus.INDEXED
92
+ self._error: str | None = None
93
+ self._screen_shots: list | None = None
94
+ self._job_id_list: str | None = None
95
+ self._imdb_from_tvdb: str | None = None
96
+
97
+ @property
98
+ def title_sanitized(self) -> str:
99
+ if not self._title_sanitized:
100
+ self._title_sanitized = ManageTitles.clean_text(self.title)
101
+ return self._title_sanitized
102
+
103
+ @title_sanitized.setter
104
+ def title_sanitized(self, value: str):
105
+ self._title_sanitized = value
106
+
107
+ @property
108
+ def crew_list(self) -> list[str]:
109
+ if not self._crew_list:
110
+ self._crew_list = self._crew(filename=self.title_sanitized)
111
+ return self._crew_list
112
+
113
+ @property
114
+ def platform_list(self) -> list[str]:
115
+ if not self._platform_list:
116
+ self._platform_list = self._platform(filename=self.title_sanitized)
117
+ return self._platform_list
118
+
119
+ @property
120
+ def game_nfo(self) -> str | None:
121
+ return self._game_nfo
122
+
123
+ @game_nfo.setter
124
+ def game_nfo(self, value: str | None):
125
+ self._game_nfo = value
126
+
127
+ @property
128
+ def game_title(self) -> str | None:
129
+ if not self._game_title:
130
+ _game_tmp = self.guess_filename.guessit_title
131
+ for crew in self.crew_list:
132
+ _game_tmp = _game_tmp.replace(crew, " ")
133
+ self._game_title = _game_tmp.strip()
134
+ return self._game_title
135
+
136
+ @property
137
+ def torrent_name(self) -> str | None:
138
+ return self._torrent_name
139
+
140
+ @torrent_name.setter
141
+ def torrent_name(self, value: str | None):
142
+ self._torrent_name = value
143
+
144
+ @property
145
+ def size(self) -> int:
146
+ return self._size
147
+
148
+ @size.setter
149
+ def size(self, value: int):
150
+ self._size = value
151
+
152
+ @property
153
+ def metainfo(self) -> str | None:
154
+ return self._metainfo
155
+
156
+ @metainfo.setter
157
+ def metainfo(self, value: str | None):
158
+ self._metainfo = value
159
+
160
+ @property
161
+ def doc_description(self) -> str | None:
162
+ return self._doc_description
163
+
164
+ @doc_description.setter
165
+ def doc_description(self, value: str | None):
166
+ self._doc_description = value
167
+
168
+ @property
169
+ def tracker_name(self) -> str | None:
170
+ return self._tracker_name
171
+
172
+ @tracker_name.setter
173
+ def tracker_name(self, value: str | None):
174
+ self._tracker_name = value
175
+
176
+ @property
177
+ def torrent_pack(self) -> bool:
178
+ return self._torrent_pack
179
+
180
+ @torrent_pack.setter
181
+ def torrent_pack(self, value: bool):
182
+ self._torrent_pack = value
183
+
184
+ @property
185
+ def tmdb_id(self) -> int | None:
186
+ return self._tmdb_id
187
+
188
+ @tmdb_id.setter
189
+ def tmdb_id(self, value: int | None):
190
+ self._tmdb_id = value
191
+
192
+ @property
193
+ def imdb_id(self) -> int | None:
194
+ return self._imdb_id
195
+
196
+ @imdb_id.setter
197
+ def imdb_id(self, value: int | None):
198
+ self._imdb_id = value
199
+
200
+ @property
201
+ def tvdb_id(self) -> int | None:
202
+ return self._tvdb_id
203
+
204
+ @tvdb_id.setter
205
+ def tvdb_id(self, value: int | None):
206
+ self._tvdb_id = value
207
+
208
+ @property
209
+ def igdb_id(self) -> int | None:
210
+ return self._igdb_id
211
+
212
+ @igdb_id.setter
213
+ def igdb_id(self, value: int | None):
214
+ self._igdb_id = value
215
+
216
+ @property
217
+ def generate_title(self) -> str | None:
218
+ if not self._generate_title and self.mediafile:
219
+ video_f = self.mediafile.video_format
220
+ audio_f = self.mediafile.audio_format
221
+ audio_lang = self.mediafile.audio_language
222
+ available_lang = ' '.join(
223
+ lang for lang in self.mediafile.available_languages if lang is not None
224
+ )
225
+
226
+ if System.category_list.get(System.TV_SHOW) in self.category:
227
+ serie = f"S{str(self.guess_season).zfill(2)}" if self.guess_season else ''
228
+ if not self.torrent_pack:
229
+ serie += f"E{str(self.guess_episode).zfill(2)}"
230
+ else:
231
+ serie = ''
232
+
233
+ self._generate_title = f"{self.guess_title} {serie} {self.resolution} {video_f} " \
234
+ f"{available_lang} {audio_f} {audio_lang.upper()}"
235
+ return self._generate_title
236
+
237
+ @generate_title.setter
238
+ def generate_title(self, value: str | None):
239
+ self._generate_title = value
240
+
241
+ @property
242
+ def guess_filename(self) -> utility.Guessit:
243
+ if not self._guess_filename:
244
+ self._guess_filename = utility.Guessit(self.title_sanitized)
245
+ return self._guess_filename
246
+
247
+ @property
248
+ def file_name(self) -> str | None:
249
+ return self._file_name
250
+
251
+ @file_name.setter
252
+ def file_name(self, value: str | None):
253
+ self._file_name = value
254
+
255
+ @property
256
+ def display_name(self) -> str | None:
257
+ return self._display_name
258
+
259
+ @display_name.setter
260
+ def display_name(self, value: str | None):
261
+ self._display_name = value
262
+ if self._display_name:
263
+ episode_title = self.guess_filename.guessit_episode_title
264
+ if episode_title:
265
+ self._display_name = " ".join(
266
+ self._display_name.replace(episode_title, "").split()
267
+ )
268
+
269
+ @property
270
+ def guess_title(self) -> str:
271
+ if not self._guess_title:
272
+ self._guess_title = self.guess_filename.guessit_title.strip()
273
+ return self._guess_title
274
+
275
+ @guess_title.setter
276
+ def guess_title(self, value: str):
277
+ self._guess_title = value
278
+
279
+ @property
280
+ def guess_season(self) -> int | None:
281
+ if not self._guess_season and System.category_list.get(System.TV_SHOW) in self.category:
282
+ self._guess_season = self.guess_filename.guessit_season
283
+ return self._guess_season
284
+
285
+ @property
286
+ def guess_episode(self) -> int | None:
287
+ if not self._episode and System.category_list.get(System.TV_SHOW) in self.category:
288
+ self._episode = self.guess_filename.guessit_episode
289
+ return self._episode
290
+
291
+ @property
292
+ def source(self) -> str | None:
293
+ if not self._source:
294
+ self._source = self.guess_filename.source
295
+ return self._source
296
+
297
+ @property
298
+ def screen_size(self) -> str | None:
299
+ if not self._screen_size:
300
+ for screen in self.title_sanitized.split():
301
+ if screen in System.RESOLUTION_labels:
302
+ self._screen_size = screen
303
+ return self._screen_size
304
+
305
+ @property
306
+ def audio_codec(self) -> str | None:
307
+ if not self._audio_codec:
308
+ self._audio_codec = self.guess_filename.audio_codec
309
+ return self._audio_codec
310
+
311
+ @property
312
+ def audio_languages(self):
313
+ if not self._audio_languages:
314
+ # Get languages from the title
315
+ filename_split = self.display_name.upper().split(" ")
316
+
317
+ for code in filename_split:
318
+ converted_code = ManageTitles.convert_iso(code)
319
+ if converted_code:
320
+ self._audio_languages.append(converted_code[0])
321
+
322
+ if not self._audio_languages:
323
+ # get from the audio track
324
+ self._audio_languages = self.languages
325
+ return self._audio_languages
326
+
327
+ @property
328
+ def subtitle(self) -> str | None:
329
+ if not self._subtitle:
330
+ self._subtitle = self.guess_filename.subtitle
331
+ return self._subtitle
332
+
333
+ @property
334
+ def torrent_file_path(self) -> Path:
335
+ return self._torrent_file_path
336
+
337
+ @property
338
+ def torrent_path(self) -> Path:
339
+ if not self._torrent_path:
340
+ if os.path.isfile(self.folder):
341
+ self._torrent_path = self.folder
342
+ if os.path.isdir(self.folder):
343
+ self._torrent_path = Path(self.folder) / self.subfolder
344
+ return self._torrent_path
345
+
346
+ @property
347
+ def category(self) -> str | None:
348
+ if self._category:
349
+ return self._category
350
+
351
+ if ManageTitles.media_docu_type(self.title):
352
+ self._category = System.category_list.get(System.DOCUMENTARY)
353
+ elif self.guess_filename.guessit_season:
354
+ self._category = System.category_list.get(System.TV_SHOW)
355
+ else:
356
+ self._category = System.category_list.get(System.MOVIE)
357
+
358
+ if self.crew_list or self.platform_list:
359
+ self._category = System.category_list.get(System.GAME)
360
+
361
+ return self._category
362
+
363
+ @category.setter
364
+ def category(self, value: str | None):
365
+ self._category = value
366
+
367
+ @property
368
+ def tag_position(self) -> list[str]:
369
+ self._tag_position = settings.prefs.TAG_POSITION_SERIE if self.category == 'series' \
370
+ else settings.prefs.TAG_POSITION_MOVIE
371
+ return self._tag_position
372
+
373
+ @property
374
+ def keyword(self) -> str | None:
375
+ return self._keyword
376
+
377
+ @keyword.setter
378
+ def keyword(self, value: str | None):
379
+ self._keyword = value
380
+
381
+ @property
382
+ def trailer(self) -> str | None:
383
+ return self._trailer
384
+
385
+ @trailer.setter
386
+ def trailer(self, value: str | None):
387
+ self._trailer = value
388
+
389
+ @property
390
+ def description(self) -> str | None:
391
+ return self._description
392
+
393
+ @description.setter
394
+ def description(self, value: str | None):
395
+ self._description = value
396
+
397
+ @property
398
+ def extracted_frames(self) -> list[bytes] | None:
399
+ return self._extracted_frames
400
+
401
+ @extracted_frames.setter
402
+ def extracted_frames(self, value: list[bytes] | None):
403
+ self._extracted_frames = value
404
+
405
+ @property
406
+ def is_hd(self) -> int:
407
+ return self._is_hd
408
+
409
+ @is_hd.setter
410
+ def is_hd(self, value: int):
411
+ self._is_hd = value
412
+
413
+ @property
414
+ def mediafile(self) -> MediaFile | None:
415
+ return self._media_file
416
+
417
+ @mediafile.setter
418
+ def mediafile(self, value: MediaFile | None):
419
+ self._media_file = value
420
+
421
+ @property
422
+ def media_to_string(self) -> str | None:
423
+ return self._media_to_string
424
+
425
+ @media_to_string.setter
426
+ def media_to_string(self, value: str | None):
427
+ self._media_to_string = value
428
+
429
+ @property
430
+ def backdrop_path(self) -> str | None:
431
+ return self._backdrop_path
432
+
433
+ @backdrop_path.setter
434
+ def backdrop_path(self, value: str | None):
435
+ self._backdrop_path = value
436
+
437
+ @property
438
+ def status(self) -> int | None:
439
+ return self._status
440
+
441
+ @status.setter
442
+ def status(self, value: int | None):
443
+ self._status = value
444
+
445
+ @property
446
+ def error(self) -> str | None:
447
+ return self._error
448
+
449
+ @error.setter
450
+ def error(self, value: str | None):
451
+ self._error = value
452
+
453
+ @property
454
+ def is_folder(self) -> bool:
455
+ return self._is_folder
456
+
457
+ @is_folder.setter
458
+ def is_folder(self, value: bool):
459
+ self._is_folder = value
460
+
461
+ @property
462
+ def screen_shots(self) -> list[str]:
463
+ return self._screen_shots
464
+
465
+ @screen_shots.setter
466
+ def screen_shots(self, value: list[str]):
467
+ self._screen_shots = value or []
468
+
469
+ def add_screen_shot(self, value: str):
470
+ self._screen_shots.append(value)
471
+
472
+ @property
473
+ def job_id_list(self) -> str | None:
474
+ return self._job_id_list
475
+
476
+ @job_id_list.setter
477
+ def job_id_list(self, value: str):
478
+ self._job_id_list = value
479
+
480
+ @property
481
+ def imdb_id_from_tvdb(self) -> str:
482
+ return self._imdb_from_tvdb
483
+
484
+ @imdb_id_from_tvdb.setter
485
+ def imdb_id_from_tvdb(self, value: str):
486
+ self._imdb_from_tvdb = value
487
+
488
+ @property
489
+ def languages(self) -> list[str]:
490
+ if not self._languages and self.mediafile:
491
+ self._languages = self.mediafile.available_languages
492
+ return self._languages
493
+
494
+ @property
495
+ def resolution(self) -> str | None:
496
+ if not self._resolution:
497
+ if self.mediafile and self.mediafile.video_height:
498
+ closest_resolution = min(
499
+ System.RESOLUTIONS,
500
+ key=lambda x: abs(int(x) - int(self.mediafile.video_height))
501
+ )
502
+ scan_type = self.mediafile.video_scan_type
503
+ if scan_type:
504
+ closest_resolution = f"{closest_resolution}p" if scan_type.lower() == "progressive" else f"{closest_resolution}i"
505
+ else:
506
+ closest_resolution = f"{closest_resolution}i" if self.mediafile.is_interlaced else f"{closest_resolution}p"
507
+ self._resolution = closest_resolution
508
+ else:
509
+ self._resolution = System.NO_RESOLUTION
510
+ return self._resolution
511
+
512
+ @staticmethod
513
+ def _crew(filename: str) -> list[str]:
514
+ regex = r"\b(" + "|".join(re.escape(p) for p in crew_patterns) + r")\b$"
515
+ return re.findall(regex, filename, re.IGNORECASE)
516
+
517
+ @staticmethod
518
+ def _platform(filename: str) -> list[str]:
519
+ regex = r"\b(" + "|".join(re.escape(p) for p in platform_patterns) + r")\b"
520
+ return re.findall(regex, filename, re.IGNORECASE)
521
+
522
+ # Serialize
523
+ def to_dict(self) -> dict:
524
+
525
+ # /// PosixPath objects must be converted to strings to be serializable
526
+ return {
527
+ "folder": str(self.folder),
528
+ "subfolder": self.subfolder,
529
+ "title": self.title,
530
+ "job_id": self.job_id,
531
+ "status": self.status,
532
+ "error": self.error,
533
+ "title_sanitized": self.title_sanitized,
534
+ "guess_title": self.guess_title,
535
+ "generate_title": self.generate_title,
536
+ "category": self.category,
537
+ "backdrop_path": self.backdrop_path,
538
+ "media_to_string": self.media_to_string,
539
+ "description": self.description,
540
+ "mediafile": self.mediafile.to_dict() if self.mediafile else None,
541
+ "tmdb_id": self.tmdb_id,
542
+ "imdb_id": self.imdb_id,
543
+ "tvdb_id": self.tvdb_id,
544
+ "igdb_id": self.igdb_id,
545
+ "imdb_id_from_tvdb": self.imdb_id_from_tvdb,
546
+ "file_name": str(self.file_name),
547
+ "display_name": self.display_name,
548
+ "torrent_name": self.torrent_name,
549
+ "torrent_path": str(self.torrent_path),
550
+ "torrent_file_path": str(self.torrent_file_path),
551
+ "torrent_pack": self.torrent_pack,
552
+ "size": self.size,
553
+ "resolution": self.resolution,
554
+ "screen_size": self.screen_size,
555
+ "source": self.source,
556
+ "audio_codec": self.audio_codec,
557
+ "audio_languages": self.audio_languages,
558
+ "subtitle": self.subtitle,
559
+ "crew_list": self.crew_list,
560
+ "platform_list": self.platform_list,
561
+ "screen_shots": self.screen_shots,
562
+ "job_id_list": self.job_id_list,
563
+
564
+ }
565
+
566
+ @classmethod
567
+ # json to Media()
568
+ def from_dict(cls, data: dict) -> "Media":
569
+ torrent_archive_path = (
570
+ Path("/home/app/torrent_archive") if os.getenv("DOCKER") == "true"
571
+ else Path(settings.prefs.TORRENT_ARCHIVE_PATH)
572
+ )
573
+
574
+ m = cls(folder=data["folder"], subfolder=data["subfolder"], torrent_archive_path=str(torrent_archive_path))
575
+ m.file_name = data.get("file_name")
576
+ m.display_name = data.get("display_name")
577
+ m.torrent_name = data.get("torrent_name")
578
+ m.size = data.get("size", 0) # todo fare cast to int
579
+ m.torrent_pack = data.get("torrent_pack", False)
580
+ m.job_id = data.get("job_id")
581
+ m.status = int(data.get("status", 0))
582
+ m.error = data.get("error")
583
+ m.tmdb_id = data.get("tmdb_id")
584
+ m.imdb_id = data.get("imdb_id")
585
+ m.tvdb_id = data.get("tvdb_id")
586
+ m.igdb_id = data.get("igdb_id")
587
+ m.imdb_id_from_tvdb = data.get("imdb_id_from_tvdb")
588
+ m.backdrop_path = data.get("backdrop_path")
589
+ m.media_to_string = data.get("media_to_string")
590
+ m.description = data.get("description")
591
+ if data.get("mediafile"):
592
+ m.mediafile = MediaFile.dict_to_mediafile(data["mediafile"])
593
+ m.generate_title = data.get("generate_title")
594
+ m.category = data.get("category")
595
+ m.screen_shots = data.get("screen_shots")
596
+ m.job_id_list = data.get("job_id_list")
597
+ return m