Created
February 8, 2025 07:52
-
-
Save HViktorTsoi/0a9cf24985ac14ae24c69799212170f5 to your computer and use it in GitHub Desktop.
A ubuntu utility for quickly cutting video.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# location: /home/xxx/.local/share/applications | |
[Desktop Entry] | |
Name=VideoCut | |
Comment=Fast video cutting tool | |
Exec=/path/to/video_cut.py %U | |
Icon=/path/to/icon.png | |
Terminal=false | |
Type=Application |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /home/hvt/miniconda3/envs/modern_torch/bin/python | |
import os, sys | |
ci_build_and_not_headless = False | |
try: | |
from cv2.version import ci_build, headless | |
ci_and_not_headless = ci_build and not headless | |
except: | |
pass | |
if sys.platform.startswith("linux") and ci_and_not_headless: | |
os.environ.pop("QT_QPA_PLATFORM_PLUGIN_PATH") | |
if sys.platform.startswith("linux") and ci_and_not_headless: | |
os.environ.pop("QT_QPA_FONTDIR") | |
import sys | |
import cv2 | |
import subprocess | |
from PyQt5.QtCore import Qt, QTimer | |
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QHBoxLayout, QWidget, QSlider, QPushButton, QFileDialog, QLabel, \ | |
QMessageBox | |
from PyQt5.QtGui import QImage, QPixmap | |
class VideoEditor(QWidget): | |
def __init__(self, input_file=None): | |
super().__init__() | |
self.setWindowTitle("视频剪辑工具") | |
self.setGeometry(100, 100, 1000, 800) | |
# 初始化 OpenCV 视频捕获 | |
self.cap = None | |
self.frame_rate = 30 # 默认帧率 | |
self.total_frames = 0 | |
# 布局 | |
layout = QVBoxLayout() | |
# 创建两个视频画面显示标签 | |
self.start_video_label = QLabel(self) | |
self.start_video_label.setFixedSize(400, 300) # 设置固定大小 | |
self.end_video_label = QLabel(self) | |
self.end_video_label.setFixedSize(400, 300) # 设置固定大小 | |
layout.addWidget(QLabel("起始时间对应的画面")) | |
layout.addWidget(self.start_video_label) | |
layout.addWidget(QLabel("终止时间对应的画面")) | |
layout.addWidget(self.end_video_label) | |
# 创建起始时间和结束时间的滑动条 | |
self.start_slider = QSlider(Qt.Horizontal) | |
self.start_slider.setRange(0, 1000) | |
self.start_slider.setTickInterval(1) | |
self.start_slider.setTickPosition(QSlider.TicksBelow) | |
layout.addWidget(QLabel("起始时间")) | |
layout.addWidget(self.start_slider) | |
self.end_slider = QSlider(Qt.Horizontal) | |
self.end_slider.setRange(0, 1000) | |
self.end_slider.setTickInterval(1) | |
self.end_slider.setTickPosition(QSlider.TicksBelow) | |
layout.addWidget(QLabel("结束时间")) | |
layout.addWidget(self.end_slider) | |
# 按钮区域 | |
btn_layout = QHBoxLayout() | |
self.load_button = QPushButton("加载视频") | |
self.load_button.clicked.connect(self.load_video) | |
self.cut_button = QPushButton("裁剪视频") | |
self.cut_button.clicked.connect(self.cut_video) | |
btn_layout.addWidget(self.load_button) | |
btn_layout.addWidget(self.cut_button) | |
layout.addLayout(btn_layout) | |
# 设置主布局 | |
self.setLayout(layout) | |
# 用于视频播放的定时器 | |
self.timer = QTimer(self) | |
self.timer.timeout.connect(self.update_frame) | |
# 视频开始和结束时间 | |
self.start_time = 0 | |
self.end_time = 0 | |
# 连接滑动条和时间更新 | |
self.start_slider.valueChanged.connect(self.update_start_time) | |
self.end_slider.valueChanged.connect(self.update_end_time) | |
if input_file is not None: | |
self.input_file = input_file | |
self.load_video() | |
else: | |
self.input_file = None | |
def load_video(self): | |
if self.input_file is None: | |
video_file, _ = QFileDialog.getOpenFileName(self, "选择视频文件", '/home/hvt/Videos', | |
"视频文件 (*.mp4 *.avi *.mov)") | |
else: | |
video_file = self.input_file | |
if video_file: | |
self.input_file = video_file | |
self.cap = cv2.VideoCapture(video_file) | |
self.frame_rate = int(self.cap.get(cv2.CAP_PROP_FPS)) # 获取视频帧率 | |
self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 获取视频帧数 | |
self.start_slider.setMaximum(self.total_frames) | |
self.end_slider.setMaximum(self.total_frames) | |
self.end_slider.setValue(self.total_frames - 1) | |
self.start_time = 0 | |
self.end_time = self.total_frames | |
self.timer.start(1000 / self.frame_rate) # 根据帧率设置定时器 | |
def update_frame(self): | |
if self.cap.isOpened(): | |
# 获取起始时间画面 | |
start_frame = self.start_slider.value() | |
self.cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) # 设置当前帧 | |
ret, frame = self.cap.read() | |
if ret: | |
self.display_frame(frame, self.start_video_label) | |
# 获取终止时间画面 | |
end_frame = self.end_slider.value() | |
self.cap.set(cv2.CAP_PROP_POS_FRAMES, end_frame) # 设置当前帧 | |
ret, frame = self.cap.read() | |
if ret: | |
self.display_frame(frame, self.end_video_label) | |
def display_frame(self, frame, label): | |
# 将 BGR 转换为 RGB 并显示 | |
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
rgb_image = cv2.resize(rgb_image, (400, 300)) | |
h, w, ch = rgb_image.shape | |
bytes_per_line = ch * w | |
q_img = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) | |
label.setPixmap(QPixmap.fromImage(q_img)) | |
def update_start_time(self, value): | |
self.start_time = value | |
# 实时更新视频播放和裁剪区域 | |
self.cap.set(cv2.CAP_PROP_POS_FRAMES, value) | |
def update_end_time(self, value): | |
self.end_time = value | |
def cut_video(self): | |
# 获取裁剪的起始和结束时间 | |
start_frame = self.start_slider.value() | |
end_frame = self.end_slider.value() | |
# 打开保存文件对话框 | |
save_path, _ = QFileDialog.getSaveFileName(self, "另存为", os.path.dirname(self.input_file), "视频文件 (*.mp4)") | |
if save_path: | |
if not save_path.endswith('.mp4'): | |
save_path += '.mp4' | |
self.perform_cut(start_frame, end_frame, save_path) | |
def perform_cut(self, start_frame, end_frame, output_file): | |
# 使用 FFmpeg 来裁剪视频 | |
self.load_button.setStyleSheet('''QWidget{background-color:#FF0000;}''') | |
# 获取视频文件路径(避免 OpenCV 的 `.mp4` 中路径无法获取) | |
command = [ | |
"ffmpeg", | |
"-y", | |
# "-i", f"'{self.input_file}'", | |
"-i", f"{self.input_file}", | |
"-ss", str(start_frame / self.frame_rate), # 转换为秒 | |
"-to", str(end_frame / self.frame_rate), # 转换为秒 | |
# "-c:v", "libx264", | |
"-c:a", "aac", "-strict", "experimental", | |
# "-preset", "ultrafast", | |
# "-crf", "23", | |
output_file | |
] | |
# print(' '.join(command)) | |
# return | |
# 执行 FFmpeg 命令 | |
print(' '.join(command)) | |
# os.system(' '.join(command)) | |
proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) | |
# for line in proc.stderr.readline(): | |
# print(line) | |
for line in iter(proc.stdout.readline, 'b'): | |
if len(line) == 0: | |
continue | |
else: | |
print(line) | |
line = line.decode() | |
if 'Qavg' in line: | |
# 弹出message box | |
QMessageBox.information(self, "完成", "视频截取完成", QMessageBox.Yes, QMessageBox.Yes) | |
exit(0) | |
if not subprocess.Popen.poll(proc) is None: | |
if line == "": | |
break | |
# process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
# stdout, stderr = process.communicate() | |
# if process.returncode == 0: | |
# print("视频裁剪完成") | |
# else: | |
# print("视频裁剪失败", stderr.decode()) | |
if __name__ == "__main__": | |
open('/tmp/video_cut.log', 'w').write(' '.join(sys.argv) + '\n') | |
app = QApplication(sys.argv) | |
editor = VideoEditor(input_file=sys.argv[1] if len(sys.argv) > 1 else None) | |
editor.show() | |
sys.exit(app.exec_()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment