FFmpeg多张图片合成视频?

news/2024/6/16 20:11:48/文章来源:https://blog.csdn.net/shelutai/article/details/138962000

前言

商家在发布商品的时候,大部分情况下是没有视频的,这样往往会造成商品展示不全等问题,而视频制作又比较麻烦,为了解决此痛点,我们需要提供一键合成视频的功能。

之所以选择 FFmpeg,是因为我们期望后续能够进行视频剪辑、字幕添加等更复杂的音视频操作。下面我们就来了解下什么是 FFmpeg。

音视频开发免费学习地址:https://ke.qq.com/course/3202131?flowToken=1042316

(点击链接免费报名,先关注,不迷路)

什么是FFmpeg

FFmpeg 是一款知名的开源音视频处理软件,它提供了丰富而友好的接口支持开发者进行二次开发,也就是说,我们可以把 FFmpeg 看作是一个跨平台的视频处理程序:

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的 音频/视频编解码库libavcodec。

FFmpeg中的 “FF” 指的是 “Fast Forward”,“mpeg” 则是 “Moving Picture Experts Group”

FFmpeg的原理

FFmpeg对音视频的处理过程可以简概为:解复用 => 解码 => 编码 => 复用器。

FFmpeg的使用

常见情况下使用 FFmpeg 首先要在当前系统配置 FFmpeg 环境,也就是对 FFmpeg 工具进行安装和配置,环境配置完成之后就可以使用命令行工具进行 FFmpeg 的调用。

FFmpeg部分简单的命令行操作示例:

获取音视频文件信息:

$ ffmpeg -i video.mp4

转换视频文件格式(转换 mp4 文件到 avi 文件):

$ ffmpeg -i video.mp4 video.avi

更改视频文件分辨率:

$ ffmpeg -i input.mp4 -filter:v scale=1280:720 -c:a copy output.mp4

视频中提取图像:

$ ffmpeg -i input.mp4 -r 1 -f image2 image-%3d.png

