Skip to content

Commit db46985

Browse files
committed
Add more flags
1 parent 1b6ccf8 commit db46985

2 files changed

Lines changed: 129 additions & 39 deletions

File tree

src/muxtools/create_offset_mkv.py

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
11
import asyncio
22
from typing import Annotated
33

4-
from typer import Argument
4+
from typer import Argument, Option
55
from ..app import app
6-
from .find_change_from_start import find_change_from_start_inner
6+
from .find_change_from_start import (
7+
find_change_from_start_inner,
8+
find_change_from_first_frame,
9+
)
710

811

9-
async def create_offset_mkv_inner(bd_path: str, target_path: str, output_path: str):
10-
bd_offset_frame, bd_fps = find_change_from_start_inner(bd_path)
12+
async def create_offset_mkv_inner(
13+
bd_path: str,
14+
target_path: str,
15+
output_path: str,
16+
output_offset: bool,
17+
silent: bool,
18+
*,
19+
keep_video: bool = False,
20+
output_offset_frame_number: bool = False,
21+
):
22+
bd_offset_frame, bd_fps, first_frame = find_change_from_start_inner(bd_path)
1123
if bd_offset_frame == -1:
1224
raise ValueError("No significant change found in BD video.")
1325
bd_offset_seconds = bd_offset_frame / bd_fps
14-
target_offset_frame, target_fps = find_change_from_start_inner(target_path)
26+
target_offset_frame, target_fps = find_change_from_first_frame(
27+
target_path, first_frame
28+
)
1529
if target_offset_frame == -1:
1630
raise ValueError("No significant change found in target video.")
1731
target_offset_seconds = target_offset_frame / target_fps
1832
# Positive if BD starts later, negative if earlier
1933
offset_seconds = round(bd_offset_seconds - target_offset_seconds, 5)
20-
print(
21-
f"Calculated offset: {offset_seconds} seconds, adjusting all non-video streams accordingly."
22-
)
34+
if not silent:
35+
print(
36+
f"Calculated offset: {offset_seconds} seconds, adjusting all non-video streams accordingly."
37+
)
2338
ffmpeg_command = [
2439
"ffmpeg",
2540
"-y",
@@ -31,8 +46,13 @@ async def create_offset_mkv_inner(bd_path: str, target_path: str, output_path: s
3146
target_path,
3247
"-map",
3348
"0",
34-
"-map",
35-
"-0:v", # Exclude original video stream
49+
]
50+
if not keep_video:
51+
ffmpeg_command += [
52+
"-map",
53+
"-0:v", # Exclude original video stream
54+
]
55+
ffmpeg_command += [
3656
"-map_metadata",
3757
"0",
3858
"-c",
@@ -45,6 +65,12 @@ async def create_offset_mkv_inner(bd_path: str, target_path: str, output_path: s
4565
await proc.communicate()
4666
if proc.returncode != 0:
4767
raise RuntimeError(f"ffmpeg command failed with return code {proc.returncode}")
68+
if output_offset:
69+
if output_offset_frame_number:
70+
offset_frames = round(offset_seconds * target_fps)
71+
print(offset_frames)
72+
else:
73+
print(offset_seconds)
4874

4975

5076
@app.command()
@@ -77,15 +103,53 @@ def create_offset_mkv(
77103
help="Path to save the output MKV file with adjusted timing.",
78104
),
79105
],
106+
output_offset: Annotated[
107+
bool,
108+
Option(
109+
help="If true, output the adjusted timing in seconds.",
110+
is_flag=True,
111+
),
112+
] = False,
113+
output_offset_frame_number: Annotated[
114+
bool,
115+
Option(
116+
help="If true, output the adjusted timing in frame numbers instead of seconds.",
117+
is_flag=True,
118+
),
119+
] = False,
120+
silent: Annotated[
121+
bool,
122+
Option(
123+
help="If true, suppress output messages (other than the offset if requested).",
124+
is_flag=True,
125+
),
126+
] = False,
127+
keep_video: Annotated[
128+
bool,
129+
Option(
130+
help="If true, keep the original video stream in the output file.",
131+
is_flag=True,
132+
),
133+
] = False,
80134
):
81135
"""Create an MKV file with adjusted timing based on offset from Blu-ray video.
82136
83137
:param bd_path: Path to the Blu-ray video file to analyze for offset.
84138
:param target_path: Path to the target video file to adjust.
85139
:param output_path: Path to save the output MKV file with adjusted timing.
140+
:param output_offset: If true, output the adjusted timing.
141+
:param output_offset_frame_number: If true, output the adjusted timing in frame numbers instead of seconds.
142+
:param silent: If true, suppress output messages (other than the offset if requested).
143+
:param keep_video: If true, keep the original video stream in the output file.
86144
"""
87145
asyncio.run(
88146
create_offset_mkv_inner(
89-
bd_path=bd_path, target_path=target_path, output_path=output_path
147+
bd_path=bd_path,
148+
target_path=target_path,
149+
output_path=output_path,
150+
output_offset=output_offset,
151+
silent=silent,
152+
keep_video=keep_video,
153+
output_offset_frame_number=output_offset_frame_number,
90154
)
91155
)

src/muxtools/find_change_from_start.py

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,32 @@
11
import cv2
22

3-
from typing import Annotated
3+
from typing import Annotated, TypedDict, TYPE_CHECKING
44

55
from typer import Argument, Option
66
from ..app import app
77

8+
if TYPE_CHECKING:
9+
from cv2.typing import MatLike
10+
from cv2 import UMat
811

9-
def find_change_from_start_inner(
10-
video_path, threshold=0, min_changed_pixels=50
11-
) -> tuple[int, float]:
12-
"""
13-
Consumes video one frame at a time to find where it diverges from Frame 1.
14-
15-
Args:
16-
video_path (str): Path to video file.
17-
threshold (int): Sensitivity (0-255). Lower = detects subtle changes.
18-
Higher = ignores compression artifacts.
19-
min_changed_pixels (int): How many pixels must change to trigger detection.
12+
Mat = MatLike | UMat
2013

21-
Returns:
22-
tuple[int, float]: Frame number where change is detected and FPS of the video.
23-
Returns -1 if no significant change is found.
24-
"""
25-
26-
# 1. Open the video stream
27-
cap = cv2.VideoCapture(video_path)
2814

29-
try:
15+
def find_change_from_first_frame(
16+
video_path: str | cv2.VideoCapture,
17+
frame: "Mat",
18+
threshold: int = 0,
19+
min_changed_pixels: int = 50,
20+
) -> tuple[int, float]:
21+
if isinstance(video_path, str):
22+
cap = cv2.VideoCapture(video_path)
3023
if not cap.isOpened():
3124
raise FileNotFoundError(f"Could not open video file: {video_path}")
32-
33-
# 2. Read Frame 1 (The Reference)
34-
ret, frame1 = cap.read()
35-
if not ret:
36-
raise ValueError("Video file is empty or unreadable.")
37-
25+
else:
26+
cap = video_path
27+
try:
3828
# Convert to grayscale to reduce complexity and ignore color noise
39-
frame1_gray = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
29+
frame1_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
4030

4131
# Optional: Apply slight blur to reduce compression artifact noise
4232
frame1_gray = cv2.GaussianBlur(frame1_gray, (21, 21), 0)
@@ -83,6 +73,42 @@ def find_change_from_start_inner(
8373
cap.release()
8474

8575

76+
def find_change_from_start_inner(
77+
video_path, threshold=0, min_changed_pixels=50
78+
) -> tuple[int, float, "Mat"]:
79+
"""
80+
Consumes video one frame at a time to find where it diverges from Frame 1.
81+
82+
Args:
83+
video_path (str): Path to video file.
84+
threshold (int): Sensitivity (0-255). Lower = detects subtle changes.
85+
Higher = ignores compression artifacts.
86+
min_changed_pixels (int): How many pixels must change to trigger detection.
87+
88+
Returns:
89+
tuple[int, float, Mat]:
90+
- Frame number where significant change is detected, or -1 if none found.
91+
- Frames per second of the video.
92+
- The first frame of the video as a Mat object.
93+
"""
94+
95+
# 1. Open the video stream
96+
cap = cv2.VideoCapture(video_path)
97+
98+
if not cap.isOpened():
99+
raise FileNotFoundError(f"Could not open video file: {video_path}")
100+
101+
# 2. Read Frame 1 (The Reference)
102+
ret, frame1 = cap.read()
103+
if not ret:
104+
raise ValueError("Video file is empty or unreadable.")
105+
106+
frame_num, fps = find_change_from_first_frame(
107+
cap, frame1, threshold=threshold, min_changed_pixels=min_changed_pixels
108+
)
109+
return frame_num + 1, fps, frame1 # +1 to account for the first frame read earlier
110+
111+
86112
@app.command()
87113
def find_change_from_start(
88114
video_path: Annotated[
@@ -111,7 +137,7 @@ def find_change_from_start(
111137
:param threshold: Sensitivity (0-255). Lower = detects subtle changes. Higher = ignores compression artifacts.
112138
:param min_changed_pixels: How many pixels must change to trigger detection.
113139
"""
114-
frame_diff_at, fps = find_change_from_start_inner(
140+
frame_diff_at, fps, _ = find_change_from_start_inner(
115141
video_path=video_path,
116142
threshold=threshold,
117143
min_changed_pixels=min_changed_pixels,

0 commit comments

Comments
 (0)