cropping for videos
This commit is contained in:
@@ -2,7 +2,7 @@ import argparse
|
|||||||
|
|
||||||
from tsmark.video_annotator import Marker
|
from tsmark.video_annotator import Marker
|
||||||
|
|
||||||
VERSION = "0.4.7"
|
VERSION = "0.5"
|
||||||
|
|
||||||
|
|
||||||
def get_options():
|
def get_options():
|
||||||
@@ -33,6 +33,15 @@ def get_options():
|
|||||||
type=float,
|
type=float,
|
||||||
help="Force FPS to play video",
|
help="Force FPS to play video",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--crop",
|
||||||
|
action="store",
|
||||||
|
dest="crop",
|
||||||
|
default=None,
|
||||||
|
required=False,
|
||||||
|
type=str,
|
||||||
|
help="predefined crop. Syntax: 'w:h:x:y' example: 1280:720:30:20",
|
||||||
|
)
|
||||||
parser.add_argument("--version", action="version", version=VERSION)
|
parser.add_argument("--version", action="version", version=VERSION)
|
||||||
parser.add_argument(action="store", dest="video")
|
parser.add_argument(action="store", dest="video")
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class Marker:
|
|||||||
self.frame_visu = []
|
self.frame_visu = []
|
||||||
self.max_res = (1280, 720)
|
self.max_res = (1280, 720)
|
||||||
self.min_res = (512, None)
|
self.min_res = (512, None)
|
||||||
|
self.crop = [(None, None), (None, None), None]
|
||||||
|
self.crop_click = 0
|
||||||
self.forced_fps = opts.fps
|
self.forced_fps = opts.fps
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -49,6 +51,10 @@ class Marker:
|
|||||||
int(self.video_reader.get(cv2.CAP_PROP_FRAME_WIDTH)),
|
int(self.video_reader.get(cv2.CAP_PROP_FRAME_WIDTH)),
|
||||||
int(self.video_reader.get(cv2.CAP_PROP_FRAME_HEIGHT)),
|
int(self.video_reader.get(cv2.CAP_PROP_FRAME_HEIGHT)),
|
||||||
]
|
]
|
||||||
|
self.video_res_original = [
|
||||||
|
int(self.video_reader.get(cv2.CAP_PROP_FRAME_WIDTH)),
|
||||||
|
int(self.video_reader.get(cv2.CAP_PROP_FRAME_HEIGHT)),
|
||||||
|
]
|
||||||
video_aspect = self.video_res[0] / self.video_res[1]
|
video_aspect = self.video_res[0] / self.video_res[1]
|
||||||
if self.video_res[0] > self.max_res[0]:
|
if self.video_res[0] > self.max_res[0]:
|
||||||
self.video_res[0] = int(self.max_res[0])
|
self.video_res[0] = int(self.max_res[0])
|
||||||
@@ -63,6 +69,20 @@ class Marker:
|
|||||||
self.video_res[1] = int(self.video_res[0] / video_aspect)
|
self.video_res[1] = int(self.video_res[0] / video_aspect)
|
||||||
|
|
||||||
self.video_res = tuple(self.video_res)
|
self.video_res = tuple(self.video_res)
|
||||||
|
self.crop = [(0, 0), tuple(self.video_res), None]
|
||||||
|
if self.opts.crop:
|
||||||
|
w, h, x, y = [int(c) for c in self.opts.crop.split(":")]
|
||||||
|
self.crop = [
|
||||||
|
(
|
||||||
|
int(self.video_res[0] * x / self.video_res_original[0]),
|
||||||
|
int(self.video_res[1] * y / self.video_res_original[1]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
int(self.video_res[0] * w / self.video_res_original[0]),
|
||||||
|
int(self.video_res[1] * h / self.video_res_original[1]),
|
||||||
|
),
|
||||||
|
True,
|
||||||
|
]
|
||||||
self.bar_start = int(self.video_res[0] * 0.05)
|
self.bar_start = int(self.video_res[0] * 0.05)
|
||||||
self.bar_end = int(self.video_res[0] * 0.95)
|
self.bar_end = int(self.video_res[0] * 0.95)
|
||||||
self.bar_top = int(self.video_res[1] * 0.90)
|
self.bar_top = int(self.video_res[1] * 0.90)
|
||||||
@@ -147,6 +167,49 @@ class Marker:
|
|||||||
(255, 255, 255),
|
(255, 255, 255),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def draw_crop(self, frame):
|
||||||
|
|
||||||
|
if self.crop[2] is None:
|
||||||
|
return
|
||||||
|
p2 = (self.crop[0][0] + self.crop[1][0], self.crop[0][1] + self.crop[1][1])
|
||||||
|
cv2.rectangle(
|
||||||
|
frame,
|
||||||
|
self.crop[0],
|
||||||
|
p2,
|
||||||
|
(0, 192, 192),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
if self.crop_click == 1:
|
||||||
|
x, y = [
|
||||||
|
int(self.video_res_original[0] * self.crop[0][0] / self.video_res[0]),
|
||||||
|
int(self.video_res_original[1] * self.crop[0][1] / self.video_res[1]),
|
||||||
|
]
|
||||||
|
self.shadow_text(
|
||||||
|
frame,
|
||||||
|
f"{x},{y}",
|
||||||
|
self.crop[0],
|
||||||
|
0.5,
|
||||||
|
1,
|
||||||
|
(0, 192, 192),
|
||||||
|
)
|
||||||
|
if self.crop_click == 2:
|
||||||
|
x, y = [
|
||||||
|
int(self.video_res_original[0] * self.crop[0][0] / self.video_res[0]),
|
||||||
|
int(self.video_res_original[1] * self.crop[0][1] / self.video_res[1]),
|
||||||
|
]
|
||||||
|
w, h = [
|
||||||
|
abs(int(self.video_res_original[0] * self.crop[1][0] / self.video_res[0])),
|
||||||
|
abs(int(self.video_res_original[1] * self.crop[1][1] / self.video_res[1])),
|
||||||
|
]
|
||||||
|
self.shadow_text(
|
||||||
|
frame,
|
||||||
|
f"{w}x{h}",
|
||||||
|
self.crop[0],
|
||||||
|
0.5,
|
||||||
|
1,
|
||||||
|
(0, 192, 192),
|
||||||
|
)
|
||||||
|
|
||||||
def draw_help(self, frame):
|
def draw_help(self, frame):
|
||||||
|
|
||||||
bottom = 80
|
bottom = 80
|
||||||
@@ -195,6 +258,7 @@ class Marker:
|
|||||||
mark frame
|
mark frame
|
||||||
space or click video
|
space or click video
|
||||||
pause
|
pause
|
||||||
|
a and s modify crop offset or size
|
||||||
f toggle 0.5x 1x or 2x FPS
|
f toggle 0.5x 1x or 2x FPS
|
||||||
v toggle HUD
|
v toggle HUD
|
||||||
h toggle help
|
h toggle help
|
||||||
@@ -211,6 +275,17 @@ class Marker:
|
|||||||
y > self.bar_top,
|
y > self.bar_top,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if self.crop_click == 1:
|
||||||
|
self.crop[0] = (x, y)
|
||||||
|
if event == cv2.EVENT_LBUTTONDOWN:
|
||||||
|
self.crop_click = 0
|
||||||
|
return
|
||||||
|
if self.crop_click == 2:
|
||||||
|
self.crop[1] = (x - self.crop[0][0], y - self.crop[0][1])
|
||||||
|
if event == cv2.EVENT_LBUTTONDOWN:
|
||||||
|
self.crop_click = 0
|
||||||
|
return
|
||||||
|
|
||||||
if event == cv2.EVENT_LBUTTONDOWN:
|
if event == cv2.EVENT_LBUTTONDOWN:
|
||||||
if in_bar:
|
if in_bar:
|
||||||
click_relative = (x - self.bar_start) / (self.bar_end - self.bar_start)
|
click_relative = (x - self.bar_start) / (self.bar_end - self.bar_start)
|
||||||
@@ -271,17 +346,48 @@ class Marker:
|
|||||||
print(self.get_help())
|
print(self.get_help())
|
||||||
|
|
||||||
def print_timestamps(self):
|
def print_timestamps(self):
|
||||||
|
|
||||||
|
if self.crop[2] is None:
|
||||||
|
cropstr = ""
|
||||||
|
else:
|
||||||
|
x, y = [
|
||||||
|
int(self.video_res_original[0] * self.crop[0][0] / self.video_res[0]),
|
||||||
|
int(self.video_res_original[1] * self.crop[0][1] / self.video_res[1]),
|
||||||
|
]
|
||||||
|
w, h = [
|
||||||
|
int(self.video_res_original[0] * self.crop[1][0] / self.video_res[0]),
|
||||||
|
int(self.video_res_original[1] * self.crop[1][1] / self.video_res[1]),
|
||||||
|
]
|
||||||
|
if w < 0:
|
||||||
|
x = x + w
|
||||||
|
w = -w
|
||||||
|
if h < 0:
|
||||||
|
y = y + h
|
||||||
|
h = -h
|
||||||
|
cropstr = f"-filter:v crop={w}:{h}:{x}:{y} "
|
||||||
self.stamps.sort()
|
self.stamps.sort()
|
||||||
print("# Timestamps:")
|
print("# Timestamps:")
|
||||||
for i, ts in enumerate(self.stamps):
|
for i, ts in enumerate(self.stamps):
|
||||||
print("# {}: {} / {}".format(i + 1, self.format_time(ts), ts))
|
print("# {}: {} / {}".format(i + 1, self.format_time(ts), ts))
|
||||||
|
if len(self.stamps) == 0:
|
||||||
|
self.stamps.append(0)
|
||||||
|
self.stamps.append(self.frames)
|
||||||
if len(self.stamps) > 0:
|
if len(self.stamps) > 0:
|
||||||
print(
|
print(
|
||||||
'ffmpeg -ss {} -to {} -i "{}" -c copy "{}.trimmed.mp4"'.format(
|
'ffmpeg -i "{}" {}-c:v mpeg2video -q:v 3 -g 1 -a copy -ss {} -to {} "{}.trimmed.mp4"'.format(
|
||||||
|
self.opts.video.replace('"', '\\"'),
|
||||||
|
cropstr,
|
||||||
self.format_time(self.stamps[0]),
|
self.format_time(self.stamps[0]),
|
||||||
self.format_time(self.stamps[-1]),
|
self.format_time(self.stamps[-1]),
|
||||||
|
os.path.splitext(self.opts.video)[0].replace('"', '\\"'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.crop[0][0] is not None:
|
||||||
|
print(
|
||||||
|
'ffmpeg -i "{}" {}-c:v mpeg2video -q:v 3 -g 1 "{}.trimmed.mp4"'.format(
|
||||||
self.opts.video.replace('"', '\\"'),
|
self.opts.video.replace('"', '\\"'),
|
||||||
self.opts.video.replace('"', '\\"'),
|
cropstr,
|
||||||
|
os.path.splitext(self.opts.video)[0].replace('"', '\\"'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -350,6 +456,7 @@ class Marker:
|
|||||||
if (not self.paused) or self.read_next:
|
if (not self.paused) or self.read_next:
|
||||||
self.read_next = False
|
self.read_next = False
|
||||||
frame_visu = cv2.resize(frame.copy(), self.video_res)
|
frame_visu = cv2.resize(frame.copy(), self.video_res)
|
||||||
|
self.draw_crop(frame_visu)
|
||||||
nr_time = self.nr / self.fps
|
nr_time = self.nr / self.fps
|
||||||
if self.show_info:
|
if self.show_info:
|
||||||
self.draw_time(frame_visu)
|
self.draw_time(frame_visu)
|
||||||
@@ -436,7 +543,12 @@ class Marker:
|
|||||||
|
|
||||||
elif k & 0xFF == ord("f"): # modify FPS
|
elif k & 0xFF == ord("f"): # modify FPS
|
||||||
FPS_modifier = (FPS_modifier + 1) % len(FPS_modifiers)
|
FPS_modifier = (FPS_modifier + 1) % len(FPS_modifiers)
|
||||||
|
elif k & 0xFF == ord("a"): # toggle crop offset
|
||||||
|
self.crop_click = 0 if self.crop_click == 1 else 1
|
||||||
|
self.crop[2] = True
|
||||||
|
elif k & 0xFF == ord("s"): # toggle crop size
|
||||||
|
self.crop_click = 0 if self.crop_click == 2 else 2
|
||||||
|
self.crop[2] = True
|
||||||
elif k & 0xFF == ord("x"): # toggle ts
|
elif k & 0xFF == ord("x"): # toggle ts
|
||||||
self.toggle_stamp()
|
self.toggle_stamp()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user