欢迎光临散文网 会员登陆 & 注册

学习如何使用FFmpeg开源软件给音频文件添加元数据以及封面

2022-11-22 23:08 作者:28283844972_bili  | 我要投稿

我们平时从网络下载到的不管是视频文件,还是音频文件也好,除了视频和音频本身之外,一般还会额外附带一些信息,视频文件中有类似封面缩略图、章节、字体等数据,音频文件中有类似曲目标题、封面、作者、发行年份等等之类的标签。但偏偏就是有这样的时候,下载到的文件描述几乎丢失殆尽或者根本没有提供,而且很确定的是这些辅助信息自己手头上正好都有,那么该如何修复这些元数据(metadata)呢?

网上能批量完成上述修复操作的软件一搜一大堆,本文的重点并不是推荐和介绍如何使用批量工具,而是依托FFmpeg开源项目文档帮助我们初步了解媒体文件元数据,并且使用FFmpeg工具套件亲手体验一回给音频文件添加专辑封面。当然,这里使用音频文件来举例只是为了简单起见且更容易上手,视频文件的元数据相比音频要更复杂些,不过这都不是问题,硬啃FFmpeg帮助手册当中的相关原文就完事了(逃

在正式开始前我想补充一下MPV播放器中加载曲目封面的行为,对于MPV来说专辑封面大致可分为内嵌和外挂两种形式,具体可以看一下官方文档中类似的描述:

--audio-display=<no|embedded-first|external-first>

Determines whether to display cover art when playing audio files and with what priority. It will display the first image found, and additional images are available as video tracks.

no: Disable display of video entirely when playing audio files. 

embedded-first : Display embedded images and external cover art, giving priority to embedded images (default). 

external-first: Display embedded images and external cover art, giving priority to external files. 

简单来说就是在播放音频的同时,优先选择内嵌还是外挂的图片作为封面,并且显示的是首个被找到的图像,其余作为可用的而视频轨,如果有的话。内嵌封面我们一般无法通过直观的手段改变媒体流轨道顺序,外挂封面有以下规律:

至于"Album"和"Cover"之间的关系举个例子会直观些,在音频文件同级目录下放置"Album.jpg"和"Cover.jpg"两张图片作为外挂封面,建议作为专辑封面的图片分辨率纵横比为1:1且文件大小尽可能地小。注意,音频文件自身是不带有任何内嵌封面,否则按照默认的优先级设定则会优先加载内嵌封面。按F9(默认快捷键)查看当前的媒体流信息:

如图,最先加载的是名为"Album"的封面,"Cover"封面作为可用视频流被加载
切换至"Cover"流也能正常显示封面,验证了文档中将其余封面作为可用视频流的描述

所以建议在指定外挂封面时,图像命名尽量与音源同名,同时也便于自己区分不同音源的封面;如果需要在不同音源之间共享同一个封面,建议命名为"Album"。外挂封面自有它的灵活性也终究有它的局限性,当遇到不同内容的同名音频文件时需要额外处理一些冲突,此外便携性自然也不如内嵌,因此才有了将媒体元数据内嵌至媒体文件的需求,正片开始……

入门学习最好的办法是模仿,找一个音频文件右键属性查看详细信息,我们所能看到的就是音频元数据可见的部分,Windows把这些识别到的内容分为"说明","媒体","音频","来源","内容"等板块,暂且先不管这些元数据是如何在音频文件中表示,总之先了解一下表述:

除了Windows自带的右键属性,其他播放器甚至是WMP在播放时也能解析出文件中的元数据,这里我们使用FFmpeg项目自带的ffprobe工具套件来查看文件的媒体流、标签信息,仔细观察元数据标签和媒体流(Stream)编号,这很重要,封面图片是作为视频流的形式存在:

其实我们可以通过以上描述信息已经能猜到各项元数据对应的英文标签,不过还是先来看看FFmpeg官方文档中有关提取媒体文件元数据的描述吧,毕竟想要自己添加元数据之前必须得找一个模板当参考。

FFmpeg is able to dump metadata from media files into a simple UTF-8-encoded INI-like text file and then load it back using the metadata muxer/demuxer.

The file format is as follows:

  1. A file consists of a header and a number of metadata tags divided into sections, each on its own line.

  2. The header is a ‘;FFMETADATA’ string, followed by a version number (now 1).

  3. Metadata tags are of the form ‘key=value’

  4. Immediately after header follows global metadata

  5. After global metadata there may be sections with per-stream/per-chapter metadata.

  6. A section starts with the section name in uppercase (i.e. STREAM or CHAPTER) in brackets (‘[’, ‘]’) and ends with next section or end of file.

  7. At the beginning of a chapter section there may be an optional timebase to be used for start/end values. It must be in form ‘TIMEBASE=num/den’, where num and den are integers. If the timebase is missing then start/end times are assumed to be in nanoseconds.

    Next a chapter section must contain chapter start and end times in form ‘START=num’, ‘END=num’, where num is a positive integer.

  8. Empty lines and lines starting with ‘;’ or ‘#’ are ignored.

  9. Metadata keys or values containing special characters (‘=’, ‘;’, ‘#’, ‘\’ and a newline) must be escaped with a backslash ‘\’.

  10. Note that whitespace in metadata (e.g. ‘foo = bar’) is considered to be a part of the tag (in the example above key is ‘foo ’, value is ‘ bar’).

A ffmetadata file might look like this:

大概的意思就是,FFmpeg提供了一个简单的以UTF-8编码,类似INI配置文本的方式,从媒体文件中提取元数据或附加回去,之后就是一些格式说明,构成媒体文件元数据的头部和标签被分成了两段,各占一行,紧跟在头部之后的是全局元数据,标签是一对键值对,再之后就是各个章节和媒体流的元数据,以';'或者‘#’打头的内容类似注释会被忽略(头部除外),具有特殊含义的字符需要用到反斜杠转义,表述太长需要换行也是。在初步了解数据结构之后,接下来是提取元数据:

By using the ffmetadata muxer and demuxer it is possible to extract metadata from an input file to an ffmetadata file, and then transcode the file into an output file with the edited ffmetadata file.

Extracting an ffmetadata file with ffmpeg goes as follows:

Reinserting edited metadata information from the FFMETADATAFILE file can be done as:

利用的是ffmetadata复用器和解复用器,前者是提取,后者是重新插入编辑好的元数据,其中input是媒体文件,ffmetadatafile是输出文本。具体请参考如下样例,音频文件会简单很多,同时我们也会看到一些不可见的元数据标签,一般的音频文件元数据大致也是如此:

元数据都是按照全局标签的形式存在,与我们用ffprobe解析得到的内容几乎一致。有了可选择的标签,接下来就是不借助Windows属性或其他可视化工具向媒体文件中添加自定义的元数据,还是回到FFmpeg文档中来:

  • -metadata[:metadata_specifier] key=value (output,per-metadata)

  • Set a metadata key/value pair.

    An optional metadata_specifier may be given to set metadata on streams, chapters or programs. See -map_metadata documentation for details.

    This option overrides metadata set with -map_metadata. It is also possible to delete metadata by using an empty value.

    For example, for setting the title in the output file:

To set the language of the first audio stream:

只看操作命令不如直接上演示:

-metadata之后直接跟标签键值对则会成为全局元数据,等同于metadata_specifier=g,且每项表现都需要对应一个-metadata选项,若使用s,c,p等metadata_specifier,则分为别设置每个或具体媒体流、章节、节目设置元数据,具体可以参考FFmpeg对应章节,关于音视频编解码器(codec)、媒体流描述符(video/audio/subtitle)、编号(Stream #[0-9])、轨道选择等基础概念请查阅FFmpeg文档。

另外并不是所有可用描述标签都能添加到文件的元数据当中,这却决于媒体封装格式是否支持。例如在这个例子中给音频流单独添加语言标签的操作是失败的,很有可能的原因是容器本身不支持。

在学会如何向音频文件中添加或修改元数据之后,下一步我们需要将封面图片内嵌到音频文件中,本质上专辑图片将会以视频流的形式,以1帧率的速度在播放音频的同时显示在播放器窗口中,所以归根结底的思路是将一个音频流和视频流合并成一份完整的音频文件,而输入的视频流是一张图片,至于这个思路对不对?我们来看一下FFmpeg官方文档中用于演示的例子:

The MP3 muxer writes a raw MP3 stream with the following optional features:

  • An ID3v2 metadata header at the beginning (enabled by default). Versions 2.3 and 2.4 are supported, the id3v2_version private option controls which one is used (3 or 4). Setting id3v2_version to 0 disables the ID3v2 header completely.

    The muxer supports writing attached pictures (APIC frames) to the ID3v2 header. The pictures are supplied to the muxer in form of a video stream with a single packet. There can be any number of those streams, each will correspond to a single APIC frame. The stream metadata tags title and comment map to APIC description and picture type respectively. See http://id3.org/id3v2.4.0-frames for allowed picture types.

    Note that the APIC frames must be written at the beginning, so the muxer will buffer the audio frames until it gets all the pictures. It is therefore advised to provide the pictures as soon as possible to avoid excessive buffering.

  • A Xing/LAME frame right after the ID3v2 header (if present). It is enabled by default, but will be written only if the output is seekable. The write_xing private option can be used to disable it. The frame contains various information that may be useful to the decoder, like the audio duration or encoder delay.

  • A legacy ID3v1 tag at the end of the file (disabled by default). It may be enabled with the write_id3v1 private option, but as its capabilities are very limited, its usage is not recommended.

Examples:

Write an mp3 with an ID3v2.3 header and an ID3v1 footer:

To attach a picture to an mp3 file select both the audio and the picture stream with map:

Write a "clean" MP3 without any extra features:

在介绍MP3复用器用法的段落中,第二项示例为我们展示了如何用-map选项选中音频和图片流向mp3文件中附加图像的操作。ID3v2元数据头默认启用,可以用过-id3v2_version选项显式指定版本,可选择的版本有3或者4,受支持比较好的是版本3,0表示完全禁用,不过我们不必过于关心这个选项,默认就好。重点关注-map选项。

map选项会把输入文件中的媒体流按照一定的顺序映射到输出文件中,所以不同map之间的前后顺序以及指定参数都非常的重要。输入原始的音频文件和封面图片,紧跟在-i选项后面,先后顺序可以颠倒,但是map参数也要随之调整。

-c copy表示原封不动地把输入的媒体流复制到输出文件中,因为我们不需要额外的转码操作,仅仅是将音频流和视频流封装在一起,所以copy完全够用。

-map 0表示选择第一项输入的所有媒体流准备进行映射操作,需要补充说明的是输入文件流以及媒体流的编号都是从0开始计数,因此就是选中原始音频文件中的全部媒体流,当然我们输入的音频文件目前只有一个音频流,也就等同于选中了它,如果需要精确描述这项参数的话,应该是-map 0:a或者-map 0:0,表示选中第一项输入的第一条音轨。

-map 1自然表示选择输入的图片作为视频流,或者是-map 1:v,-map 1:0,映射编号严格与输入文件流的顺序对应。如果实在对map选项的用法不清楚可以先转到FFmpeg文档阅读相关说明。

map选项之间的先后顺序,会影响被选中的媒体流最终映射到输出文件中的先后顺序。示例中首先被选中的音频流,在输出文件中的表现形式为Stream #0:0 Audio,同理后选中的图像表现为Stream #0:1 Video,在这个例子中顺序并不会影响实际播放效果,但每当着手处理一个媒体文件的时候,你最好预先使用分析工具了解文件中的各项媒体流的分布情况。

之后的两项-metadata:s:v都是在针对视频流的部分单独添加一些具有描述作用的元数据标签。前面提到过,仅使用-metadata选项一般是针对全局的元数据标签做一些修改,但紧跟着使用s限定符将修改范围缩小至各个媒体流,最后通过v限定符将范围缩小至视频流这一项,也就是说所有修改元数据的操作仅针对视频流,解释为给专辑封面添加一些描述。看个人喜好,并不是必须的操作。

下面实际演示一下效果,封面是一张640*640分辨率,大小约为530KiB的图片,原始无封面音频文件大小约为2.57MiB,操作如下:

正好观察一下FFmpeg解析的过程,加深一下记忆,重点是输入输出流的变化嗷:

执行FFmpeg操作的过程输出

再次使用ffprobe工具检验一下输出得到的文件元数据是否正确添加(修改):

实际上专辑封面的comment标签内容是存在一定的内容限制,不识别的内容会被标记为Other

使用MPV播放器播放新生成的音频文件确认效果,顺带检查一下封面信息。可以看到,外挂封面已经内嵌到音频文件中,因为默认是内嵌封面优先,所以即使存在外挂封面也不会加载作为可用的备选视频流:

按F9调出流信息,按`调出控制台

Shift + i(默认快捷键)调出媒体统计信息,可以观察到封面图片被封装到音频文件中之后,MPV识别到的编码格式变为PNG,但也不是绝对的,在我试验的另外一份样本中是MJPEG,原因暂时未知。另外外挂封面图片的编码格式也是存在一定限制的,最常见的PNG和JPEG还好说,WEBM等其他格式是否可以内嵌暂时没尝试过。

观察一下新生成的音频文件大小约3.08MiB,已知原始音频文件大小约为2.57MiB,原始封面图片大小约为530KiB,添加的元数据内容大小基本忽略不计,可以看到添加封面就是将原文件的音频数据和图像数据封装到一块儿,生成的目标文件大小基本却决于这两项数值,非常的简单粗暴,所以选择需要内嵌的封面时大小尽量小一些,毕竟封面不会给音质带来任何提升,仅作为视觉装饰的存在。因此我们可以把内嵌封面的操作最简化为以下的近似操作:

想要剔除音频文件中的内嵌封面及其元数据可以试试这个,这样会保留原文件中的音频部分以及全局元数据标签:

或者你直接-vn就完事了(逃:

任何修改文件内容的操作都存在风险,建议仅对原文件的副本开展实验,避免涉及覆盖原文件的操作。

另外,内嵌封面可以嵌入多张图片,每张封面都会作为独立的视频流封装到音频文件中,就像切换视频流那样,可以在播放期间切换音频的专辑封面,MPV播放器默认显示的是编号为0的首张封面,类似这样:

操作方法同上,不过是在添加输入的时候多加了几张图片,再做映射等额外的步骤。当然这个默认的视频流是可以在执行内嵌时通过选项进行设置的,若没有特别指定则按照map到输出文件中的顺序由FFmpeg自动安排。

总之,多看看这些开源项目的官方文档准没坏处,听我讲不仅有些概念解释不到位而且还不保证内容一定正确,有错误有纰漏在所难免,现学现卖罢了(逃


参考资料:

  • https://ffmpeg.org/documentation.html

  • https://mpv.io/manual/stable/

  • https://www.cnblogs.com/sztom/p/14952650.html



学习如何使用FFmpeg开源软件给音频文件添加元数据以及封面的评论 (共 条)

分享到微博请遵守国家法律