我有一个小米摄像头,用它录出来的视频全部都是3s一段3s一段的。其中有几个小时的视频我需要保存,当初直接把摄像头的卡文件导出来重命名掉了,那时候没有注意,之后想剪辑/发送给别人的时候发现疯了:
1.剪辑的话,py导入特别卡
2.发送的话,打包发完打开对面是一脸懵
但是我已经重命名了文件,要是把对象再插回摄像头用自带软件导出也太折腾了。思来想去一番后,我觉得先合成,再剪辑/发送
但是问题又来了,最开始我使用格式工厂合并,但是发现格式工厂默认会对全部文件搞一次重新编码,这就把我的电脑搞得特别卡,总共几个小时1w多个文件编码了几个小时都好不了
左思右想后,还是决定自己用py整一个(因为我好像没有在格式工厂里面找到相关的功能可以禁用掉自动编码)
我决定使用FFmpeg进行操作,FFmpeg需要在官网上下载,这里需要根据各自的os选择
然后要将这个添加到工作路径中,你可以
1.把这个添加到系统变量里面(我由于没有大量的使用需求,加上希望换一个电脑也可以用,就选择下面一个方案)
2.使用 subprocess.run 指定 ffmpeg 的路径
就像这样:
# 使用 subprocess.run 指定 ffmpeg 的路径,并将输出重定向到日志文件with open(log_file, "w") as log:result = subprocess.run([ffmpeg_path] + command.split(), stdout=log, stderr=log)
这个path应该是ffmpeg.exe结尾的路径,具体可以在ffmpeg的包里面一个bin文件夹下看到、
接下来是实际操作部分:
FFmpeg 命令的格式我就不说了,毕竟也是查来了,里面两个关键的点:
1.需要提供一个带有文件路径的txt文件
可以这样生成(一定要加utf8,要不然可能会以gbk的形式生成,导致FFmpeg 识别不了中文字符)
with open("file_list.txt", "w",encoding="utf-8") as f:for file in video_files:f.write(f"file '{file}'\n")
2.以copy模式构造ffmpeg命令
command = f"-f concat -safe 0 -i file_list.txt -c copy {output_file}"
源代码:
import os
import subprocess
import send2trash
import timedef merge_videos_in_directory(input_dir, output_file, ffmpeg_path):start_time = time.time() # 记录开始时间print("脚本开始运行时间:", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time)))"""使用 FFmpeg 从指定目录合并所有视频文件,保留原画质,并将原文件移动到回收站。"""video_files = [os.path.join(input_dir, file) for file in os.listdir(input_dir)if file.endswith(('.mp4', '.avi', '.mov', '.mkv'))]video_files.sort()if not video_files:print("没有找到视频文件。")returnwith open("file_list.txt", "w",encoding="utf-8") as f:for file in video_files:f.write(f"file '{file}'\n")# 构造FFmpeg 命令command = f"-f concat -safe 0 -i file_list.txt -c copy {output_file}"log_file = "ffmpeg_log.txt" # 日志文件路径try:# 使用 subprocess.run 指定 ffmpeg 的路径,并将输出重定向到日志文件with open(log_file, "w") as log:result = subprocess.run([ffmpeg_path] + command.split(), stdout=log, stderr=log)if result.returncode != 0:print(f"FFmpeg 命令执行失败,详情请查看日志文件:{log_file}")returnelse:print("FFmpeg 命令执行成功,日志已保存到:", log_file)except Exception as e:print(f"运行 FFmpeg 时发生错误: {e}")returnsend2trash.send2trash("file_list.txt")end_time = time.time() # 记录结束时间elapsed_time = end_time - start_time # 计算总耗时hours, rem = divmod(elapsed_time, 3600)minutes, seconds = divmod(rem, 60)print("脚本结束运行时间:", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(end_time)))print("总耗时:{:0>2}:{:0>2}:{:05.2f}".format(int(hours), int(minutes), seconds))send2trash.send2trash(input_dir)input_directory = r"你的一堆视频文件的路径"
output_video = "你的输出文件名.mkv"
ffmpeg_path = r"ffmpeg.exe的ffmpeg程序路径"merge_videos_in_directory(input_directory, output_video, ffmpeg_path)
最后,先拿几千个文件试了一下,效果很好,几分钟就完成了:
脚本开始运行时间: 2025-03-14 17:43:30
FFmpeg 命令执行成功,日志已保存到: ffmpeg_log.txt
脚本结束运行时间: 2025-03-14 17:48:13
总耗时:00:04:43.12
文件大小也差不多,应该没问题
ps:我最早使用的输出文件名是mp4,然后报错了:
FFmpeg 命令执行失败: ffmpeg version 2025-03-13-git-958c46800e-essentials_build-www.gyan.dev Copyright (c) 2000-2025 the FFmpeg developersbuilt with gcc 14.2.0 (Rev1, Built by MSYS2 project)configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-sdl2 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libgme --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libtheora --enable-libvo-amrwbenc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-librubberbandlibavutil 59. 59.100 / 59. 59.100libavcodec 61. 33.102 / 61. 33.102libavformat 61. 9.107 / 61. 9.107libavdevice 61. 4.100 / 61. 4.100libavfilter 10. 9.100 / 10. 9.100libswscale 8. 13.101 / 8. 13.101libswresample 5. 4.100 / 5. 4.100libpostproc 58. 4.100 / 58. 4.100
[aist#0:0/pcm_alaw @ 0000024bcba1cd00] Guessed Channel Layout: mono
Input #0, concat, from 'file_list.txt':Duration: N/A, start: 0.000000, bitrate: 1215 kb/sStream #0:0(und): Audio: pcm_alaw (alaw / 0x77616C61), 8000 Hz, mono, s16, 64 kb/sMetadata:creation_time : 2022-10-13T03:50:14.000000Zvendor_id : [0][0][0][0]Stream #0:1(und): Video: hevc (Main) (hvc1 / 0x31637668), yuv420p(tv, unknown/bt709/unknown), 2304x1296, 1151 kb/s, 20 fps, 20 tbr, 90k tbnMetadata:creation_time : 2022-10-13T03:50:14.000000Zvendor_id : [0][0][0][0]
Stream mapping:Stream #0:1 -> #0:0 (copy)Stream #0:0 -> #0:1 (copy)
[mp4 @ 0000024bcb7b2640] Could not find tag for codec pcm_alaw in stream #1, codec not currently supported in container
[out#0/mp4 @ 0000024bcb7b2440] Could not write header (incorrect codec parameters ?): Invalid argument
Conversion failed!
查了一下之后,发现是小米挖的坑:它使用了一个叫做PCM Alaw的编码,这个编码正常是不允许出现在mp4文件里面的。这只能说 小米啊小米 还得是您
所以后面我把输出文件名改为了兼容性更强一点的mkv文件,后面想转mp4的话用格式工厂走一下就好了
pps:
我只测试过全都是我自己那款小米摄像头出来的mp4文件格式,虽然说我在检测文件夹下视频路径的代码里面加了好几个类型,但是不保证会不会多种东西杂在一起又闹出什么不可描述的奇葩事,所以建议转码的时候将相同源的放在一起转,最后再一起合并(是相同源,比如说一个摄像机出来的这种。因为叫mp4的文件未必内部编码都是相同的)
ppps:
如果你不想看到print,全部注释掉就好
pppps:
数据有风险,由于ffmpeg并不是在所有情况下都会报错:
1.比如我的代码之前在一些特定文件名下出错了,但是是直接正常回调(result.returncode甚至是0,不是其他的。)就导致它往下走了删除,我是发现文件大小不对才意识到的。
2.视频文件不能是损坏的或者空文件 如果是那样的话可能会报错:
[mov,mp4,m4a,3gp,3g2,mj2 @ 00000190127b9680] Format mov,mp4,m4a,3gp,3g2,mj2 detected only with low score of 1, misdetection possible!
[mov,mp4,m4a,3gp,3g2,mj2 @ 00000190127b9680] moov atom not found
[concat @ 0000019011b2f7c0] Impossible to open 'F:\record\20221013_045834.mp4'
[in#0/concat @ 0000019011b2f3c0] Error during demuxing: Invalid data found when processing input
[out#0/matroska @ 0000019012445180] video:393957KiB audio:31858KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: 0.471186%
frame=81560 fps=13679 q=-1.0 Lsize= 427822KiB time=01:07:58.00 bitrate= 859.4kbits/s speed= 684x
并且会直接中断,留下目前为止的剩余合并好的文件
所以建议可以把
send2trash.send2trash(input_dir)
删了,检查无误后再手动删除