方法一:
视频较长,分辨率较大,这个效果很好,不耗用内存 ffmpeg
import subprocessimport globimport osfrom natsort import natsortedbase_dir = r'C:\Users\Administrator\Videos\shuiyin\result'output_file = r'output_shuiyin.mp4'video_paths = glob.glob(base_dir + '/*.mp4')video_paths = natsorted(video_paths)with open('file_list.txt', 'w') as f: for file in video_paths: f.write(f"file '{file}'\n")ffmpeg_command = [ 'ffmpeg', '-f', 'concat', # 指定拼接模式 '-safe', '0', # 允许绝对路径 '-i', 'file_list.txt', # 输入的文件列表 '-c:v', 'libx264', # 使用 libx264 编码器 '-c:a', 'aac', # 使用 aac 编码音频 '-strict', 'experimental',# 使用实验性编码 output_file # 输出文件路径]subprocess.run(ffmpeg_command, check=True) print(f"视频拼接完成,输出文件:{output_file}")
方法二:
利用imageio,适合视频较短
import globfrom natsort import natsortedfrom moviepy.editor import VideoFileClip, concatenate_videoclipsimport globimport os.path from natsort import natsortedimport cv2import imageio if __name__ == '__main__': #内存 base_dir =r"C:\Users\Administrator\Videos\shuiyin\0127" base_dir =r'C:\Users\Administrator\Videos\shuiyin\result' output_path = "pinjie_shuiyin.mp4" video_paths =glob.glob(base_dir +'/*.mp4') video_paths=natsorted(video_paths) imgs=[] res = [] for file in video_paths: cap_a = cv2.VideoCapture(file) # 打开视频B fps = cap_a.get(cv2.CAP_PROP_FPS) frame_count = 0 print(file) while True: ret, frame_a = cap_a.read() if not ret: break # 如果没有读取到帧,则跳出循环 res.append(cv2.cvtColor(frame_a, cv2.COLOR_BGR2RGB)) frame_count += 1 # 释放视频资源 cap_a.release() imageio.mimsave(output_path, res, "mp4", fps=fps, macro_block_size=None)
方法三:
使用FFmpeg进行视频拼接,主要以两个文件为例进行合并,并且只转换其中的视频流
vector<string> fileList = { url_origin,url_add };//这是两个文件//获得原始输入视频文件编码等信息const AVOutputFormat* ofmt = NULL;//输出格式AVFormatContext* ifmt_ctx = NULL, * ofmt_ctx = NULL;//视频数据维护对象AVPacket* pkt = NULL;//数据包int ret;//函数执行返回码int stream_index;//数据流索引pkt = av_packet_alloc();//初始化数据包结构if (!pkt){return;}if ((ret = avformat_open_input(&ifmt_ctx, url_origin, 0, 0) < 0)){goto end;//打开文件失败}//获得输出文件名string out_file;auto name = ifmt_ctx->iformat->name;//自动识别文件的封装类型//hevc只能使用MP4或者hevc封装才能完成转换,其余封装报错,因为这里进行了自动识别可以不用管具体格式out_file.replace(out_file.find('.')+1, 3, name);const char* out_filename = out_file.c_str();//根据第一个文件获得其中的编码等参数,这里要求两个文件的编码格式一样就是因为在写入文件时用的是相同的配置没有进行转码等操作if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0){goto end;}avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);if (!ofmt_ctx){goto end;}ofmt = ofmt_ctx->oformat;//查找视频流并复制视频流的参数到输出流for (int i = 0; i < ifmt_ctx->nb_streams; ++i){AVStream* in_stream = ifmt_ctx->streams[i];AVCodecParameters* in_codecpar = in_stream->codecpar;if (in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO)//非视频流跳过{continue;}AVStream* out_stream = avformat_new_stream(ofmt_ctx, NULL);//创建输出流if (!out_stream){goto end;}ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);//复制解码器参数if (ret < 0){goto end;}out_stream->time_base = in_stream->time_base;//复制时间基stream_index = i;out_stream->codecpar->codec_tag = 0;break;}avformat_close_input(&ifmt_ctx);//关闭文件//打开输出文件if (!(ofmt->flags & AVFMT_NOFILE)){ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);if (ret < 0){goto end;}}ret = avformat_write_header(ofmt_ctx, NULL);//写入头信息,如编码等内容if (ret < 0){goto end;}int64_t i = 0;//用于计算时间戳,同时也是帧数int64_t p_max_dts = 0;//用于拼文件的时间戳for (int index = 0; index < fileList.size(); ++index)//遍历文件{if ((ret = avformat_open_input(&ifmt_ctx, fileList[index].c_str(), 0, 0)) < 0){goto end;}if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0)//查找文件流信息{goto end;}//对流直接进行转写while (1){AVStream* in_stream, * out_stream;ret = av_read_frame(ifmt_ctx, pkt);if (ret < 0){break;}pkt->stream_index = stream_index;//视频流编号//这里做一个提示,因为上述的例子只有视频没有音频所以不会越界,如果存在多种流的这里需要看一下你new了几个流,是否会越界in_stream = ifmt_ctx->streams[stream_index];out_stream = ofmt_ctx->streams[stream_index];//这里要对时间戳进行处理,否则写入的时候会失败//单帧时长int64_t frameDuration = av_rescale_q(1, av_inv_q(in_stream->time_base), in_stream->r_frame_rate);//将单帧的时间从输入流转化到输出流时间int64_t _t = av_rescale_q(frameDuration, in_stream->time_base, out_stream->time_base);//计算时间戳,并进行累计以推算后面的时间戳p_max_dts = _t * (i);pkt->dts = p_max_dts;pkt->pts = pkt->dts;//如果音视频都需要写入可能需要这个函数:av_interleaved_write_frame,他会进行交叉写入//pkt现在是空的,这个函数会获得pkt内容的所有权并重置,因此不需要unref,但是write_frame情况不同,需要手动释放ret = av_write_frame(ofmt_ctx, pkt);//直接将包写入输出文件不进行解码av_packet_unref(pkt);if (ret < 0){break;}++i;}//关闭文件avformat_close_input(&ifmt_ctx);}av_write_trailer(ofmt_ctx);//写文件尾end:av_packet_free(&pkt);//这里传指针,因为要将pkt设为nullavformat_close_input(&ifmt_ctx);//同理if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE)){avio_closep(&ofmt_ctx->pb);//avio打开要释放}avformat_free_context(ofmt_ctx);if (ret < 0 && ret != AVERROR_EOF){return;//异常结束}
这个示例可以完成视频流的复制拼接,是一个比较简单的示例,要求文件编码等信息必须一致,不进行转码,速度比较快。