diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index 2abc45e..c2fda56 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -58,8 +58,15 @@ from unshackle.core.tracks.attachment import Attachment from unshackle.core.tracks.hybrid import Hybrid from unshackle.core.utilities import get_system_fonts, is_close_match, time_elapsed_since from unshackle.core.utils import tags -from unshackle.core.utils.click_types import (LANGUAGE_RANGE, QUALITY_LIST, SEASON_RANGE, ContextData, MultipleChoice, - SubtitleCodecChoice, VideoCodecChoice) +from unshackle.core.utils.click_types import ( + LANGUAGE_RANGE, + QUALITY_LIST, + SEASON_RANGE, + ContextData, + MultipleChoice, + SubtitleCodecChoice, + VideoCodecChoice, +) from unshackle.core.utils.collections import merge_dict from unshackle.core.utils.subprocess import ffprobe from unshackle.core.vaults import Vaults @@ -403,6 +410,7 @@ class dl: # Check if dovi_tool is available when hybrid mode is requested if any(r == Video.Range.HYBRID for r in range_): from unshackle.core.binaries import DoviTool + if not DoviTool: self.log.error("Unable to run hybrid mode: dovi_tool not detected") self.log.error("Please install dovi_tool from https://github.com/quietvoid/dovi_tool") @@ -954,6 +962,8 @@ class dl: task_tracks.videos = [hybrid_track] multiplex_tasks.append((task_id, task_tracks)) + + console.print() else: # Normal mode: process each video track separately for video_track in title.tracks.videos or [None]: diff --git a/unshackle/core/tracks/hybrid.py b/unshackle/core/tracks/hybrid.py index de5151d..6f8ee43 100644 --- a/unshackle/core/tracks/hybrid.py +++ b/unshackle/core/tracks/hybrid.py @@ -30,7 +30,11 @@ class Hybrid: self.hdr_type = "HDR10" self.hevc_file = f"{self.hdr_type}-DV.hevc" - console.print(Padding(Rule("[rule.text]HDR10+DV Hybrid"), (1, 2))) + # Get resolution info from HDR10 track for display + hdr10_track = next((v for v in videos if v.range == Video.Range.HDR10), None) + self.resolution = f"{hdr10_track.height}p" if hdr10_track and hdr10_track.height else "Unknown" + + console.print(Padding(Rule(f"[rule.text]HDR10+DV Hybrid ({self.resolution})"), (1, 2))) for video in self.videos: if not video.path or not os.path.exists(video.path): @@ -55,7 +59,6 @@ class Hybrid: self.extract_stream(save_path, "HDR10") elif video.range == Video.Range.DV: self.extract_stream(save_path, "DV") - # self.extract_dv_stream(video, save_path) self.extract_rpu([video for video in videos if video.range == Video.Range.DV][0]) if os.path.isfile(config.directories.temp / "RPU_UNT.bin"): @@ -106,142 +109,6 @@ class Hybrid: self.log.error(f"x Failed extracting {type_} stream") sys.exit(1) - def ffmpeg_task(self, save_path, output, task_id): - p = subprocess.Popen( - [ - "ffmpeg", - "-nostdin", - "-i", - str(save_path), - "-c:v", - "copy", - str(output), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - bufsize=1, - universal_newlines=True, - ) - - self.progress.start_task(task_id) - - for line in p.stderr: - if "frame=" in line: - self.progress.update(task_id, advance=0) - p.wait() - - return p.returncode - - def extract_hdr10_stream(self, video, save_path): - type_ = "HDR10" - if os.path.isfile(Path(config.directories.temp / f"{type_}.hevc")): - return - if self.source == "itunes" or self.source == "appletvplus": - self.log.info("+ Muxing HDR10 stream for fixing MP4 file") - subprocess.run( - [ - "mkvmerge", - "-o", - Path(config.directories.temp / "hdr10.mkv"), - save_path, - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - self.log.info(f"+ Extracting {type_} stream") - extract_stream = subprocess.run( - [ - "ffmpeg", - "-nostdin", - "-stats", - "-i", - Path(config.directories.temp / "hdr10.mkv"), - "-c:v", - "copy", - Path(config.directories.temp / f"{type_}.hevc"), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - if extract_stream.returncode: - Path.unlink(Path(config.directories.temp / f"{type_}.hevc")) - self.log.error(f"x Failed extracting {type_} stream") - sys.exit(1) - else: - extract_stream = subprocess.run( - [ - "ffmpeg", - "-nostdin", - "-stats", - "-i", - save_path, - "-c:v", - "copy", - Path(config.directories.temp / f"{type_}.hevc"), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - if extract_stream.returncode: - Path.unlink(Path(config.directories.temp / f"{type_}.hevc")) - self.log.error(f"x Failed extracting {type_} stream") - sys.exit(1) - - def extract_dv_stream(self, video, save_path): - type_ = "DV" - if os.path.isfile(Path(config.directories.temp / f"{type_}.hevc")): - return - if self.source == "itunes" or self.source == "appletvplus": - self.log.info("+ Muxing Dolby Vision stream for fixing MP4 file") - subprocess.run( - [ - "mkvmerge", - "-o", - Path(config.directories.temp / "dv.mkv"), - save_path, - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - self.log.info("+ Extracting Dolby Vision stream") - extract_stream = subprocess.run( - [ - "ffmpeg", - "-nostdin", - "-stats", - "-i", - Path(config.directories.temp / "dv.mkv"), - "-an", - "-c:v", - "copy", - "-f", - "hevc", - Path(config.directories.temp / "out_1.h265"), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - if extract_stream.returncode: - Path.unlink(Path(config.directories.temp / f"{type_}.hevc")) - self.log.error(f"x Failed extracting {type_} stream") - sys.exit(1) - else: - extract_stream = subprocess.run( - [ - "mp4demuxer", - "--input-file", - save_path, - "--output-folder", - Path(config.directories.temp), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - if extract_stream.returncode: - Path.unlink(Path(config.directories.temp / f"{type_}.hevc")) - self.log.error(f"x Failed extracting {type_} stream") - sys.exit(1) - def extract_rpu(self, video, untouched=False): if os.path.isfile(config.directories.temp / "RPU.bin") or os.path.isfile( config.directories.temp / "RPU_UNT.bin" @@ -315,34 +182,6 @@ class Hybrid: # Update rpu_file to use the edited version self.rpu_file = "RPU_L6.bin" - def mode_3(self): - """Convert RPU to Mode 3""" - with open(config.directories.temp / "M3.json", "w+") as mode3_file: - json.dump({"mode": 3}, mode3_file, indent=3) - - if not os.path.isfile(config.directories.temp / "RPU_M3.bin"): - self.log.info("+ Converting RPU to Mode 3") - mode3 = subprocess.run( - [ - str(DoviTool), - "editor", - "-i", - config.directories.temp / self.rpu_file, - "-j", - config.directories.temp / "M3.json", - "-o", - config.directories.temp / "RPU_M3.bin", - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - if mode3.returncode: - Path.unlink(config.directories.temp / "RPU_M3.bin") - self.log.exit("x Failed converting RPU to Mode 3") - - self.rpu_file = "RPU_M3.bin" - def injecting(self): if os.path.isfile(config.directories.temp / self.hevc_file): return