2 Star 4 Fork 3

马腾飞/ffmpeg-python视频批量拼接软件

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
video_control.py 39.04 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
import sys
import os
import logging
import ffmpeg
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class WorkThread(QThread):
"""
合并视频,修改尺寸。
线程函数执行可以获取主线程的元素的值。
但是不能改变主线程中元素的值,也就是不能操作PyQt5中的元素进行设定值,否则会报错。
可以通过给主线程发送信号的方式,让主线程通过信号进行操作。
"""
finishedSignal = pyqtSignal(str)
processingSignal = pyqtSignal(str)
ffprobe_cmd_path = "ffprobe.exe"
ffmpeg_cmd_path = "ffmpeg.exe"
# 定义可执行文件的路径和文件名,专门用于处理音视频的工具
# 开源,有官网,但是命令非常复杂,使用ffmpeg-python这个库,可以简化复杂的命令
def __init__(self, obj, parent=None):
super(WorkThread, self).__init__(parent)
self.obj = obj
# def addWaterMark(self, objPos:tuple, waterMark:str, vclip:VideoFileClip, displayDuration:int, displayTimes:int):
# """
# :objPos 水印或者logo显示的位置
# :waterMark 水印或者logo的图片路径
# :vclip 视频文件对象
# :displayDuration logo显示时长,单位是秒
# :displayTimes logo显示次数
# """
# videoDuration = vclip.duration
# everyTime = math.floor(videoDuration / (displayTimes - 1))
# watermarkList = []
# for i in range(displayTimes):
# start = int(everyTime * i)
# watermark = ImageClip(waterMark)
# watermark = watermark.set_position(objPos)
# if i == 0:
# watermark = watermark.set_duration(displayDuration)\
# .set_start(start)
# elif i == displayTimes - 1:
# watermark = watermark.set_duration(displayDuration)\
# .set_start(videoDuration - displayDuration)
# else:
# watermark = watermark.set_duration(displayDuration)\
# .set_start((start * i) - int(displayDuration / 2))
# watermarkList.append(watermark)
# return CompositeVideoClip([vclip] + watermarkList)
def resizeVideo(self, from_size: tuple, to_size: tuple, stream):
"""
# 视频流处理,进行缩放和裁剪
from_size (int, int)原始视频的尺寸
to_size (int, int) 需要缩放到这个尺寸
stream 视频流
# 缩放过程中,注意一个问题,当导入的视频名称相同,或者是同一个视频,则可能报错
# 报错内容大致是多个过滤器无法使用同一个视频流,这里应该可以选择视频流副本进行过滤
# 但是使用filter将视频流分流的方式还是报错,需要在进一步研究处理方法
# 出现这个错误的起因是,因为片头片尾是同一个视频文件,所以读入片头片尾时,导致报错
# 目前的解决办法是:将片头复制一份,名称不同则可以正常进行处理了。
"""
print("执行缩放程序", from_size, to_size)
ow = to_size[0]
oh = to_size[1]
sw = from_size[0]
sh = from_size[1]
if not (ow == sw and oh == sh):
if sw / sh > 1:
print("横屏比例调整")
if float(sw / sh) < float(ow / oh):
print("以宽度合适调整")
rate = float(ow / sw)
print("<", rate)
subh = int((sh * rate - oh) / 2)
print(sw, sh, ow, oh, subh)
scale_stream = ffmpeg.filter(
# stream["v"], "scale", width=ow, height=-2
stream,
"scale",
width=ow,
height=-2,
)
crop_stream = ffmpeg.filter(
scale_stream, "crop", x=0, y=subh, out_w=ow, out_h=oh
)
# crop_stream = ffmpeg.filter(crop_stream, "setdar", "16/9")
# crop_stream = ffmpeg.filter(crop_stream, "setsar", "1/1")
else:
print("以高度合适调整")
rate = float(oh / sh)
print(">", rate)
subw = int((sw * rate - ow) / 2)
print(sw, sh, ow, oh, subw)
scale_stream = ffmpeg.filter(
# stream["v"], "scale", width=-2, height=oh
stream,
"scale",
width=-2,
height=oh,
)
crop_stream = ffmpeg.filter(
scale_stream, "crop", x=subw, y=0, out_w=ow, out_h=oh
)
# crop_stream = ffmpeg.filter(crop_stream, "setdar", "16/9")
# crop_stream = ffmpeg.filter(crop_stream, "setsar", "1/1")
else:
print("竖屏比例调整")
if float(sw / sh) > float(ow / oh):
print("以高度合适调整")
rate = float(oh / sh)
print(">", rate)
subw = int((sw * rate - ow) / 2)
print(sw, sh, ow, oh, subw)
scale_stream = ffmpeg.filter(
# stream["v"], "scale", width=-2, height=oh
stream,
"scale",
width=-2,
height=oh,
)
crop_stream = ffmpeg.filter(
scale_stream, "crop", x=subw, y=0, out_w=ow, out_h=oh
)
# crop_stream = ffmpeg.filter(crop_stream, "setdar", "16/9")
# crop_stream = ffmpeg.filter(crop_stream, "setsar", "1/1")
else:
print("以宽度合适调整")
rate = float(ow / sw)
print("<", rate)
subh = int((sh * rate - oh) / 2)
print(sw, sh, ow, oh, subh)
scale_stream = ffmpeg.filter(
# stream["v"], "scale", width=ow, height=-2
stream,
"scale",
width=ow,
height=-2,
)
crop_stream = ffmpeg.filter(
scale_stream, "crop", x=0, y=subh, out_w=ow, out_h=oh
)
crop_stream = ffmpeg.filter(
crop_stream, "setdar", "16/9"
) # 设定像素宽高比,这里不能使用16:9
crop_stream = ffmpeg.filter(
crop_stream, "setsar", "1/1"
) # 设定显示宽高比,这里不能使用1:1
# 这里如果设置的字符中含有冒号,程序会报错,会变成 -- \: -- 从而无法获得正确的比例。
# 这两个参数具体含义不太清楚,以下是GPT给的解释。
# 在视频流中,SAR是指"Sample Aspect Ratio", 表示视频像素的宽高比,
# 而DAR是指"Display Aspect Ratio",表示在播放设备上实际显示视频的宽高比。
# 具体来说,SAR定义了每一个像素的横向与纵向的物理长度比例。
# 例如,标准的PAL制式视频的SAR为4:3,这意味着对于每个像素,
# 它们的宽度和高度之间的比率是4:3。但是,由于一些历史遗留原因,
# 许多常见的视频编解码器(如H.264或MPEG-4)可以使用非正方形像素。
# 因此,如果我们按照正确的宽高比例来显示这些像素,必须修改指定SAR的标准,
# 以便播放设备能够正确地显示视频。
# DAR则定义了最终呈现在屏幕上的图像的宽高比。
# 显然,这可以通过根据SAR来缩放原始分辨率的方式在播放时进行操作。
# 举个例子,如果我们有一个采用16:9 SAR编码的视频,
# 并且希望在DAR为4:3的设备上播放,则可以通过调整画面的缩放比例来实现适应屏幕。
# 这可能需要在水平方向和垂直方向上进行裁剪或填充以达到适合的宽高比,
# 但是最终呈现的视频将具有4:3的DAR。
# 在实际应用中,SAR和DAR对于正确显示视频内容是至关重要的。
# 因此,确保视频的SAR和DAR被正确设置并与目标播放设备兼容是非常重要的。
return crop_stream
else:
print("视频大小符合尺寸要求,无需缩放。")
return stream
def processingVideos(
self, video_list: list, specify_size: tuple, watermark: str, output_path: str
):
"""
video_list list[str] 文件路径+名称的字符串列表
specify_size (int, int) 需要设定的尺寸
watermark 图片文件的路径+名称的字符串
output_path 视频输出的路径+名称的字符串
"""
# 将视频读入内存,并生成一个视频流列表,用于生成拼接视频。
inputs = []
for index, video in enumerate(video_list):
# 获取视频大小,看是否需要修改尺寸
probe = ffmpeg.probe(
video, cmd=self.ffprobe_cmd_path
) # 这个是专门用于读取媒体信息的可执行文件。
width = next(
(
stream
for stream in probe["streams"]
if stream["codec_type"] == "video"
),
None,
)[
"width"
] # 从视频文件信息中,找到视频宽度的信息。
height = next(
(
stream
for stream in probe["streams"]
if stream["codec_type"] == "video"
),
None,
)[
"height"
] # 从视频文件信息中,找到视频高度的信息。
input_stream = ffmpeg.input(video) # 读入视频文件
video_stream = input_stream.video
audio_stream = input_stream.audio # 这里将音频单独分离出来,否则部分过滤器无法正常工作。
video_resized_stream = self.resizeVideo(
[width, height], specify_size, video_stream
) # 缩放程序中,只对视频流进行处理
inputs.append(video_resized_stream)
inputs.append(audio_stream)
# 视频音频流放入一个列表中,以便合并时使用。
# 这里在循环中处理是因为不止一个视频片段,其中可能包含片头,正片,片尾三部分
# 当然片头、片尾也可以不添加
# 合并视频
merged_stream = ffmpeg.concat(*inputs, v=1, a=1) # 这里对音视频进行拼接,inputs 中可能存在6个片段
# 分别是:片头视频流、音频流;正片视频流、音频流;片尾视频流、音频流。
# 参数中v=1,a=1是代表,最终合成的视频中,只包含1条(三条视频合成一条)视频流。1条音频流(3合1)。
output_params = {
# "format": "mp4", # 这里的容器格式不设置是因为在设置文件名称时已经带有格式后缀了。
"vcodec": "libx264", # mp4的常规编码格式,比较通用。
# "ac": "2", # 设置音频通道数
"acodec": "libmp3lame", # 设置音频编码,MP3的常规编码格式。
# "b:a": "128k", # 添加音频码率参数
"r": 25, # 设置帧率
}
extra_args = "-c:v h264_nvenc"
if os.path.exists(output_path):
# 删除已存在的文件
os.remove(output_path)
# 有水印的path则添加水印图
if watermark:
# 添加水印图
watermark = ffmpeg.input("watermark.png")
watermarked_video = merged_stream.overlay(
watermark, x="(w-overlay_w)/2", y="(h-overlay_h)/2"
)
# 输出文件加水印
output_video = ffmpeg.output(
watermarked_video, merged_stream, output_path, **output_params
)
else:
# 输出文件
output_video = ffmpeg.output(
merged_stream, output_path, **output_params
).overwrite_output()
try:
output_video.run(cmd=self.ffmpeg_cmd_path)
except ffmpeg.Error as e:
print("运行出现错误:----------sta")
print(e.stderr)
print("运行出现错误:----------end")
def run(self):
# self.buttonRun.setDisabled(False)
pathlist = self.obj.dragTable.get_first_column_values(0)
output_pathlist = self.obj.dragTable.get_first_column_values(1)
output_namelist = self.obj.dragTable.get_first_column_values(2)
finished_list = self.obj.dragTable.get_first_column_values(3)
# print("这是从表格传入的内容:", pathlist)
# print("这是从表格传入的名字列表:", output_pathlist)
# print("这是从表格传入的输出路径:", output_namelist)
# print("这是从表格传入的输出路径:", finished_list)
videoList = []
if not pathlist:
self.finishedSignal.emit("thread-finished")
return None
for index, path in enumerate(pathlist):
if os.path.isfile(path):
videoList.append(path)
if not videoList:
# 如果列表为空则不进行任何操作。
self.finishedSignal.emit("thread-finished")
return None
startVideoClip = None
endVideoClip = None
path = None
if os.path.isfile(self.obj.lineTextLeft.text()):
startVideoClip = self.obj.lineTextLeft.text()
if os.path.isfile(self.obj.lineTextMiddle.text()):
endVideoClip = self.obj.lineTextMiddle.text()
if os.path.isfile(self.obj.sptlineTextLeft.text()):
startSVideoClip = self.obj.sptlineTextLeft.text()
if os.path.isfile(self.obj.spwlineTextRight.text()):
endSVideoClip = self.obj.spwlineTextRight.text()
for index, video in enumerate(videoList):
cellstate = self.obj.dragTable.item(index, 3)
# print("------self.obj.dragTable.item(index, 3)", cellstate)
if cellstate is not None:
if cellstate.text() == "完成":
# 跳过已完成的视频
print("视频处理已完成,跳过本条视频输出。")
continue
elif cellstate.text() == "":
print("视频处理未完成,正在输出。")
else:
print("视频处理未完成,正在输出。")
# 确定缩放尺寸,目标尺寸
probe = ffmpeg.probe(video, cmd=self.ffprobe_cmd_path)
width = next(
(
stream
for stream in probe["streams"]
if stream["codec_type"] == "video"
),
None,
)["width"]
height = next(
(
stream
for stream in probe["streams"]
if stream["codec_type"] == "video"
),
None,
)["height"]
objSize = (width, height)
vlist = []
if objSize[0] > objSize[1]:
# 横屏,添加横向片头片尾
if startVideoClip:
vlist.append(startVideoClip) # 加片头文件路径
vlist.append(video) # 加正片文件路径
if endVideoClip:
vlist.append(endVideoClip) # 加片尾文件路径
else:
# 纵屏,添加纵向片头片尾
if startSVideoClip:
vlist.append(startSVideoClip)
vlist.append(video)
if endSVideoClip:
vlist.append(endSVideoClip)
# # 获取正片的文件名
# fname = os.path.basename(pathlist[index]) # 原来的文件名
# newfname = (
# fname.rsplit(".", 1)[0] + "_converted.mp4"
# ) # + fname.rsplit(".", 1)[1] # 新生成的文件名
# # 获取路径,如果未填写路径,则获取正片的路径
# if os.path.isdir(self.obj.lineTextOutPath.text()):
# path = self.obj.lineTextOutPath.text()
# else:
# # 如果输入的路径不正确,则使用正片原来的路径
# path = os.path.dirname(pathlist[index])
# print("输出一下vlist")
# print(vlist)
# print("输出一下vlist===end")
# 这里因为后期这个修改后的名称直接从表格中获取到了,所以这里不再需要单独拼接一个新的文件名称
self.processingVideos(
vlist,
objSize,
None,
os.path.join(output_pathlist[index], output_namelist[index]),
)
# 输出一个信号,告知主界面当前已完成的径路+文件名称
self.processingSignal.emit(pathlist[index])
# 列表全部循环完毕后发送执行完毕的信号
self.finishedSignal.emit("thread-finished")
# 退出线程
self.quit()
class DropTextEdit(QTextEdit):
"""
定义了一个可以实现拖拽功能文本框
"""
droped = pyqtSignal(str)
def __init__(self, parent: QWidget = None):
super(DropTextEdit, self).__init__(parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, evn):
# print("鼠标已进入")
evn.accept() # 这里设置当拖拽内容进入到该控件时,接收事件对象
# 否则在先触发这个事件函数时,默认会执行忽略事件。
# 以致于后面的DropEvent函数无法得到事件源。
def dropEvent(self, evn):
# print("文件已拖入")
path = evn.mimeData().text()
p = path.replace("file:///", "")
self.droped.emit(p)
# 这里其实可以直接对内容进行设置,不需要给主界面发送信号了。
# 具体实现参考 DropLineEdit 类。
class DragableTable(QTableWidget):
"""
实现一个可以拖拽单个文件、多个文件以及文件夹的表格。
拖拽的多个文件将会纵向填充第一列内容。内容是文件的路径+名称
拖拽文件夹时,将会遍历文件夹下的所有文件,并根据过滤后缀名,将路径+名称填充到表格的第一列。
"""
delete_signal = pyqtSignal()
paste_signal = pyqtSignal()
def __init__(self):
super().__init__()
self.setAcceptDrops(True) # 允许拖拽操作
self.setColumnCount(4) # 设置表格列数为1
self.setRowCount(0) # 设置表格列数为1
# self.verticalHeader().setVisible(False) # 隐藏表格垂直表头
self.setHorizontalHeaderLabels(["原路径名称", "输出路径", "输出名称", "状态"])
horizontalHeader = self.horizontalHeader()
horizontalHeader.setFont(QFont("Arial", 12, QFont.Weight.Bold))
horizontalHeader.setStyleSheet("background-color: #d0d0d0;")
horizontalHeader.setStyleSheet(
"QHeaderView::section { border: 2px solid gray; }"
)
verticalHeader = self.verticalHeader()
verticalHeader.setFont(QFont("Arial", 12, QFont.Weight.Bold))
verticalHeader.setStyleSheet("background-color: #d0d0d0;")
verticalHeader.setStyleSheet(
"QHeaderView::section { width:20px; border: 2px solid gray; }"
)
# -----------这里创建右键菜单,并实现菜单对应的功能-------
# 创建剪贴板
self.clipboard = QApplication.clipboard()
# 创建右键菜单
self.context_menu = QMenu(self)
self.remove_row_action = QAction("删除行 Del", self)
self.context_menu.addAction(self.remove_row_action)
self.copy_action = QAction("复制 Ctr+C", self)
self.context_menu.addAction(self.copy_action)
self.paste_action = QAction("粘贴 Ctr+V", self)
self.context_menu.addAction(self.paste_action)
# 信号槽连接
self.remove_row_action.triggered.connect(self.remove_row)
self.copy_action.triggered.connect(self.copy_selection)
self.paste_action.triggered.connect(self.paste_selection)
# 监听键盘事件
self.keyPressEvent = self.on_key_press_event
def on_key_press_event(self, event):
# 实现几个快捷键功能,分别对应右键菜单的三个功能
if event.matches(QKeySequence.StandardKey.Delete):
# 按下delete键
self.remove_row()
elif event.matches(QKeySequence.StandardKey.Paste):
# 按下ctrl+v键
self.paste_selection()
elif event.matches(QKeySequence.StandardKey.Copy):
self.copy_selection()
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls(): # 如果mimeData中含有URLs
event.accept() # 接受拖拽事件
else:
event.ignore() # 忽略拖拽事件
def dragMoveEvent(self, event):
event.accept()
def dropEvent(self, event: QDropEvent):
# 将单个文件、多个文件或文件夹拖入时,进行填充单元格的方法。填充到第一列中。
# 文件夹拖入时将遍历文件夹以及子文件的全部文件列表,选择视频文件进行填充。
# 且每填充一个文件,第二列和第三列将拆解第一列的内容,生成单独的路径和新的文件名。
extname = [
"mp4",
"flv",
"wmv",
"mov",
"mkv",
"avi",
"3pg",
"rm",
"rmvb",
] # 根据扩展名来限定文件
# print(event.mimeData().urls())
for url in event.mimeData().urls(): # 遍历mimeData中的所有URLs
file_path = url.toString().replace("file:///", "")
if os.path.isdir(file_path):
for root, dirs, files in os.walk(file_path):
for file in files:
if file.rsplit(".", 1)[-1].lower() in extname:
row_count = self.rowCount() # 获取当前行数
self.insertRow(row_count) # 在最后面插入一行
item_0 = QTableWidgetItem(
os.path.join(root, file)
) # 创建QTableWidgetItem对象
new_file = file.rsplit(".", 1)[0] + "_converted.mp4"
item_1 = QTableWidgetItem(root) # 创建QTableWidgetItem对象
item_2 = QTableWidgetItem(new_file) # 创建QTableWidgetItem对象
self.setItem(row_count, 0, item_0) # 设置单元格内容
self.setItem(row_count, 1, item_1) # 设置单元格内容
self.setItem(row_count, 2, item_2) # 设置单元格内容
elif os.path.isfile(file_path):
if file_path.rsplit(".", 1)[-1].lower() in extname:
row_count = self.rowCount() # 获取当前行数
self.insertRow(row_count) # 在最后面插入一行
item_0 = QTableWidgetItem(file_path) # 创建QTableWidgetItem对象
path, file = file_path.rsplit("/", 1)
new_file = file.rsplit(".", 1)[0] + "_converted.mp4"
item_1 = QTableWidgetItem(path) # 创建QTableWidgetItem对象
item_2 = QTableWidgetItem(new_file) # 创建QTableWidgetItem对象
self.setItem(row_count, 0, item_0) # 设置单元格内容
self.setItem(row_count, 1, item_1) # 设置单元格内容
self.setItem(row_count, 2, item_2) # 设置单元格内容
def contextMenuEvent(self, event):
# 当点击右键时,在鼠标的位置显示右键菜单
self.context_menu.exec_(event.globalPos())
def remove_row(self):
# 删除选中行
indexes = self.selectedIndexes()
for index in indexes[::-1]:
# 反向排序列表后删除,如果正向列表进行删除会导致下面的行删除失败。
# 因为下面的行序号row的值-1,因序号被改变而删除失败。
self.removeRow(index.row())
def copy_selection(self):
# 获取选中的内容
selected = []
for item in self.selectedItems():
if item.isSelected():
selected.append(item.text())
# 把选中的内容拼接成字符串
selection = "\n".join(selected)
# 把选中的内容复制到剪贴板
self.clipboard.setText(selection)
def paste_selection(self):
# 粘贴内容,根据最少单元格原则粘贴
# 当复制的内容只有一条时,则可以将内容复制给多个选中的单元格
items = self.selectedItems()
text = self.clipboard.text()
if len(text.splitlines()) > 1:
if len(items) > len(text.splitlines()):
for i, t in enumerate(text.splitlines()):
items[i].setText(t)
else:
for i, item in enumerate(items):
item.setText(text.splitlines()[i])
elif len(text.splitlines()) == 1:
for i, item in enumerate(items):
item.setText(text)
def changeState(self, name):
# 用于修改表格中,状态列表的文本内容。
# print("传递信号给的参数是:", name)
items = self.findItems(name, Qt.MatchFlag.MatchExactly)
# items = self.findItems(name)
# 这里貌似必须添加Qt.MatchExactly这个参数,否则会报错。
# 完整的参数应该是Qt.MatchFlag.MatchExactly这个
# print(items, len(items))
if items:
# print(items[0].row())
item_text = QTableWidgetItem("完成") # 创建QTableWidgetItem对象
self.setItem(items[0].row(), 3, item_text) # 设置单元格内容
def get_first_column_values(self, column):
# 用于获取某一列的值,返回一个列表,包含这一列中,每个单元格的文本内容
values = []
for i in range(self.rowCount()):
item = self.item(i, column) # 获取行数、列数对应的单元格的对象,
# 这里不能使用itemAt方法,因为这个方法获取的是在x,y坐标处的单元格,而不是行列数。
# 这个坐标是相对于这个控件也就是QTableWidget左上的像素距离。
if item is not None:
values.append(item.text()) # 获取单元格的文本内容
return values
class DropLineEdit(QLineEdit):
def __init__(self, parent: QWidget = None):
super(DropLineEdit, self).__init__(parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, evn):
# print("鼠标已进入")
evn.accept()
def dropEvent(self, evn):
# print("文件已拖入")
path = evn.mimeData().text()
pathstr = path.replace("file:///", "")
self.setText(pathstr)
class VideoConcatenater(QWidget):
def __init__(self):
super().__init__()
self.title = "视频转换器"
self.setWindowTitle(self.title)
self.setGeometry(100, 100, 1500, 700)
self.initUI()
# self.listLeft = []
def initUI(self):
# self.dropTextEditLeft = DropTextEdit(self)
# self.dropTextEditLeft.setLineWidth(200)
# self.dropTextEditLeft.setStyleSheet("border: 2px solid #000;")
# self.dropTextEditLeft.droped.connect(self.dropInTextEdit)
self.dragTable = DragableTable()
self.dropTextEditLeftButton = QPushButton("选择视频文件或文件夹", self)
self.dropTextEditLeftButton.clicked.connect(
lambda: self.onClicked(self.dropTextEditLeftButton)
)
# self.textEditMiddle = QTextEdit(self)
# self.textEditMiddle.setLineWidth(1000)
# self.textEditMiddle.setStyleSheet(
# "border: 2px solid #000; background-color:#252526; color:#ccc"
# )
self.ptLabel = QLabel("横片头文件", self)
self.lineTextLeft = DropLineEdit(self)
self.ptButton = QPushButton("选择横片头文件", self)
self.ptButton.clicked.connect(lambda: self.onClicked(self.ptButton))
self.pwLabel = QLabel("横片尾文件", self)
self.lineTextMiddle = DropLineEdit(self)
self.pwButton = QPushButton("选择横片尾文件", self)
self.pwButton.clicked.connect(lambda: self.onClicked(self.pwButton))
self.lgLabel = QLabel("LOGO文件", self)
self.lineTextRight = DropLineEdit(self)
self.lgButton = QPushButton("选择LOGO文件", self)
self.lgButton.clicked.connect(lambda: self.onClicked(self.lgButton))
self.sptLabel = QLabel("竖片头文件", self)
self.sptlineTextLeft = DropLineEdit(self)
self.sptButton = QPushButton("选择竖片头文件", self)
self.sptButton.clicked.connect(lambda: self.onClicked(self.sptButton))
self.spwLabel = QLabel("竖片尾文件", self)
self.spwlineTextRight = DropLineEdit(self)
self.spwButton = QPushButton("选择竖片尾文件", self)
self.spwButton.clicked.connect(lambda: self.onClicked(self.spwButton))
# self.opLabel = QLabel("输出位置", self)
# self.lineTextOutPath = DropLineEdit(self)
# self.opButton = QPushButton("选择输出位置", self)
# self.opButton.clicked.connect(lambda: self.onClicked(self.opButton))
self.buttonRun = QPushButton("开始处理", self)
self.buttonRun.clicked.connect(self.composeVideo)
gridLayout_0 = QGridLayout()
gridLayout_1 = QGridLayout()
# gridLayout_1.addWidget(self.dropTextEditLeft, 0, 0)
gridLayout_1.addWidget(self.dragTable, 0, 0)
# gridLayout_1.addWidget(self.textEditMiddle, 0, 1)
gridLayout_2 = QGridLayout()
# gridLayout_2.addWidget(self.dropTextEditLeftButton, 0, 0, 1, 2)
# gridLayout_2.addWidget(self.buttonRun, 0, 2, 1, 2)
# gridLayout_2.addWidget(self.ptLabel, 1, 0)
# gridLayout_2.addWidget(self.lineTextLeft, 1, 1)
# gridLayout_2.addWidget(self.ptButton, 1, 2)
# gridLayout_2.addWidget(self.pwLabel, 1, 3)
# gridLayout_2.addWidget(self.lineTextMiddle, 1, 4)
# gridLayout_2.addWidget(self.pwButton, 1, 5)
# gridLayout_2.addWidget(self.lgLabel, 1, 6)
# gridLayout_2.addWidget(self.lineTextRight, 1, 7)
# gridLayout_2.addWidget(self.lgButton, 1, 8)
# gridLayout_2.addWidget(self.sptLabel, 2, 0)
# gridLayout_2.addWidget(self.sptlineTextLeft, 2, 1)
# gridLayout_2.addWidget(self.sptButton, 2, 2)
# gridLayout_2.addWidget(self.spwLabel, 2, 3)
# gridLayout_2.addWidget(self.spwlineTextRight, 2, 4)
# gridLayout_2.addWidget(self.spwButton, 2, 5)
# gridLayout_2.addWidget(self.opLabel, 2, 6)
# gridLayout_2.addWidget(self.lineTextOutPath, 2, 7)
# gridLayout_2.addWidget(self.opButton, 2, 8)
gridLayout_2.addWidget(self.ptLabel, 0, 0)
gridLayout_2.addWidget(self.lineTextLeft, 0, 1)
gridLayout_2.addWidget(self.ptButton, 0, 2)
gridLayout_2.addWidget(self.pwLabel, 0, 3)
gridLayout_2.addWidget(self.lineTextMiddle, 0, 4)
gridLayout_2.addWidget(self.pwButton, 0, 5)
gridLayout_2.addWidget(self.lgLabel, 0, 6)
gridLayout_2.addWidget(self.lineTextRight, 0, 7)
gridLayout_2.addWidget(self.lgButton, 0, 8)
gridLayout_2.addWidget(self.sptLabel, 1, 0)
gridLayout_2.addWidget(self.sptlineTextLeft, 1, 1)
gridLayout_2.addWidget(self.sptButton, 1, 2)
gridLayout_2.addWidget(self.spwLabel, 1, 3)
gridLayout_2.addWidget(self.spwlineTextRight, 1, 4)
gridLayout_2.addWidget(self.spwButton, 1, 5)
gridLayout_2.addWidget(self.dropTextEditLeftButton, 1, 6)
gridLayout_2.addWidget(self.buttonRun, 1, 7, 1, 2)
gridLayout_0.addLayout(gridLayout_1, 0, 0)
gridLayout_0.addLayout(gridLayout_2, 1, 0)
self.setLayout(gridLayout_0)
self.show()
# def open_file_dialog(self):
# """
# Function to open file dialog
# Returns selected file path(s)
# """
# options = QFileDialog.Options()
# options |= QFileDialog.DontUseNativeDialog
# files, _ = QFileDialog.getOpenFileNames(
# None, "选择文件", "", "All Files (*.*)", options=options
# )
# if len(files) == 1:
# return files[0]
# elif len(files) > 1:
# return files
# else: # 弹出文件夹选择对话框
# options |= QFileDialog.ShowDirsOnly
# folder_path = QFileDialog.getExistingDirectory(
# None, "选择文件夹", "", options=options
# )
# return folder_path
def onClicked(self, button):
# 可以将打开方式放入到具体的button中,这样就可以选择不同的方式了
options = QFileDialog.Options()
options |= QFileDialog.Option.DontUseNativeDialog
# selected_path = QFileDialog.getExistingDirectory(
# self, "打开文件或文件夹", "", options=options
# )
selected_path, _ = QFileDialog.getOpenFileName(
None, "选择文件", "", "All Files (*.*)", options=options
)
# QFileDialog.getOpenFileNames() 选择多个文件
# QFileDialog.getOpenFileName() 选择单个文件
if not selected_path:
return
if button == self.ptButton:
if os.path.isfile(selected_path):
self.lineTextLeft.setText(selected_path)
elif button == self.pwButton:
if os.path.isfile(selected_path):
self.lineTextMiddle.setText(selected_path)
elif button == self.lgButton:
if os.path.isfile(selected_path):
self.lineTextRight.setText(selected_path)
elif button == self.sptButton:
if os.path.isfile(selected_path):
self.sptlineTextLeft.setText(selected_path)
elif button == self.spwButton:
if os.path.isfile(selected_path):
self.spwlineTextRight.setText(selected_path)
# elif button == self.opButton:
# if os.path.isdir(selected_path):
# self.lineTextOutPath.setText(selected_path)
# elif button == self.dropTextEditLeftButton:
# self.dropInTextEdit(selected_path)
# self.dragTable.dropEvent() 这里需要的drop事件源,但是这里获取的文件夹路径列表
# def dropInTextEdit(self, filepath):
# """
# 拖动文件或者文件夹到该文本区域,遍历文件夹以及子文件夹中的文件,提取文件的信息
# :filepath string 文件夹或者文件的完整路径
# """
# extname = [
# "mp4",
# "flv",
# "wmv",
# "mov",
# "mkv",
# "avi",
# "3pg",
# "rm",
# "rmvb",
# ] # 根据扩展名来限定文件
# if os.path.isfile(filepath):
# self.dropTextEditLeft.append(filepath)
# elif os.path.isdir(filepath):
# for root, dirs, files in os.walk(filepath):
# # self.textEditRight.append("文件夹:" + root)
# for file in files:
# if filepath.rsplit(".")[0].lower() in extname:
# self.dropTextEditLeft.append(os.path.join(root, file))
# QApplication.processEvents() # 线程更新界面显示
# elif type(filepath) == str:
# for file in filepath.splitlines():
# if file.rsplit(".")[-1].lower() in extname:
# self.dropTextEditLeft.append(file)
def composeVideo(self):
# print("运行按钮被点击了。")
# 启动线程去处理视频,这样不会让主界面处于未响应状态。
self.buttonRun.setDisabled(True)
self.buttonRun.setText("执行中...")
self.workThread = WorkThread(self)
self.workThread.start()
self.workThread.processingSignal.connect(self.threadProcessing)
self.workThread.finishedSignal.connect(self.threadFinished)
def threadProcessing(self, data):
# 接收线程发出的信号,处理完一条视频时会发出一次信号。
# 信号内容是处理完的视频路径+名称。
self.dragTable.changeState(name=data) # 用于修改表格中状态一列中的内容。
# 根据视频路径+名称,找到对应行,在对应行的状态列写入信息。
def threadFinished(self, data):
# 接收线程将列表中的视频全部处理完的信号。
if data == "thread-finished":
self.buttonRun.setDisabled(False)
self.buttonRun.setText("开始处理")
if __name__ == "__main__":
try:
app = QApplication(sys.argv)
window = VideoConcatenater()
sys.exit(app.exec_())
except Exception as e:
logging.basicConfig(
filename="VideoConcatenater.log",
format="[%(asctime)s - %(filename)s - %(levelname)s : %(message)s]",
level=logging.INFO,
filemode="a",
datefmt="%Y-%m-%d%I:%M:%S %p",
)
logging.info(e.__traceback__.tb_frame.f_globals["__file__"]) # 发生异常所在的文件
logging.info(e.__traceback__.tb_lineno) # 发生异常所在的行数
logging.info(e)
# 这里将异常内容写入到一个文件里,但是貌似无法获得组件内部报错的信息,可能需要让异常上传才可以。
# 具体原因需要对异常处理的内容进行深入学习才能完全处理。
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Python
1
https://gitee.com/summerwithsun/ffmpeg-python.git
[email protected]:summerwithsun/ffmpeg-python.git
summerwithsun
ffmpeg-python
ffmpeg-python视频批量拼接软件
master

搜索帮助