Unit3Dup 0.9.11__tar.gz → 0.9.13__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.
- {unit3dup-0.9.11 → unit3dup-0.9.13}/PKG-INFO +3 -3
- {unit3dup-0.9.11 → unit3dup-0.9.13}/README.rst +2 -2
- {unit3dup-0.9.11 → unit3dup-0.9.13}/Unit3Dup.egg-info/PKG-INFO +3 -3
- {unit3dup-0.9.11 → unit3dup-0.9.13}/Unit3Dup.egg-info/SOURCES.txt +1 -1
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/command.py +14 -16
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/settings.py +99 -5
- unit3dup-0.9.13/common/tags.py +291 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/utility.py +49 -29
- {unit3dup-0.9.11 → unit3dup-0.9.13}/pyproject.toml +1 -1
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/__main__.py +14 -3
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/bot.py +3 -2
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media.py +6 -47
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media_manager/ContentManager.py +5 -6
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media_manager/DocuManager.py +3 -3
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media_manager/GameManager.py +3 -3
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media_manager/TorrentManager.py +3 -5
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media_manager/VideoManager.py +30 -25
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/torrent.py +2 -2
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/upload.py +3 -3
- unit3dup-0.9.11/common/p2p_tags.py +0 -332
- {unit3dup-0.9.11 → unit3dup-0.9.13}/LICENSE +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/Unit3Dup.egg-info/dependency_links.txt +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/Unit3Dup.egg-info/entry_points.txt +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/Unit3Dup.egg-info/requires.txt +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/Unit3Dup.egg-info/top_level.txt +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/bdinfo_string.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/bittorrent.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/constants.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/database.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/ftpx/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/ftpx/client.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/ftpx/core/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/ftpx/core/ftpx_service.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/ftpx/core/ftpx_session.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/ftpx/core/menu.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/ftpx/core/models/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/ftpx/core/models/list.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/igdb/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/igdb/client.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/igdb/core/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/igdb/core/api.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/igdb/core/models/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/igdb/core/models/search.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/igdb/core/platformid.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/igdb/core/tags.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/imageHost.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/mediaresult.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/sessions/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/sessions/agents.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/sessions/exceptions.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/sessions/session.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/api.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/keywords.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/movie/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/movie/alternative_titles.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/movie/details.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/movie/movie.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/movie/nowplaying.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/movie/release_info.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/tvshow/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/tvshow/alternative.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/tvshow/details.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/tvshow/on_the_air.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/tvshow/translations.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/models/tvshow/tvshow.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/theMovieDB/core/videos.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/trailers/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/trailers/api.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/trailers/response.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/external_services/tvdb.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/extractor.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/frames.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/mediainfo.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/mediainfo_string.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/title.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/torrent_clients.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/trackers/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/trackers/data.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/trackers/itt.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/trackers/sis.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/common/trackers/trackers.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/requirements.txt +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/setup.cfg +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/automode.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/duplicate.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/exceptions.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media_manager/MediaInfoManager.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media_manager/SeedManager.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media_manager/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/media_manager/common.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/pvtDocu.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/pvtTorrent.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/pvtTracker.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/unit3dup/pvtVideo.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/view/__init__.py +0 -0
- {unit3dup-0.9.11 → unit3dup-0.9.13}/view/custom_console.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Unit3Dup
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.13
|
|
4
4
|
Summary: An uploader for the Unit3D torrent tracker
|
|
5
5
|
Author: Parzival
|
|
6
6
|
License-Expression: MIT
|
|
@@ -60,8 +60,8 @@ It performs the following tasks:
|
|
|
60
60
|
- Seeding in qBittorrent, Transmission or rTorrent
|
|
61
61
|
- Reseeding one or more torrents at a time
|
|
62
62
|
- Seed your torrents across different OS
|
|
63
|
-
-
|
|
64
|
-
|
|
63
|
+
- Generate various tags for titles: ``version``, ``resolution``, ``uhd``, ``platform``, ``source``, ``remux``, ``multi``, ``acodec``, ``channels``, ``flag``, ``subtitle``, ``vcodec``, ``hdr``, ``video_encoder``
|
|
64
|
+
|
|
65
65
|
|
|
66
66
|
unit3dup can grab the first page, convert it to an image (using xpdf),
|
|
67
67
|
and then the bot can upload it to an image host, then add the link to the torrent page description.
|
|
@@ -29,8 +29,8 @@ It performs the following tasks:
|
|
|
29
29
|
- Seeding in qBittorrent, Transmission or rTorrent
|
|
30
30
|
- Reseeding one or more torrents at a time
|
|
31
31
|
- Seed your torrents across different OS
|
|
32
|
-
-
|
|
33
|
-
|
|
32
|
+
- Generate various tags for titles: ``version``, ``resolution``, ``uhd``, ``platform``, ``source``, ``remux``, ``multi``, ``acodec``, ``channels``, ``flag``, ``subtitle``, ``vcodec``, ``hdr``, ``video_encoder``
|
|
33
|
+
|
|
34
34
|
|
|
35
35
|
unit3dup can grab the first page, convert it to an image (using xpdf),
|
|
36
36
|
and then the bot can upload it to an image host, then add the link to the torrent page description.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Unit3Dup
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.13
|
|
4
4
|
Summary: An uploader for the Unit3D torrent tracker
|
|
5
5
|
Author: Parzival
|
|
6
6
|
License-Expression: MIT
|
|
@@ -60,8 +60,8 @@ It performs the following tasks:
|
|
|
60
60
|
- Seeding in qBittorrent, Transmission or rTorrent
|
|
61
61
|
- Reseeding one or more torrents at a time
|
|
62
62
|
- Seed your torrents across different OS
|
|
63
|
-
-
|
|
64
|
-
|
|
63
|
+
- Generate various tags for titles: ``version``, ``resolution``, ``uhd``, ``platform``, ``source``, ``remux``, ``multi``, ``acodec``, ``channels``, ``flag``, ``subtitle``, ``vcodec``, ``hdr``, ``video_encoder``
|
|
64
|
+
|
|
65
65
|
|
|
66
66
|
unit3dup can grab the first page, convert it to an image (using xpdf),
|
|
67
67
|
and then the bot can upload it to an image host, then add the link to the torrent page description.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
2
|
import os
|
|
4
3
|
import argparse
|
|
5
4
|
from common.utility import System
|
|
6
5
|
from common.settings import Load
|
|
7
6
|
|
|
7
|
+
|
|
8
8
|
class CommandLine:
|
|
9
9
|
"""
|
|
10
10
|
Class to handle user input from the command line
|
|
@@ -27,9 +27,9 @@ class CommandLine:
|
|
|
27
27
|
parser.add_argument("-u", "--upload", type=str, help="Upload path")
|
|
28
28
|
parser.add_argument("-f", "--folder", type=str, help="Upload folder")
|
|
29
29
|
parser.add_argument("-scan", "--scan", type=str, help="Scan folder")
|
|
30
|
+
parser.add_argument("-b", "--buildtags", action="store_true", help="Auto build torrent name")
|
|
30
31
|
|
|
31
32
|
parser.add_argument("-reseed", "--reseed", action="store_true", help="reseed folder")
|
|
32
|
-
parser.add_argument("-gentitle", "--gentitle", action="store_true", help="")
|
|
33
33
|
parser.add_argument("-watcher", "--watcher", action="store_true", help="Start watcher")
|
|
34
34
|
|
|
35
35
|
parser.add_argument("-notitle", "--notitle", type=str, help="")
|
|
@@ -40,19 +40,19 @@ class CommandLine:
|
|
|
40
40
|
parser.add_argument('-force', nargs='?', const="movie", type=str, default=None)
|
|
41
41
|
parser.add_argument("-noseed", "--noseed", action="store_true", help="No seeding after upload")
|
|
42
42
|
parser.add_argument("-noup", "--noup", action="store_true", help="Torrent only. No upload")
|
|
43
|
-
parser.add_argument("-
|
|
43
|
+
parser.add_argument("-dup", "--duplicate", action="store_true", help="Find duplicates")
|
|
44
44
|
parser.add_argument("-personal", "--personal", action="store_true", help="Set to personal release")
|
|
45
45
|
|
|
46
46
|
parser.add_argument("-ftp", "--ftp", action="store_true", help="Connect to FTP")
|
|
47
47
|
|
|
48
48
|
# optional
|
|
49
|
-
parser.add_argument("-
|
|
50
|
-
parser.add_argument("-
|
|
49
|
+
parser.add_argument("-dmp", "--dump", action="store_true", help="Download all torrent titles")
|
|
50
|
+
parser.add_argument("-sch", "--search", type=str, help="Search for torrent")
|
|
51
51
|
parser.add_argument("-db", "--dbsave", action="store_true", help="Save the search results")
|
|
52
52
|
parser.add_argument("-i", "--info", type=str, help="Get info on torrent")
|
|
53
53
|
parser.add_argument("-up", "--uploader", type=str, help="Search by uploader")
|
|
54
|
-
parser.add_argument("-
|
|
55
|
-
parser.add_argument("-
|
|
54
|
+
parser.add_argument("-d", "--description", type=str, help="Search by description")
|
|
55
|
+
parser.add_argument("-bd", "--bdinfo", type=str, help="Show BDInfo")
|
|
56
56
|
parser.add_argument("-m", "--mediainfo", type=str, help="Show MediaInfo")
|
|
57
57
|
parser.add_argument("-st", "--startyear", type=str, help="Start year")
|
|
58
58
|
parser.add_argument("-en", "--endyear", type=str, help="End year")
|
|
@@ -68,8 +68,8 @@ class CommandLine:
|
|
|
68
68
|
parser.add_argument("-playid", "--playlist_id", type=str, help="Playlist ID")
|
|
69
69
|
parser.add_argument("-coll", "--collection_id", type=str, help="Collection ID")
|
|
70
70
|
parser.add_argument("-free", "--freelech", type=str, help="Freelech discount")
|
|
71
|
-
parser.add_argument("-
|
|
72
|
-
parser.add_argument("-
|
|
71
|
+
parser.add_argument("-al", "--alive", action="store_true", help="Alive torrent")
|
|
72
|
+
parser.add_argument("-dd", "--dead", action="store_true", help="Dead torrent")
|
|
73
73
|
parser.add_argument("-dy", "--dying", action="store_true", help="Dying torrent")
|
|
74
74
|
|
|
75
75
|
parser.add_argument(
|
|
@@ -98,8 +98,6 @@ class CommandLine:
|
|
|
98
98
|
"-pr", "--prelease", action="store_true", help="Personal release torrent"
|
|
99
99
|
)
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
101
|
# Parsing the User cli
|
|
104
102
|
self.args: parser = parser.parse_args()
|
|
105
103
|
|
|
@@ -113,10 +111,10 @@ class CommandLine:
|
|
|
113
111
|
# Test -force flag
|
|
114
112
|
if self.args.force:
|
|
115
113
|
self.args.force = self.args.force[:10]
|
|
116
|
-
if self.args.force.lower() not in [
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
if self.args.force.lower() not in [System.category_list[System.MOVIE],
|
|
115
|
+
System.category_list[System.GAME],
|
|
116
|
+
System.category_list[System.TV_SHOW],
|
|
117
|
+
System.category_list[System.DOCUMENTARY]]:
|
|
120
118
|
self.args.force = None
|
|
121
119
|
print("Invalid -force category")
|
|
122
|
-
exit()
|
|
120
|
+
exit()
|
|
@@ -15,7 +15,8 @@ from common.utility import ManageTitles
|
|
|
15
15
|
from common import trackers
|
|
16
16
|
|
|
17
17
|
config_file = "Unit3Dbot.json"
|
|
18
|
-
|
|
18
|
+
user_tags_file = "tags_list.json"
|
|
19
|
+
version = "0.9.13"
|
|
19
20
|
|
|
20
21
|
if os.name == "nt":
|
|
21
22
|
WATCHER_DESTINATION_PATH: Path = Path(os.getenv("LOCALAPPDATA", ".")) / "Unit3Dup_config" / "watcher_destination_path"
|
|
@@ -23,6 +24,7 @@ if os.name == "nt":
|
|
|
23
24
|
CACHE_PATH: Path = Path(os.getenv("LOCALAPPDATA", ".")) / "Unit3Dup_config" / "cache_path"
|
|
24
25
|
TORRENT_ARCHIVE_PATH: Path = Path(os.getenv("LOCALAPPDATA", ".")) / "Unit3Dup_config" / "torrent_archive_path"
|
|
25
26
|
DEFAULT_JSON_PATH: Path = Path(os.getenv("LOCALAPPDATA", ".")) / "Unit3Dup_config" / f"{config_file}"
|
|
27
|
+
USER_TAGS_PATH: Path = Path(os.getenv("LOCALAPPDATA", ".")) / "Unit3Dup_config" / f"{user_tags_file}"
|
|
26
28
|
|
|
27
29
|
else:
|
|
28
30
|
WATCHER_DESTINATION_PATH: Path = Path.home() / "Unit3Dup_config" / "watcher_destination_path"
|
|
@@ -30,7 +32,7 @@ else:
|
|
|
30
32
|
CACHE_PATH: Path = Path.home() / "Unit3Dup_config" / "cache_path"
|
|
31
33
|
TORRENT_ARCHIVE_PATH: Path = Path.home() / "Unit3Dup_config" / "torrent_archive_path"
|
|
32
34
|
DEFAULT_JSON_PATH: Path = Path.home() / "Unit3Dup_config" / f"{config_file}"
|
|
33
|
-
|
|
35
|
+
USER_TAGS_PATH: Path = Path.home() / "Unit3Dup_config" / f"{user_tags_file}"
|
|
34
36
|
|
|
35
37
|
def get_default_path(field: str)-> str:
|
|
36
38
|
default_paths = {
|
|
@@ -250,12 +252,13 @@ class Validate:
|
|
|
250
252
|
print(f"-> Invalid TAG position. The list is empty !")
|
|
251
253
|
exit(1)
|
|
252
254
|
|
|
253
|
-
if len(position_list)
|
|
255
|
+
if len(position_list) > 17 or len(position_list) < 5:
|
|
254
256
|
print(f"-> Invalid TAG position list. Wrong number of elements !")
|
|
255
257
|
exit(1)
|
|
256
258
|
|
|
257
259
|
for tag in position_list:
|
|
258
|
-
if tag.lower() not in ["
|
|
260
|
+
if tag.lower() not in ["title", "year", "season", "version", "resolution", "uhd", "platform", "source", "remux",
|
|
261
|
+
"multi", "acodec", "channels", "flag", "subtitle", "vcodec", "hdr", "video_encoder"]:
|
|
259
262
|
print(f"-> Invalid TAG position '{tag}'. Please fix your configuration file")
|
|
260
263
|
exit(1)
|
|
261
264
|
|
|
@@ -521,6 +524,92 @@ class Load:
|
|
|
521
524
|
def _load_config(self):
|
|
522
525
|
self.config = self.load_config()
|
|
523
526
|
|
|
527
|
+
@staticmethod
|
|
528
|
+
def create_tags_list_file(path: Path):
|
|
529
|
+
"""
|
|
530
|
+
Creates a tags list file
|
|
531
|
+
"""
|
|
532
|
+
TAG_TYPES = {
|
|
533
|
+
"REMUX": "remux",
|
|
534
|
+
"WEB-DL": "source",
|
|
535
|
+
"WEB-DLMUX": "source",
|
|
536
|
+
"WEBMUX": "source",
|
|
537
|
+
"WEBRIP": "source",
|
|
538
|
+
"BD-UNTOUCHED": "source",
|
|
539
|
+
"TS": "source",
|
|
540
|
+
"CAM": "source",
|
|
541
|
+
"HDTS": "source",
|
|
542
|
+
"MD": "source",
|
|
543
|
+
"UHDRIP": "source",
|
|
544
|
+
"BLURAY": "source",
|
|
545
|
+
"BRRIP": "source",
|
|
546
|
+
"BDRIP": "source",
|
|
547
|
+
"FHDRIP": "source",
|
|
548
|
+
|
|
549
|
+
"ATVP": "platform",
|
|
550
|
+
"AMZN": "platform",
|
|
551
|
+
"AMC": "platform",
|
|
552
|
+
"CN": "platform",
|
|
553
|
+
"CR": "platform",
|
|
554
|
+
"DCU": "platform",
|
|
555
|
+
"DISC": "platform",
|
|
556
|
+
"DSCP": "platform",
|
|
557
|
+
"DSNY": "platform",
|
|
558
|
+
"DSNP": "platform",
|
|
559
|
+
"DPLY": "platform",
|
|
560
|
+
"ESPN": "platform",
|
|
561
|
+
"FOOD": "platform",
|
|
562
|
+
"FOX": "platform",
|
|
563
|
+
"PLAY": "platform",
|
|
564
|
+
"HBO": "platform",
|
|
565
|
+
"HMAX": "platform",
|
|
566
|
+
"HGTV": "platform",
|
|
567
|
+
"HIST": "platform",
|
|
568
|
+
"HULU": "platform",
|
|
569
|
+
"MTOD": "platform",
|
|
570
|
+
"NATG": "platform",
|
|
571
|
+
"NF": "platform",
|
|
572
|
+
"NICK": "platform",
|
|
573
|
+
"NOW": "platform",
|
|
574
|
+
"PMNT": "platform",
|
|
575
|
+
"PMTP": "platform",
|
|
576
|
+
"PCOK": "platform",
|
|
577
|
+
"RKTN": "platform",
|
|
578
|
+
"SHO": "platform",
|
|
579
|
+
"SKST": "platform",
|
|
580
|
+
"STAN": "platform",
|
|
581
|
+
"STRP": "platform",
|
|
582
|
+
"STZ": "platform",
|
|
583
|
+
"TIMV": "platform",
|
|
584
|
+
|
|
585
|
+
"REPACK": "version",
|
|
586
|
+
"EXTENDED": "version",
|
|
587
|
+
"SUBBED": "version",
|
|
588
|
+
"MUX": "version",
|
|
589
|
+
"REMASTERED": "version",
|
|
590
|
+
"READNFO": "version",
|
|
591
|
+
"UNRATED": "version",
|
|
592
|
+
"LIMITED": "version",
|
|
593
|
+
"VU": "version",
|
|
594
|
+
"STV": "version",
|
|
595
|
+
"RECODE": "version",
|
|
596
|
+
"INTERNAL": "version",
|
|
597
|
+
"PROPER": "version",
|
|
598
|
+
"DUAL": "version",
|
|
599
|
+
"UNTOUCHED": "version",
|
|
600
|
+
"COMPLETE": "version",
|
|
601
|
+
"COMPLETA": "version",
|
|
602
|
+
|
|
603
|
+
"X264": "video_encoder",
|
|
604
|
+
"X265": "video_encoder",
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
608
|
+
with open(path, "w", encoding="utf-8") as tags_list_file:
|
|
609
|
+
json.dump(TAG_TYPES, tags_list_file, ensure_ascii=False, indent=4)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
|
|
524
613
|
|
|
525
614
|
@staticmethod
|
|
526
615
|
def create_default_json_file(path: Path):
|
|
@@ -578,7 +667,8 @@ class Load:
|
|
|
578
667
|
"PASSIMA_PRIORITY": 5,
|
|
579
668
|
"IMARIDE_PRIORITY": 6,
|
|
580
669
|
"NUMBER_OF_SCREENSHOTS": 4,
|
|
581
|
-
"TAGS_POSITION": ["
|
|
670
|
+
"TAGS_POSITION": ["title", "year", "season", "version", "resolution", "uhd", "platform", "source", "remux",
|
|
671
|
+
"multi", "acodec", "channels", "flag", "subtitle", "hdr", "vcodec", "video_encoder"],
|
|
582
672
|
"YOUTUBE_FAV_CHANNEL_ID": "UCGCbxpnt25hWPFLSbvwfg_w",
|
|
583
673
|
"YOUTUBE_CHANNEL_ENABLE": "False",
|
|
584
674
|
"DUPLICATE_ON": "true",
|
|
@@ -653,6 +743,10 @@ class Load:
|
|
|
653
743
|
print(f"Create default configuration file: {DEFAULT_JSON_PATH}")
|
|
654
744
|
Load.create_default_json_file(DEFAULT_JSON_PATH)
|
|
655
745
|
|
|
746
|
+
if not USER_TAGS_PATH.exists():
|
|
747
|
+
print(f"Create default Tags_list file: {USER_TAGS_PATH}")
|
|
748
|
+
Load.create_tags_list_file(USER_TAGS_PATH)
|
|
749
|
+
|
|
656
750
|
# Since the last bot version there might are new attributes
|
|
657
751
|
# Load the json file, find the difference between json file and the code. Update the user's json file
|
|
658
752
|
update_config = JsonConfig(default_json_path=DEFAULT_JSON_PATH)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from common.mediainfo import MediaFile
|
|
5
|
+
from common.utility import ManageTitles
|
|
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
|
+
"HDR10PLUS": "HDR10+",
|
|
13
|
+
"HDRPLUS+": "HDR10+",
|
|
14
|
+
"HDR10+": "HDR10+",
|
|
15
|
+
"HDR10": "HDR10",
|
|
16
|
+
"HDR10 / HDR10": "HDR10",
|
|
17
|
+
"DOVI": "DV",
|
|
18
|
+
"HDR": "HDR",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
audio_translate = {
|
|
22
|
+
"AC3": "DD",
|
|
23
|
+
"AAC LC": "AAC",
|
|
24
|
+
"AC-3": "DD",
|
|
25
|
+
"EAC3": "DD+",
|
|
26
|
+
"E-AC3": "DD+",
|
|
27
|
+
"E-AC-3": "DD+",
|
|
28
|
+
"E-AC-3 JOC": "DD+",
|
|
29
|
+
"DTS": "DTS",
|
|
30
|
+
"DTS ES": "DTS-ES",
|
|
31
|
+
"DTS ES XLL": "DTS-HD MA",
|
|
32
|
+
"DTS XLL": "DTS-HD MA",
|
|
33
|
+
"MLP FBA 16-ch": "TrueHD",
|
|
34
|
+
"MPEG Audio": "MPEG",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
video_translate = {
|
|
38
|
+
"AVC": "H.264",
|
|
39
|
+
"HEVC": "H.265",
|
|
40
|
+
"H265": "H.265",
|
|
41
|
+
"H264": "H.264",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
video_encoder_translate = {
|
|
45
|
+
"X265": "x.265",
|
|
46
|
+
"X264": "x.264",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SearchTags(object):
|
|
51
|
+
def __init__(self, filename, title: str, year: str, season: int, episode: int,
|
|
52
|
+
mediafile: MediaFile, tags_position: list, tags_list: dict, releaser_sign: str):
|
|
53
|
+
|
|
54
|
+
self.tags_position = tags_position
|
|
55
|
+
self.releaser_sign = releaser_sign
|
|
56
|
+
self.mediafile = mediafile
|
|
57
|
+
self.filename = filename
|
|
58
|
+
self.episode = episode
|
|
59
|
+
self.season = season
|
|
60
|
+
self.title = title
|
|
61
|
+
self.year = year
|
|
62
|
+
self.tags_dict = {}
|
|
63
|
+
self.tags_position = tags_position
|
|
64
|
+
self.TAG_TYPES = tags_list
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def normalize_version_tag(tag: str) -> str:
|
|
68
|
+
tag_esc = re.escape(tag)
|
|
69
|
+
# Filter hyphenated,space compounds
|
|
70
|
+
tag_esc = tag_esc.replace(r'\ ', r'[.\s_-]*')
|
|
71
|
+
return tag_esc
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def normalize_platform_tag(tag: str) -> str:
|
|
75
|
+
# escape
|
|
76
|
+
tag_esc = re.escape(tag)
|
|
77
|
+
return tag_esc
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def normalize_sources(tag: str) -> str:
|
|
81
|
+
tag_esc = re.escape(tag)
|
|
82
|
+
# Filter hyphenated,space compounds
|
|
83
|
+
tag_esc = tag_esc.replace(r'\ ', r'[.\s_-]*')
|
|
84
|
+
return tag_esc
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def normalize_video_encoder(tag: str) -> str:
|
|
88
|
+
tag_esc = re.escape(tag)
|
|
89
|
+
tag_esc = re.sub(r'([A-Z])(\d+)', r'\1[._-]?\2', tag_esc)
|
|
90
|
+
return tag_esc
|
|
91
|
+
|
|
92
|
+
def process(self) -> str:
|
|
93
|
+
patterns = []
|
|
94
|
+
|
|
95
|
+
# loop sorted TAG_TYPES dictionary
|
|
96
|
+
for i, (tag, category) in enumerate(
|
|
97
|
+
sorted(self.TAG_TYPES.items(), key=lambda x: len(x[0]), reverse=True)
|
|
98
|
+
):
|
|
99
|
+
if category == "version":
|
|
100
|
+
norm = self.normalize_version_tag(tag)
|
|
101
|
+
elif category == "platform":
|
|
102
|
+
norm = self.normalize_platform_tag(tag)
|
|
103
|
+
elif category == "source":
|
|
104
|
+
norm = self.normalize_sources(tag)
|
|
105
|
+
elif category == "video_encoder":
|
|
106
|
+
norm = self.normalize_video_encoder(tag)
|
|
107
|
+
else:
|
|
108
|
+
norm = re.escape(tag)
|
|
109
|
+
# Save a regex pattern for each category
|
|
110
|
+
patterns.append([norm, category])
|
|
111
|
+
|
|
112
|
+
# Run regex
|
|
113
|
+
for p, category in patterns:
|
|
114
|
+
regex = re.compile(r'(?<!\w)' + p + r'(?!\w)', re.IGNORECASE)
|
|
115
|
+
matches = regex.findall(self.filename)
|
|
116
|
+
if matches:
|
|
117
|
+
self.tags_dict.setdefault(category, []).append(matches[0])
|
|
118
|
+
|
|
119
|
+
# /// Read from mediainfo
|
|
120
|
+
updated_category = {}
|
|
121
|
+
for category in self.tags_position:
|
|
122
|
+
if category == "acodec":
|
|
123
|
+
updated_category = self.mediainfo_audio(category=category)
|
|
124
|
+
|
|
125
|
+
elif category == "vcodec":
|
|
126
|
+
updated_category = self.mediainfo_video(category=category)
|
|
127
|
+
|
|
128
|
+
elif category == "hdr":
|
|
129
|
+
updated_category = self.mediainfo_hdr(category=category)
|
|
130
|
+
|
|
131
|
+
elif category == "uhd":
|
|
132
|
+
updated_category = self.mediainfo_uhd(category=category)
|
|
133
|
+
|
|
134
|
+
elif category == "subtitle":
|
|
135
|
+
if self.mediafile.subtitle_track:
|
|
136
|
+
updated_category = {'subtitle': "SUBS" if len(self.mediafile.subtitle_track) > 1 else "SUB"}
|
|
137
|
+
|
|
138
|
+
if updated_category:
|
|
139
|
+
self.tags_dict.update(updated_category)
|
|
140
|
+
|
|
141
|
+
# /// Add S#E#, title, Year
|
|
142
|
+
se_str = ''
|
|
143
|
+
if self.season is not None and self.episode is not None:
|
|
144
|
+
se_str = f"S{self.season:02d}E{self.episode:02d}"
|
|
145
|
+
elif self.season is not None:
|
|
146
|
+
se_str = f"S{self.season:02d}"
|
|
147
|
+
elif self.episode is not None:
|
|
148
|
+
se_str = f"E{self.episode:02d}"
|
|
149
|
+
|
|
150
|
+
self.tags_dict.update({'title': self.title})
|
|
151
|
+
if self.year:
|
|
152
|
+
self.tags_dict.update({'year': self.year})
|
|
153
|
+
if se_str:
|
|
154
|
+
self.tags_dict.update({'season': se_str})
|
|
155
|
+
|
|
156
|
+
# /// Add Sign
|
|
157
|
+
if not self.releaser_sign:
|
|
158
|
+
filename, _ = os.path.splitext(os.path.basename(self.filename))
|
|
159
|
+
m = re.search(r'-([A-Za-z0-9]+)$', filename)
|
|
160
|
+
self.releaser_sign = f"-{m.group(1)}" if m and m.group(1) not in self.TAG_TYPES else ""
|
|
161
|
+
else:
|
|
162
|
+
self.releaser_sign = f"-{self.releaser_sign}"
|
|
163
|
+
|
|
164
|
+
# /// Order according to tag position
|
|
165
|
+
tags_dict = {
|
|
166
|
+
k: self.tags_dict[k]
|
|
167
|
+
for k in self.tags_position
|
|
168
|
+
if k in self.tags_dict
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# /// Build the title
|
|
172
|
+
build = []
|
|
173
|
+
for k, v in tags_dict.items():
|
|
174
|
+
if isinstance(v, list):
|
|
175
|
+
build.append(' '.join(v))
|
|
176
|
+
else:
|
|
177
|
+
build.append(str(v))
|
|
178
|
+
|
|
179
|
+
refactored = ' '.join(build) + self.releaser_sign
|
|
180
|
+
return refactored
|
|
181
|
+
|
|
182
|
+
def mediainfo_audio(self, category: str) -> dict:
|
|
183
|
+
languages = []
|
|
184
|
+
audio_codecs = []
|
|
185
|
+
if self.mediafile.audio_track:
|
|
186
|
+
for audio in self.mediafile.audio_track:
|
|
187
|
+
other_format = audio.get('other_format', [])
|
|
188
|
+
if other_format:
|
|
189
|
+
codec_translated = audio_translate.get(other_format[0], '')
|
|
190
|
+
if not codec_translated:
|
|
191
|
+
codec_translated = other_format[0]
|
|
192
|
+
# Check Atmos
|
|
193
|
+
dolby = audio.get('commercial_name', "").lower()
|
|
194
|
+
atmos = 'Atmos' if 'atmos' in dolby else ''
|
|
195
|
+
# Add audio codec
|
|
196
|
+
channel_s = audio.get('channel_s', 0)
|
|
197
|
+
# Add channels
|
|
198
|
+
ch = {2: "2.0", 6: "5.1", 8: "7.1"}.get(channel_s, "")
|
|
199
|
+
if f"{codec_translated} {ch} {atmos}".strip() not in audio_codecs:
|
|
200
|
+
audio_codecs.append(f"{codec_translated} {ch} {atmos}".strip())
|
|
201
|
+
# print(f"Mediainfo {other_format} -> {codec_translated} {ch} {atmos}")
|
|
202
|
+
|
|
203
|
+
# Add flags
|
|
204
|
+
for l in audio.get('other_language', []):
|
|
205
|
+
c = ManageTitles.convert_iso(l)
|
|
206
|
+
if c:
|
|
207
|
+
if isinstance(c, list):
|
|
208
|
+
languages.append(c[0])
|
|
209
|
+
else:
|
|
210
|
+
languages.append(c)
|
|
211
|
+
break
|
|
212
|
+
languages = list(dict.fromkeys(languages))
|
|
213
|
+
# Add multilanguage tag when languages > 2
|
|
214
|
+
if len(languages) > 2:
|
|
215
|
+
self.tags_dict.update({'multi': 'MULTI'})
|
|
216
|
+
|
|
217
|
+
audio_codecs.extend(languages)
|
|
218
|
+
return {category: audio_codecs}
|
|
219
|
+
|
|
220
|
+
def mediainfo_video(self, category: str) -> dict:
|
|
221
|
+
codec_translated = {}
|
|
222
|
+
if self.mediafile.video_track:
|
|
223
|
+
for video in self.mediafile.video_track:
|
|
224
|
+
video_format = video.get('format', "")
|
|
225
|
+
codec_translated = video_translate.get(video_format, video_format)
|
|
226
|
+
if codec_translated:
|
|
227
|
+
return {category: codec_translated}
|
|
228
|
+
return codec_translated
|
|
229
|
+
|
|
230
|
+
def mediainfo_hdr(self, category: str) -> dict:
|
|
231
|
+
if self.mediafile.video_track:
|
|
232
|
+
for video in self.mediafile.video_track:
|
|
233
|
+
hdr_format_commercial = video.get('hdr_format_commercial', "")
|
|
234
|
+
hdr_format = video.get('hdr_format', "")
|
|
235
|
+
# Check hdr
|
|
236
|
+
if hdr_format_commercial:
|
|
237
|
+
# print(f"hdr_format_commercial: {hdr_format_commercial}")
|
|
238
|
+
# print(f"hdr_format: {hdr_format}")
|
|
239
|
+
hdr = ''
|
|
240
|
+
if hdr_format_commercial in hdr_map:
|
|
241
|
+
# print(
|
|
242
|
+
# f"hdr_format_commercial: {hdr_format_commercial} -> Tag: {hdr_map[hdr_format_commercial]}")
|
|
243
|
+
hdr = hdr_map[hdr_format_commercial]
|
|
244
|
+
# Check dolby vision
|
|
245
|
+
if 'DOLBY VISION' in hdr_format_commercial.upper() or 'DOLBY VISION' in hdr_format.upper():
|
|
246
|
+
hdr = f"DOLBY VISION {hdr}"
|
|
247
|
+
# print(hdr)
|
|
248
|
+
return {category: hdr_map[hdr]}
|
|
249
|
+
return {}
|
|
250
|
+
|
|
251
|
+
def mediainfo_uhd(self, category: str) -> dict:
|
|
252
|
+
"""
|
|
253
|
+
identify resolution based on Height and Width tolerance 5%
|
|
254
|
+
"""
|
|
255
|
+
result = {}
|
|
256
|
+
if self.mediafile.video_track:
|
|
257
|
+
video_height = int(self.mediafile.video_track[0].get('height', 0))
|
|
258
|
+
video_width = int(self.mediafile.video_track[0].get('width', 0))
|
|
259
|
+
|
|
260
|
+
# print(f"VideoTrack : W{video_width} x H{video_height}")
|
|
261
|
+
|
|
262
|
+
# Calculate range 5%
|
|
263
|
+
def in_range(value, standard):
|
|
264
|
+
tol = standard * 0.05
|
|
265
|
+
return standard - tol <= value <= standard + tol
|
|
266
|
+
|
|
267
|
+
# /// UHD
|
|
268
|
+
if video_height >= 2000 or video_width >= 3840:
|
|
269
|
+
result[category] = 'UHD'
|
|
270
|
+
result['resolution'] = '2160p'
|
|
271
|
+
# /// Full HD
|
|
272
|
+
elif in_range(video_height, 1080) or in_range(video_width, 1920):
|
|
273
|
+
result[category] = 'FullHD'
|
|
274
|
+
result['resolution'] = '1080p'
|
|
275
|
+
# /// HD
|
|
276
|
+
elif in_range(video_height, 720) or in_range(video_width, 1280):
|
|
277
|
+
result[category] = 'HD'
|
|
278
|
+
result['resolution'] = '720p'
|
|
279
|
+
# /// SD 576p
|
|
280
|
+
elif in_range(video_height, 576) or in_range(video_width, 768):
|
|
281
|
+
result[category] = 'SD'
|
|
282
|
+
result['resolution'] = '576p'
|
|
283
|
+
# /// SD 480p
|
|
284
|
+
elif in_range(video_height, 480) or in_range(video_width, 640):
|
|
285
|
+
result[category] = 'SD'
|
|
286
|
+
result['resolution'] = '480p'
|
|
287
|
+
else:
|
|
288
|
+
result[category] = 'unknown'
|
|
289
|
+
result['resolution'] = f'{video_width}x{video_height}'
|
|
290
|
+
|
|
291
|
+
return result
|