Code format

This commit is contained in:
2025-09-04 15:15:16 +02:00
parent d739e40862
commit 22bba12d7e

View File

@@ -34,7 +34,7 @@ class VideoEditor:
ZOOM_INCREMENT = 0.1
# Supported video extensions
VIDEO_EXTENSIONS = {'.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv', '.webm', '.m4v'}
VIDEO_EXTENSIONS = {".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv", ".webm", ".m4v"}
def __init__(self, path: str):
self.path = Path(path)
@@ -98,13 +98,16 @@ class VideoEditor:
"""Get all video files from a directory, sorted by name"""
video_files = []
for file_path in directory.iterdir():
if file_path.is_file() and file_path.suffix.lower() in self.VIDEO_EXTENSIONS:
if (
file_path.is_file()
and file_path.suffix.lower() in self.VIDEO_EXTENSIONS
):
video_files.append(file_path)
return sorted(video_files)
def _load_video(self, video_path: Path):
"""Load a video file and initialize video properties"""
if hasattr(self, 'cap') and self.cap:
if hasattr(self, "cap") and self.cap:
self.cap.release()
self.video_path = video_path
@@ -137,7 +140,9 @@ class VideoEditor:
self.cut_end_frame = None
self.display_offset = [0, 0]
print(f"Loaded video: {self.video_path.name} ({self.current_video_index + 1}/{len(self.video_files)})")
print(
f"Loaded video: {self.video_path.name} ({self.current_video_index + 1}/{len(self.video_files)})"
)
def switch_to_video(self, index: int):
"""Switch to a specific video by index"""
@@ -172,11 +177,15 @@ class VideoEditor:
def seek_video(self, frames_delta: int):
"""Seek video by specified number of frames"""
target_frame = max(0, min(self.current_frame + frames_delta, self.total_frames - 1))
target_frame = max(
0, min(self.current_frame + frames_delta, self.total_frames - 1)
)
self.current_frame = target_frame
self.load_current_frame()
def seek_video_with_modifier(self, direction: int, shift_pressed: bool, ctrl_pressed: bool):
def seek_video_with_modifier(
self, direction: int, shift_pressed: bool, ctrl_pressed: bool
):
"""Seek video with different frame counts based on modifiers"""
if ctrl_pressed:
frames = direction * 60 # Ctrl: 60 frames
@@ -223,7 +232,7 @@ class VideoEditor:
w = min(w, processed_frame.shape[1] - x)
h = min(h, processed_frame.shape[0] - y)
if w > 0 and h > 0:
processed_frame = processed_frame[y:y+h, x:x+w]
processed_frame = processed_frame[y : y + h, x : x + w]
# Apply rotation
if self.rotation_angle != 0:
@@ -234,7 +243,9 @@ class VideoEditor:
height, width = processed_frame.shape[:2]
new_width = int(width * self.zoom_factor)
new_height = int(height * self.zoom_factor)
processed_frame = cv2.resize(processed_frame, (new_width, new_height), interpolation=cv2.INTER_LINEAR)
processed_frame = cv2.resize(
processed_frame, (new_width, new_height), interpolation=cv2.INTER_LINEAR
)
# Handle zoom center and display offset
if new_width > self.window_width or new_height > self.window_height:
@@ -272,7 +283,9 @@ class VideoEditor:
brightness_value = self.brightness * 2.55
# Apply brightness and contrast: new_pixel = contrast * old_pixel + brightness
adjusted = cv2.convertScaleAbs(frame, alpha=self.contrast, beta=brightness_value)
adjusted = cv2.convertScaleAbs(
frame, alpha=self.contrast, beta=brightness_value
)
return adjusted
def adjust_brightness(self, delta: int):
@@ -300,41 +313,103 @@ class VideoEditor:
self.timeline_rect = (bar_x_start, bar_y, bar_width, self.TIMELINE_BAR_HEIGHT)
# Draw timeline background
cv2.rectangle(frame, (bar_x_start, bar_y), (bar_x_end, bar_y + self.TIMELINE_BAR_HEIGHT), self.TIMELINE_COLOR_BG, -1)
cv2.rectangle(frame, (bar_x_start, bar_y), (bar_x_end, bar_y + self.TIMELINE_BAR_HEIGHT), self.TIMELINE_COLOR_BORDER, 1)
cv2.rectangle(
frame,
(bar_x_start, bar_y),
(bar_x_end, bar_y + self.TIMELINE_BAR_HEIGHT),
self.TIMELINE_COLOR_BG,
-1,
)
cv2.rectangle(
frame,
(bar_x_start, bar_y),
(bar_x_end, bar_y + self.TIMELINE_BAR_HEIGHT),
self.TIMELINE_COLOR_BORDER,
1,
)
# Draw progress
if self.total_frames > 0:
progress = self.current_frame / max(1, self.total_frames - 1)
progress_width = int(bar_width * progress)
if progress_width > 0:
cv2.rectangle(frame, (bar_x_start, bar_y), (bar_x_start + progress_width, bar_y + self.TIMELINE_BAR_HEIGHT), self.TIMELINE_COLOR_PROGRESS, -1)
cv2.rectangle(
frame,
(bar_x_start, bar_y),
(bar_x_start + progress_width, bar_y + self.TIMELINE_BAR_HEIGHT),
self.TIMELINE_COLOR_PROGRESS,
-1,
)
# Draw current position handle
handle_x = bar_x_start + progress_width
handle_y = bar_y + self.TIMELINE_BAR_HEIGHT // 2
cv2.circle(frame, (handle_x, handle_y), self.TIMELINE_HANDLE_SIZE // 2, self.TIMELINE_COLOR_HANDLE, -1)
cv2.circle(frame, (handle_x, handle_y), self.TIMELINE_HANDLE_SIZE // 2, self.TIMELINE_COLOR_BORDER, 2)
cv2.circle(
frame,
(handle_x, handle_y),
self.TIMELINE_HANDLE_SIZE // 2,
self.TIMELINE_COLOR_HANDLE,
-1,
)
cv2.circle(
frame,
(handle_x, handle_y),
self.TIMELINE_HANDLE_SIZE // 2,
self.TIMELINE_COLOR_BORDER,
2,
)
# Draw cut points
if self.cut_start_frame is not None:
cut_start_progress = self.cut_start_frame / max(1, self.total_frames - 1)
cut_start_progress = self.cut_start_frame / max(
1, self.total_frames - 1
)
cut_start_x = bar_x_start + int(bar_width * cut_start_progress)
cv2.line(frame, (cut_start_x, bar_y), (cut_start_x, bar_y + self.TIMELINE_BAR_HEIGHT), self.TIMELINE_COLOR_CUT_POINT, 3)
cv2.putText(frame, "1", (cut_start_x - 5, bar_y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, self.TIMELINE_COLOR_CUT_POINT, 1)
cv2.line(
frame,
(cut_start_x, bar_y),
(cut_start_x, bar_y + self.TIMELINE_BAR_HEIGHT),
self.TIMELINE_COLOR_CUT_POINT,
3,
)
cv2.putText(
frame,
"1",
(cut_start_x - 5, bar_y - 5),
cv2.FONT_HERSHEY_SIMPLEX,
0.4,
self.TIMELINE_COLOR_CUT_POINT,
1,
)
if self.cut_end_frame is not None:
cut_end_progress = self.cut_end_frame / max(1, self.total_frames - 1)
cut_end_x = bar_x_start + int(bar_width * cut_end_progress)
cv2.line(frame, (cut_end_x, bar_y), (cut_end_x, bar_y + self.TIMELINE_BAR_HEIGHT), self.TIMELINE_COLOR_CUT_POINT, 3)
cv2.putText(frame, "2", (cut_end_x - 5, bar_y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, self.TIMELINE_COLOR_CUT_POINT, 1)
cv2.line(
frame,
(cut_end_x, bar_y),
(cut_end_x, bar_y + self.TIMELINE_BAR_HEIGHT),
self.TIMELINE_COLOR_CUT_POINT,
3,
)
cv2.putText(
frame,
"2",
(cut_end_x - 5, bar_y - 5),
cv2.FONT_HERSHEY_SIMPLEX,
0.4,
self.TIMELINE_COLOR_CUT_POINT,
1,
)
def draw_crop_overlay(self, canvas, start_x, start_y, frame_width, frame_height):
"""Draw crop overlay on canvas using screen coordinates"""
# Draw preview rectangle (green) - already in screen coordinates
if self.crop_preview_rect:
x, y, w, h = self.crop_preview_rect
cv2.rectangle(canvas, (int(x), int(y)), (int(x + w), int(y + h)), (0, 255, 0), 2)
cv2.rectangle(
canvas, (int(x), int(y)), (int(x + w), int(y + h)), (0, 255, 0), 2
)
# Draw final crop rectangle (red) - convert from video to screen coordinates
if self.crop_rect:
@@ -345,7 +420,9 @@ class VideoEditor:
original_height, original_width = self.current_display_frame.shape[:2]
available_height = self.window_height - self.TIMELINE_HEIGHT
scale = min(self.window_width / original_width, available_height / original_height)
scale = min(
self.window_width / original_width, available_height / original_height
)
if scale < 1.0:
new_width = int(original_width * scale)
new_height = int(original_height * scale)
@@ -359,8 +436,13 @@ class VideoEditor:
screen_w = w * new_width / original_width
screen_h = h * new_height / original_height
cv2.rectangle(canvas, (int(screen_x), int(screen_y)),
(int(screen_x + screen_w), int(screen_y + screen_h)), (255, 0, 0), 2)
cv2.rectangle(
canvas,
(int(screen_x), int(screen_y)),
(int(screen_x + screen_w), int(screen_y + screen_h)),
(255, 0, 0),
2,
)
def display_current_frame(self):
"""Display the current frame with all overlays"""
@@ -368,7 +450,9 @@ class VideoEditor:
return
# Apply crop, zoom, and rotation transformations for preview
display_frame = self.apply_crop_zoom_and_rotation(self.current_display_frame.copy())
display_frame = self.apply_crop_zoom_and_rotation(
self.current_display_frame.copy()
)
if display_frame is None:
return
@@ -391,25 +475,59 @@ class VideoEditor:
start_y = (available_height - frame_height) // 2
start_x = (self.window_width - frame_width) // 2
canvas[start_y:start_y + frame_height, start_x:start_x + frame_width] = display_frame
canvas[start_y : start_y + frame_height, start_x : start_x + frame_width] = (
display_frame
)
# Draw crop overlay
if self.crop_rect or self.crop_preview_rect:
self.draw_crop_overlay(canvas, start_x, start_y, frame_width, frame_height)
# Add info overlay
rotation_text = f" | Rotation: {self.rotation_angle}°" if self.rotation_angle != 0 else ""
brightness_text = f" | Brightness: {self.brightness}" if self.brightness != 0 else ""
contrast_text = f" | Contrast: {self.contrast:.1f}" if self.contrast != 1.0 else ""
rotation_text = (
f" | Rotation: {self.rotation_angle}°" if self.rotation_angle != 0 else ""
)
brightness_text = (
f" | Brightness: {self.brightness}" if self.brightness != 0 else ""
)
contrast_text = (
f" | Contrast: {self.contrast:.1f}" if self.contrast != 1.0 else ""
)
info_text = f"Frame: {self.current_frame}/{self.total_frames} | Speed: {self.playback_speed:.1f}x | Zoom: {self.zoom_factor:.1f}x{rotation_text}{brightness_text}{contrast_text} | {'Playing' if self.is_playing else 'Paused'}"
cv2.putText(canvas, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(canvas, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 1)
cv2.putText(
canvas,
info_text,
(10, 30),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
(255, 255, 255),
2,
)
cv2.putText(
canvas, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 1
)
# Add video navigation info
if len(self.video_files) > 1:
video_text = f"Video: {self.current_video_index + 1}/{len(self.video_files)} - {self.video_path.name}"
cv2.putText(canvas, video_text, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
cv2.putText(canvas, video_text, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 1)
cv2.putText(
canvas,
video_text,
(10, 60),
cv2.FONT_HERSHEY_SIMPLEX,
0.6,
(255, 255, 255),
2,
)
cv2.putText(
canvas,
video_text,
(10, 60),
cv2.FONT_HERSHEY_SIMPLEX,
0.6,
(0, 0, 0),
1,
)
y_offset = 90
else:
y_offset = 60
@@ -417,15 +535,49 @@ class VideoEditor:
# Add crop info
if self.crop_rect:
crop_text = f"Crop: {int(self.crop_rect[0])},{int(self.crop_rect[1])} {int(self.crop_rect[2])}x{int(self.crop_rect[3])}"
cv2.putText(canvas, crop_text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
cv2.putText(canvas, crop_text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 1)
cv2.putText(
canvas,
crop_text,
(10, y_offset),
cv2.FONT_HERSHEY_SIMPLEX,
0.6,
(255, 255, 255),
2,
)
cv2.putText(
canvas,
crop_text,
(10, y_offset),
cv2.FONT_HERSHEY_SIMPLEX,
0.6,
(0, 0, 0),
1,
)
y_offset += 30
# Add cut info
if self.cut_start_frame is not None or self.cut_end_frame is not None:
cut_text = f"Cut: {self.cut_start_frame or '?'} - {self.cut_end_frame or '?'}"
cv2.putText(canvas, cut_text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
cv2.putText(canvas, cut_text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 1)
cut_text = (
f"Cut: {self.cut_start_frame or '?'} - {self.cut_end_frame or '?'}"
)
cv2.putText(
canvas,
cut_text,
(10, y_offset),
cv2.FONT_HERSHEY_SIMPLEX,
0.6,
(255, 255, 255),
2,
)
cv2.putText(
canvas,
cut_text,
(10, y_offset),
cv2.FONT_HERSHEY_SIMPLEX,
0.6,
(0, 0, 0),
1,
)
# Draw timeline
self.draw_timeline(canvas)
@@ -483,9 +635,13 @@ class VideoEditor:
if flags & cv2.EVENT_FLAG_CTRLKEY:
if event == cv2.EVENT_MOUSEWHEEL:
if flags > 0: # Scroll up
self.zoom_factor = min(self.MAX_ZOOM, self.zoom_factor + self.ZOOM_INCREMENT)
self.zoom_factor = min(
self.MAX_ZOOM, self.zoom_factor + self.ZOOM_INCREMENT
)
else: # Scroll down
self.zoom_factor = max(self.MIN_ZOOM, self.zoom_factor - self.ZOOM_INCREMENT)
self.zoom_factor = max(
self.MIN_ZOOM, self.zoom_factor - self.ZOOM_INCREMENT
)
def set_crop_from_screen_coords(self, screen_rect):
"""Convert screen coordinates to video frame coordinates and set crop"""
@@ -497,14 +653,18 @@ class VideoEditor:
available_height = self.window_height - self.TIMELINE_HEIGHT
# Calculate how the original frame is displayed (after crop/zoom/rotation)
display_frame = self.apply_crop_zoom_and_rotation(self.current_display_frame.copy())
display_frame = self.apply_crop_zoom_and_rotation(
self.current_display_frame.copy()
)
if display_frame is None:
return
display_height, display_width = display_frame.shape[:2]
# Calculate scale for the display frame
scale = min(self.window_width / display_width, available_height / display_height)
scale = min(
self.window_width / display_width, available_height / display_height
)
if scale < 1.0:
final_display_width = int(display_width * scale)
final_display_height = int(display_height * scale)
@@ -558,7 +718,6 @@ class VideoEditor:
original_w = min(original_w, original_width - original_x)
original_h = min(original_h, original_height - original_y)
if original_w > 10 and original_h > 10: # Minimum size check
# Save current crop for undo
if self.crop_rect:
@@ -581,15 +740,19 @@ class VideoEditor:
def render_video(self, output_path: str):
"""Optimized video rendering with multithreading and batch processing"""
if not output_path.endswith('.mp4'):
output_path += '.mp4'
if not output_path.endswith(".mp4"):
output_path += ".mp4"
print(f"Rendering video to {output_path}...")
start_time = time.time()
# Determine frame range
start_frame = self.cut_start_frame if self.cut_start_frame is not None else 0
end_frame = self.cut_end_frame if self.cut_end_frame is not None else self.total_frames - 1
end_frame = (
self.cut_end_frame
if self.cut_end_frame is not None
else self.total_frames - 1
)
if start_frame >= end_frame:
print("Invalid cut range!")
@@ -612,8 +775,10 @@ class VideoEditor:
output_height = int(crop_height * self.zoom_factor)
# Use mp4v codec (most compatible with MP4)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, self.fps, (output_width, output_height))
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
out = cv2.VideoWriter(
output_path, fourcc, self.fps, (output_width, output_height)
)
if not out.isOpened():
print("Error: Could not open video writer!")
@@ -632,7 +797,9 @@ class VideoEditor:
break
# Process and write frame directly (minimize memory copies)
processed_frame = self._process_frame_for_render(frame, output_width, output_height)
processed_frame = self._process_frame_for_render(
frame, output_width, output_height
)
if processed_frame is not None:
out.write(processed_frame)
@@ -645,9 +812,14 @@ class VideoEditor:
progress = frames_written / total_output_frames * 100
elapsed = current_time - start_time
fps_rate = frames_written / elapsed
eta = (elapsed / frames_written) * (total_output_frames - frames_written)
print(f"Progress: {progress:.1f}% | {frames_written}/{total_output_frames} | "
f"FPS: {fps_rate:.1f} | ETA: {eta:.1f}s\r", end="")
eta = (elapsed / frames_written) * (
total_output_frames - frames_written
)
print(
f"Progress: {progress:.1f}% | {frames_written}/{total_output_frames} | "
f"FPS: {fps_rate:.1f} | ETA: {eta:.1f}s\r",
end="",
)
last_progress_update = current_time
out.release()
@@ -657,7 +829,9 @@ class VideoEditor:
avg_fps = total_frames_written / total_time if total_time > 0 else 0
print(f"\nVideo rendered successfully to {output_path}")
print(f"Rendered {total_frames_written} frames in {total_time:.2f}s (avg {avg_fps:.1f} FPS)")
print(
f"Rendered {total_frames_written} frames in {total_time:.2f}s (avg {avg_fps:.1f} FPS)"
)
return True
def _process_frame_for_render(self, frame, output_width: int, output_height: int):
@@ -675,7 +849,7 @@ class VideoEditor:
h = min(h, h_frame - y)
if w > 0 and h > 0:
frame = frame[y:y+h, x:x+w]
frame = frame[y : y + h, x : x + w]
else:
return None
@@ -693,14 +867,27 @@ class VideoEditor:
intermediate_height = int(height * self.zoom_factor)
# If zoom results in different dimensions than output, resize directly to output
if intermediate_width != output_width or intermediate_height != output_height:
frame = cv2.resize(frame, (output_width, output_height), interpolation=cv2.INTER_LINEAR)
if (
intermediate_width != output_width
or intermediate_height != output_height
):
frame = cv2.resize(
frame,
(output_width, output_height),
interpolation=cv2.INTER_LINEAR,
)
else:
frame = cv2.resize(frame, (intermediate_width, intermediate_height), interpolation=cv2.INTER_LINEAR)
frame = cv2.resize(
frame,
(intermediate_width, intermediate_height),
interpolation=cv2.INTER_LINEAR,
)
# Final size check and resize if needed
if frame.shape[1] != output_width or frame.shape[0] != output_height:
frame = cv2.resize(frame, (output_width, output_height), interpolation=cv2.INTER_LINEAR)
frame = cv2.resize(
frame, (output_width, output_height), interpolation=cv2.INTER_LINEAR
)
return frame
@@ -749,19 +936,21 @@ class VideoEditor:
# Note: OpenCV doesn't provide direct access to modifier keys in waitKey
# We'll handle this through special key combinations
if key == ord('q') or key == 27: # ESC
if key == ord("q") or key == 27: # ESC
break
elif key == ord(' '):
elif key == ord(" "):
self.is_playing = not self.is_playing
elif key == ord('a') or key == ord('A'):
elif key == ord("a") or key == ord("A"):
# Check if it's uppercase A (Shift+A)
if key == ord('A'):
self.seek_video_with_modifier(-1, True, False) # Shift+A: -10 frames
if key == ord("A"):
self.seek_video_with_modifier(
-1, True, False
) # Shift+A: -10 frames
else:
self.seek_video_with_modifier(-1, False, False) # A: -1 frame
elif key == ord('d') or key == ord('D'):
elif key == ord("d") or key == ord("D"):
# Check if it's uppercase D (Shift+D)
if key == ord('D'):
if key == ord("D"):
self.seek_video_with_modifier(1, True, False) # Shift+D: +10 frames
else:
self.seek_video_with_modifier(1, False, False) # D: +1 frame
@@ -769,45 +958,49 @@ class VideoEditor:
self.seek_video_with_modifier(-1, False, True) # Ctrl+A: -60 frames
elif key == 4: # Ctrl+D
self.seek_video_with_modifier(1, False, True) # Ctrl+D: +60 frames
elif key == ord('-') or key == ord('_'):
elif key == ord("-") or key == ord("_"):
self.rotate_clockwise()
print(f"Rotated to {self.rotation_angle}°")
elif key == ord('w'):
self.playback_speed = min(self.MAX_PLAYBACK_SPEED, self.playback_speed + self.SPEED_INCREMENT)
elif key == ord('s'):
self.playback_speed = max(self.MIN_PLAYBACK_SPEED, self.playback_speed - self.SPEED_INCREMENT)
elif key == ord('e') or key == ord('E'):
elif key == ord("w"):
self.playback_speed = min(
self.MAX_PLAYBACK_SPEED, self.playback_speed + self.SPEED_INCREMENT
)
elif key == ord("s"):
self.playback_speed = max(
self.MIN_PLAYBACK_SPEED, self.playback_speed - self.SPEED_INCREMENT
)
elif key == ord("e") or key == ord("E"):
# Brightness adjustment: E (increase), Shift+E (decrease)
if key == ord('E'):
if key == ord("E"):
self.adjust_brightness(-5)
print(f"Brightness: {self.brightness}")
else:
self.adjust_brightness(5)
print(f"Brightness: {self.brightness}")
elif key == ord('r') or key == ord('R'):
elif key == ord("r") or key == ord("R"):
# Contrast adjustment: R (increase), Shift+R (decrease)
if key == ord('R'):
if key == ord("R"):
self.adjust_contrast(-0.1)
print(f"Contrast: {self.contrast:.1f}")
else:
self.adjust_contrast(0.1)
print(f"Contrast: {self.contrast:.1f}")
elif key == ord('u'):
elif key == ord("u"):
self.undo_crop()
elif key == ord('c'):
elif key == ord("c"):
if self.crop_rect:
self.crop_history.append(self.crop_rect)
self.crop_rect = None
elif key == ord('1'):
elif key == ord("1"):
self.cut_start_frame = self.current_frame
print(f"Set cut start at frame {self.current_frame}")
elif key == ord('2'):
elif key == ord("2"):
self.cut_end_frame = self.current_frame
print(f"Set cut end at frame {self.current_frame}")
elif key == ord('n'):
elif key == ord("n"):
if len(self.video_files) > 1:
self.previous_video()
elif key == ord('N'):
elif key == ord("N"):
if len(self.video_files) > 1:
self.next_video()
elif key == 13: # Enter
@@ -823,8 +1016,12 @@ class VideoEditor:
def main():
parser = argparse.ArgumentParser(description="Fast Video Editor - Crop, Zoom, and Cut videos")
parser.add_argument("video", help="Path to video file or directory containing videos")
parser = argparse.ArgumentParser(
description="Fast Video Editor - Crop, Zoom, and Cut videos"
)
parser.add_argument(
"video", help="Path to video file or directory containing videos"
)
args = parser.parse_args()