[備忘錄](méi) 使用FFmpeg開(kāi)源軟件剔除音頻文件中多余的章節(jié)信息
從網(wǎng)絡(luò)上下載得到的音頻樣本,莫名其妙地在文件中附加了兩個(gè)無(wú)用的章節(jié)信息。其中一項(xiàng)附加在文件末尾處標(biāo)注章節(jié)持續(xù)時(shí)長(zhǎng)為0,從而導(dǎo)致我目前在用的 MPV Player 當(dāng)處于后臺(tái)播放時(shí),一旦遇到文件結(jié)尾處始終會(huì)因?yàn)檫@個(gè)無(wú)效的章節(jié)而自動(dòng)暫停,需要手動(dòng)切換至前臺(tái)才能恢復(fù)播放,非常地?zé)o語(yǔ)。
于是著手分析原因所在,有意瞥了一眼 OSD 發(fā)現(xiàn)了兩個(gè)多余的章節(jié)標(biāo)注,對(duì)我來(lái)說(shuō)這兩個(gè)章節(jié)標(biāo)注完全多余,因此目標(biāo)就是盡可能簡(jiǎn)單地剔除文件中自帶的章節(jié)信息。多媒體編輯軟件手上只有FFmpeg可用,嘗試成功后暫且做個(gè)備忘吧。

定位到問(wèn)題所在之后,再用 FFmpeg 開(kāi)源工具套件中的三劍客之一 ffprobe 來(lái)分析一下目標(biāo)文件的媒體信息,如圖所示,文件中確實(shí)記錄有"Chapters"字段,其下的兩個(gè)章節(jié)元數(shù)據(jù)標(biāo)題名分別為"ch0","ch1",同時(shí)記錄的還有起止時(shí)間點(diǎn),除此之外再無(wú)其他說(shuō)明,可以明顯看到"ch1"章節(jié)的起始時(shí)間和結(jié)束時(shí)間記錄相同,也就是說(shuō)該章節(jié)標(biāo)注的持續(xù)時(shí)長(zhǎng)為0,實(shí)屬莫名其妙。

由此也可得知,常見(jiàn)的章節(jié)標(biāo)注可以通過(guò)元數(shù)據(jù)的方式附加到媒體文件中去,并在播放期間被多媒體播放器解析并發(fā)揮作用,多個(gè)章節(jié)元數(shù)據(jù)以節(jié)段(Section)的形式組織在一起,共同組成章節(jié)字段,在 ffprobe 的分析結(jié)果中以 "Chapter #input_file_index:chapter_index" 的形式呈現(xiàn)并區(qū)分。
下一步是查閱 FFmpeg 官方手冊(cè)中有關(guān)章節(jié)處理的選項(xiàng)說(shuō)明,以"chapter"作為關(guān)鍵詞在文檔中進(jìn)行檢索,找到以下說(shuō)明:
-map_chapters?input_file_index?(output)
Copy chapters from input file with index?input_file_index?to the next output file. If no chapter mapping is specified, then chapters are copied from the first input file with at least one chapter. Use a negative file index to disable any chapter copying.
把具有?input_file_index 索引的輸入文件中的章節(jié)復(fù)制到下一項(xiàng)輸出文件。若沒(méi)有指定章節(jié)映射,則從首個(gè)輸入文件中復(fù)制至少一個(gè)章節(jié)。使用負(fù)的文件索引禁用任何章節(jié)的復(fù)制。
因?yàn)槟繕?biāo)很明確也很簡(jiǎn)單,就是剔除輸入文件中的全部章節(jié)字段,所以結(jié)合上述文檔說(shuō)明僅需對(duì)單輸入文件使用一個(gè)帶負(fù)文件索引值的?map_chapters 選項(xiàng)即可:
效果顯而易見(jiàn),輸出文件中的章節(jié)信息已全部剔除:

清除全部的章節(jié)信息似乎非常簡(jiǎn)單,同時(shí)也引出了向文件中添加章節(jié)信息的需求,在僅使用 ffmpeg 工具套件的前提下,簡(jiǎn)單地提供一下目前我所知道的兩個(gè)方案,其中一個(gè)方案同樣也能達(dá)成刪除章節(jié)段落的目的,不過(guò)處理數(shù)據(jù)的粒度更加細(xì)致,允許刪改特定的章節(jié)段落:
場(chǎng)景1:只需要添加、刪改幾個(gè)章節(jié)元數(shù)據(jù)字段,沒(méi)有增刪章節(jié)段落/修改章節(jié)起止時(shí)間的需求。
可直接在命令行中使用?metadata 選項(xiàng)指定章節(jié)索引修改或新增元數(shù)據(jù)字段,例如我需要在上述案例的文件中修改末尾章節(jié)標(biāo)注的標(biāo)題,并想要添加一個(gè)"comment"字段做些備注:
效果如下,想要?jiǎng)h除一個(gè)現(xiàn)有的章節(jié)元數(shù)據(jù)也很簡(jiǎn)單,令相應(yīng)的字段值為空(例如空字串)即可:

