Skip to content

Instantly share code, notes, and snippets.

@HViktorTsoi
Created February 8, 2025 07:52
Show Gist options
  • Save HViktorTsoi/0a9cf24985ac14ae24c69799212170f5 to your computer and use it in GitHub Desktop.
Save HViktorTsoi/0a9cf24985ac14ae24c69799212170f5 to your computer and use it in GitHub Desktop.
A ubuntu utility for quickly cutting video.
# 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
#! /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