Unit3Dup 0.9.8__tar.gz → 0.9.10__tar.gz

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 (102) hide show
  1. {unit3dup-0.9.8 → unit3dup-0.9.10}/PKG-INFO +1 -1
  2. {unit3dup-0.9.8 → unit3dup-0.9.10}/Unit3Dup.egg-info/PKG-INFO +1 -1
  3. unit3dup-0.9.10/common/p2p_tags.py +328 -0
  4. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/settings.py +4 -4
  5. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/utility.py +17 -1
  6. {unit3dup-0.9.8 → unit3dup-0.9.10}/pyproject.toml +1 -1
  7. unit3dup-0.9.8/common/p2p_tags.py +0 -371
  8. {unit3dup-0.9.8 → unit3dup-0.9.10}/LICENSE +0 -0
  9. {unit3dup-0.9.8 → unit3dup-0.9.10}/README.rst +0 -0
  10. {unit3dup-0.9.8 → unit3dup-0.9.10}/Unit3Dup.egg-info/SOURCES.txt +0 -0
  11. {unit3dup-0.9.8 → unit3dup-0.9.10}/Unit3Dup.egg-info/dependency_links.txt +0 -0
  12. {unit3dup-0.9.8 → unit3dup-0.9.10}/Unit3Dup.egg-info/entry_points.txt +0 -0
  13. {unit3dup-0.9.8 → unit3dup-0.9.10}/Unit3Dup.egg-info/requires.txt +0 -0
  14. {unit3dup-0.9.8 → unit3dup-0.9.10}/Unit3Dup.egg-info/top_level.txt +0 -0
  15. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/__init__.py +0 -0
  16. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/bdinfo_string.py +0 -0
  17. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/bittorrent.py +0 -0
  18. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/command.py +0 -0
  19. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/constants.py +0 -0
  20. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/database.py +0 -0
  21. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/__init__.py +0 -0
  22. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/ftpx/__init__.py +0 -0
  23. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/ftpx/client.py +0 -0
  24. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/ftpx/core/__init__.py +0 -0
  25. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/ftpx/core/ftpx_service.py +0 -0
  26. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/ftpx/core/ftpx_session.py +0 -0
  27. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/ftpx/core/menu.py +0 -0
  28. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/ftpx/core/models/__init__.py +0 -0
  29. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/ftpx/core/models/list.py +0 -0
  30. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/igdb/__init__.py +0 -0
  31. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/igdb/client.py +0 -0
  32. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/igdb/core/__init__.py +0 -0
  33. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/igdb/core/api.py +0 -0
  34. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/igdb/core/models/__init__.py +0 -0
  35. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/igdb/core/models/search.py +0 -0
  36. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/igdb/core/platformid.py +0 -0
  37. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/igdb/core/tags.py +0 -0
  38. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/imageHost.py +0 -0
  39. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/mediaresult.py +0 -0
  40. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/sessions/__init__.py +0 -0
  41. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/sessions/agents.py +0 -0
  42. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/sessions/exceptions.py +0 -0
  43. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/sessions/session.py +0 -0
  44. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/__init__.py +0 -0
  45. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/__init__.py +0 -0
  46. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/api.py +0 -0
  47. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/keywords.py +0 -0
  48. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/__init__.py +0 -0
  49. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/movie/__init__.py +0 -0
  50. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/movie/alternative_titles.py +0 -0
  51. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/movie/details.py +0 -0
  52. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/movie/movie.py +0 -0
  53. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/movie/nowplaying.py +0 -0
  54. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/movie/release_info.py +0 -0
  55. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/tvshow/__init__.py +0 -0
  56. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/tvshow/alternative.py +0 -0
  57. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/tvshow/details.py +0 -0
  58. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/tvshow/on_the_air.py +0 -0
  59. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/tvshow/translations.py +0 -0
  60. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/models/tvshow/tvshow.py +0 -0
  61. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/theMovieDB/core/videos.py +0 -0
  62. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/trailers/__init__.py +0 -0
  63. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/trailers/api.py +0 -0
  64. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/trailers/response.py +0 -0
  65. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/external_services/tvdb.py +0 -0
  66. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/extractor.py +0 -0
  67. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/frames.py +0 -0
  68. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/mediainfo.py +0 -0
  69. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/mediainfo_string.py +0 -0
  70. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/title.py +0 -0
  71. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/torrent_clients.py +0 -0
  72. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/trackers/__init__.py +0 -0
  73. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/trackers/data.py +0 -0
  74. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/trackers/itt.py +0 -0
  75. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/trackers/sis.py +0 -0
  76. {unit3dup-0.9.8 → unit3dup-0.9.10}/common/trackers/trackers.py +0 -0
  77. {unit3dup-0.9.8 → unit3dup-0.9.10}/requirements.txt +0 -0
  78. {unit3dup-0.9.8 → unit3dup-0.9.10}/setup.cfg +0 -0
  79. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/__init__.py +0 -0
  80. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/__main__.py +0 -0
  81. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/automode.py +0 -0
  82. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/bot.py +0 -0
  83. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/duplicate.py +0 -0
  84. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/exceptions.py +0 -0
  85. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media.py +0 -0
  86. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media_manager/ContentManager.py +0 -0
  87. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media_manager/DocuManager.py +0 -0
  88. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media_manager/GameManager.py +0 -0
  89. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media_manager/MediaInfoManager.py +0 -0
  90. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media_manager/SeedManager.py +0 -0
  91. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media_manager/TorrentManager.py +0 -0
  92. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media_manager/VideoManager.py +0 -0
  93. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media_manager/__init__.py +0 -0
  94. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/media_manager/common.py +0 -0
  95. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/pvtDocu.py +0 -0
  96. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/pvtTorrent.py +0 -0
  97. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/pvtTracker.py +0 -0
  98. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/pvtVideo.py +0 -0
  99. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/torrent.py +0 -0
  100. {unit3dup-0.9.8 → unit3dup-0.9.10}/unit3dup/upload.py +0 -0
  101. {unit3dup-0.9.8 → unit3dup-0.9.10}/view/__init__.py +0 -0
  102. {unit3dup-0.9.8 → unit3dup-0.9.10}/view/custom_console.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Unit3Dup
