我正在尝试使用 ffmpeg 或 mkvextract 从 mkv 文件中提取(如果有很多,则是第一个)字幕轨道(仅通过 CLI,没有 GUI)。
但这样做有一个缺点:我必须手动输入输出名称。在批处理中,你不知道字幕是否是.srt
,.ass
或者甚至是不同的格式,所以盲目地写作subs.srt
听起来不是一个好主意。
有没有办法提取字幕同时保持原始格式?
例如,ffmpeg 是否有办法获取字幕格式信息,然后使用该类型执行类似上述命令的操作?(理想情况下是通过工具的本机方法,与操作系统无关)
我可以做这个:
ffmpeg -i video.mkv subs.srt
或这个:
ffmpeg -i video.mkv -map 0:s:0 subs.srt
但是有没有办法避免指定输出名称?例如,使用 mkvextract 我可以这样做:
mkvextract attachments ..\input_video.mkv 1
我会收到警告(不是错误):
“未指定目标文件名,将使用附件名称。”
这实际上是我想要的行为,但这适用于附件。
没有mkvextract subtitles
mkvextract将字幕视为轨道的一部分。因此,如果轨道顺序混乱,字幕不是第 3 条轨道,则使用 mkvextract 的类似命令将不起作用。
总结
ffmpeg
:我被(可能)错误的文件扩展名困住了mkvextract
:我存在提取一些随机音轨而不是字幕的风险。
答案1
我们可以使用 FFprobe 来获取字幕编解码器名称,并相应地应用提取的字幕文件扩展名。
示例批处理文件基于下列答案。
假设 MKV 文件名为in1.mkv
,我们可以使用以下 Windows 批处理文件:
set fname=in1
set mkv_fname=%fname%.mkv
@echo off
SETLOCAL
::Extract subtitles codec name from file using FFprobe
for /F "tokens=1 delims='\n'" %%a in ('ffprobe.exe "-v" "error" "-select_streams" "s:0" "-show_entries" "stream=codec_name" "-of" "default=noprint_wrappers=1:nokey=1" "%mkv_fname%"') do (
ENDLOCAL
set tmp_subs_codec=%%a
)
::Remove the space from subs_codec
set subs_codec=%tmp_subs_codec:~0,-1%
if %subs_codec%==ass ffmpeg -y -vn -an -dn -i %mkv_fname% -c copy -map 0:s:0 %fname%.ass
if %subs_codec%==subrip ffmpeg -y -vn -an -dn -i %mkv_fname% -c copy -map 0:s:0 %fname%.srt
对于带参数的批处理文件,替换set fname=in1
为set fname=%1
Linux Bash 示例:
fname=in1
mkv_fname=$fname".mkv"
subs_codec=$(ffprobe -v error -select_streams s:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 $mkv_fname 2>&1)
if [ "$subs_codec" = "ass" ]; then
ffmpeg -y -vn -an -dn -i $mkv_fname -c copy -map 0:s:0 $fname.ass
fi
if [ "$subs_codec" = "subrip" ]; then
ffmpeg -y -vn -an -dn -i $mkv_fname -c copy -map 0:s:0 $fname.srt
fi
答案2
Rotem 的回答启发了我并给了我技巧,所以我找到了一种更简单的方法来做到这一点并想与大家分享。
使用 PowerShell 中的 ffprobe,我们使用 ffprobe 获取有组织的 json 上的信息,然后使用结构化数组及其访问器,而不是在输出字符串上进行 grep/select:
$ff_info_array = .\ffprobe.exe -v quiet -print_format json -show_format -show_streams "video.mkv" | ConvertFrom-Json
foreach ($entry in $ff_info_array.streams){
if ($entry.codec_type -eq "subtitle") {
$sub_type=$entry.codec_name #this will contain .srt, .ass ,etc
if($entry.disposition.default){
write-host "found default sub and its extension is $sub_type "
$internal_sub_found = $true
$idx = $entry.index
$concat= $idx.""
#we can now extract the subtitle like above but add the exertion in the name or via mkvextract:
.\mkvextract.exe -q tracks "video.mp4" "$idx"":myvideo_output.$sub_type"
}
}