From 4a773d1db04af8605182965b7b7f6105d1b51f7d Mon Sep 17 00:00:00 2001 From: chu23465 <130033130+chu23465@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:24:28 +0530 Subject: [PATCH] Features Max audio compatibility added. Changes for Hybrid DV/HDR. --- install.sh | 1 + vinetrimmer/commands/dl.py | 20 +++- .../hisense_smarttv_he55a7000euwts_sl3000.prd | Bin 2108 -> 2108 bytes vinetrimmer/objects/tracks.py | 109 ++++++++++++++++-- vinetrimmer/utils/click.py | 2 + 5 files changed, 121 insertions(+), 11 deletions(-) diff --git a/install.sh b/install.sh index a2b7edf..496c8cb 100644 --- a/install.sh +++ b/install.sh @@ -4,6 +4,7 @@ python -m pip install poetry==1.8.5 poetry config virtualenvs.in-project true poetry lock --no-update poetry install +sudo add-apt-repository ppa:ubuntuhandbook1/apps sudo apt update sudo apt-get install ffmpeg aria2 mkvtoolnix libmediainfo0v5 ffmpeg --version diff --git a/vinetrimmer/commands/dl.py b/vinetrimmer/commands/dl.py index 9a7e7e2..f9d11a2 100644 --- a/vinetrimmer/commands/dl.py +++ b/vinetrimmer/commands/dl.py @@ -302,8 +302,9 @@ def dl(ctx, profile, cdm, *_, **__): except ValueError as e: raise log.exit(f" - {e}") - device_name = device.system_id if "vmp" in dir(device) else device.get_name() - log.info(f" + Loaded {device.__class__.__name__}: {device_name} (L{device.security_level})") + device_name = device.system_id if "vmp" in dir(device) else device.get_name().replace("_", " ").upper() + s = "" if "vmp" in dir(device) else "S" + log.info(f" + Loaded {device.__class__.__name__}: {device_name} ({s}L{device.security_level})") cdm = Cdm.from_device(device) if "vmp" in dir(device) else CdmPr.from_device(device) if profile: @@ -425,7 +426,10 @@ def result(ctx, service, quality, closest_resolution, range_, wanted, alang, sla log.warning(f" - No {quality}p resolution available, using closest available: {closest_res}p") quality = closest_res - title.tracks.select_videos(by_quality=quality, by_vbitrate=vbitrate, by_range=range_, one_only=True) + if range_ == "DV+HDR": + title.tracks.select_videos_multi(["HDR10", "DV"], by_quality=quality, by_vbitrate=vbitrate) + else: + title.tracks.select_videos(by_quality=quality, by_vbitrate=vbitrate, by_range=range_, one_only=True) title.tracks.select_audios(by_language=alang, by_bitrate=abitrate, with_descriptive=audio_description, by_codec=acodec, by_channels=audio_channels, max_audio_compatability=max_audio_compatability) title.tracks.select_subtitles(by_language=slang, with_forced=True) except ValueError as e: @@ -714,12 +718,22 @@ def result(ctx, service, quality, closest_resolution, range_, wanted, alang, sla if isinstance(track, TextTrack) and strip_sdh: track.strip_sdh() log.info("Stripped SDH subtitles to CC with subby") + + if skip_title: for track in title.tracks: track.delete() continue if keys: continue + + if range_ == "DV+HDR": + try: + hybrid_path = title.tracks.make_hybrid() + log.info(f" + Hybrid DV+HDR created: {hybrid_path}") + except Exception as e: + log.warning(f" - Skipped Hybrid DV+HDR: {e}") + if not list(title.tracks) and not title.tracks.chapters: continue # mux all final tracks to a single mkv file diff --git a/vinetrimmer/devices/hisense_smarttv_he55a7000euwts_sl3000.prd b/vinetrimmer/devices/hisense_smarttv_he55a7000euwts_sl3000.prd index ead43c7633a3bc49c3f5a585779e7946e5b7db98..dc1f8f00ff8aed91a96a42304cbdf2f8f2dcd3ad 100644 GIT binary patch delta 516 zcmdlZut#7*N_~Y3>ynyXyXUdSoZ`HhcJRi{n#|Wxmk;-zb(b{SuYPC7+5@V$x5~A@ z{>I*Xw@qwY*R@!y3sPnOLs#Ct%AT<;+O$e*@q(AKXZ5lsr{BN*=ecR)JBM#^Lb3H# z8|Gf)>(819=@rN`8NFO zd3IYqp%V*pw%GZukm|mX^dAT&-(#KI^>hCG-7b3)*PV8Cn!JS3p`KEg0qti5V@4*h zQ%DR-NsO?B8pt%+fW^hZ@Yx?Rt%wZ?_x``ynctqJ*fDwK@4)#17R~iyJj-||ANKli o!_~}PNYWww>u>3mk{8xK{xymJ=9E|;C$G&H1t)85Vzyxg05ci!K>z>% delta 516 zcmdlZut#7*O8p9nX`4S^?NLgdxGAuOtM@N!jOC@2ZuOE&-iB3Y6u*V3Dc3K%TIJ5N z%jQ{#iN(4XBJ*y%*X>-jowr0Q;O-R(KEsaB$tK;Y8b@wj{=IU;o4*VCq@Mm>#l!Z& zzu`>k1<9;(-7f{6GE+mQTtDBjy<>;@k@EPG3+n05R_yV)F;6wqu`|}YXrjQjSKc== zU3YlMe9N00H#=))_f1u9E)GG$e?5=C zV8~2NxH(yc(I8+$%i2%p?=G7CaF(+7(M<_U85p>CFhBs)DP5M+X%o6OcC7or>k&L_ zZts+WtFkv_m$}R~<=&7UVf!Blriv}Q;p;B9=Gx|Oe_fh&%O)>jbf~A)WkCBG!I+T= z>=Y7%QW7I9p$0NdHehjaNV|GzM@i4rw_*yb()VO7sl58_?88p>ikyXZA_sa>1X~nl o#s^q%U!GFhdiLNywzh38hHN?$s}l=^Y_GIQevdNQ#B9R~05N&-?EnA( diff --git a/vinetrimmer/objects/tracks.py b/vinetrimmer/objects/tracks.py index 97d59d0..5cc05f1 100644 --- a/vinetrimmer/objects/tracks.py +++ b/vinetrimmer/objects/tracks.py @@ -4,6 +4,7 @@ import logging import math import os import re +import time import shutil import subprocess import sys @@ -1188,6 +1189,20 @@ class Tracks: if one_only and self.videos: self.videos = [self.videos[0]] + def select_videos_multi(self, ranges: list[str], by_quality=None, by_vbitrate=None) -> None: + selected = [] + for r in ranges: + temp = Tracks() + temp.videos = self.videos.copy() + temp.select_videos(by_range=r, by_quality=by_quality, one_only=False) + if by_vbitrate: + temp.videos = [x for x in temp.videos if int(x.bitrate) <= int(by_vbitrate * 1001)] + if temp.videos: + best = max(temp.videos, key=lambda x: x.bitrate) + selected.append(best) + unique = {(v.width, v.height, v.codec): v for v in selected} + self.videos = list(unique.values()) + def select_audios( self, with_descriptive: bool = True, @@ -1214,18 +1229,20 @@ class Tracks: try: inner_audios.extend( - list( - filter( - lambda x: ( - any(y for y in self.AUDIO_CODEC_MAP[codec] if y in x.codec) - and x.channels == channels - ), + list(filter( + lambda x: (any + ( + y for y in self.AUDIO_CODEC_MAP[codec] if y in x.codec) + and x.channels == channels + ), self.audios - ) - ) + )) ) + audios.append(max(inner_audios, key=lambda x: x.bitrate)) except: pass + unique = {(v.bitrate, v.codec, v.channels): v for v in audios} + self.audios = list(unique.values()) else: if by_codec: @@ -1314,6 +1331,82 @@ class Tracks: from vinetrimmer import parsers return parsers.ism.parse(**kwargs) + def make_hybrid(self) -> str: + start_time = time.time() + logsi = logging.getLogger("Hybrid") + logsi.info(" + Processing to Hybrid") + + hdr = next((t for t in self.videos if t.hdr10 and not t.dv), None) + dv = next((t for t in self.videos if t.dv and not t.hdr10), None) + if not hdr or not dv: + raise ValueError("Hybrid failed: track HDR10 and DV not correct.") + hdr_path = Path(hdr.locate()) + dv_path = Path(dv.locate()) + hybrid_path = hdr_path.with_name(hdr_path.stem + "_hybrid.hevc") + hybrid_path = Path(self.make_hybrid_dv_hdr(str(dv_path), str(hdr_path), str(hybrid_path))) + timeout = 10 + waited = 0 + while not hybrid_path.exists() or os.path.getsize(hybrid_path) < 10000: + time.sleep(0.25) + waited += 0.25 + if waited >= timeout: + raise FileNotFoundError(f"Hybrid file never appeared or too small: {hybrid_path}") + hdr.swap(str(hybrid_path)) + # Hapus DV-only + self.videos = [v for v in self.videos if not (v.dv and not v.hdr10)] + #self._cleanup_paths = [dv_path, hybrid_path] + try: + if hdr_path.exists(): + hdr_path.unlink() + if dv_path.exists(): + dv_path.unlink() + except Exception as e: + logsi.warning(f" - Failed to delete the temp file: {e}") + end_time = time.time() + duration = format_duration(end_time - start_time) + logsi.info(gradient_text(f" + Finish processing Hybrid in {duration}!", (173, 255, 47), (0, 191, 255))) + + return str(hybrid_path) + + def make_hybrid_dv_hdr(dv_file: str, hdr_file: str, output_file: str = None) -> str: + dovi_tool = shutil.which("dovi_tool") or "./binaries/dovi_tool" + if not os.path.isfile(dovi_tool): + raise FileNotFoundError("dovi_tool not found.") + def extract_hevc(input_file, output_file): + subprocess.run( + ["ffmpeg", "-y", "-i", input_file, "-c", "copy", "-bsf:v", "hevc_mp4toannexb", "-f", "hevc", output_file], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + + dv_file = Path(dv_file) + hdr_file = Path(hdr_file) + # Convert .mp4/.mkv to .hevc if needed + if not dv_file.suffix == ".hevc": + raw_dv = dv_file.with_suffix(".hevc") + extract_hevc(str(dv_file), str(raw_dv)) + dv_file = raw_dv + if not hdr_file.suffix == ".hevc": + raw_hdr = hdr_file.with_suffix(".hevc") + extract_hevc(str(hdr_file), str(raw_hdr)) + hdr_file = raw_hdr + + output_file = Path(output_file or hdr_file.with_name(hdr_file.stem + "_hybrid.hevc")).resolve() + rpu_file = Path("RPU.bin") + temp_output = Path("temp_hybrid.hevc") + subprocess.run([dovi_tool, "extract-rpu", "-i", str(dv_file), "-o", str(rpu_file)], check=True) + subprocess.run([dovi_tool, "inject-rpu", "-i", str(hdr_file), "-r", str(rpu_file), "-o", str(temp_output)], check=True) + if temp_output.exists(): + shutil.move(str(temp_output), str(output_file)) + + if rpu_file.exists(): + rpu_file.unlink() + + if not output_file.exists(): + raise FileNotFoundError(f"Hybrid failed: {output_file} is not found.") + return str(output_file) + def mux(self, prefix): """ Takes the Video, Audio and Subtitle Tracks, and muxes them into an MKV file. diff --git a/vinetrimmer/utils/click.py b/vinetrimmer/utils/click.py index 4916e01..3427f40 100644 --- a/vinetrimmer/utils/click.py +++ b/vinetrimmer/utils/click.py @@ -146,6 +146,8 @@ def range_param(ctx, param, value): "hlg": "HLG", "dv": "DV", "dovi": "DV", + "dv+hdr": "DV+HDR", + "hdr+dv": "DV+HDR", })