3
- Version: 0.9.8
3
+ Version: 0.9.10
4
4
  Summary: An uploader for the Unit3D torrent tracker
5
5
  Author: Parzival
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Unit3Dup
3
- Version: 0.9.8
3
+ Version: 0.9.10
4
4
  Summary: An uploader for the Unit3D torrent tracker
5
5
  Author: Parzival
6
6
  License-Expression: MIT
@@ -0,0 +1,328 @@
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+ import re
4
+ from collections import deque
5
+ from common.utility import ManageTitles
6
+ from common.mediainfo import MediaFile
7
+
8
+ TAG_TYPES = {
9
+ "WEB-DL": "source",
10
+ "WEBDL": "source",
11
+ "WEB-DLMUX": "source",
12
+ "WEBDLMUX": "source",
13
+ "WEBMUX": "source",
14
+ "WEBRIP": "source",
15
+ "BD-UNTOUCHED": "source",
16
+ "REMUX": "source",
17
+ "VU": "source",
18
+ "UHD": "source",
19
+ "UHDRIP": "source",
20
+ "BLURAY": "source",
21
+
22
+ "AMZN": "platform",
23
+ "AMC": "platform",
24
+ "CN": "platform",
25
+ "CR": "platform",
26
+ "DCU": "platform",
27
+ "DISC": "platform",
28
+ "DSCP": "platform",
29
+ "DSNY": "platform",
30
+ "DSNP": "platform",
31
+ "DPLY": "platform",
32
+ "ESPN": "platform",
33
+ "FOOD": "platform",
34
+ "FOX": "platform",
35
+ "PLAY": "platform",
36
+ "HBO": "platform",
37
+ "HMAX": "platform",
38
+ "HGTV": "platform",
39
+ "HIST": "platform",
40
+ "HULU": "platform",
41
+ "MTOD": "platform",
42
+ "NATG": "platform",
43
+ "NF": "platform",
44
+ "NICK": "platform",
45
+ "NOW": "platform",
46
+ "PMNT": "platform",
47
+ "PMTP": "platform",
48
+ "PCOK": "platform",
49
+ "RKTN": "platform",
50
+ "SHO": "platform",
51
+ "SKST": "platform",
52
+ "STAN": "platform",
53
+ "STRP": "platform",
54
+ "STZ": "platform",
55
+ "TIMV": "platform",
56
+
57
+ "ITA": "flag",
58
+ "ENG": "flag",
59
+ "FRA": "flag",
60
+ "GER": "flag",
61
+ "ESP": "flag",
62
+ "JPN": "flag",
63
+ "JAP": "flag",
64
+ "POR": "flag",
65
+ "PRT": "flag",
66
+
67
+ "SUB": "subtitle",
68
+ "SUBS": "subtitle",
69
+
70
+ "ATMOS": "audio",
71
+ "TRUEHD": "audio",
72
+ "DTSHD": "audio",
73
+ "DTS-HD": "audio",
74
+ "DTS-HD MA": "audio",
75
+ "DDP7.1": "audio",
76
+ "DDP5.1": "audio",
77
+ "DDP2.0": "audio",
78
+ "DTS": "audio",
79
+ "XLL": "audio",
80
+ "DD7.1": "audio",
81
+ "DD5.1": "audio",
82
+ "DD2.0": "audio",
83
+ "DD+ 7.1": "audio",
84
+ "DD+ 5.1": "audio",
85
+ "DD+ 2.0": "audio",
86
+ "DTS-HD MA 7.1": "audio",
87
+ "DTS-HD MA 5.1": "audio",
88
+ "DTS-HD MA 2.0": "audio",
89
+ "DD 7.1": "audio",
90
+ "DD 5.1": "audio",
91
+ "DD 2.0": "audio",
92
+ "AAC2.0": "audio",
93
+ "AAC5.1": "audio",
94
+ "AC3": "audio",
95
+ "DD": "audio",
96
+ "DD+": "audio",
97
+ "DDP": "audio",
98
+ "E-AC3": "audio",
99
+ "EAC3": "audio",
100
+ "AC-3": "audio",
101
+ "AAC": "audio",
102
+ "AVC": "audio",
103
+
104
+ "7.1": "channels",
105
+ "5.1": "channels",
106
+ "2.0": "channels",
107
+
108
+ "H.264": "video",
109
+ "X.264": "video",
110
+ "X264": "video",
111
+ "H264": "video",
112
+ "H.265": "video",
113
+ "X.265": "video",
114
+ "X265": "video",
115
+ "H265": "video",
116
+ "HEVC": "video",
117
+ "DV": "video",
118
+ "HDR10": "video",
119
+ "DVHDR10": "video",
120
+ "DVHDR": "video",
121
+ "HDRPLUS+": "video",
122
+ "HDR10PLUS": "video",
123
+ "HDR": "video",
124
+ "HDR10+": "video",
125
+ "FHDRIP": "video",
126
+ "FULL HD": "video",
127
+ "FULLHD": "video",
128
+ "HD": "video",
129
+ "UHD 4K": "video",
130
+
131
+ "REPACK": "version",
132
+ "EXTENDED": "version",
133
+
134
+ "4320P": "resolution",
135
+ "2160P": "resolution",
136
+ "1080P": "resolution",
137
+ "720P": "resolution",
138
+ "576P": "resolution",
139
+ "480P": "resolution",
140
+ }
141
+
142
+
143
+ class P2pTags:
144
+ def __init__(
145
+ self, filename: str, title: str, year: str, mediafile_resolution: str,
146
+ season: int, episode: int, episode_title: str, releaser_sign: str,
147
+ tags_position: list, mediafile: MediaFile
148
+ ):
149
+ self.filename = filename
150
+ self.title = title
151
+ self.year = year
152
+ self.mediafile_resolution = mediafile_resolution
153
+ self.season = season
154
+ self.episode = episode
155
+ self.episode_title = episode_title
156
+ self.releaser_sign = releaser_sign
157
+ self.mediafile = mediafile
158
+ self.tags_position = tags_position
159
+ self.sign_in_title: str | None = None
160
+
161
+ self.tags_sorted = self._extract_tags()
162
+
163
+ def _extract_tags(self) -> list:
164
+ search_tags = sorted(TAG_TYPES.keys(), key=len, reverse=True)
165
+ pattern = re.compile(r'\b(?:' + '|'.join(map(re.escape, search_tags)) + r')\b', re.IGNORECASE)
166
+
167
+ # Search for tags in the title
168
+ tags_match = list(dict.fromkeys(pattern.findall(self.filename)))
169
+ # Search for channels
170
+ ch = ''
171
+ if self.mediafile.audio_track:
172
+ channel_s = self.mediafile.audio_track[0].get('channel_s', 0)
173
+ ch = {2: "2.0", 6: "5.1", 8: "7.1"}.get(channel_s, "")
174
+
175
+ # Categories of found tags
176
+ categories = [TAG_TYPES.get(tag.upper()) for tag in tags_match]
177
+ # Add video codec only if there is no video categories
178
+ if 'video' not in categories and self.mediafile.video_track:
179
+ video_codec = self.mediafile.video_track[0].get('format', "")
180
+ if video_codec:
181
+ tags_match.append(video_codec)
182
+
183
+ # Add audio codec only if there is no video categories
184
+ if 'audio' not in categories and self.mediafile.audio_track:
185
+ for audio in self.mediafile.audio_track:
186
+ # Other_format https://github.com/sbraz/pymediainfo/discussions/119#discussioncomment-2330673
187
+ # con format mi restituisce solo DTS
188
+ # Use other_format per format che hanno uno o piu tag
189
+ other_format = audio.get('other_format', [])
190
+ if other_format:
191
+ tags_match.append(other_format[0])
192
+
193
+ # Add tag language if there is no tag
194
+ flags = self._audio_lang()
195
+ missing_flags = [tag for tag in flags if tag not in tags_match]
196
+ tags_match.extend(missing_flags)
197
+
198
+ # Add subtitle tag if subtitle_track exist and there is no 'sub' in the title
199
+ if 'subtitle' not in categories and self.mediafile.subtitle_track:
200
+ tags_match.append("SUBS" if len(self.mediafile.subtitle_track) > 1 else "SUB")
201
+
202
+ return self._normalize_tags(tags_match, ch)
203
+
204
+ def _audio_lang(self) -> set:
205
+ if not self.mediafile:
206
+ return set()
207
+ langs = set()
208
+ for track in self.mediafile.audio_track:
209
+ for l in track.get('other_language', []):
210
+ c = ManageTitles.convert_iso(l)
211
+ if c:
212
+ if isinstance(c, list):
213
+ langs.update(c)
214
+ else:
215
+ langs.update(c)
216
+ break
217
+ return langs
218
+
219
+ def _normalize_tags(self, tags_match: list, channel_tag: str) -> list:
220
+ audio_translate = {
221
+ "AC3": "DD",
222
+ "AC-3": "DD",
223
+ "EAC3": "DD+",
224
+ "E-AC3": "DD+",
225
+ "DDP2.0": "DD+",
226
+ "DDP5.1": "DD+",
227
+ "DDP7.1": "DD+",
228
+ "DDP": "DD+",
229
+ "DTS XLL": "DTS-HD MA",
230
+ }
231
+
232
+ video_translate = {
233
+ "x264": "H.264",
234
+ "X.264": "H.264",
235
+ "X264": "H.264",
236
+ "AVC": "H.264",
237
+ "HEVC": "H.265",
238
+ "H265": "H.265",
239
+ "X.265": "H.265",
240
+ "X265": "H.265",
241
+ "x265": "H.265",
242
+ }
243
+
244
+ resolution_lower = {"4320P", "2160P", "1080P", "720P", "576P", "480P"}
245
+ codec_lower = {"X.264", "X265", "X.265", "X264"}
246
+
247
+ # verify the tags found in the title
248
+ normalized = []
249
+ for tag in tags_match:
250
+ t = tag.upper()
251
+ if t in audio_translate:
252
+ codec = audio_translate[t]
253
+ tag = f"{codec} {channel_tag}" if channel_tag else codec
254
+ elif t in video_translate:
255
+ tag = video_translate[t]
256
+ elif t in resolution_lower or t in codec_lower:
257
+ tag = t.lower()
258
+ elif t == "SUB":
259
+ tag = "SUBS" if len(self.mediafile.subtitle_track) > 1 else "SUB"
260
+ normalized.append(tag)
261
+
262
+ # Check if a 'resolution' tag exists and add mediafile resolution if it doesn't
263
+ if not any(TAG_TYPES.get(tag.upper()) == "resolution" for tag in normalized):
264
+ normalized.append(self.mediafile_resolution)
265
+
266
+ # Sort and alternate audio/flag tags
267
+ return self._sort_tags(normalized, channel_tag)
268
+
269
+ def _sort_tags(self, tags: list, channel_tag: str) -> list:
270
+ # /// Sort tags based on 2 rules:
271
+ # 1) Order by tag_positions
272
+ # 2) For audio and flag categories alternate them (audio/flag/audio/flag)
273
+ # We can't sort each tags so we create dedicated list
274
+ audio_q, flag_q, channel_q = deque(), deque(), deque()
275
+ other_groups = {}
276
+
277
+ for tag in tags:
278
+ cat = TAG_TYPES.get(tag.upper(), "unknown")
279
+ if cat == "audio":
280
+ audio_q.append(tag.upper())
281
+ elif cat == "channels":
282
+ channel_q.append(tag)
283
+ elif cat == "flag":
284
+ flag_q.append(tag.upper())
285
+ elif cat == "video":
286
+ other_groups.setdefault(cat, []).append(tag.upper())
287
+ else:
288
+ other_groups.setdefault(cat, []).append(tag)
289
+
290
+ # Alternate only if we have at least 2 audio and 2 flag tags
291
+ mixed = []
292
+ if len(audio_q) >= 2 and len(flag_q) >= 2:
293
+ while audio_q or flag_q:
294
+ if audio_q: mixed.append(audio_q.popleft())
295
+ if channel_q: mixed.append(channel_q.popleft())
296
+ if flag_q: mixed.append(flag_q.popleft())
297
+ else:
298
+ mixed = list(audio_q) + list(channel_q) + list(flag_q)
299
+
300
+ result = []
301
+ for cat in self.tags_position:
302
+ if cat in ("audio", "channels", "flag") and mixed:
303
+ result.extend(mixed)
304
+ mixed = []
305
+ elif cat in other_groups:
306
+ result.extend(other_groups[cat])
307
+ return result
308
+
309
+ def process(self) -> str:
310
+ se_str = ''
311
+ if self.season is not None and self.episode is not None:
312
+ se_str = f"S{self.season:02d}E{self.episode:02d}"
313
+ elif self.season is not None:
314
+ se_str = f"S{self.season:02d}"
315
+ elif self.episode is not None:
316
+ se_str = f"E{self.episode:02d}"
317
+
318
+ # Detect releaser sign in filename if not provided
319
+ if not self.releaser_sign:
320
+ filename, _ = os.path.splitext(os.path.basename(self.filename))
321
+ m = re.search(r'-([A-Za-z0-9]+)$', filename)
322
+ self.sign_in_title = f"-{m.group(1)}" if m and m.group(1) not in TAG_TYPES else ""
323
+ else:
324
+ self.sign_in_title = f"-{self.releaser_sign}"
325
+
326
+ parts = [self.title, str(self.year), se_str]
327
+ filtered_parts = [p for p in parts if p]
328
+ return f"{' '.join(filtered_parts)} {' '.join(self.tags_sorted)}{self.sign_in_title}"
@@ -15,7 +15,7 @@ from common.utility import ManageTitles
15
15
  from common import trackers
