From 4de9251f95780eeafee64ceca473fa792586a040 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 30 Jul 2025 21:39:34 +0000 Subject: [PATCH] =?UTF-8?q?feat(tracks):=20=E2=9C=A8=20Add=20duration=20fi?= =?UTF-8?q?x=20handling=20for=20video=20and=20hybrid=20tracks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unshackle/commands/dl.py | 4 ++++ unshackle/core/tracks/tracks.py | 40 ++++++++++++++++++++------------- unshackle/core/tracks/video.py | 2 ++ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index 54661ca..ac18949 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -959,6 +959,9 @@ class dl: # Create track pair for this resolution resolution_tracks = [hdr10_track, matching_dv] + for track in resolution_tracks: + track.needs_duration_fix = True + # Run the hybrid processing for this resolution Hybrid(resolution_tracks, self.service) @@ -982,6 +985,7 @@ class dl: hybrid_track = deepcopy(hdr10_track) hybrid_track.path = hybrid_output_path hybrid_track.range = Video.Range.DV # It's now a DV track + hybrid_track.needs_duration_fix = True task_tracks.videos = [hybrid_track] multiplex_tasks.append((task_id, task_tracks)) diff --git a/unshackle/core/tracks/tracks.py b/unshackle/core/tracks/tracks.py index 40cf8fc..a680021 100644 --- a/unshackle/core/tracks/tracks.py +++ b/unshackle/core/tracks/tracks.py @@ -331,21 +331,31 @@ class Tracks: if not vt.path or not vt.path.exists(): raise ValueError("Video Track must be downloaded before muxing...") events.emit(events.Types.TRACK_MULTIPLEX, track=vt) - cl.extend( - [ - "--language", - f"0:{vt.language}", - "--default-track", - f"0:{i == 0}", - "--original-flag", - f"0:{vt.is_original_lang}", - "--compression", - "0:none", # disable extra compression - "(", - str(vt.path), - ")", - ] - ) + + # Prepare base arguments + video_args = [ + "--language", + f"0:{vt.language}", + "--default-track", + f"0:{i == 0}", + "--original-flag", + f"0:{vt.is_original_lang}", + "--compression", + "0:none", # disable extra compression + ] + + # Add FPS fix if needed (typically for hybrid mode to prevent sync issues) + if hasattr(vt, "needs_duration_fix") and vt.needs_duration_fix and vt.fps: + video_args.extend( + [ + "--default-duration", + f"0:{vt.fps}fps" if isinstance(vt.fps, str) else f"0:{vt.fps:.3f}fps", + "--fix-bitstream-timing-information", + "0:1", + ] + ) + + cl.extend(video_args + ["(", str(vt.path), ")"]) for i, at in enumerate(self.audio): if not at.path or not at.path.exists(): diff --git a/unshackle/core/tracks/video.py b/unshackle/core/tracks/video.py index de1d77a..9069692 100644 --- a/unshackle/core/tracks/video.py +++ b/unshackle/core/tracks/video.py @@ -237,6 +237,8 @@ class Video(Track): except Exception as e: raise ValueError("Expected fps to be a number, float, or a string as numerator/denominator form, " + str(e)) + self.needs_duration_fix = False + def __str__(self) -> str: return " | ".join( filter(