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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from models.movie import AltTitle
|
|
5
|
+
from models.tv import Alternative, DataResponse
|
|
6
|
+
from repositories.interfaces import MovieRepositoryInterface
|
|
7
|
+
from services.utility import ManageTitles
|
|
8
|
+
from config.settings import get_settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# based on old code unit3dup 0.8.21
|
|
12
|
+
class MediaService:
|
|
13
|
+
def __init__(self, repository: MovieRepositoryInterface):
|
|
14
|
+
self.repo = repository
|
|
15
|
+
self.settings = get_settings()
|
|
16
|
+
|
|
17
|
+
async def fetch(self, media):
|
|
18
|
+
async def get_keyword() -> str | None:
|
|
19
|
+
keywords = await self.repo.keywords(movie_id=show.get_id(), category=media.category)
|
|
20
|
+
return " ".join([key.name for key in keywords]) if keywords else None
|
|
21
|
+
|
|
22
|
+
async def get_trailer() -> str | None:
|
|
23
|
+
if self.settings.prefs.SKIP_YOUTUBE:
|
|
24
|
+
return None
|
|
25
|
+
trailers = await self.repo.videos(movie_id=show.get_id(), category=media.category)
|
|
26
|
+
if trailers:
|
|
27
|
+
trailer = next((video.key for video in trailers.results if video.type.lower() == 'trailer' # .results
|
|
28
|
+
and video.site.lower() == 'youtube'), None)
|
|
29
|
+
return trailer if trailer else None
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
results = await self.repo.search(query=media.guess_title, category=media.category)
|
|
33
|
+
for show in results:
|
|
34
|
+
if show.get_date() and media.guess_filename.guessit_year:
|
|
35
|
+
if not datetime.strptime(show.get_date(), '%Y-%m-%d').year == media.guess_filename.guessit_year:
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
# Search for title
|
|
39
|
+
if ManageTitles.fuzzyit(str1=media.guess_title, str2=ManageTitles.clean_text(show.get_title())) > 95:
|
|
40
|
+
media.tmdb_id = show.get_id()
|
|
41
|
+
media.imdb_id_from_tvdb = show.get_imdb()
|
|
42
|
+
media.keyword = await get_keyword()
|
|
43
|
+
media.trailer = await get_trailer()
|
|
44
|
+
media.backdrop_path = show.get_poster_path()
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
if ManageTitles.fuzzyit(str1=media.guess_title, str2=ManageTitles.clean_text(show.get_original())) > 95:
|
|
48
|
+
media.tmdb_id = show.get_id()
|
|
49
|
+
media.imdb_id_from_tvdb = show.get_imdb()
|
|
50
|
+
media.keyword = await get_keyword()
|
|
51
|
+
media.trailer = await get_trailer()
|
|
52
|
+
media.backdrop_path = show.get_poster_path()
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
alt_list = []
|
|
56
|
+
for show in results:
|
|
57
|
+
# Search for alternative title TODO: rework dataclass
|
|
58
|
+
alternative = await self.repo.alternative(movie_id=show.get_id(), category=media.category)
|
|
59
|
+
if isinstance(alternative, DataResponse):
|
|
60
|
+
alt_list = [alt.title for alt in alternative.results]
|
|
61
|
+
if isinstance(alternative, AltTitle):
|
|
62
|
+
alt_list = [alt.title for alt in alternative.titles]
|
|
63
|
+
for title in alt_list:
|
|
64
|
+
if ManageTitles.fuzzyit(str1=media.guess_title, str2=ManageTitles.clean_text(title)) > 95:
|
|
65
|
+
media.tmdb_id = show.get_id()
|
|
66
|
+
media.keyword = await get_keyword()
|
|
67
|
+
media.trailer = await get_trailer()
|
|
68
|
+
media.backdrop_path = show.get_poster_path()
|
|
69
|
+
return True
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class MediaService2:
|
|
74
|
+
def __init__(self, repository: MovieRepositoryInterface):
|
|
75
|
+
self.repo = repository
|
|
76
|
+
|
|
77
|
+
async def fetch(self, media):
|
|
78
|
+
results = await self.repo.search(query=media.guess_title, category=media.category)
|
|
79
|
+
|
|
80
|
+
for show in results:
|
|
81
|
+
if show.get_date() and media.guess_filename.guessit_year:
|
|
82
|
+
if not datetime.strptime(show.get_date(), '%Y-%m-%d').year == media.guess_filename.guessit_year:
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
# Search for title
|
|
86
|
+
if ManageTitles.fuzzyit(str1=media.guess_title, str2=ManageTitles.clean_text(show.get_title())) > 95:
|
|
87
|
+
media.imdb_id_from_tvdb = show.get_imdb()
|
|
88
|
+
media.tvdb_id = show.get_id()
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
if ManageTitles.fuzzyit(str1=media.guess_title, str2=ManageTitles.clean_text(show.get_original())) > 95:
|
|
92
|
+
media.imdb_id_from_tvdb = show.get_imdb()
|
|
93
|
+
media.tvdb_id = show.get_id()
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
# Search for alternative title ( translations field)
|
|
97
|
+
alternative = show.get_translations()
|
|
98
|
+
title_ita = alternative.get('ita', None)
|
|
99
|
+
title_eng = alternative.get('eng', None)
|
|
100
|
+
|
|
101
|
+
if title_ita:
|
|
102
|
+
if ManageTitles.fuzzyit(str1=media.guess_title, str2=ManageTitles.clean_text(title_ita)) > 95:
|
|
103
|
+
media.imdb_id_from_tvdb = show.get_imdb()
|
|
104
|
+
media.tvdb_id = show.get_id()
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
# The result is not always in english
|
|
108
|
+
if title_eng:
|
|
109
|
+
if ManageTitles.fuzzyit(str1=media.guess_title, str2=ManageTitles.clean_text(title_eng)) > 95:
|
|
110
|
+
media.imdb_id_from_tvdb = show.get_imdb()
|
|
111
|
+
media.tvdb_id = show.get_id()
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
return False
|
services/tags_service.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import re
|
|
3
|
+
from services.utility import ManageTitles
|
|
4
|
+
from config.logger import get_logger
|
|
5
|
+
from models.media import Media
|
|
6
|
+
|
|
7
|
+
# From hdr format
|
|
8
|
+
hdr_map = {
|
|
9
|
+
"DOLBY VISION": "DV",
|
|
10
|
+
"DOLBY VISION HDR": "DV HDR",
|
|
11
|
+
"DOLBY VISION HDR10": "DV HDR10",
|
|
12
|
+
"DOLBY VISION HDR10+": "DV HDR10+",
|
|
13
|
+
"HDR10PLUS": "HDR10+",
|
|
14
|
+
"HDRPLUS+": "HDR10+",
|
|
15
|
+
"HDR10+": "HDR10+",
|
|
16
|
+
"HDR10": "HDR10",
|
|
17
|
+
"BLU-RAY / HDR10": "HDR10",
|
|
18
|
+
"HDR10 / HDR10": "HDR10",
|
|
19
|
+
"HDR10 / HDR10+": "HDR10+",
|
|
20
|
+
"HDR10 / HDR10 / HDR10+": "HDR10+",
|
|
21
|
+
"SMPTE ST 2086": "HDR10",
|
|
22
|
+
"SMPTE ST 2094": "HDR10+",
|
|
23
|
+
|
|
24
|
+
"DOVI": "DV",
|
|
25
|
+
"HDR": "HDR",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
audio_translate = {
|
|
29
|
+
"AC3": "DD",
|
|
30
|
+
"AAC LC": "AAC",
|
|
31
|
+
"AAC LC SBR": "HE-AAC",
|
|
32
|
+
"AC-3": "DD",
|
|
33
|
+
"EAC3": "DD+",
|
|
34
|
+
"E-AC3": "DD+",
|
|
35
|
+
"E-AC-3": "DD+",
|
|
36
|
+
"E-AC-3 JOC": "DD+",
|
|
37
|
+
"DTS": "DTS",
|
|
38
|
+
"DTS ES": "DTS-ES",
|
|
39
|
+
"DTS ES XLL": "DTS-HD MA",
|
|
40
|
+
"DTS XLL": "DTS-HD MA",
|
|
41
|
+
"MLP FBA 16-ch": "TrueHD",
|
|
42
|
+
"MPEG Audio": "MPEG",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
video_translate = {
|
|
46
|
+
"AVC": "H.264",
|
|
47
|
+
"HEVC": "H.265",
|
|
48
|
+
"H265": "H.265",
|
|
49
|
+
"H264": "H.264",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
video_encoder_translate = {
|
|
53
|
+
"X265": "x.265",
|
|
54
|
+
"X264": "x.264",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SearchTags(object):
|
|
59
|
+
def __init__(self, filename, title: str, year: str | None, season: int | None, episode: int | None,
|
|
60
|
+
media: Media, tags_position: list, tags_list: dict, signs_list: dict, ban_list: dict,
|
|
61
|
+
releaser_sign: str):
|
|
62
|
+
|
|
63
|
+
self.tags_position = tags_position
|
|
64
|
+
self.releaser_sign = releaser_sign
|
|
65
|
+
self.mediafile = media.mediafile
|
|
66
|
+
self.filename = filename
|
|
67
|
+
self.episode = episode
|
|
68
|
+
self.season = season
|
|
69
|
+
self.title = title
|
|
70
|
+
self.year = year
|
|
71
|
+
self.tags_dict = {}
|
|
72
|
+
self.tags_position = tags_position
|
|
73
|
+
self.TAG_TYPES: dict = tags_list
|
|
74
|
+
self.SIGNS_LIST: dict = signs_list
|
|
75
|
+
self.BAN_LIST: dict = ban_list
|
|
76
|
+
self.logger = get_logger(self.__class__.__name__)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def normalize_version_tag(tag: str) -> str:
|
|
80
|
+
tag_esc = re.escape(tag)
|
|
81
|
+
return tag_esc
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def normalize_part_tag(title: str) -> str | None:
|
|
85
|
+
"""
|
|
86
|
+
Extract substring PartX
|
|
87
|
+
Try to remove noisy chars and return a normalized tag
|
|
88
|
+
Part1, Part 1, Part.1, [Part 1], Parte1, Pt1, Prt 2,
|
|
89
|
+
"""
|
|
90
|
+
pattern = r'[\[\(]?\b(?:Part|Parte|Pt|Prt)[\s\.-]*?(\d+)\b[\]\)]?'
|
|
91
|
+
match = re.search(pattern, title, re.IGNORECASE)
|
|
92
|
+
if match:
|
|
93
|
+
part_number = match.group(1)
|
|
94
|
+
return f"Part {part_number}"
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def normalize_platform_tag(tag: str) -> str:
|
|
99
|
+
tag_esc = re.escape(tag)
|
|
100
|
+
return tag_esc
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def normalize_sources(tag: str) -> str:
|
|
104
|
+
tag_esc = re.escape(tag)
|
|
105
|
+
return tag_esc
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def normalize_video_encoder(tag: str) -> str:
|
|
109
|
+
tag_esc = re.escape(tag)
|
|
110
|
+
tag_esc = re.sub(r'([A-Z])(\d+)', r'\1[._-]?\2', tag_esc)
|
|
111
|
+
return tag_esc
|
|
112
|
+
|
|
113
|
+
def build_title(self, dictionary: dict) -> str:
|
|
114
|
+
# /// Build the title
|
|
115
|
+
build = []
|
|
116
|
+
for k, v in dictionary.items():
|
|
117
|
+
if isinstance(v, list):
|
|
118
|
+
for item in v:
|
|
119
|
+
build.append(str(item))
|
|
120
|
+
else:
|
|
121
|
+
build.append(str(v))
|
|
122
|
+
|
|
123
|
+
refactored = ' '.join(build) + self.releaser_sign
|
|
124
|
+
return refactored
|
|
125
|
+
|
|
126
|
+
def process(self) -> str:
|
|
127
|
+
patterns = []
|
|
128
|
+
|
|
129
|
+
# Remove banned items from categories
|
|
130
|
+
self.tags_position = [x for x in self.tags_position if x not in self.BAN_LIST]
|
|
131
|
+
|
|
132
|
+
# loop sorted TAG_TYPES dictionary
|
|
133
|
+
for i, (tag, category) in enumerate(
|
|
134
|
+
sorted(self.TAG_TYPES.items(), key=lambda x: len(x[0]), reverse=True)
|
|
135
|
+
):
|
|
136
|
+
if category == "version":
|
|
137
|
+
norm = self.normalize_version_tag(tag)
|
|
138
|
+
elif category == "platform":
|
|
139
|
+
norm = self.normalize_platform_tag(tag)
|
|
140
|
+
elif category == "source":
|
|
141
|
+
norm = self.normalize_sources(tag)
|
|
142
|
+
elif category == "video_encoder":
|
|
143
|
+
norm = self.normalize_video_encoder(tag)
|
|
144
|
+
else:
|
|
145
|
+
norm = re.escape(tag)
|
|
146
|
+
# Save a regex pattern for each category
|
|
147
|
+
patterns.append([norm, category])
|
|
148
|
+
|
|
149
|
+
# Run regex
|
|
150
|
+
for p, category in patterns:
|
|
151
|
+
regex = re.compile(r'(?<!\w)' + p + r'(?!\w)', re.IGNORECASE)
|
|
152
|
+
matches = regex.findall(self.filename)
|
|
153
|
+
if matches:
|
|
154
|
+
self.tags_dict.setdefault(category, []).append(matches[0])
|
|
155
|
+
|
|
156
|
+
# /// Tags with no categories
|
|
157
|
+
# Identify PartX
|
|
158
|
+
norm = self.normalize_part_tag(self.filename)
|
|
159
|
+
if norm:
|
|
160
|
+
# Skip if it is part of title es: "Wicked.Parte.2.2025.iTA" Title = Wicked Parte 2
|
|
161
|
+
if not any(t in self.title.lower() for t in ['part', 'parte']):
|
|
162
|
+
self.tags_dict.update({'part': norm})
|
|
163
|
+
|
|
164
|
+
# /// Read from mediainfo
|
|
165
|
+
updated_category = {}
|
|
166
|
+
for category in self.tags_position:
|
|
167
|
+
if category == "acodec":
|
|
168
|
+
updated_category = self.mediainfo_audio(category=category)
|
|
169
|
+
|
|
170
|
+
elif category == "vcodec":
|
|
171
|
+
updated_category = self.mediainfo_video(category=category)
|
|
172
|
+
|
|
173
|
+
elif category == "video_encoder":
|
|
174
|
+
if self.tags_dict.get('video_encoder', None):
|
|
175
|
+
self.tags_dict['video_encoder'][0] = self.tags_dict['video_encoder'][0].lower()
|
|
176
|
+
|
|
177
|
+
elif category == "hdr":
|
|
178
|
+
updated_category = self.mediainfo_hdr(category=category)
|
|
179
|
+
if not updated_category:
|
|
180
|
+
updated_category = {category: 'SDR'}
|
|
181
|
+
|
|
182
|
+
elif category == "uhd":
|
|
183
|
+
updated_category = self.mediainfo_uhd(category=category)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
elif category == "subtitle":
|
|
187
|
+
if self.mediafile.subtitle_tracks:
|
|
188
|
+
updated_category = {'subtitle': "SUBS" if len(self.mediafile.subtitle_tracks) > 1 else "SUB"}
|
|
189
|
+
|
|
190
|
+
if updated_category:
|
|
191
|
+
self.tags_dict.update(updated_category)
|
|
192
|
+
|
|
193
|
+
# /// Add S#E#, title, Year
|
|
194
|
+
se_str = ''
|
|
195
|
+
if self.season is not None and self.episode is not None:
|
|
196
|
+
se_str = f"S{self.season:02d}E{self.episode:02d}"
|
|
197
|
+
elif self.season is not None:
|
|
198
|
+
se_str = f"S{self.season:02d}"
|
|
199
|
+
elif self.episode is not None:
|
|
200
|
+
se_str = f"E{self.episode:02d}"
|
|
201
|
+
|
|
202
|
+
self.tags_dict.update({'title': self.title})
|
|
203
|
+
if self.year:
|
|
204
|
+
self.tags_dict.update({'year': self.year})
|
|
205
|
+
if se_str:
|
|
206
|
+
self.tags_dict.update({'season': se_str})
|
|
207
|
+
|
|
208
|
+
if not self.releaser_sign:
|
|
209
|
+
# If releaser_sign is not defined in the configuration file,
|
|
210
|
+
# try to detect a known sign from SIGN_LIST
|
|
211
|
+
self.releaser_sign = self.detect_releaser(self.filename, self.SIGNS_LIST)
|
|
212
|
+
|
|
213
|
+
# /// Order according to tag position
|
|
214
|
+
tags_dict = {
|
|
215
|
+
k: self.tags_dict[k]
|
|
216
|
+
for k in self.tags_position
|
|
217
|
+
if k in self.tags_dict
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
new_title = self.build_title(tags_dict)
|
|
221
|
+
return new_title
|
|
222
|
+
|
|
223
|
+
def mediainfo_audio(self, category: str) -> dict:
|
|
224
|
+
languages = []
|
|
225
|
+
audio_codecs = []
|
|
226
|
+
if self.mediafile.audio_tracks:
|
|
227
|
+
for audio in self.mediafile.audio_tracks:
|
|
228
|
+
other_format = audio.get('other_format', [])
|
|
229
|
+
if other_format:
|
|
230
|
+
codec_translated = audio_translate.get(other_format[0], '')
|
|
231
|
+
if not codec_translated:
|
|
232
|
+
codec_translated = other_format[0]
|
|
233
|
+
# Check Atmos
|
|
234
|
+
dolby = audio.get('commercial_name', "").lower()
|
|
235
|
+
atmos = 'Atmos' if 'atmos' in dolby else ''
|
|
236
|
+
# Add audio codec
|
|
237
|
+
channel_s = audio.get('channel_s', 0)
|
|
238
|
+
# Add channels
|
|
239
|
+
ch = {2: "2.0", 6: "5.1", 8: "7.1"}.get(channel_s, "")
|
|
240
|
+
if f"{codec_translated} {ch} {atmos}".strip() not in audio_codecs:
|
|
241
|
+
audio_codecs.append(f"{codec_translated} {ch} {atmos}".strip())
|
|
242
|
+
# print(f"Mediainfo {other_format} -> {codec_translated} {ch} {atmos}")
|
|
243
|
+
|
|
244
|
+
# Add flags
|
|
245
|
+
for l in audio.get('other_language', []):
|
|
246
|
+
c = ManageTitles.convert_iso(l)
|
|
247
|
+
if c:
|
|
248
|
+
if isinstance(c, list):
|
|
249
|
+
languages.append(c[0])
|
|
250
|
+
else:
|
|
251
|
+
languages.append(c)
|
|
252
|
+
break
|
|
253
|
+
languages = list(dict.fromkeys(languages))
|
|
254
|
+
# Add multilanguage tag when languages > 2
|
|
255
|
+
if len(languages) > 2:
|
|
256
|
+
self.tags_dict.update({'multi': 'MULTI'})
|
|
257
|
+
|
|
258
|
+
audio_codecs.extend(languages)
|
|
259
|
+
return {category: audio_codecs}
|
|
260
|
+
|
|
261
|
+
def mediainfo_video(self, category: str) -> dict:
|
|
262
|
+
codec_translated = {}
|
|
263
|
+
if self.mediafile.video_tracks:
|
|
264
|
+
for video in self.mediafile.video_tracks:
|
|
265
|
+
video_format = video.get('format', "")
|
|
266
|
+
codec_translated = video_translate.get(video_format, video_format)
|
|
267
|
+
if codec_translated:
|
|
268
|
+
return {category: codec_translated}
|
|
269
|
+
return codec_translated
|
|
270
|
+
|
|
271
|
+
def mediainfo_hdr(self, category: str) -> dict:
|
|
272
|
+
if self.mediafile.video_tracks:
|
|
273
|
+
for video in self.mediafile.video_tracks:
|
|
274
|
+
hdr_format_commercial = video.get('hdr_format_commercial', "")
|
|
275
|
+
hdr_format = video.get('hdr_format', "")
|
|
276
|
+
colour_primaries = video.get('color_primaries', "")
|
|
277
|
+
matrix_coefficients = video.get('matrix_coefficients', "")
|
|
278
|
+
bit_depth = video.get('bit_depth', "")
|
|
279
|
+
transfer_characteristics = video.get('transfer_characteristics', "")
|
|
280
|
+
|
|
281
|
+
# Check hdr
|
|
282
|
+
if hdr_format_commercial:
|
|
283
|
+
# print(f"hdr_format_commercial: {hdr_format_commercial}")
|
|
284
|
+
# print(f"hdr_format: {hdr_format}")
|
|
285
|
+
hdr = ''
|
|
286
|
+
if hdr_format_commercial.upper() in hdr_map:
|
|
287
|
+
# print(
|
|
288
|
+
# f"hdr_format_commercial: {hdr_format_commercial} -> Tag: {hdr_map[hdr_format_commercial]}")
|
|
289
|
+
hdr = hdr_map[hdr_format_commercial.upper()]
|
|
290
|
+
# Check dolby vision
|
|
291
|
+
if hdr not in hdr_map:
|
|
292
|
+
self.logger.info(
|
|
293
|
+
f"<> HDR Warning: '{hdr_format_commercial}' not found in hdr_map")
|
|
294
|
+
if 'DOLBY VISION' in hdr_format_commercial.upper() or 'DOLBY VISION' in hdr_format.upper():
|
|
295
|
+
hdr = f"DOLBY VISION {hdr}"
|
|
296
|
+
return {category: hdr_map.get(hdr, '*HDR')}
|
|
297
|
+
else:
|
|
298
|
+
if "2020" in colour_primaries and "2020" in matrix_coefficients:
|
|
299
|
+
if bit_depth == 10 and transfer_characteristics.strip() == 'PQ':
|
|
300
|
+
return {category: 'PQ10'}
|
|
301
|
+
else:
|
|
302
|
+
self.logger.warning(f"<> PQ10 Warning:")
|
|
303
|
+
self.logger.info(f"colour_primaries: {colour_primaries}")
|
|
304
|
+
self.logger.info(f"matrix_coefficients: {matrix_coefficients}")
|
|
305
|
+
self.logger.info(f"bit_depth: |{bit_depth}|")
|
|
306
|
+
self.logger.info(f"transfer_characteristics: |{transfer_characteristics}|")
|
|
307
|
+
|
|
308
|
+
return {}
|
|
309
|
+
|
|
310
|
+
def mediainfo_uhd(self, category: str) -> dict:
|
|
311
|
+
"""
|
|
312
|
+
identify resolution based on Height and Width tolerance 5%
|
|
313
|
+
"""
|
|
314
|
+
result = {}
|
|
315
|
+
if self.mediafile.video_tracks:
|
|
316
|
+
video_height = int(self.mediafile.video_tracks[0].get('height', 0))
|
|
317
|
+
video_width = int(self.mediafile.video_tracks[0].get('width', 0))
|
|
318
|
+
|
|
319
|
+
# print(f"VideoTrack : W{video_width} x H{video_height}")
|
|
320
|
+
|
|
321
|
+
# Calculate range 5%
|
|
322
|
+
def in_range(value, standard):
|
|
323
|
+
tol = standard * 0.05
|
|
324
|
+
return standard - tol <= value <= standard + tol
|
|
325
|
+
|
|
326
|
+
# /// UHD
|
|
327
|
+
if in_range(video_width, 3840) or in_range(video_height, 2160):
|
|
328
|
+
result[category] = 'UHD'
|
|
329
|
+
result['resolution'] = '2160p'
|
|
330
|
+
# /// Full HD
|
|
331
|
+
elif in_range(video_height, 1080) or in_range(video_width, 1920):
|
|
332
|
+
result[category] = 'FullHD'
|
|
333
|
+
result['resolution'] = '1080p'
|
|
334
|
+
# /// HD
|
|
335
|
+
elif in_range(video_height, 720) or in_range(video_width, 1280):
|
|
336
|
+
result[category] = 'HD'
|
|
337
|
+
result['resolution'] = '720p'
|
|
338
|
+
# /// SD 576p
|
|
339
|
+
elif in_range(video_height, 576) or in_range(video_width, 768):
|
|
340
|
+
result[category] = 'SD'
|
|
341
|
+
result['resolution'] = '576p'
|
|
342
|
+
# /// SD 480p
|
|
343
|
+
elif in_range(video_height, 480) or in_range(video_width, 640):
|
|
344
|
+
result[category] = 'SD'
|
|
345
|
+
result['resolution'] = '480p'
|
|
346
|
+
else:
|
|
347
|
+
result[category] = 'unknown'
|
|
348
|
+
result['resolution'] = f'{video_width}x{video_height}'
|
|
349
|
+
|
|
350
|
+
return result
|
|
351
|
+
|
|
352
|
+
@staticmethod
|
|
353
|
+
def detect_releaser(name: str, signs_list: dict) -> str:
|
|
354
|
+
"""
|
|
355
|
+
normalize both signs_list and base_name
|
|
356
|
+
find the start/end position of the matched sign
|
|
357
|
+
extract the substring from the original base_name
|
|
358
|
+
"""
|
|
359
|
+
# Strip the title
|
|
360
|
+
base_name = str(name).strip()
|
|
361
|
+
|
|
362
|
+
# sort dictionary from the longest to shortest to avoid partial result (es. 'crew' instead di 'mircrew')
|
|
363
|
+
tokens_signs_list_sorted = sorted(signs_list.keys(), key=len, reverse=True)
|
|
364
|
+
|
|
365
|
+
video_exts = [
|
|
366
|
+
"mp4", "mkv", "avi", "mov", "wmv", "flv", "webm", "mpeg", "mpg", "m4v", "ts", "3gp"
|
|
367
|
+
]
|
|
368
|
+
|
|
369
|
+
# Regex per catturare l'estensione alla fine (case insensitive)
|
|
370
|
+
pattern = rf"\.({'|'.join(video_exts)})$"
|
|
371
|
+
|
|
372
|
+
# # Search for signs in the base_name only at the end of the string
|
|
373
|
+
base_name = re.sub(pattern, "", base_name, flags=re.IGNORECASE)
|
|
374
|
+
|
|
375
|
+
# Search for signs in the base_name_normalized
|
|
376
|
+
for token in tokens_signs_list_sorted:
|
|
377
|
+
token = str(token)
|
|
378
|
+
pattern = re.escape(token)
|
|
379
|
+
match = re.search(pattern, base_name, re.IGNORECASE)
|
|
380
|
+
|
|
381
|
+
if match:
|
|
382
|
+
# Sign must be the last words
|
|
383
|
+
base_name_len = len(base_name)
|
|
384
|
+
match_len = match.end() - base_name_len
|
|
385
|
+
if match_len == 0:
|
|
386
|
+
# Capture any characters from the start to the end of base_name
|
|
387
|
+
sign = base_name[match.start(): match.end()]
|
|
388
|
+
return f"-{sign}"
|
|
389
|
+
return ""
|