吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4306|回复: 26
收起左侧

[Python 原创] 使用ffmpeg对视频截取\裁剪\转gif等操作 小工具

[复制链接]
zwc720 发表于 2025-4-21 14:49
本帖最后由 zwc720 于 2025-4-23 08:55 编辑

现在用AI编写程序很方便,就随手用python+ffmpeg做了个视频处理的小工具, 需要安装库: pip install opencv-python pyqt5, 本地电脑需配置ffmpeg系统路径. 程序实现的功能较常用,我自己够用了


本来是python语言区,大家可以自己修改添加新功能,没准备打包. 而且打包文件太大了.
分享链接
https://wwol.lanzoum.com/iZ9L62u86aob 想要的自取


[Python] 纯文本查看 复制代码
import sys
import os
import subprocess
import cv2
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QLabel, QCheckBox, QLineEdit, QMessageBox, QProgressBar
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import QTimer, Qt


class VideoProcessor(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        # 允许接受拖拽事件
        self.setAcceptDrops(True)

        self.cap = None
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_frame)
        self.fixed_width = 640
        self.fixed_height = 480
        self.is_playing = False

    def initUI(self):
        # 创建布局
        main_layout = QVBoxLayout()

        # 选择视频文件
        file_layout = QHBoxLayout()
        self.file_label = QLabel("未选择文件")
        file_button = QPushButton("选择视频文件")
        file_button.clicked.connect(self.select_file)
        file_layout.addWidget(self.file_label)
        file_layout.addWidget(file_button)
        main_layout.addLayout(file_layout)

        # 视频信息显示
        self.info_label = QLabel()
        main_layout.addWidget(self.info_label)

        # 视频预览
        self.video_label = QLabel()
        main_layout.addWidget(self.video_label)

        # 进度条和时长显示布局
        self.progress_layout = QHBoxLayout()
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.mousePressEvent = self.progress_bar_mouse_press
        self.progress_bar.mouseMoveEvent = self.progress_bar_mouse_move
        self.progress_layout.addWidget(self.progress_bar)
        self.time_label = QLabel("0:00 / 0:00")
        self.progress_layout.addWidget(self.time_label)
        main_layout.addLayout(self.progress_layout)

        # 播放控制按钮
        self.control_layout = QHBoxLayout()
        self.play_button = QPushButton("播放")
        self.play_button.clicked.connect(self.toggle_video)
        self.stop_button = QPushButton("停止")
        self.stop_button.clicked.connect(self.stop_video)
        self.control_layout.addWidget(self.play_button)
        self.control_layout.addWidget(self.stop_button)
        main_layout.addLayout(self.control_layout)

        # 裁剪功能
        crop_layout = QHBoxLayout()
        self.crop_checkbox = QCheckBox("裁剪视频")
        self.start_time_input = QLineEdit()
        self.start_time_input.setPlaceholderText("开始时间 (00:00:00 或 00:00 或秒数)")
        self.end_time_input = QLineEdit()
        self.end_time_input.setPlaceholderText("结束时间 (00:00:00 或 00:00 或秒数)")
        self.start_time_input.textChanged.connect(self.auto_check_crop)
        self.end_time_input.textChanged.connect(self.auto_check_crop)
        crop_layout.addWidget(self.crop_checkbox)
        crop_layout.addWidget(self.start_time_input)
        crop_layout.addWidget(self.end_time_input)
        main_layout.addLayout(crop_layout)

        # 上下左右裁边功能
        trim_layout = QHBoxLayout()
        self.trim_checkbox = QCheckBox("上下左右裁边")
        self.left_trim_input = QLineEdit()
        self.left_trim_input.setPlaceholderText("左边裁边像素")
        self.right_trim_input = QLineEdit()
        self.right_trim_input.setPlaceholderText("右边裁边像素")
        self.top_trim_input = QLineEdit()
        self.top_trim_input.setPlaceholderText("上边裁边像素")
        self.bottom_trim_input = QLineEdit()
        self.bottom_trim_input.setPlaceholderText("下边裁边像素")
        self.left_trim_input.textChanged.connect(self.auto_check_trim)
        self.right_trim_input.textChanged.connect(self.auto_check_trim)
        self.top_trim_input.textChanged.connect(self.auto_check_trim)
        self.bottom_trim_input.textChanged.connect(self.auto_check_trim)
        trim_layout.addWidget(self.trim_checkbox)
        trim_layout.addWidget(self.left_trim_input)
        trim_layout.addWidget(self.right_trim_input)
        trim_layout.addWidget(self.top_trim_input)
        trim_layout.addWidget(self.bottom_trim_input)
        main_layout.addLayout(trim_layout)

        # 压缩功能
        compress_layout = QHBoxLayout()
        self.compress_checkbox = QCheckBox("压缩视频")
        self.crf_input = QLineEdit()
        self.crf_input.setPlaceholderText("CRF 值 (18 - 28) 越大越不清晰")
        self.crf_input.textChanged.connect(self.auto_check_compress)
        compress_layout.addWidget(self.compress_checkbox)
        compress_layout.addWidget(self.crf_input)
        main_layout.addLayout(compress_layout)

        # 修改分辨率功能
        resolution_layout = QHBoxLayout()
        self.resolution_checkbox = QCheckBox("修改分辨率")
        self.width_input = QLineEdit()
        self.width_input.setPlaceholderText("宽度")
        self.height_input = QLineEdit()
        self.height_input.setPlaceholderText("高度(可选)")
        self.width_input.textChanged.connect(self.auto_check_resolution)
        self.height_input.textChanged.connect(self.auto_check_resolution)
        resolution_layout.addWidget(self.resolution_checkbox)
        resolution_layout.addWidget(self.width_input)
        resolution_layout.addWidget(self.height_input)
        main_layout.addLayout(resolution_layout)

        # 截图功能
        screenshot_layout = QHBoxLayout()
        self.screenshot_checkbox = QCheckBox("截指定时间的截图")
        self.screenshot_time_input = QLineEdit()
        self.screenshot_time_input.setPlaceholderText("截图时间 (00:00:00 或 00:00 或秒数)")
        self.screenshot_time_input.textChanged.connect(self.auto_check_screenshot)
        screenshot_layout.addWidget(self.screenshot_checkbox)
        screenshot_layout.addWidget(self.screenshot_time_input)
        main_layout.addLayout(screenshot_layout)

        # 视频转GIF功能
        gif_layout = QHBoxLayout()
        self.gif_checkbox = QCheckBox("转换为GIF")
        self.gif_start_time_input = QLineEdit()
        self.gif_start_time_input.setPlaceholderText("开始时间 (00:00:00 或 00:00 或秒数)")
        self.gif_end_time_input = QLineEdit()
        self.gif_end_time_input.setPlaceholderText("结束时间 (00:00:00 或 00:00 或秒数)")
        self.gif_fps_input = QLineEdit()
        self.gif_fps_input.setPlaceholderText("FPS (默认15)")
        self.gif_scale_input = QLineEdit()
        self.gif_scale_input.setPlaceholderText("缩放比例 (0-1, 默认0.5)")
        self.gif_start_time_input.textChanged.connect(self.auto_check_gif)
        self.gif_end_time_input.textChanged.connect(self.auto_check_gif)
        self.gif_fps_input.textChanged.connect(self.auto_check_gif)
        self.gif_scale_input.textChanged.connect(self.auto_check_gif)
        gif_layout.addWidget(self.gif_checkbox)
        gif_layout.addWidget(self.gif_start_time_input)
        gif_layout.addWidget(self.gif_end_time_input)
        gif_layout.addWidget(self.gif_fps_input)
        gif_layout.addWidget(self.gif_scale_input)
        main_layout.addLayout(gif_layout)

        # 处理按钮
        self.process_button = QPushButton("处理视频")
        self.process_button.clicked.connect(self.on_btn_process)
        main_layout.addWidget(self.process_button)

        # 初始隐藏播放控制按钮和进度条
        self.play_button.setVisible(False)
        self.stop_button.setVisible(False)
        self.progress_bar.setVisible(False)
        self.process_button.setEnabled(False)

        self.setLayout(main_layout)
        self.setWindowTitle('视频处理工具')
        self.show()

    def select_file(self):
        file_dialog = QFileDialog()
        file_path, _ = file_dialog.getOpenFileName(self, "选择视频文件", "", "视频文件 (*.mp4)")
        if file_path:
            self.file_label.setText(file_path)
            self.cap = cv2.VideoCapture(file_path)
            self.show_video_info(file_path)
            self.toggle_video()
            # 显示播放控制按钮和进度条
            self.play_button.setVisible(True)
            self.stop_button.setVisible(True)
            self.progress_bar.setVisible(True)
            self.process_button.setEnabled(True)

            # 让 play_button 获得焦点
            self.play_button.setFocus()

    def show_video_info(self, file_path):
        width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        fps = self.cap.get(cv2.CAP_PROP_FPS)
        frame_count = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
        duration = frame_count / fps
        file_size = os.path.getsize(file_path) / (1024 * 1024)  # 转换为 MB
        bitrate = (file_size * 8 * 1024 * 1024) / (duration * 1000)  # 计算码率 (kbps)

        info_text = f"分辨率: {width}x{height} | 文件大小: {file_size:.2f} MB | 码率: {bitrate:.2f} kbps"
        self.info_label.setText(info_text)

        total_seconds = int(duration)
        minutes, seconds = divmod(total_seconds, 60)
        self.total_time_str = f"{minutes}:{seconds:02d}"
        self.time_label.setText(f"0:00 / {self.total_time_str}")

    def toggle_video(self):
        if self.cap:
            if self.timer.isActive():
                self.timer.stop()
                self.is_playing = False
                self.play_button.setText("播放")
            else:
                self.timer.start(30)
                self.is_playing = True
                self.play_button.setText("暂停")

    def stop_video(self):
        if self.timer.isActive():
            self.timer.stop()
        if self.cap:
            self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
            self.progress_bar.setValue(0)
            self.time_label.setText(f"0:00 / {self.total_time_str}")
            ret, frame = self.cap.read()
            if ret:
                self.display_frame(frame)
            self.is_playing = False
            self.play_button.setText("播放")

    def update_frame(self):
        if self.cap:
            ret, frame = self.cap.read()
            if ret:
                self.display_frame(frame)
                total_frames = self.cap.get(cv2.CAP_PROP_FRAME_COUNT)
                current_frame = self.cap.get(cv2.CAP_PROP_POS_FRAMES)
                progress = int((current_frame / total_frames) * 100)
                self.progress_bar.setValue(progress)

                fps = self.cap.get(cv2.CAP_PROP_FPS)
                current_seconds = int(current_frame / fps)
                minutes, seconds = divmod(current_seconds, 60)
                current_time_str = f"{minutes}:{seconds:02d}"
                self.time_label.setText(f"{current_time_str} / {self.total_time_str}")
            else:
                self.stop_video()

    def display_frame(self, frame):
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        frame = cv2.resize(frame, (self.fixed_width, self.fixed_height))
        height, width, channel = frame.shape
        bytesPerLine = 3 * width
        qImg = QImage(frame.data, width, height, bytesPerLine, QImage.Format_RGB888)
        pixmap = QPixmap.fromImage(qImg)
        self.video_label.setPixmap(pixmap)

    def convert_time_format(self, time_str):
        parts = time_str.split(':')
        if len(parts) == 2:
            minutes, seconds = map(int, parts)
            return f"{minutes * 60 + seconds}"
        elif len(parts) == 3:
            hours, minutes, seconds = map(int, parts)
            return f"{hours * 3600 + minutes * 60 + seconds}"
        return time_str

    def on_btn_process(self):
        input_file = self.file_label.text()
        if not os.path.exists(input_file):
            QMessageBox.warning(self, "错误", "请选择有效的视频文件")
            return

        self.pause_video()
        self.process_button.setEnabled(False)

        if self.crop_checkbox.isChecked() or self.trim_checkbox.isChecked() or self.compress_checkbox.isChecked() or self.resolution_checkbox.isChecked():
            self.process_video(input_file)

        if self.screenshot_checkbox.isChecked():
            self.process_screenshot(input_file)

        if self.gif_checkbox.isChecked():
            self.process_gif(input_file)

    def process_video(self, input_file):

        output_file = os.path.splitext(input_file)[0] + "_processed.mp4"
        ffmpeg_cmd = ['ffmpeg', '-i', input_file, '-y']

        if self.crop_checkbox.isChecked():
            start_time = self.start_time_input.text() or '0'
            end_time = self.end_time_input.text() or '0'
            if int(end_time) > int(start_time):
                start_time = self.convert_time_format(start_time)
                end_time = self.convert_time_format(end_time)
                ffmpeg_cmd.extend(['-ss', start_time, '-to', end_time])

        if self.trim_checkbox.isChecked():
            left_trim = self.left_trim_input.text() or '0'
            right_trim = self.right_trim_input.text() or '0'
            top_trim = self.top_trim_input.text() or '0'
            bottom_trim = self.bottom_trim_input.text() or '0'
            if int(left_trim) + int(right_trim) + int(top_trim) + int(bottom_trim) > 0:
                width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                new_width = width - int(left_trim) - int(right_trim)
                new_height = height - int(top_trim) - int(bottom_trim)
                ffmpeg_cmd.extend(['-filter:v', f'crop={new_width}:{new_height}:{left_trim}:{top_trim}'])

        if self.compress_checkbox.isChecked():
            crf = self.crf_input.text()
            if crf:
                ffmpeg_cmd.extend(['-crf', crf])

        if self.resolution_checkbox.isChecked():
            width = self.width_input.text()
            height = self.height_input.text()
            if width:
                if not height:
                    # 获取原视频的宽度和高度
                    original_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                    original_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                    # 计算等比例缩放后的高度
                    height = int((int(width) / original_width) * original_height)
                ffmpeg_cmd.extend(['-s', f'{width}x{height}'])

        ffmpeg_cmd.append(output_file)
        try:
            subprocess.run(ffmpeg_cmd, check=True)
            QMessageBox.information(self, "成功", "视频处理完成")
        except subprocess.CalledProcessError as e:
            QMessageBox.warning(self, "错误", f"视频处理失败: {e}")
        finally:
            self.process_button.setEnabled(True)

    def process_screenshot(self, input_file):
        screenshot_time = self.screenshot_time_input.text()
        if screenshot_time:
            screenshot_time = self.convert_time_format(screenshot_time)
            screenshot_file = os.path.splitext(input_file)[0] + f"_screenshot_{screenshot_time}.jpg"
            screenshot_cmd = ['ffmpeg', '-i', input_file, '-y', '-ss', screenshot_time, '-vframes', '1', screenshot_file]
            try:
                subprocess.run(screenshot_cmd, check=True)
                QMessageBox.information(self, "成功", f"截图已保存为: {screenshot_file}")
            except subprocess.CalledProcessError as e:
                QMessageBox.warning(self, "错误", f"截图失败: {e}")
            finally:
                self.process_button.setEnabled(True)

    def process_gif(self, input_file):
        gif_output = os.path.splitext(input_file)[0] + ".gif"
        gif_cmd = ['ffmpeg', '-i', input_file]
        
        # 添加开始和结束时间
        start_time = self.gif_start_time_input.text()
        end_time = self.gif_end_time_input.text()
        if start_time:
            start_time = self.convert_time_format(start_time)
            gif_cmd.extend(['-ss', start_time])
        if end_time:
            end_time = self.convert_time_format(end_time)
            gif_cmd.extend(['-to', end_time])
        
        # 添加FPS设置
        fps = self.gif_fps_input.text() if self.gif_fps_input.text() else "15"
        
        # 添加缩放设置
        scale = self.gif_scale_input.text() if self.gif_scale_input.text() else "0.5"
        
        # 添加GIF转换参数
        gif_cmd.extend([
            '-vf', f'fps={fps},scale=iw*{scale}:ih*{scale}:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse',
            '-loop', '0', '-y',
            gif_output
        ])
        
        try:
            subprocess.run(gif_cmd, check=True)
            QMessageBox.information(self, "成功", f"GIF已保存为: {gif_output}")
        except subprocess.CalledProcessError as e:
            QMessageBox.warning(self, "错误", f"GIF转换失败: {e}")
        finally:
            self.process_button.setEnabled(True)

    def progress_bar_mouse_press(self, event):
        if event.button() == Qt.LeftButton:
            total_width = self.progress_bar.width()
            click_x = event.pos().x()
            progress = int((click_x / total_width) * 100)
            self.seek_video(progress)

    def progress_bar_mouse_move(self, event):
        if event.buttons() & Qt.LeftButton:
            total_width = self.progress_bar.width()
            click_x = event.pos().x()
            progress = int((click_x / total_width) * 100)
            self.seek_video(progress)

    def seek_video(self, progress):
        if self.cap:
            total_frames = self.cap.get(cv2.CAP_PROP_FRAME_COUNT)
            target_frame = int((progress / 100) * total_frames)
            self.cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame)

            fps = self.cap.get(cv2.CAP_PROP_FPS)
            current_seconds = int(target_frame / fps)
            minutes, seconds = divmod(current_seconds, 60)
            current_time_str = f"{minutes}:{seconds:02d}"
            self.time_label.setText(f"{current_time_str} / {self.total_time_str}")
            self.progress_bar.setValue(progress)

            ret, frame = self.cap.read()
            if ret:
                self.display_frame(frame)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Space:
            self.toggle_video()

    def pause_video(self):
        if self.timer.isActive():
            self.timer.stop()
            self.is_playing = False
            self.play_button.setText("播放")

    def auto_check_crop(self):
        if self.start_time_input.text() or self.end_time_input.text():
            self.crop_checkbox.setChecked(True)

    def auto_check_trim(self):
        if self.left_trim_input.text() or self.right_trim_input.text() or self.top_trim_input.text() or self.bottom_trim_input.text():
            self.trim_checkbox.setChecked(True)

    def auto_check_compress(self):
        if self.crf_input.text():
            self.compress_checkbox.setChecked(True)

    def auto_check_resolution(self):
        if self.width_input.text() or self.height_input.text():
            self.resolution_checkbox.setChecked(True)

    def auto_check_screenshot(self):
        if self.screenshot_time_input.text():
            self.screenshot_checkbox.setChecked(True)

    def auto_check_gif(self):
        if (self.gif_start_time_input.text() or self.gif_end_time_input.text() or 
            self.gif_fps_input.text() or self.gif_scale_input.text()):
            self.gif_checkbox.setChecked(True)

    def dragEnterEvent(self, event):
            # 检查拖拽的是否是文件
            if event.mimeData().hasUrls():
                event.acceptProposedAction()
            else:
                event.ignore()

    def dropEvent(self, event):
        # 获取拖拽的文件路径
        for url in event.mimeData().urls():
            file_path = url.toLocalFile()
            # 检查文件是否为视频文件(简单示例,可根据需要扩展)
            if file_path.lower().endswith(('.mp4', '.avi', '.mov')):
                self.file_label.setText(file_path)
                self.cap = cv2.VideoCapture(file_path)
                self.show_video_info(file_path)
                self.toggle_video()
                # 显示播放控制按钮和进度条
                self.play_button.setVisible(True)
                self.stop_button.setVisible(True)
                self.progress_bar.setVisible(True)
                self.process_button.setEnabled(True)

                # 让 play_button 获得焦点
                self.play_button.setFocus()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    processor = VideoProcessor()
    sys.exit(app.exec_())
