我有 65 个 .mov 视频文件,分别名为 tape_01.mov、tape_02.mov、...、tape_65.mov。每个视频文件大约 2.5 小时长,占用数 GB。它们是 VHS 录像带(我家的“家庭电影”)的数字副本。
每个 2.5 小时的 .mov 视频文件包含许多“章节”(有时场景以淡入黑屏结束,然后新的场景淡入)。
我的主要目标是将 65 个大文件按章节分割成小文件。我希望通过检测“场景”之间的黑框来自动完成此操作。
我可以使用 ffmpeg(或其他程序)传入 65 个原始文件名,让它自动遍历每个视频并将其分割,将得到的片段命名为 tape_01-ch_01.mov、tape_01-ch_02.mov、tape_01-ch_03.mov 等吗?我希望它能做到这一点没有重新编码(我希望它是一个简单的无损切片)。
我怎样才能做到这一点?
以下是我想要的步骤:
- 遍历一个包含 .mov 文件的文件夹(名为 tape_01.mov、tape_02.mov、...、tape_65.mov),将它们导出为 mp4 文件(名为 tape_01.mp4、tape_02.mp4、...、tape_65.mp4)。我希望此步骤的压缩设置易于配置。创建 .mp4 文件后,原始 .mov 文件应该仍然存在。
- 遍历 mp4 文件:为每个 mp4 文件生成一个文本文件,指定“场景”之间黑屏片段的开始时间和持续时间。每个 mp4 可以有 0 个或多个这样的黑屏片段。开始时间和持续时间应精确到秒的几分之一。HH:MM:SS 格式不够精确。
- 然后,我将能够手动复查这些文本文件,看看它们是否正确识别了场景变化(黑色片段)。如果需要,我可以调整时间。
- 另一个脚本将读取每个 mp4 文件及其 txt 文件,并根据文本文件的行数(此处指示的时间)将 mp4 分割成尽可能多的部分(以超快且无损的方式)。分割应发生在每个黑色片段的中间。这是一个示例 mp4 文件(出于隐私目的删除了音频)具有淡入淡出到黑色的场景变化。在创建较小的 mp4 文件时,原始 mp4 文件应保持不变。较小的 mp4 文件应采用 tape_01_001.mp4、tape_01_002.mp4、tape_01_003.mp4 等命名方案。
- 奖励!如果另一个脚本可以迭代小型 mp4 文件并为每个文件生成一个 .png 图像(该图像是此人正在尝试的屏幕截图的马赛克),那就太棒了:使用 FFmpeg 为视频制作有意义的缩略图
我希望这些脚本是可以在 Mac、Ubuntu 和 Windows Git Bash 中运行的 shell 脚本。
答案1
这里有两个 PowerShell 脚本,用于根据黑场将长视频分割成较小的章节。
将它们保存为 Detect_black.ps1 和 Cut_black.ps1。下载适用于 Windows 的 ffmpeg并在选项部分下告诉脚本您的 ffmpeg.exe 和视频文件夹的路径。
这两个脚本都不会触及现有视频文件,它们保持不变。
但是,您将在输入视频所在的同一位置获得几个新文件
- 每个视频都有一个日志文件,其中包含两个使用的 ffmpeg 命令的控制台输出
- 每个视频都有一个 CSV 文件,其中包含所有黑场的时间戳,以便进行手动微调
- 根据之前检测到的黑色场景数量,生成几个新视频
第一个运行的脚本:Detect_black.ps1
### Options __________________________________________________________________________________________________________
$ffmpeg = ".\ffmpeg.exe" # Set path to your ffmpeg.exe; Build Version: git-45581ed (2014-02-16)
$folder = ".\Videos\*" # Set path to your video folder; '\*' must be appended
$filter = @("*.mov","*.mp4") # Set which file extensions should be processed
$dur = 4 # Set the minimum detected black duration (in seconds)
$pic = 0.98 # Set the threshold for considering a picture as "black" (in percent)
$pix = 0.15 # Set the threshold for considering a pixel "black" (in luminance)
### Main Program ______________________________________________________________________________________________________
foreach ($video in dir $folder -include $filter -exclude "*_???.*" -r){
### Set path to logfile
$logfile = "$($video.FullName)_ffmpeg.log"
### analyse each video with ffmpeg and search for black scenes
& $ffmpeg -i $video -vf blackdetect=d=`"$dur`":pic_th=`"$pic`":pix_th=`"$pix`" -an -f null - 2> $logfile
### Use regex to extract timings from logfile
$report = @()
Select-String 'black_start:.*black_end:' $logfile | % {
$black = "" | Select start, end, cut
# extract start time of black scene
$start_s = $_.line -match '(?<=black_start:)\S*(?= black_end:)' | % {$matches[0]}
$start_ts = [timespan]::fromseconds($start_s)
$black.start = "{0:HH:mm:ss.fff}" -f ([datetime]$start_ts.Ticks)
# extract duration of black scene
$end_s = $_.line -match '(?<=black_end:)\S*(?= black_duration:)' | % {$matches[0]}
$end_ts = [timespan]::fromseconds($end_s)
$black.end = "{0:HH:mm:ss.fff}" -f ([datetime]$end_ts.Ticks)
# calculate cut point: black start time + black duration / 2
$cut_s = ([double]$start_s + [double]$end_s) / 2
$cut_ts = [timespan]::fromseconds($cut_s)
$black.cut = "{0:HH:mm:ss.fff}" -f ([datetime]$cut_ts.Ticks)
$report += $black
}
### Write start time, duration and the cut point for each black scene to a seperate CSV
$report | Export-Csv -path "$($video.FullName)_cutpoints.csv" –NoTypeInformation
}
它是如何工作的
第一个脚本遍历所有与指定扩展名匹配但与模式不匹配的视频文件*_???.*
,因为新的视频章节已被命名<filename>_###.<ext>
,我们想要将其排除。
它搜索所有黑场,并将开始时间戳和黑场持续时间写入名为<video_name>_cutpoints.txt
它还会计算剪切点,如下所示:cutpoint = black_start + black_duration / 2
。之后,视频会根据这些时间戳进行分段。
cutpoints.txt 文件示例视频将显示:
start end cut
00:03:56.908 00:04:02.247 00:03:59.578
00:08:02.525 00:08:10.233 00:08:06.379
运行后,您可以根据需要手动操作剪切点。如果再次运行脚本,所有旧内容都会被覆盖。手动编辑时请小心谨慎,并将您的工作保存到其他地方。
对于示例视频,检测黑色场景的 ffmpeg 命令是
$ffmpeg -i "Tape_10_3b.mp4" -vf blackdetect=d=4:pic_th=0.98:pix_th=0.15 -an -f null
脚本的选项部分有 3 个重要的数字可以编辑
d=4
意味着只检测超过 4 秒的黑色场景pic_th=0.98
是将图片视为“黑色”的阈值(百分比)pix=0.15
设置将像素视为“黑色”(亮度)的阈值。由于您使用的是旧 VHS 视频,因此视频中没有完全黑色的场景。默认值 10 不起作用,我不得不稍微增加阈值
如果出现任何问题,请检查相应的日志文件<video_name>__ffmpeg.log
。如果缺少以下几行,请增加上述数字,直到检测到所有黑色场景:
[blackdetect @ 0286ec80]
black_start:236.908 black_end:242.247 black_duration:5.33877
要运行的第二个脚本:cut_black.ps1
### Options __________________________________________________________________________________________________________
$ffmpeg = ".\ffmpeg.exe" # Set path to your ffmpeg.exe; Build Version: git-45581ed (2014-02-16)
$folder = ".\Videos\*" # Set path to your video folder; '\*' must be appended
$filter = @("*.mov","*.mp4") # Set which file extensions should be processed
### Main Program ______________________________________________________________________________________________________
foreach ($video in dir $folder -include $filter -exclude "*_???.*" -r){
### Set path to logfile
$logfile = "$($video.FullName)_ffmpeg.log"
### Read in all cutpoints from *_cutpoints.csv; concat to string e.g "00:03:23.014,00:06:32.289,..."
$cuts = ( Import-Csv "$($video.FullName)_cutpoints.csv" | % {$_.cut} ) -join ","
### put together the correct new name, "%03d" is a generic number placeholder for ffmpeg
$output = $video.directory.Fullname + "\" + $video.basename + "_%03d" + $video.extension
### use ffmpeg to split current video in parts according to their cut points
& $ffmpeg -i $video -f segment -segment_times $cuts -c copy -map 0 $output 2> $logfile
}
它是如何工作的
第二个脚本以与第一个脚本相同的方式遍历所有视频文件。它只读取切cutpoints.txt
来自视频对应的时间戳。
接下来,它会为章节文件组合一个合适的文件名,并告诉 ffmpeg 对视频进行分段。目前,视频是在没有重新编码的情况下进行切片的(超快且无损)。因此,由于 ffmpeg 只能在 key_frames 处进行剪切,因此剪切点时间戳可能会有 1-2 秒的误差。由于我们只是复制而不是重新编码,因此我们无法自行插入 key_frames。
示例视频的命令是
$ffmpeg -i "Tape_10_3b.mp4" -f segment -segment_times "00:03:59.578,00:08:06.379" -c copy -map 0 "Tape_10_3b_(%03d).mp4"
如果出现任何问题,请查看相应的 ffmpeg.log
参考
去做
询问 OP CSV 格式是否比文本文件更适合用作切点文件,这样你可以更轻松地使用 Excel 编辑它们
» 已实现实现将时间戳格式化为 [hh]:[mm]:[ss],[毫秒] 而非仅仅秒数
» 已实现实现一个 ffmpeg 命令,为每一章创建 mosaik png 文件
» 已实现详细说明这是否-c copy
足以满足 OP 的情况,或者我们是否需要完全重新编码。
好像 Ryan 已经在上面了。
答案2
这是福利:第三个 PowerShell 脚本生成了马赛克缩略图
您将收到每个章节视频的一张图片,它们都像下面的示例一样。
### Options __________________________________________________________________________________________________________
$ffmpeg = ".\ffmpeg.exe" # Set path to your ffmpeg.exe; Build Version: git-45581ed (2014-02-16)
$folder = ".\Videos\*" # Set path to your video folder; '\*' must be appended
$x = 5 # Set how many images per x-axis
$y = 4 # Set how many images per y-axis
$w = 384 # Set thumbnail width in px (full image width: 384px x 5 = 1920px,)
### Main Program ______________________________________________________________________________________________________
foreach ($video in dir $folder -include "*_???.mp4" -r){
### get video length in frames, an ingenious method
$log = & $ffmpeg -i $video -vcodec copy -an -f null $video 2>&1
$frames = $log | Select-String '(?<=frame=.*)\S+(?=.*fps=)' | % { $_.Matches } | % { $_.Value }
$frame = [Math]::floor($frames / ($x * $y))
### put together the correct new picture name
$output = $video.directory.Fullname + "\" + $video.basename + ".jpg"
### use ffmpeg to create one mosaic png per video file
### Basic explanation for -vf options: http://trac.ffmpeg.org/wiki/FilteringGuide
#1 & $ffmpeg -y -i $video -vf "select=not(mod(n\,`"$frame`")),scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
#2 & $ffmpeg -y -i $video -vf "yadif,select=not(mod(n\,`"$frame`")),scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
& $ffmpeg -y -i $video -vf "mpdecimate,yadif,select=not(mod(n\,`"$frame`")),scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
#4 & $ffmpeg -y -i $video -vf "select='gt(scene\,0.06)',scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 -vsync vfr $output
#5 & $ffmpeg -y -i $video -vf "yadif,select='gt(scene\,0.06)',scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 -vsync vfr $output
#6 & $ffmpeg -y -i $video -vf "mpdecimate,yadif,select='gt(scene\,0.06)',scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 -vsync vfr $output
#7 & $ffmpeg -y -i $video -vf "thumbnail,scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
#8 & $ffmpeg -y -i $video -vf "yadif,thumbnail,scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
#9 & $ffmpeg -y -i $video -vf "mpdecimate,yadif,thumbnail,scale=`"$w`":-1,tile=`"$x`"x`"$y`"" -frames:v 1 $output
}
主要思想是获取整个视频的连续图像流。我们使用 ffmpeg 的选择选项。
首先,我们用一种巧妙的方法检索总帧数(例如 2000),并将其除以我们的默认缩略图数量(例如 5 x 4 = 20)。因此,我们希望每 100 帧生成一张图像,因为2000 / 20 = 100
生成缩略图的 ffmpeg 命令可能如下所示
ffmpeg -y -i input.mp4 -vf "mpdecimate,yadif,select=not(mod(n\,100)),scale=384:-1,tile=5x4" -frames:v 1 output.png
在上面的代码中,你会看到 9 种不同的-vf
组合包含由...组成
select=not(mod(n\,XXX))
其中 XXX 是计算出的帧速率thumbnail
自动选择最具代表性的帧select='gt(scene\,XXX)
+-vsync vfr
其中 XXX 是你必须遵守的门槛mpdecimate
- 删除近似重复的帧。有效消除黑色场景yadif
- 对输入图像进行去隔行处理。不知道为什么,但它确实有效
我认为版本 3 是最佳选择。其他版本均已注释掉,但您仍可以尝试。我能够使用 、 和 删除大部分模糊缩略图mpdecimate
。yadif
太棒select=not(mod(n\,XXX))
了!
为您示例视频我得到了这些预览
我上传了所有缩略图由这些版本创建。请查看它们以进行全面比较。
答案3
感谢这两个脚本,这是分割视频的好方法。
不过,我还是遇到了一些问题,视频播放后无法显示正确的时间,而且我无法跳转到特定时间。一个解决方案是使用 Mp4Box 对流进行解复用,然后进行复用。
对我来说,另一种更简单的方法是使用 mkvmerge 进行分割。因此,两个脚本都必须更改。对于 detect_black.ps1,只需将“*.mkv”添加到过滤器选项中,但如果您只从 mp4 文件开始,则不一定如此。
### Options
$mkvmerge = ".\mkvmerge.exe" # Set path to mkvmerge.exe
$folder = ".\*" # Set path to your video folder; '\*' must be appended
$filter = @("*.mov", "*.mp4", "*.mkv") # Set which file extensions should be processed
### Main Program
foreach ($video in dir $folder -include $filter -exclude "*_???.*" -r){
### Set path to logfile
$logfile = "$($video.FullName)_ffmpeg.log"
### Read in all cutpoints from *_cutpoints.csv; concat to string e.g "00:03:23.014,00:06:32.289,..."
$cuts = ( Import-Csv "$($video.FullName)_cutpoints.csv" | % {$_.cut} ) -join ","
### put together the correct new name, "%03d" is a generic number placeholder for mkvmerge
$output = $video.directory.Fullname + "\" + $video.basename + "-%03d" + ".mkv"
### use mkvmerge to split current video in parts according to their cut points and convert to mkv
& $mkvmerge -o $output --split timecodes:$cuts $video
}
功能相同,但到目前为止视频时间没有问题。谢谢你的启发。
答案4
我希望我错了,但我认为你问的问题是不可能的。在重新编码视频时可能可以,但作为“简单无损切片”,我不知道有什么方法。
许多视频编辑程序都具有自动分析功能或类似功能,可以在黑帧上分割视频,但这需要对视频进行重新编码。
祝你好运!