16
16
 
17
17
  config_file = "Unit3Dbot.json"
18
- version = "0.9.8"
18
+ version = "0.9.10"
19
19
 
20
20
  if os.name == "nt":
21
21
  WATCHER_DESTINATION_PATH: Path = Path(os.getenv("LOCALAPPDATA", ".")) / "Unit3Dup_config" / "watcher_destination_path"
@@ -250,12 +250,12 @@ class Validate:
250
250
  print(f"-> Invalid TAG position. The list is empty !")
251
251
  exit(1)
252
252
 
253
- if len(position_list) != 7:
253
+ if len(position_list) != 9:
254
254
  print(f"-> Invalid TAG position list. Wrong number of elements !")
255
255
  exit(1)
256
256
 
257
257
  for tag in position_list:
258
- if tag.lower() not in ["resolution", "source", "audio", "channels", "flag", "subtitle", "video"]:
258
+ if tag.lower() not in ["version","resolution", "platform","source", "audio", "channels", "flag", "subtitle", "video"]:
259
259
  print(f"-> Invalid TAG position '{tag}'. Please fix your configuration file")
260
260
  exit(1)
261
261
 
@@ -578,7 +578,7 @@ class Load:
578
578
  "PASSIMA_PRIORITY": 5,
579
579
  "IMARIDE_PRIORITY": 6,