微信截图_20250421143942.png

免费评分

参与人数 4吾爱币 +8 热心值 +4 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
xiaofeng4929 + 1 谢谢@Thanks!
lian52yy + 1 + 1 谢谢@Thanks!
feishibudong + 1 热心回复!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

ldw471427015 发表于 2025-4-21 16:53
就是在网上下载的分段式tf文件格式的视频(都是乱码.tf)  可以合并嘛
 楼主| zwc720 发表于 2025-4-23 08:27
开创者 发表于 2025-4-22 11:19
打包好了,测试了一下,裁剪好像没效果。压缩是越压越大,5MB压缩后成了9MB。
不知道是那操作不对,还 ...

裁剪尺寸上下左右都不留空(用0代替), 裁边尺寸小也不容易看出来. 压缩涉及码率, 码率本来就低的, 不合适的crf值会加大码率.  如果愿意修改代码的话,压缩用码率代替CRF
wpdzdx 发表于 2025-4-21 17:08
feishibudong 发表于 2025-4-21 17:09
这个小工具写的很实用的,感谢
一场荒唐半生梦 发表于 2025-4-21 17:10
楼主有打包的吗
wangwh 发表于 2025-4-21 17:21
看着一堆代码,就是不会用呀
fakerx 发表于 2025-4-21 17:21
正好需要,试试看,谢谢。
pw0n 发表于 2025-4-21 17:31

感谢分享
lp-cg 发表于 2025-4-21 18:24
求个成品来着
wjl999 发表于 2025-4-21 18:25
可以,正好想了解ffmpeg
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-12 18:15

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表