學(xué)習(xí)如何使用FFmpeg開源軟件給音頻文件添加元數(shù)據(jù)以及封面
我們平時從網(wǎng)絡(luò)下載到的不管是視頻文件,還是音頻文件也好,除了視頻和音頻本身之外,一般還會額外附帶一些信息,視頻文件中有類似封面縮略圖、章節(jié)、字體等數(shù)據(jù),音頻文件中有類似曲目標(biāo)題、封面、作者、發(fā)行年份等等之類的標(biāo)簽。但偏偏就是有這樣的時候,下載到的文件描述幾乎丟失殆盡或者根本沒有提供,而且很確定的是這些輔助信息自己手頭上正好都有,那么該如何修復(fù)這些元數(shù)據(jù)(metadata)呢?
網(wǎng)上能批量完成上述修復(fù)操作的軟件一搜一大堆,本文的重點并不是推薦和介紹如何使用批量工具,而是依托FFmpeg開源項目文檔幫助我們初步了解媒體文件元數(shù)據(jù),并且使用FFmpeg工具套件親手體驗一回給音頻文件添加專輯封面。當(dāng)然,這里使用音頻文件來舉例只是為了簡單起見且更容易上手,視頻文件的元數(shù)據(jù)相比音頻要更復(fù)雜些,不過這都不是問題,硬啃FFmpeg幫助手冊當(dāng)中的相關(guān)原文就完事了(逃
在正式開始前我想補(bǔ)充一下MPV播放器中加載曲目封面的行為,對于MPV來說專輯封面大致可分為內(nèi)嵌和外掛兩種形式,具體可以看一下官方文檔中類似的描述:
--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.?
簡單來說就是在播放音頻的同時,優(yōu)先選擇內(nèi)嵌還是外掛的圖片作為封面,并且顯示的是首個被找到的圖像,其余作為可用的而視頻軌,如果有的話。內(nèi)嵌封面我們一般無法通過直觀的手段改變媒體流軌道順序,外掛封面有以下規(guī)律:
至于"Album"和"Cover"之間的關(guān)系舉個例子會直觀些,在音頻文件同級目錄下放置"Album.jpg"和"Cover.jpg"兩張圖片作為外掛封面,建議作為專輯封面的圖片分辨率縱橫比為1:1且文件大小盡可能地小。注意,音頻文件自身是不帶有任何內(nèi)嵌封面,否則按照默認(rèn)的優(yōu)先級設(shè)定則會優(yōu)先加載內(nèi)嵌封面。按F9(默認(rèn)快捷鍵)查看當(dāng)前的媒體流信息:


所以建議在指定外掛封面時,圖像命名盡量與音源同名,同時也便于自己區(qū)分不同音源的封面;如果需要在不同音源之間共享同一個封面,建議命名為"Album"。外掛封面自有它的靈活性也終究有它的局限性,當(dāng)遇到不同內(nèi)容的同名音頻文件時需要額外處理一些沖突,此外便攜性自然也不如內(nèi)嵌,因此才有了將媒體元數(shù)據(jù)內(nèi)嵌至媒體文件的需求,正片開始……

入門學(xué)習(xí)最好的辦法是模仿,找一個音頻文件右鍵屬性查看詳細(xì)信息,我們所能看到的就是音頻元數(shù)據(jù)可見的部分,Windows把這些識別到的內(nèi)容分為"說明","媒體","音頻","來源","內(nèi)容"等板塊,暫且先不管這些元數(shù)據(jù)是如何在音頻文件中表示,總之先了解一下表述:

除了Windows自帶的右鍵屬性,其他播放器甚至是WMP在播放時也能解析出文件中的元數(shù)據(jù),這里我們使用FFmpeg項目自帶的ffprobe工具套件來查看文件的媒體流、標(biāo)簽信息,仔細(xì)觀察元數(shù)據(jù)標(biāo)簽和媒體流(Stream)編號,這很重要,封面圖片是作為視頻流的形式存在:

其實我們可以通過以上描述信息已經(jīng)能猜到各項元數(shù)據(jù)對應(yīng)的英文標(biāo)簽,不過還是先來看看FFmpeg官方文檔中有關(guān)提取媒體文件元數(shù)據(jù)的描述吧,畢竟想要自己添加元數(shù)據(jù)之前必須得找一個模板當(dāng)參考。
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:
A file consists of a header and a number of metadata tags divided into sections, each on its own line.
The header is a ‘;FFMETADATA’ string, followed by a version number (now 1).
Metadata tags are of the form ‘key=value’
Immediately after header follows global metadata
After global metadata there may be sections with per-stream/per-chapter metadata.
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.
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.
Empty lines and lines starting with ‘;’ or ‘#’ are ignored.
Metadata keys or values containing special characters (‘=’, ‘;’, ‘#’, ‘\’ and a newline) must be escaped with a backslash ‘\’.
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:
大概的意思就是,F(xiàn)Fmpeg提供了一個簡單的以UTF-8編碼,類似INI配置文本的方式,從媒體文件中提取元數(shù)據(jù)或附加回去,之后就是一些格式說明,構(gòu)成媒體文件元數(shù)據(jù)的頭部和標(biāo)簽被分成了兩段,各占一行,緊跟在頭部之后的是全局元數(shù)據(jù),標(biāo)簽是一對鍵值對,再之后就是各個章節(jié)和媒體流的元數(shù)據(jù),以';'或者‘#’打頭的內(nèi)容類似注釋會被忽略(頭部除外),具有特殊含義的字符需要用到反斜杠轉(zhuǎn)義,表述太長需要換行也是。在初步了解數(shù)據(jù)結(jié)構(gòu)之后,接下來是提取元數(shù)據(jù):
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復(fù)用器和解復(fù)用器,前者是提取,后者是重新插入編輯好的元數(shù)據(jù),其中input是媒體文件,ffmetadatafile是輸出文本。具體請參考如下樣例,音頻文件會簡單很多,同時我們也會看到一些不可見的元數(shù)據(jù)標(biāo)簽,一般的音頻文件元數(shù)據(jù)大致也是如此:
元數(shù)據(jù)都是按照全局標(biāo)簽的形式存在,與我們用ffprobe解析得到的內(nèi)容幾乎一致。有了可選擇的標(biāo)簽,接下來就是不借助Windows屬性或其他可視化工具向媒體文件中添加自定義的元數(shù)據(jù),還是回到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之后直接跟標(biāo)簽鍵值對則會成為全局元數(shù)據(jù),等同于metadata_specifier=g,且每項表現(xiàn)都需要對應(yīng)一個-metadata選項,若使用s,c,p等metadata_specifier,則分為別設(shè)置每個或具體媒體流、章節(jié)、節(jié)目設(shè)置元數(shù)據(jù),具體可以參考FFmpeg對應(yīng)章節(jié),關(guān)于音視頻編解碼器(codec)、媒體流描述符(video/audio/subtitle)、編號(Stream?#[0-9])、軌道選擇等基礎(chǔ)概念請查閱FFmpeg文檔。
另外并不是所有可用描述標(biāo)簽都能添加到文件的元數(shù)據(jù)當(dāng)中,這卻決于媒體封裝格式是否支持。例如在這個例子中給音頻流單獨添加語言標(biāo)簽的操作是失敗的,很有可能的原因是容器本身不支持。
在學(xué)會如何向音頻文件中添加或修改元數(shù)據(jù)之后,下一步我們需要將封面圖片內(nèi)嵌到音頻文件中,本質(zhì)上專輯圖片將會以視頻流的形式,以1幀率的速度在播放音頻的同時顯示在播放器窗口中,所以歸根結(jié)底的思路是將一個音頻流和視頻流合并成一份完整的音頻文件,而輸入的視頻流是一張圖片,至于這個思路對不對?我們來看一下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復(fù)用器用法的段落中,第二項示例為我們展示了如何用-map選項選中音頻和圖片流向mp3文件中附加圖像的操作。ID3v2元數(shù)據(jù)頭默認(rèn)啟用,可以用過-id3v2_version選項顯式指定版本,可選擇的版本有3或者4,受支持比較好的是版本3,0表示完全禁用,不過我們不必過于關(guān)心這個選項,默認(rèn)就好。重點關(guān)注-map選項。
map選項會把輸入文件中的媒體流按照一定的順序映射到輸出文件中,所以不同map之間的前后順序以及指定參數(shù)都非常的重要。輸入原始的音頻文件和封面圖片,緊跟在-i選項后面,先后順序可以顛倒,但是map參數(shù)也要隨之調(diào)整。
-c copy表示原封不動地把輸入的媒體流復(fù)制到輸出文件中,因為我們不需要額外的轉(zhuǎn)碼操作,僅僅是將音頻流和視頻流封裝在一起,所以copy完全夠用。
-map 0表示選擇第一項輸入的所有媒體流準(zhǔn)備進(jìn)行映射操作,需要補(bǔ)充說明的是輸入文件流以及媒體流的編號都是從0開始計數(shù),因此就是選中原始音頻文件中的全部媒體流,當(dāng)然我們輸入的音頻文件目前只有一個音頻流,也就等同于選中了它,如果需要精確描述這項參數(shù)的話,應(yīng)該是-map 0:a或者-map 0:0,表示選中第一項輸入的第一條音軌。
-map 1自然表示選擇輸入的圖片作為視頻流,或者是-map 1:v,-map 1:0,映射編號嚴(yán)格與輸入文件流的順序?qū)?yīng)。如果實在對map選項的用法不清楚可以先轉(zhuǎn)到FFmpeg文檔閱讀相關(guān)說明。
map選項之間的先后順序,會影響被選中的媒體流最終映射到輸出文件中的先后順序。示例中首先被選中的音頻流,在輸出文件中的表現(xiàn)形式為Stream #0:0 Audio,同理后選中的圖像表現(xiàn)為Stream #0:1 Video,在這個例子中順序并不會影響實際播放效果,但每當(dāng)著手處理一個媒體文件的時候,你最好預(yù)先使用分析工具了解文件中的各項媒體流的分布情況。
之后的兩項-metadata:s:v都是在針對視頻流的部分單獨添加一些具有描述作用的元數(shù)據(jù)標(biāo)簽。前面提到過,僅使用-metadata選項一般是針對全局的元數(shù)據(jù)標(biāo)簽做一些修改,但緊跟著使用s限定符將修改范圍縮小至各個媒體流,最后通過v限定符將范圍縮小至視頻流這一項,也就是說所有修改元數(shù)據(jù)的操作僅針對視頻流,解釋為給專輯封面添加一些描述。看個人喜好,并不是必須的操作。
下面實際演示一下效果,封面是一張640*640分辨率,大小約為530KiB的圖片,原始無封面音頻文件大小約為2.57MiB,操作如下:
正好觀察一下FFmpeg解析的過程,加深一下記憶,重點是輸入輸出流的變化嗷:

再次使用ffprobe工具檢驗一下輸出得到的文件元數(shù)據(jù)是否正確添加(修改):

使用MPV播放器播放新生成的音頻文件確認(rèn)效果,順帶檢查一下封面信息??梢钥吹?,外掛封面已經(jīng)內(nèi)嵌到音頻文件中,因為默認(rèn)是內(nèi)嵌封面優(yōu)先,所以即使存在外掛封面也不會加載作為可用的備選視頻流:

Shift + i(默認(rèn)快捷鍵)調(diào)出媒體統(tǒng)計信息,可以觀察到封面圖片被封裝到音頻文件中之后,MPV識別到的編碼格式變?yōu)镻NG,但也不是絕對的,在我試驗的另外一份樣本中是MJPEG,原因暫時未知。另外外掛封面圖片的編碼格式也是存在一定限制的,最常見的PNG和JPEG還好說,WEBM等其他格式是否可以內(nèi)嵌暫時沒嘗試過。