缺點(diǎn)也很明顯,上述方法只能對(duì)已有的章節(jié)段落進(jìn)行修改,無(wú)法新增加一個(gè)獨(dú)立的章節(jié)段落,無(wú)法刪除已有的章節(jié)段落,無(wú)法修改章節(jié)段落的起止時(shí)間等等。另外每修改一項(xiàng)元數(shù)據(jù)字段都需要調(diào)用一次 metadata 選項(xiàng),閱讀、理解和糾錯(cuò)的難度成倍增加,因此不適合個(gè)位數(shù)以上的字段修改。
場(chǎng)景2:中小規(guī)模地增加或刪改章節(jié)元數(shù)據(jù),需要新增、刪改若干章節(jié)段落,起止時(shí)間等。
之前的這篇專(zhuān)欄中有提到利用從媒體文件中導(dǎo)出元數(shù)據(jù)配置文件的方式,學(xué)習(xí)并驗(yàn)證元數(shù)據(jù)標(biāo)簽。

同樣也可以通過(guò)修改導(dǎo)出后的配置文件,再重新導(dǎo)回至原文件的方式修改章節(jié)段落。
ffmetadata 遵循類(lèi)似 ini?文件的格式。下面是官方文檔給出的例子:
文件一般在首行含有標(biāo)記頭,以分號(hào)開(kāi)啟。上例中的標(biāo)記頭是?
FFMETADATA
,版本是 1元數(shù)據(jù)以鍵值對(duì)?
key=value
?的形式書(shū)寫(xiě),每對(duì)占一行。注意,等號(hào)兩側(cè)的空格會(huì)被保留,因此不推薦留空格。特殊字符
文件中的行如果以?
;
?或?#
?開(kāi)頭,則表示注釋。如果鍵值對(duì)中的值含有字符等號(hào)?
=
,反斜線(xiàn)?\
,或者注釋符號(hào)?;
?或?#
,那么必須在它們前面加上一個(gè)額外的反斜線(xiàn)來(lái)轉(zhuǎn)義。元數(shù)據(jù)可以按章(chapter)或者流(stream)分節(jié),每一節(jié)的名稱(chēng)需要?大寫(xiě)?且放在中括號(hào)內(nèi)。在第一節(jié)之前的鍵值對(duì),表示全局元數(shù)據(jù)。
每一節(jié)內(nèi)可以指定一個(gè)?
TIMEBASE=n1/n2
?鍵,表示章節(jié)起止時(shí)刻的時(shí)間單位,其中?n1
?與?n2
?都必須是正整數(shù)。上例中的?1/1000
?表示千分之一秒(即毫秒),因而?END=60000
?表示第一章在第 60000 毫秒也即第 60 秒處結(jié)束。如果未指定?TIMEBASE
?鍵的值,那么會(huì)默認(rèn)使用納秒(十億分之一秒,即?\(10^{-9}\)?秒)。
以本文案例中的音頻文件為例,觀(guān)察導(dǎo)出得到的 ffmetadata 配置文件內(nèi)容。顯然,只要剔除所有的 Chapter 段落配置重新導(dǎo)入文件也能實(shí)現(xiàn)先前的目標(biāo),不過(guò)暫且先不這么做,照貓畫(huà)虎,嘗試修改章節(jié)起止時(shí)間并新增一個(gè)章節(jié)段落:
重新導(dǎo)入到輸出文件中,再次使用 ffprobe 分析,基本滿(mǎn)足需求:

以上結(jié)論僅為我個(gè)人片面理解 FFmpeg 文檔說(shuō)明,以及使用 FFmpeg 工具套件經(jīng)驗(yàn)總結(jié)所得,出現(xiàn)理解偏差與疏漏在所難免,歡迎在評(píng)論區(qū)指出,以及討論 FFmpeg 工具套件的更多高級(jí)玩法。

熱知識(shí):
Windows除了在右鍵菜單中添加"快速打開(kāi)終端至當(dāng)前目錄"的選項(xiàng)之外,試試在資源管理器中使用 Alt + D 或 Ctrl + L 快捷鍵切換焦點(diǎn)至地址導(dǎo)航欄輸入"cmd" 或者?"powershell" 回車(chē)。
參考資料:
https://ffmpeg.org/documentation.html
https://wklchris.github.io/blog/FFmpeg/FFmpeg.html
