在黑帧(场景变化)处自动将较大的 .mov 视频文件分割为较小的文件?

在黑帧(场景变化)处自动将较大的 .mov 视频文件分割为较小的文件?

我有 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 等吗?我希望它能做到这一点没有重新编码(我希望它是一个简单的无损切片)。

我怎样才能做到这一点?

以下是我想要的步骤:

  1. 遍历一个包含 .mov 文件的文件夹(名为 tape_01.mov、tape_02.mov、...、tape_65.mov),将它们导出为 mp4 文件(名为 tape_01.mp4、tape_02.mp4、...、tape_65.mp4)。我希望此步骤的压缩设置易于配置。创建 .mp4 文件后,原始 .mov 文件应该仍然存在。
  2. 遍历 mp4 文件:为每个 mp4 文件生成一个文本文件,指定“场景”之间黑屏片段的开始时间和持续时间。每个 mp4 可以有 0 个或多个这样的黑屏片段。开始时间和持续时间应精确到秒的几分之一。HH:MM:SS 格式不够精确。
  3. 然后,我将能够手动复查这些文本文件,看看它们是否正确识别了场景变化(黑色片段)。如果需要,我可以调整时间。
  4. 另一个脚本将读取每个 mp4 文件及其 txt 文件,并根据文本文件的行数(此处指示的时间)将 mp4 分割成尽可能多的部分(以超快且无损的方式)。分割应发生在每个黑色片段的中间。这是一个示例 mp4 文件(出于隐私目的删除了音频)具有淡入淡出到黑色的场景变化。在创建较小的 mp4 文件时,原始 mp4 文件应保持不变。较小的 mp4 文件应采用 tape_01_001.mp4、tape_01_002.mp4、tape_01_003.mp4 等命名方案。
  5. 奖励!如果另一个脚本可以迭代小型 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 是最佳选择。其他版本均已注释掉,但您仍可以尝试。我能够使用 、 和 删除大部分模糊缩略图mpdecimateyadif太棒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

我希望我错了,但我认为你问的问题是不可能的。在重新编码视频时可能可以,但作为“简单无损切片”,我不知道有什么方法。

许多视频编辑程序都具有自动分析功能或类似功能,可以在黑帧上分割视频,但这需要对视频进行重新编码。

祝你好运!

相关内容