觀察一下新生成的音頻文件大小約3.08MiB,已知原始音頻文件大小約為2.57MiB,原始封面圖片大小約為530KiB,添加的元數(shù)據(jù)內(nèi)容大小基本忽略不計,可以看到添加封面就是將原文件的音頻數(shù)據(jù)和圖像數(shù)據(jù)封裝到一塊兒,生成的目標(biāo)文件大小基本卻決于這兩項數(shù)值,非常的簡單粗暴,所以選擇需要內(nèi)嵌的封面時大小盡量小一些,畢竟封面不會給音質(zhì)帶來任何提升,僅作為視覺裝飾的存在。因此我們可以把內(nèi)嵌封面的操作最簡化為以下的近似操作:
想要剔除音頻文件中的內(nèi)嵌封面及其元數(shù)據(jù)可以試試這個,這樣會保留原文件中的音頻部分以及全局元數(shù)據(jù)標(biāo)簽:
或者你直接-vn就完事了(逃:
任何修改文件內(nèi)容的操作都存在風(fēng)險,建議僅對原文件的副本開展實驗,避免涉及覆蓋原文件的操作。
另外,內(nèi)嵌封面可以嵌入多張圖片,每張封面都會作為獨立的視頻流封裝到音頻文件中,就像切換視頻流那樣,可以在播放期間切換音頻的專輯封面,MPV播放器默認(rèn)顯示的是編號為0的首張封面,類似這樣:

操作方法同上,不過是在添加輸入的時候多加了幾張圖片,再做映射等額外的步驟。當(dāng)然這個默認(rèn)的視頻流是可以在執(zhí)行內(nèi)嵌時通過選項進(jìn)行設(shè)置的,若沒有特別指定則按照map到輸出文件中的順序由FFmpeg自動安排。

總之,多看看這些開源項目的官方文檔準(zhǔn)沒壞處,聽我講不僅有些概念解釋不到位而且還不保證內(nèi)容一定正確,有錯誤有紕漏在所難免,現(xiàn)學(xué)現(xiàn)賣罷了(逃

參考資料:
https://ffmpeg.org/documentation.html
https://mpv.io/manual/stable/
https://www.cnblogs.com/sztom/p/14952650.html