580
580
  "NUMBER_OF_SCREENSHOTS": 4,
581
- "TAGS_POSITION": ["resolution", "source", "audio", "channels", "flag", "subtitle", "video"],
581
+ "TAGS_POSITION": ["version","resolution", "platform", "source", "audio", "channels", "flag", "subtitle", "video"],
582
582
  "YOUTUBE_FAV_CHANNEL_ID": "UCGCbxpnt25hWPFLSbvwfg_w",
583
583
  "YOUTUBE_CHANNEL_ENABLE": "False",
584
584
  "DUPLICATE_ON": "true",
@@ -43,17 +43,33 @@ class ManageTitles:
43
43
  "ES-MX": "ESP"
44
44
  }
45
45
 
46
+ # From mediainfo
47
+ long_name = {
48
+ "ENGLISH": "ENG",
49
+ "ITALIAN": "ITA",
50
+ "GERMAN": "DEU",
51
+ "FRENCH": "FRA",
52
+ "SPANISH": "ESP",
53
+ "JAPANESE": "JPN",
54
+ "BRAZILIAN": "BRA",
55
+ "RUSSIAN": "RUS",
56
+ "CHINESE": "CHN",
57
+ "AMERICAN": "USA",
58
+ "BRITISH": "GBR"
59
+ }
46
60
  @staticmethod
