Skip to content
This repository was archived by the owner on Jun 14, 2026. It is now read-only.

Commit 8dad6f8

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents f230cab + 1c98598 commit 8dad6f8

33 files changed

Lines changed: 1604 additions & 1428 deletions

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Better just to be on this fork and bug me about my bugs, rather than bugging som
2626
- Can automagically search qBitTorrent version 5+ clients for matching existing torrent.
2727
- Generates proper name for your upload using Mediainfo/BDInfo and TMDb/IMDb conforming to site rules.
2828
- Checks for existing releases already on site.
29-
- Uploads to ACM/Aither/AL/ANT/AR/ASC/BHD/BHDTV/BLU/BT/CBR/DC/DP/FRIKI/FNP/FL/HDB/HDS/HDT/HHD/HP/HUNO/ITT/LCD/LST/LT/MTV/NBL/OE/OTW/PSS/PT/PTP/PTER/PTT/RF/R4E(limited)/RAS/RTF/SAM/SHRI/SN/SP/SPD/STC/STT/TLC/THR/TL/TOCA/TVC/TTG/UHD/ULCX/UTP/YOINK/YUS
29+
- Uploads to ACM/Aither/AL/ANT/AR/ASC/BHD/BHDTV/BLU/BT/CBR/DC/DP/FRIKI/FNP/FL/HDB/HDS/HDT/HHD/HP/HUNO/ITT/LCD/LDU/LST/LT/MTV/NBL/OE/OTW/PSS/PT/PTP/PTER/PTT/RF/R4E(limited)/RAS/RTF/SAM/SHRI/SN/SP/SPD/STC/STT/TLC/THR/TL/TOCA/TVC/TTG/UHD/ULCX/UTP/YOINK/YUS
3030
- Adds to your client with fast resume, seeding instantly (rtorrent/qbittorrent/deluge/watch folder).
3131
- ALL WITH MINIMAL INPUT!
3232
- Currently works with .mkv/.mp4/Blu-ray/DVD/HD-DVDs.

‎data/example-config.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@
249249

250250
"TRACKERS": {
251251
# Which trackers do you want to upload to?
252-
# Available tracker: ACM, AITHER, AL, ANT, AR, ASC, BHD, BHDTV, BLU, BT, CBR, DC, DP, FNP, FRIKI, HDB, HDS, HDT, HHD, HUNO, ITT, LCD, LST, LT, MTV, NBL, OE, OTW, PSS, PT, PTER, PTP, PTT, R4E, RAS, RF, RTF, SAM, SN, STC, THR, TIK, TL, TOCA, UHD, ULCX, UTP, YOINK, YUS
252+
# Available tracker: ACM, AITHER, AL, ANT, AR, ASC, BHD, BHDTV, BLU, BT, CBR, DC, DP, FNP, FRIKI, HDB, HDS, HDT, HHD, HUNO, ITT, LCD, LDU, LST, LT, MTV, NBL, OE, OTW, PSS, PT, PTER, PTP, PTT, R4E, RAS, RF, RTF, SAM, SN, STC, THR, TIK, TL, TOCA, UHD, ULCX, UTP, YOINK, YUS
253253
# Only add the trackers you want to upload to on a regular basis
254254
"default_trackers": "",
255255

@@ -453,6 +453,13 @@
453453
"announce_url": "https://locadora.cc/announce/customannounceurl",
454454
"anon": False,
455455
},
456+
"LDU": {
457+
# Instead of using the tracker acronym for folder name when sym/hard linking, you can use a custom name
458+
"link_dir_name": "",
459+
"api_key": "",
460+
"announce_url": "https://theldu.to/announce/customannounceurl",
461+
"anon": False,
462+
},
456463
"LST": {
457464
# Instead of using the tracker acronym for folder name when sym/hard linking, you can use a custom name
458465
"link_dir_name": "",

‎data/version.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
__version__ = "5.1.5"
2+
3+
"""
4+
Changelog for version 5.1.5 (2025-07-18):
5+
6+
## What's Changed
7+
* Fix LT edit name by @Hielito2 in https://github.com/Audionut/Upload-Assistant/pull/595
8+
* HUNO encode checks by @Audionut in https://github.com/Audionut/Upload-Assistant/pull/600
9+
* Update ULCX Banned Release Groups by @backstab5983 in https://github.com/Audionut/Upload-Assistant/pull/601
10+
* Fix filenames in Description when uploading TV [ ] by @Hielito2 in https://github.com/Audionut/Upload-Assistant/pull/603
11+
* Handles None imdb_id string by @jacobcxdev in https://github.com/Audionut/Upload-Assistant/pull/606
12+
* Fix variable reuse by @moontime-goose in https://github.com/Audionut/Upload-Assistant/pull/607
13+
* Add image restriction to DigitalCore by @PythonCoderAS in https://github.com/Audionut/Upload-Assistant/pull/609
14+
* Dp banned groups by @OrbitMPGH in https://github.com/Audionut/Upload-Assistant/pull/611
15+
* centralized language handling by @Audionut in https://github.com/Audionut/Upload-Assistant/pull/604
16+
* Add randomness to image taking function and cleanup by @Hielito2 in https://github.com/Audionut/Upload-Assistant/pull/608
17+
* ASC - remove dependency on tracker API by @wastaken7 in https://github.com/Audionut/Upload-Assistant/pull/610
18+
* BT - remove dependency on tracker API by @wastaken7 in https://github.com/Audionut/Upload-Assistant/pull/612
19+
* Add LDU support by @Audionut in https://github.com/Audionut/Upload-Assistant/pull/613
20+
* Other fixes here and there.
21+
22+
## New Contributors
23+
* @jacobcxdev made their first contribution in https://github.com/Audionut/Upload-Assistant/pull/606
24+
* @moontime-goose made their first contribution in https://github.com/Audionut/Upload-Assistant/pull/607
25+
* @PythonCoderAS made their first contribution in https://github.com/Audionut/Upload-Assistant/pull/609
26+
* @OrbitMPGH made their first contribution in https://github.com/Audionut/Upload-Assistant/pull/611
27+
28+
**Full Changelog**: https://github.com/Audionut/Upload-Assistant/compare/5.1.4.1...5.1.5
29+
"""
30+
131
__version__ = "5.1.4.1"
232

333
"""

‎requirements.txt‎

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
1-
torf
2-
guessit
3-
ffmpeg-python
4-
pymediainfo; sys_platform == "win32"
5-
pymediainfo==6.0.1; sys_platform == "linux"
6-
pymediainfo==6.0.1; sys_platform == "darwin"
1+
aiofiles
2+
aiohttp
73
anitopy
4+
beautifulsoup4
5+
bencode.py
6+
click
87
cli-ui
9-
qbittorrent-api
108
deluge-client
11-
requests
12-
pyimgbox
9+
discord
10+
ffmpeg-python
11+
guessit
12+
httpx
13+
Jinja2
14+
langcodes
1315
nest_asyncio
14-
bencode.py
15-
unidecode
16-
beautifulsoup4
16+
packaging
17+
Pillow
18+
psutil
19+
pyimgbox
20+
pymediainfo; sys_platform == "win32"
21+
pymediainfo==6.0.1; sys_platform == "darwin"
22+
pymediainfo==6.0.1; sys_platform == "linux"
23+
pyotp
1724
pyoxipng
25+
pyparsebluray
26+
qbittorrent-api
27+
requests
1828
rich
19-
Jinja2
20-
pyotp
21-
click
22-
aiohttp
23-
Pillow
29+
tmdbsimple
30+
torf
2431
tqdm
25-
urllib3
26-
httpx
27-
aiofiles
28-
pyparsebluray
29-
langcodes
3032
transmission_rpc
31-
psutil
32-
packaging
33-
tmdbsimple
34-
discord
33+
unidecode
34+
urllib3

‎src/args.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,12 @@ def parse(self, args, meta):
105105
parser.add_argument('--no-year', dest='no_year', action='store_true', required=False, help="Remove Year from title")
106106
parser.add_argument('--no-aka', dest='no_aka', action='store_true', required=False, help="Remove AKA from title")
107107
parser.add_argument('--no-dub', dest='no_dub', action='store_true', required=False, help="Remove Dubbed from title")
108+
parser.add_argument('--no-dual', dest='no_dual', action='store_true', required=False, help="Remove Dual-Audio from title")
108109
parser.add_argument('--no-tag', dest='no_tag', action='store_true', required=False, help="Remove Group Tag from title")
109110
parser.add_argument('--no-edition', dest='no_edition', action='store_true', required=False, help="Remove Edition from title")
110111
parser.add_argument('--dual-audio', dest='dual_audio', action='store_true', required=False, help="Add Dual-Audio to the title")
111112
parser.add_argument('-ol', '--original-language', dest='manual_language', nargs=1, required=False, help="Set original audio language")
112-
parser.add_argument('-oil', '--only-if-languages', dest='has_languages', nargs=1, required=False, help="Require at least one of the languages to upload. Comma separated list e.g. 'en,fr,pt'", type=str)
113+
parser.add_argument('-oil', '--only-if-languages', dest='has_languages', nargs='*', required=False, help="Require at least one of the languages to upload. Comma separated list e.g. 'English, French, Spanish'", type=str)
113114
parser.add_argument('-ns', '--no-seed', action='store_true', required=False, help="Do not add torrent to the client")
114115
parser.add_argument('-year', '--year', dest='manual_year', nargs=1, required=False, help="Override the year found", type=int, default=0)
115116
parser.add_argument('-ptp', '--ptp', nargs=1, required=False, help="PTP torrent id/permalink", type=str)

‎src/audio.py‎

Lines changed: 14 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -18,86 +18,21 @@ async def get_audio_v2(mi, meta, bdinfo):
1818
# Channels
1919
chan = bdinfo.get('audio', [{}])[0].get('channels', '')
2020
else:
21-
track_num = -1
2221
tracks = mi.get('media', {}).get('track', [])
23-
highest_channels = -1
24-
original_language = meta.get('original_language', '')
25-
26-
# First try: Find highest channel count track in original language (if known)
27-
if original_language:
28-
for i, t in enumerate(tracks):
29-
if t.get('@type') != "Audio":
30-
continue
31-
32-
# Skip commentary tracks
33-
if "commentary" in (t.get('Title') or '').lower():
34-
continue
35-
36-
# Check if this is in the original language
37-
audio_language = t.get('Language', '')
38-
language_match = False
39-
40-
if isinstance(audio_language, str):
41-
if audio_language.startswith(original_language):
42-
language_match = True
43-
44-
# For some language variants
45-
variants = ['zh', 'cn', 'cmn', 'no', 'nb']
46-
if any(audio_language.startswith(var) for var in variants) and any(original_language.startswith(var) for var in variants):
47-
language_match = True
48-
49-
# Only consider this track if it's in original language
50-
if language_match:
51-
# Get channel count
52-
channels = t.get('Channels_Original', t.get('Channels', 0))
53-
try:
54-
if not str(channels).isnumeric():
55-
channels = t.get('Channels', 0)
56-
channels_float = float(str(channels).replace('.1', '.1'))
57-
except (ValueError, TypeError):
58-
channels_float = 0
59-
60-
# Update if this has more channels than previous best
61-
if channels_float > highest_channels:
62-
highest_channels = channels_float
63-
track_num = i
64-
65-
# Second try: If no original language track found or original language not known,
66-
# just find the track with highest channel count
67-
if track_num == -1:
68-
for i, t in enumerate(tracks):
69-
if t.get('@type') != "Audio":
70-
continue
71-
72-
# Skip commentary tracks
73-
if "commentary" in (t.get('Title') or '').lower():
74-
continue
75-
76-
# Get channel count
77-
channels = t.get('Channels_Original', t.get('Channels', 0))
78-
try:
79-
if not str(channels).isnumeric():
80-
channels = t.get('Channels', 0)
81-
channels_float = float(str(channels).replace('.1', '.1'))
82-
except (ValueError, TypeError):
83-
channels_float = 0
84-
85-
if channels_float > highest_channels:
86-
highest_channels = channels_float
87-
track_num = i
88-
89-
# Fallback to first audio track if no valid track was found
90-
if track_num == -1:
91-
for i, t in enumerate(tracks):
92-
if t.get('@type') == "Audio":
93-
track_num = i
94-
break
95-
96-
# If still no audio track found, use track 2 (common index for first audio track)
97-
if track_num == -1:
98-
track_num = 2
22+
audio_tracks = [t for t in tracks if t.get('@type') == "Audio"]
23+
first_audio_track = None
24+
if audio_tracks:
25+
tracks_with_order = [t for t in audio_tracks if t.get('StreamOrder')]
26+
if tracks_with_order:
27+
first_audio_track = min(tracks_with_order, key=lambda x: int(x.get('StreamOrder', '999')))
28+
else:
29+
tracks_with_id = [t for t in audio_tracks if t.get('ID')]
30+
if tracks_with_id:
31+
first_audio_track = min(tracks_with_id, key=lambda x: int(x.get('ID', '999')))
32+
else:
33+
first_audio_track = audio_tracks[0]
9934

100-
track = tracks[track_num] if len(tracks) > track_num else {}
35+
track = first_audio_track if first_audio_track else {}
10136
format = track.get('Format', '')
10237
commercial = track.get('Format_Commercial', '') or track.get('Format_Commercial_IfAny', '')
10338

@@ -183,11 +118,6 @@ async def get_audio_v2(mi, meta, bdinfo):
183118
console.print(f"[bold red]This release has a(n) {audio_language} audio track, and may be considered bloated")
184119
time.sleep(5)
185120

186-
if not audio_language:
187-
if meta['debug']:
188-
console.print("DEBUG: Audio language is None or empty, setting to 'und'")
189-
audio_language = "und"
190-
191121
if (
192122
orig_lang == "en"
193123
and eng
@@ -197,7 +127,7 @@ async def get_audio_v2(mi, meta, bdinfo):
197127
meta['bloated'] = True
198128
time.sleep(5)
199129

200-
if (eng and (orig or non_en_non_commentary)) or (orig and non_en_non_commentary) and len(audio_tracks) > 1:
130+
if ((eng and (orig or non_en_non_commentary)) or (orig and non_en_non_commentary)) and len(audio_tracks) > 1 and not meta.get('no_dual', False):
201131
dual = "Dual-Audio"
202132
elif eng and not orig and orig_lang not in ['zxx', 'xx', 'en', None] and not meta.get('no_dub', False):
203133
dual = "Dubbed"
@@ -297,27 +227,3 @@ async def get_audio_v2(mi, meta, bdinfo):
297227
audio = f"{dual} {codec or ''} {format_settings or ''} {chan or ''}{extra or ''}"
298228
audio = ' '.join(audio.split())
299229
return audio, chan, has_commentary
300-
301-
302-
async def get_audio_languages(mi, meta):
303-
tracks = mi.get('media', {}).get('track', [])
304-
305-
languages = []
306-
307-
for i, t in enumerate(tracks):
308-
if t.get('@type') != "Audio":
309-
continue
310-
311-
language = t.get('Language', '')
312-
if meta['debug']:
313-
console.print(f"DEBUG: Track {i} Language = {language} ({type(language)})")
314-
315-
if isinstance(language, str): # Normal case
316-
languages.append(language.lower())
317-
elif isinstance(language, dict): # Handle unexpected dict case
318-
if 'value' in language: # Check if a known key exists
319-
extracted = language['value']
320-
if isinstance(extracted, str):
321-
languages.append(extracted.lower())
322-
323-
return languages

‎src/cleanup.py‎

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
import threading
44
import multiprocessing
55
import sys
6+
import os
7+
import subprocess
8+
import re
69
from src.console import console
710
from concurrent.futures import ThreadPoolExecutor
11+
if os.name == "posix":
12+
import termios
813

914
running_subprocesses = set()
1015
thread_executor: ThreadPoolExecutor = None
@@ -15,7 +20,7 @@ async def cleanup():
1520
"""Ensure all running tasks, threads, and subprocesses are properly cleaned up before exiting."""
1621
# console.print("[yellow]Cleaning up tasks before exiting...[/yellow]")
1722

18-
# ✅ Step 1: Shutdown ThreadPoolExecutor **before checking for threads**
23+
# Step 1: Shutdown ThreadPoolExecutor **before checking for threads**
1924
global thread_executor
2025
if thread_executor:
2126
# console.print("[yellow]Shutting down thread pool executor...[/yellow]")
@@ -146,3 +151,58 @@ def kill_all_threads():
146151
# console.print(f" - {t.name} (Alive: {t.is_alive()})")
147152

148153
# console.print("[green]Thread cleanup completed.[/green]")
154+
155+
156+
# Wrapped "erase key check and save" in tty check so that Python won't complain if UA is called by a script
157+
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty() and not sys.stdin.closed:
158+
try:
159+
output = subprocess.check_output(['stty', '-a']).decode()
160+
erase_key = re.search(r' erase = (\S+);', output).group(1)
161+
except (IOError, OSError):
162+
pass
163+
164+
165+
def reset_terminal():
166+
"""Reset the terminal while allowing the script to continue running (Linux/macOS only)."""
167+
if os.name != "posix":
168+
return
169+
170+
try:
171+
if not sys.stderr.closed:
172+
sys.stderr.flush()
173+
174+
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty() and not sys.stdin.closed:
175+
try:
176+
subprocess.run(["stty", "sane"], check=False)
177+
subprocess.run(["stty", "erase", erase_key], check=False) # explicitly restore backspace character to original value
178+
if hasattr(termios, 'tcflush'):
179+
termios.tcflush(sys.stdin.fileno(), termios.TCIOFLUSH)
180+
subprocess.run(["stty", "-ixon"], check=False)
181+
except (IOError, OSError):
182+
pass
183+
184+
if not sys.stdout.closed:
185+
try:
186+
sys.stdout.write("\033[0m")
187+
sys.stdout.flush()
188+
sys.stdout.write("\033[?25h")
189+
sys.stdout.flush()
190+
except (IOError, ValueError):
191+
pass
192+
193+
# Kill background jobs
194+
try:
195+
os.system("jobs -p | xargs -r kill 2>/dev/null")
196+
except Exception:
197+
pass
198+
199+
if not sys.stderr.closed:
200+
sys.stderr.flush()
201+
202+
except Exception as e:
203+
try:
204+
if not sys.stderr.closed:
205+
sys.stderr.write(f"Error during terminal reset: {e}\n")
206+
sys.stderr.flush()
207+
except Exception:
208+
pass # At this point we can't do much more

0 commit comments

Comments
 (0)