当然,FFmpeg 还可以进行更多的操作,在此不进行更多的举例,感兴趣的同学可以参考官方文档(http://www.ffmpeg.org/)

FFmpeg在Node.js中的应用

在Node.js中有一个非常好用的模块,它就是 fluent-ffmpeg:

This library abstracts the complex command-line usage of ffmpeg into a fluent, easy to use node.js module. In order to be able to use this module, make sure you have ffmpeg installed on your system (including all necessary encoding libraries like libmp3lame or libx264).

简而言之 fluent-ffmpeg 对 FFmpeg 复杂的命令行进行了一定的封装,抽象为我们使用起来非常舒服的各类方法和API,可以看下它的一些常见操作:

fluent-ffmpeg的部分简单示例:

指定输入:

ffmpeg('input1.avi').input('input2.avi').input(fs.createReadStream('input3.avi'));

音频选项:

// 禁用音频
ffmpeg('input1.avi').noAudio();// 设置音频比特率
ffmpeg('input1.avi').audioBitrate(128);// 设置音频频率
ffmpeg('input1.avi').audioFrequency(22050);

视频选项:

// 设置编解码器
ffmpeg('input1.avi').videoCodec('libx264');// 设置输出帧大小和纵横比
ffmpeg('input1.avi').size('640x?').aspect('4:3');

远程环境使用fluent-ffmpeg(@ffmpeg-installer/ffmpeg)

了解到 fluent-ffmpeg 之后,我们会发现它简介中提到的重要一点(make sure you have ffmpeg installed on your system (including all necessary encoding libraries like libmp3lame or libx264).),请确保在系统上安装了 ffmpeg (包括所有必要的编码库,如libmp3lame或libx264)。那么随之而来的一个问题就是,当我把服务部署到远程机器时,远程机器如果没有安装过FFmpeg环境怎么办。

解决这个问题我们用到的是另一个库 @ffmpeg-installer/ffmpeg:

Installs a binary of ffmpeg for the current platform and provides a path and version. Supports Linux, Windows and Mac OS/X.

@ffmpeg-installer/ffmpeg 能为当前平台安装 FFmpeg 二进制文件,让我们具备在多个环境中去调用 FFmpeg 的能力。它和 fluent-ffmpeg 结合使用,只需如下操作:

const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
const ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(ffmpegPath);

将图片合成为视频

下面就是我们想要使用 FFmpeg 进行的工作,将多张不同大小尺寸的图片合成为带有一定动画切换效果的视频:

(为方便上传,视频转换成了gif)

动图封面

整个过程分为以下几步:

1、输入被操作对象

确定基础视频和目标视频,并输入基础视频。这个步骤主要就是确认了输入和输出的目标。

// 基础视频
const baseVideo = path.join(__dirname, '../assets/baseVideo30.mp4')// 目标视频
const savePath = path.join(__dirname, '../assets/temp-video.mp4')// 基础视频输入
let baseInput = await ffmpeg().input(baseVideo)

2、图片输入

方法和视频的输入相同,因为存在多张图片,所以使用for循环输入

for (let i = 0; i < img_list.length; i++) { baseInput = await baseInput.input(img_list[i])
}

3、complexFilter动画处理

实现这个功能的主要逻辑都在于动画部分,要计算好我们如何让多张图片进行有规律的切换,然后将动画规律嵌套到代码内。上面动画的规则其实就是每张图片都从最左侧运动进入居中位置,停留数秒后,向下方运动直到出视频外。

complexFilter 方法允许为命令设置复杂的 filtergraph 。这个API其实对应原生 FFmpeg 的-filter_complex命令,-filter_complex 可以帮助我们实现加字幕、裁剪、缩放、旋转等。我们这里使用的就是 -filter_complex 对输入对象的控制和处理能力。

首先需要处理图片的大小,让不同比例的图片都能缩放为可以居中并且全部展示的大小,如果我们不对图片进行大小处理的话,默认情况会使用图片的原有大小(即使图片大小超出了视频大小范围)。

处理图片和视频大小使用的是 scale 滤镜,首先将视频比例设置为宽高640、480:

let complexFilter = '[0:v]scale=w=640:h=480[videobase];'

上面的代码我们进行一下拆分解释:

// 0-操作对象编号 v-对象内视频信息
'[0:v]'// scale滤镜,设置输入目标的宽高
'scale=w=640:h=480'// outputs输出流 相当于对当前操作后的对象进行标记
'[videobase]'

将图片大小根据视频比例进行宽高设置:

const videoWidth = 640 // 视频宽
const videoHeight = 480 // 视频高
for (let i = 0; i < img_list.length; i++) {complexFilter += `[${i + 1}:v]scale=w='iw*min(${videoWidth}/iw,${videoHeight}/ih)':h='ih*min(${videoWidth}/iw,${videoHeight}/ih)'[img${i + 1}];`
}

上面的代码我们进行一下拆分解释,

// 此处同[0:v],但是0代表我们第一个输入(base视频),所以根据for循环,每次累加得到输入的图片
'[${i + 1}:v]'// iw ih 就是 inputs width 和 inputs height,代表当前操作的图片的原始宽高
// 此处我们将两个值与视频宽高分别对比,并使用 min() 取最小值进行缩放。
'scale=w='iw*min(${videoWidth}/iw,${videoHeight}/ih)':h='ih*min(${videoWidth}/iw,${videoHeight}/ih)''// 同为 outputs 输出标识,即经过此次循环,我们得到了 img1, img2, img3 ..... 等输出对象
'[img${i + 1}]' 

视频和图片的大小比例处理完成后我们使用 overlay 滤镜进行动画处理,其实就是将图片在视频内进行位移。overlay 的能力就是覆盖,将多个输入源进行相互覆盖处理。我们来看这部分的全部代码:

// imgInterval 是通过图片数量和视频长度计算的每张图片展示间隔。
for (let i = 0; i < img_list.length; i++) { // 输入图片的动画控制if (i === 0) {complexFilter += `[videobase][img${i + 1}]overlay='main_w/2-overlay_w/2':'if(gte(t, ${(i + 1) * imgInterval}), min(main_h/2-overlay_h/2+(t-${(i + 1) * imgInterval})*900,main_h), main_h/2-overlay_h/2)'${i === img_list.length - 1 ? '' : `[a${i}];`}`} else {complexFilter += `[a${i - 1}][img${i + 1}]overlay='if(gte(t, ${i * imgInterval}), min(-overlay_w+(t-${i * imgInterval})*900,main_w/2-overlay_w/2),NAN)':'if(gte(t, ${(i + 1) * imgInterval}), min(main_h/2-overlay_h/2+(t-${(i + 1) * imgInterval})*900,main_h), main_h/2-overlay_h/2)'${i === img_list.length - 1 ? '' : `[a${i}];`}`}
}

我们将以上代码拆解,首先,第一张图片默认视频封面,也就是没有入场效果,直接在视频中心的,所以当 i === 0 时单独处理,根据时间判断,当停留时间到达后,会以每秒钟900的速度从下方移动出视频:

// videobase 为上面标记的视频对象,img1 为上面标记的第一张图片对象
// 此处的能力就是将输入对象 img1 覆盖在 videobase 上
'[videobase][img${i + 1}]'// overlay=x(横坐标轴相关操作):y(纵坐标轴相关操作)
// 第一张图片x轴不需要进行移动
'overlay='main_w/2-overlay_w/2''// FFmpeg 的 if语句: if (条件, 条件成立, 条件不成立) 
// main_w - 整个视频的宽度
// overlay_w - 当前操控的输入对象(图片)的宽度
// gte(t, ${(i + 1) * imgInterval})  t为当前时间,通过时间控制是否移动
// min(main_h/2-overlay_h/2+(t-${(i + 1) * imgInterval})*900,main_h) 当前高度,900为移动速度(与时间正比), main_h就是临界值
'if(gte(t, ${(i + 1) * imgInterval}), min(main_h/2-overlay_h/2+(t-${(i + 1) * imgInterval})*900,main_h), main_h/2-overlay_h/2)'

非第一行图片时增加了x轴的处理,根据图片排名和图片停留时间计算出开始运动的时间,并由此刻从左侧进入视频。在居中位置停留后,由下方移出,方法同以上y轴的移动方法:

// 覆盖标识
'[a${i - 1}][img${i + 1}]'// 同上解释,main_w/2-overlay_w/2 为x轴移动的临界值,也就是居中位置, 900 就是x轴与时间成正比的速度
'if(gte(t, ${i * imgInterval}), min(-overlay_w+(t-${i * imgInterval})*900,main_w/2-overlay_w/2),NAN)'

需要注意的地方就是每个图片滤镜完成后的输出标识,此标识需出现在下一张图片的被覆盖对象位置上:

'[a${i - 1}][img${i + 1}]' // [a${i - 1}] 即上一张图片的输出标识

滤镜添加完毕后我们就已经完成了主要的工作,整个动画效果都已经衔接在了一起,最后得到的 complexFilter 命令如下所示:

'[0:v]scale=w=640:h=480[videobase];[1:v]scale=w='iw*min(640/iw,480/ih)':h='ih*min(640/iw,480/ih)'[img1];[2:v]scale=w='iw*min(640/iw,480/ih)':h='ih*min(640/iw,480/ih)'[img2];[3:v]scale=w='iw*min(640/iw,480/ih)':h='ih*min(640/iw,480/ih)'[img3];[4:v]scale=w='iw*min(640/iw,480/ih)':h='ih*min(640/iw,480/ih)'[img4];[videobase][img1]overlay='main_w/2-overlay_w/2':'if(gte(t, 3.75), min(main_h/2-overlay_h/2+(t-3.75)*900,main_h), main_h/2-overlay_h/2)'[a0];[a0][img2]overlay='if(gte(t, 3.75), min(-overlay_w+(t-3.75)*900,main_w/2-overlay_w/2),NAN)':'if(gte(t, 7.5), min(main_h/2-overlay_h/2+(t-7.5)*900,main_h), main_h/2-overlay_h/2)'[a1];[a1][img3]overlay='if(gte(t, 7.5), min(-overlay_w+(t-7.5)*900,main_w/2-overlay_w/2),NAN)':'if(gte(t, 11.25), min(main_h/2-overlay_h/2+(t-11.25)*900,main_h), main_h/2-overlay_h/2)'[a2];[a2][img4]overlay='if(gte(t, 11.25), min(-overlay_w+(t-11.25)*900,main_w/2-overlay_w/2),NAN)':'if(gte(t, 15), min(main_h/2-overlay_h/2+(t-15)*900,main_h), main_h/2-overlay_h/2)''

最后我们可以进行一些其他操作,并设置输出路径

baseInput.complexFilter([ // 上面的滤镜complexFilter,]).videoBitrate('2048k') // 比特率.aspect('4:3') // 视频比例.duration(video_long) // 视频停止时间.on('end', () => { // 视频处理完成console.log('video one end');taskInfo.savePath = savePath;resolve();}).on('error', (error) => { // 视频处理失败console.log('an error happend: create one video' + error);reject(error);}).save(savePath); // 保存路径
})

注意mp4的编码问题

大部分同学对MP4的理解是后缀为 .mp4 的文件,但其实MP4有非常复杂的含义(参考MPEG-4 Part 14(http://en.wikipedia.org/wiki/Mp4)),它本身不是一种简单的视频格式,而是一个包装了视频和音频格式的容器。MP4的视频格式可以有 DivX 也可有 H264,vp8,vp9,theora。

每个浏览器因为专利费等原因对不同格式的视频支持情况也不相同,具体可以参考HTML5 video(https://en.wikipedia.org/wiki/HTML5_video)

所以在此次需求中,就遇到了视频无法在浏览器播放的问题,原因就是一开始的basevideo是直接由 FFmpeg 默认产生。为了解决这个问题,我们使用了一个底层编码为 H264 格式的底视频。这里如果各位同学对 FFmpeg 有深入研究,有更好的解决方案的话,欢迎提供其他解决思路~

总结

以上就是本次对 FFmpeg 的一些介绍和实际开发中的使用,这只是 FFmpeg 的冰山一角,它还有很多更加强大的能力,大家如果对音视频感兴趣可以深入进行学习。

当然,如果对本文中实际的解决方案有疑问或者有更好的建议,欢迎进行讨论~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_1046726.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s 声明式资源管理

一、资源配置清单的管理 1.1 查看资源配置清单 声明式管理方法&#xff1a; 1.适合于对资源的修改操作 2.声明式资源管理方法依赖于资源配置清单文件对资源进行管理 资源配置清单文件有两种格式&#xff1a;yaml&#xff08;人性化&#xff0c;易读&#xff09;&#xff0c;j…

ISSC-原神启动

题目&#xff1a; f12 python代码攻击Tomcat漏洞 import struct def pack_string(s):if s is None:return struct.pack(">h", -1)l len(s)return struct.pack(">H%dsb" % l, l, s.encode(utf8), 0) def unpack(stream, fmt):size struct.calcsiz…

python demo

文章背景&#xff0c;记录python 小demo 集合 1、使用python matplotlib库描绘曲线 import matplotlib.pyplot as NLAx_index [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100] y_index [6.1, 7.4, 9.1, 11.1, 12.69, 14.35, 16.1, 1…

金丝雀发布(灰度发布)介绍 及 声明式管理方法简介

目录 一 应用发布策略 1&#xff0c;滚动发布&#xff08;k8s默认&#xff09; 2&#xff0c;蓝绿发布 3&#xff0c;金丝雀发布 二 金丝雀发布&#xff08;Canary Release&#xff09; &#xff08;灰度发布&#xff09; 1&#xff0c;金丝雀发布图解 2&#xff0…

23设计模式—责任链

文章目录 0、模式类型1、Chain of Resposibility1.1、动机1.2、实现责任链的方式实现&#xff1a; 1.3、模式定义1.4、结构1.5、要点总结 0、模式类型 "数据结构"模式 尝尝有一些组件在内部具有特定的数据结构&#xff0c;如果让客户程序依赖这些特定的数据结构&am…

一款数字化管理平台源码:云MES系统(附架构图、流程)技术架构:springboot + vue-element-plus-admin

制造生产企业打造数字化生产管控的系统&#xff0c;从原材料、生产报工、生产过程、质检、设备、仓库等整个业务流程的管理和控制&#xff0c;合理安排生产计划、实时监控生产、优化生产工艺、降低不良产出和运营成本&#xff1b; 技术架构&#xff1a;springboot vue-elemen…

网创教程wordpress插件自动采集并发布

使用教程 出现404的请搞定自己网站的伪静态。一般都是伪静态问题。 需要定制可以联系我。 本次更新主要更新了。界面的设置。用户可以直接设置文章的分类。 设置文章发布的金额。 使插件更加的人性化。优化了采集更新发布的代码。 更新了网站的界面。 主要功能&#xff1a; w…

溪谷联运SDK功能全面解析

近期&#xff0c;备受用户关注的手游联运10.0.0版本上线了&#xff0c;不少用户也选择了版本更新&#xff0c;其中也再次迎来了SDK的更新。溪谷软件和大家一起盘点一下溪谷SDK的功能都有哪些吧。 一、溪谷SDK具有完整的运营功能和高度扩展性 1.登录&#xff1a;登录是SDK最基础…

GPT‑4o普通账户也可以免费用

网址 https://chatgpt.com/ 试了一下&#xff0c;免费的确实显示GPT‑4o的模型&#xff0c;问了一下可以联网&#xff0c;不知道能不能通过插件出图 有兴趣的可以试试

selenium环境安装和web自动化基础

webUI自动化背景 因为web页面经常会变化&#xff0c;所以UI自动化测试的维护成本很高。不如接口的适用面广&#xff0c;所以大部分公司会做接口自动化测试&#xff0c;但是未必会做UI自动化测试&#xff1b; UI自动化测试要做也是覆盖冒烟测试&#xff0c;不会到很高的覆盖率&a…

Llama模型家族之使用 Supervised Fine-Tuning(SFT)微调预训练Llama 3 语言模型(一) LLaMA-Factory简介

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

QTextCodec NO such file or directory让qt6兼容qt5

首先在.pro 文件中新加 QT core5compat这时会报错 链接 报错之后修复qt&#xff0c;新加兼容模块&#xff0c;见链接。

深度学习之基于Tensorflow的卷积神经网络手写数字识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 手写数字识别是计算机视觉和模式识别领域的一个重要问题。传统的识别方法往往依赖于复杂的特征工程和…

Python数据可视化(六)

实现事件处理效果 我们借助 matplotlib 可以实现事件处理效果&#xff0c;例如&#xff0c;单击关闭画布会出现画布被关闭的文本提 示&#xff0c;在画布上的图形界面任意位置单击可以获得放大后的此处图形界面等。下面&#xff0c;我们就挑选一些 典型的事件处理案例来讲解实现…

Linux信号:信号的概念及意义

目录 一、什么是信号 kill-l查看信号 二、信号的产生 2.1系统调用 kill raise abort 2.2软件条件 13)SIGPIPE pipe信号 14&#xff09;SIGAKARM alarm信号 2.2硬件中断 2.3异常 8)SIGFPE 除0异常 11)SIGSEGV 野指针 2.4信号处理的常见方式 三、Core Dump和term…

【NLP】文本分类

n-gram 的局限性 n-gram 只能对于填空这样的通顺性问题做出推测&#xff0c;但是没有办法完全解决句子的语义问题&#xff0c;从而无法实现文本的分类 文本的分类&#xff0c;就是将文本在语义的理解下划分到特定的主题下 手工规则 如一些垃圾过滤系统&#xff0c;需要人工制…

24V/8A大功率自动调频同步升压电路外围元件少

一、概述&#xff1a; PC5248 芯片是一款具有 600KHz 的自动调节频率、高效率、宽输入范围的电流模式升压&#xff08;BOOST&#xff09;芯片&#xff0c;且具有高效率同步升压功能和可调限流功能。 该电源芯片内部全集成低内阻 20A 功率 MOSFET&#xff0c;可以实现大功率输…

buuctf的RSA(二)

1.RSA 知道 flag.enc 和 pub.key&#xff0c;典型的加密、解密 将pub,key 改为pub.txt 打开后发现公钥 在RSA公私钥分解 Exponent、Modulus&#xff0c;Rsa公私钥指数、系数(模数)分解--查错网 进行解密 得到e65537 n8693448229604811919066606200349480058890565…

(六)DockerCompose安装与配置

DockerCompose简介 Compose 项目是 Docker 官方的开源项目&#xff0c;负责实现对 Docker 容器集群的快速编排。使用前面介绍的Dockerfile我们很容易定义一个单独的应用容器。然而在日常开发工作中&#xff0c;经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现…