47
61
  def convert_iso(code) -> list | str | None:
48
62
  """ Convert iso 2 to 3 """
49
63
  code = code.upper()
50
-
51
64
  # if it's 'multilang'
52
65
  if '-' in code:
53
66
  codes = code.split('-')
54
67
  else:
55
68
  codes = [code]
56
69
 
70
+ if code in ManageTitles.long_name:
71
+ codes = [ManageTitles.long_name[code]]
72
+
57
73
  result = []
58
74
  for part in codes:
59
75
  # Capture the 2 or 3 letter code followed by '-' or end string
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  dynamic = ["dependencies"]
7
7
  name = "Unit3Dup"
8
- version = "0.9.8"
8
+ version = "0.9.10"
9
9
  description = "An uploader for the Unit3D torrent tracker"
10
10
  readme = "README.rst"
11
11
  requires-python = ">=3.10"
@@ -1,371 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- import os
3
- import re
4
- from common.utility import ManageTitles
5
- from common.mediainfo import MediaFile
6
- from collections import deque
7
-
8
- TAG_TYPES = {
9
- "WEB-DL": "source",
10
- "WEBDL": "source",
11
- "WEB-DLMUX": "source",
12
- "WEBDLMUX": "source",
13
- "WEBMUX": "source",
14
- "WEBRIP": "source",
15
- "BD-UNTOUCHED": "source",
16
- "REMUX": "source",
17
- "VU": "source",
18
- "UHD": "source",
19
- "BLURAY": "source",
20
- "AMZN": "source",
21
- "AMC": "source",
22
- "CN": "source",
23
- "CR": "source",
24
- "DCU": "source",
25
- "DISC": "source",
26
- "DSCP": "source",
27
- "DSNY": "source",
28
- "DSNP": "source",
29
- "DPLY": "source",
30
- "ESPN": "source",
31
- "FOOD": "source",
32
- "FOX": "source",
33
- "PLAY": "source",
34
- "HBO": "source",
35
- "HMAX": "source",
36
- "HGTV": "source",
37
- "HIST": "source",
38
- "HULU": "source",
39
- "MTOD": "source",
40
- "NATG": "source",
41
- "NF": "source",
42
- "NICK": "source",
43
- "NOW": "source",
44
- "PMNT": "source",
45
- "PMTP": "source",
46
- "PCOK": "source",
47
- "RKTN": "source",
48
- "SHO": "source",
49
- "SKST": "source",
50
- "STAN": "source",
51
- "STRP": "source",
52
- "STZ": "source",
53
- "TIMV": "source",
54
-
55
- "SUB": "subtitle",
56
- "SUBS": "subtitle",
57
- "ITA": "flag",
58
- "ENG": "flag",
59
- "FRA": "flag",
60
- "GER": "flag",
61
- "ESP": "flag",
62
- "JPN": "flag",
63
- "JAP": "flag",
64
- "POR": "flag",
65
- "PRT": "flag",
66
-
67
- "ATMOS": "audio",
68
- "TRUEHD": "audio",
69
- "DTSHD": "audio",
70
- "DTS-HD": "audio",
71
- "DTS-HD MA": "audio",
72
- "DDP7.1": "audio",
73
- "DDP5.1": "audio",
74
- "DDP2.0": "audio",
75
- "DTS": "audio",
76
-
77
- "DD7.1": "audio",
78
- "DD5.1": "audio",
79
- "DD2.0": "audio",
80
-
81
- "DD+ 7.1": "audio",
82
- "DD+ 5.1": "audio",
83
- "DD+ 2.0": "audio",
84
-
85
- "DD 7.1": "audio",
86
- "DD 5.1": "audio",
87
- "DD 2.0": "audio",
88
-
89
- "AAC2.0": "audio",
90
- "AAC5.1": "audio",
91
- "AC3": "audio",
92
- "DD": "audio",
93
- "DD+": "audio",
94
- "DDP": "audio",
95
- "E-AC3": "audio",
96
- "EAC3": "audio",
97
- "AC-3": "audio",
98
- "AAC": "audio",
99
- "AVC": "audio",
100
-
101
- "7.1": "channels",
102
- "5.1": "channels",
103
- "2.0": "channels",
104
-
105
- "H.264": "video",
106
- "X.264": "video",
107
- "X264": "video",
108
- "H264": "video",
109
- "H.265": "video",
110
- "X.265": "video",
111
- "X265": "video",
112
- "H265": "video",
113
- "HEVC": "video",
114
- "DV": "video",
115
- "HDR10": "video",
116
- "DVHDR10": "video",
117
- "DVHDR": "video",
118
- "HDRPLUS+": "video",
119
- "HDR10PLUS": "video",
120
- "HDR": "video",
121
- "HDR10+": "video",
122
- "FHDRIP": "video",
123
- "UHDRIP": "video",
124
- "FULL HD": "video",
125
- "FULLHD": "video",
126
- "HD": "video",
127
- "UHD 4K": "video",
128
-
129
- "4320P": "resolution",
130
- "2160P": "resolution",
131
- "1080P": "resolution",
132
- "720P": "resolution",
133
- "576P": "resolution",
134
- "480P": "resolution",
135
- }
136
-
137
-
138
- class P2pTags:
139
- def __init__(self, filename: str, title: str, year: str, mediafile_resolution: str, season: int, episode: int,
140
- episode_title: str, releaser_sign: str, tags_position: list, mediafile: MediaFile):
141
-
142
- self.filename = filename
143
- self.title = title
144
- self.year = year
145
- self.mediafile_resolution = mediafile_resolution
146
- self.season = season
147
- self.episode = episode
148
- self.episode_title = episode_title
149
- self.releaser_sign = releaser_sign
150
- self.mediafile = mediafile
151
- self.tags_position = tags_position
152
- self.sign_in_title: str | None = None
153
-
154
- search_tags = sorted(TAG_TYPES.keys(), key=len, reverse=True)
155
- pattern = re.compile(
156
- r'\b(?:' + '|'.join(map(re.escape, search_tags)) + r')\b',
157
- re.IGNORECASE
158
- )
159
-
160
- # Search for tags in the title
161
- tags_match = pattern.findall(filename.upper())
162
- # remove dope
163
- tags_match = list(dict.fromkeys(tags_match))
164
-
165
- # Search for channels
166
- channel_s = self.mediafile.audio_track[0].get('channel_s', None)
167
- ch: str = ''
168
- # map channels
169
- if channel_s == 6:
170
- ch = "5.1"
171
- elif channel_s == 8:
172
- ch = "7.1"
173
- elif channel_s == 2:
174
- ch = "2.0"
175
-
176
- # extract categories results
177
- categories = [TAG_TYPES.get(tag) for tag in tags_match]
178
-
179
- # Add video codec only if there is no video categories
180
- if 'video' not in categories:
181
- video_codec = self.mediafile.video_track[0].get('format', "") if self.mediafile.video_track else ""
182
- tags_match.append(video_codec)
183
-
184
- # Add audio codec only if there is no audio categories
185
- if 'audio' not in categories:
186
- audio_format = self.mediafile.audio_track[0].get('format', "") if self.mediafile.audio_track else ""
187
- tags_match.append(audio_format)
188
-
189
- # Add tag language if there is no tag
190
- flags = self._audio_lang()
191
- # are all the language tags from mediaInfo available in the title?
192
- if len(flags) != tags_match.count('flag'):
193
- missing_flags = [tag for tag in flags if tag not in tags_match]
194
- tags_match.extend(missing_flags)
195
-
196
- # Add subtitle tag if subtitle_track exist and there is no 'sub' in the title
197
- if 'subtitle' not in categories:
198
- sub_tag = "SUBS" if len(self.mediafile.subtitle_track) > 1 else "SUB"
199
- if self.mediafile.subtitle_track:
200
- tags_match.append(sub_tag)
201
-
202
- # Translate audio codec
203
- audio_translate = {
204
- "AC3": "DD",
205
- "AC-3": "DD",
206
- "EAC3": "DD+",
207
- "E-AC3": "DD+",
208
- "DDP2.0": "DD+",
209
- "DDP5.1": "DD+",
210
- "DDP7.1": "DD+",
211
- "DDP": "DD+",
212
- }
213
-
214
- # Translate video codec
215
- video_translate = {
216
- "H.264": "x264",
217
- "X.264": "x264",
218
- "X264": "x264",
219
- "AVC": "x264",
220
- "H.265": "x265",
221
- "H265": "x265",
222
- "X.265": "x265",
223
- "X265": "x265",
224
- "HEVC": "x265",
225
- }
226
-
227
- # lower the res..
228
- resolution_lower = {"4320P", "2160P", "1080P", "720P", "576P", "480P"}
229
- codec_lower = {'X.264', 'X265', 'X.265', 'X264'}
230
-
231
- has_channel_tag = ch in tags_match
232
- new_tags = []
233
-
234
- # verify the tags found in the title
235
- for tag in tags_match:
236
- t = tag.upper()
237
- if t in audio_translate:
238
- codec = audio_translate[t]
239
- # Add channels only if it does not exist
240
- if ch and not has_channel_tag:
241
- tag = f"{codec} {ch}"
242
- else:
243
- tag = codec
244
-
245
- elif t in video_translate:
246
- tag = video_translate[t]
247
-
248
- # Lower the res
249
- elif t in resolution_lower:
250
- tag = t.lower()
251
-
252
- # Fix the 'sub' word
253
- elif t == "SUB":
254
- sub_tag = "SUBS" if len(self.mediafile.subtitle_track) > 1 else "SUB"
255
- tag = sub_tag
256
-
257
- elif t in codec_lower:
258
- tag = t.lower()
259
-
260
- new_tags.append(tag)
261
-
262
- tags_match = new_tags
263
-
264
- # Check if a 'resolution' tag exists and add mediafile resolution if it doesn't
265
- if not any(TAG_TYPES.get(tag.upper()) == "resolution" for tag in tags_match):
266
- tags_match.append(self.mediafile_resolution)
267
-
268
- # /// Sort tags based on 2 rules:
269
- # 1) Order by tag_positions
270
- # 2) For audio and flag categories alternate them (audio/flag/audio/flag)
271
- # We can't sort each tags so we create dedicated list
272
- audio_q = deque()
273
- flag_q = deque()
274
- channel_q = deque()
275
-
276
- # This dict is for other categories
277
- other_groups = {}
278
-
279
- # Isolate audio and flag tags
280
- # All other tags will follow the normal precedence order
281
- for tag in tags_match:
282
- cat = TAG_TYPES.get(tag.upper(), "unknown")
283
- if cat == "audio":
284
- # Add audio tag to its queue
285
- audio_q.append(tag)
286
- elif cat == "channels":
287
- channel_q.append(tag)
288
- elif cat == "flag":
289
- # Add flag tag to its queue
290
- flag_q.append(tag)
291
- else:
292
- # Add the other tags to other_groups
293
- other_groups.setdefault(cat, []).append(tag)
294
-
295
- # Alternate only if we have at least 2 audio and 2 flag tags
296
- if len(audio_q) >= 2 and len(flag_q) >= 2:
297
- mixed_audio_ch_flag = []
298
- # Alternate
299
- while audio_q or flag_q:
300
- if audio_q:
301
- # get the first left audio and append to new list
302
- mixed_audio_ch_flag.append(audio_q.popleft())
303
- if channel_q:
304
- mixed_audio_ch_flag.append(channel_q.popleft())
305
- if flag_q:
306
- # get the first right flag and append to new list
307
- mixed_audio_ch_flag.append(flag_q.popleft())
308
- else:
309
- # otherwise goes for normal precedence
310
- mixed_audio_ch_flag = list(audio_q) + list(channel_q) + list(flag_q)
311
-
312
- # Rebuild the title ordering each tag+ audio/flag by tag_position
313
- result = []
314
- for cat in self.tags_position:
315
- if cat in ("audio", "channels", "flag"):
316
- # Insert the audio/flag processed tags
317
- if mixed_audio_ch_flag:
318
- result.extend(mixed_audio_ch_flag)
319
- # clear
320
- mixed_audio_ch_flag = []
321
- elif cat in other_groups:
322
- result.extend(other_groups[cat])
323
- self.tags_sorted = result
324
-
325
- def _audio_lang(self) -> list:
326
-
327
- if not self.mediafile:
328
- return []
329
-
330
- lang: list = []
331
- for track in self.mediafile.audio_track:
332
- l = track.get('other_language', None)
333
- if l:
334
- for t in l:
335
- c = ManageTitles.convert_iso(t)
336
- if c:
337
- if isinstance(c,list):
338
- lang.extend(c)
339
- break
340
- else:
341
- lang.append(c)
342
- break
343
- return lang
344
-
345
- def process(self) -> str:
346
- se_str = ''
347
- if self.season is not None and self.episode is not None:
348
- se_str = f"S{self.season:02d}E{self.episode:02d}"
349
- elif self.season is not None:
350
- se_str = f"S{self.season:02d}"
351
- elif self.episode is not None:
352
- se_str = f"E{self.episode:02d}"
353
-
354
- if not self.releaser_sign:
355
- # Search for a sign in the title
356
- base_name = os.path.basename(self.filename)
357
- filename, file_ext = os.path.splitext(base_name)
358
- m = re.search(r'-([A-Za-z0-9]+)$', filename)
359
-
360
- if m and m.group(1) not in TAG_TYPES:
361
- self.sign_in_title = f"-{m.group(1)}"
362
- else:
363
- self.sign_in_title = ""
364
- else:
365
- self.sign_in_title = f"-{self.releaser_sign}"
366
-
367
- parts = [self.title, str(self.year), se_str, self.episode_title]
368
- filtered_parts = [part for part in parts if part]
369
- title = ' '.join(filtered_parts)
370
-
371
- return f"{title} {' '.join(self.tags_sorted)}{self.sign_in_title}"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes