Tự động chia các tệp video .mov lớn thành các tệp nhỏ hơn ở các khung màu đen (thay đổi cảnh)?


11

Tôi có 65 tệp video .mov có tên là băng_01.mov, băng_02.mov, ..., băng_65.mov. Mỗi cái dài khoảng 2,5 giờ và chiếm nhiều gigabyte. Chúng là bản sao kỹ thuật số của các băng VHS ("phim gia đình" của gia đình tôi).

Mỗi tệp video .mov dài 2,5 giờ chứa nhiều "chương" (theo nghĩa là đôi khi cảnh kết thúc mờ dần trên màn hình đen, và sau đó một cảnh mới mờ dần).

Mục tiêu chính của tôi là có 65 tệp lớn được cắt thành các tệp nhỏ hơn theo chương. Và tôi muốn điều đó được thực hiện tự động thông qua việc phát hiện các khung màu đen giữa các "cảnh".

Tôi có thể sử dụng ffmpeg (hoặc bất cứ thứ gì) để vượt qua 65 tên tệp gốc và để nó tự động lặp lại qua từng video và cắt nó xuống, đặt tên cho các mảnh kết quả là băng_01-ch_01.mov, băng_01-ch_02.mov, băng_01-ch_03.mov, Vân vân? Và tôi muốn nó làm điều đó mà không cần mã hóa lại (tôi muốn nó là một lát cắt đơn giản).

Tôi có thể làm cái này như thế nào?

Đây là các bước tôi muốn:

  1. Lặp lại một thư mục các tệp .mov (có tên là băng_01.mov, băng_02.mov, ..., băng_65.mov) để xuất chúng dưới dạng tệp mp4 (có tên là băng_01.mp4, băng_02.mp4, ..., băng_65.mp4) . Tôi muốn cài đặt nén dễ dàng cấu hình cho bước này. Các tệp .mov ban đầu vẫn tồn tại sau khi các tệp .mp4 được tạo.
  2. Lặp lại các tệp mp4: đối với mỗi tệp mp4, tạo tệp văn bản chỉ định thời gian bắt đầu và thời lượng của các phân đoạn màu đen giữa các "cảnh". Mỗi mp4 có thể có 0 hoặc nhiều hơn các cảnh vỡ đen này. Thời gian bắt đầu và thời lượng nên chính xác đến một phần của giây. Định dạng HH: MM: SS không đủ chính xác.
  3. Sau đó tôi sẽ có thể kiểm tra thủ công các tệp văn bản này để xem liệu chúng có xác định chính xác các thay đổi cảnh (phân đoạn màu đen) hay không. Tôi có thể điều chỉnh thời gian nếu tôi muốn.
  4. Một tập lệnh khác sẽ đọc từng tệp mp4 và tệp txt của nó và phân tách (theo cách siêu nhanh và không mất dữ liệu) mp4 thành nhiều phần cần thiết dựa trên các dòng của tệp văn bản (thời gian biểu ở đó). Việc chia tách sẽ xảy ra ở giữa mỗi phân đoạn màu đen. Dưới đây là một tệp mp4 mẫu (đã loại bỏ âm thanh cho mục đích riêng tư) có các thay đổi cảnh mờ dần sang đen. Các tệp mp4 gốc sẽ không bị ảnh hưởng khi các tệp mp4 nhỏ hơn được tạo. Các tệp mp4 nhỏ hơn nên có sơ đồ đặt tên của băng_01_001.mp4, băng_01_002.mp4, băng_01_003.mp4, v.v.
  5. Tặng kem! Thật tuyệt vời nếu một tập lệnh khác sau đó có thể lặp lại các tệp mp4 nhỏ và tạo một hình ảnh .png cho mỗi hình ảnh đó là một bức ảnh chụp màn hình như người này đang cố gắng: Hình thu nhỏ có ý nghĩa cho Video bằng FFmpeg

Tôi muốn các tập lệnh này trở thành tập lệnh shell mà tôi có thể chạy trong Mac, Ubuntu và Windows Git Bash.

Câu trả lời:


11

Dưới đây là hai tập lệnh PowerShell để chia các video dài thành các chương nhỏ hơn bằng các cảnh đen.

Lưu chúng dưới dạng Detect_black.ps1 và Cut_black.ps1. Tải xuống ffmpeg cho Windows và cho kịch bản đường dẫn đến ffmpeg.exe và thư mục video của bạn trong phần tùy chọn.

nhập mô tả hình ảnh ở đây

Cả hai tập lệnh sẽ không chạm vào các tập tin video hiện có, chúng vẫn còn nguyên.
Tuy nhiên, bạn sẽ nhận được một vài tệp mới tại cùng một nơi có video đầu vào của bạn

  • Một logfile cho mỗi video với đầu ra giao diện điều khiển cho cả hai lệnh ffmpeg được sử dụng
  • Tệp CSV trên mỗi video có tất cả dấu thời gian của cảnh đen để tinh chỉnh thủ công
  • Một vài video mới tùy thuộc vào số lượng cảnh đen được phát hiện trước đó

nhập mô tả hình ảnh ở đây


Kịch bản đầu tiên để chạy: 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
}

Làm thế nào nó hoạt động

Tập lệnh đầu tiên lặp qua tất cả các tệp video khớp với phần mở rộng được chỉ định và không khớp với mẫu *_???.*, vì các chương video mới được đặt tên <filename>_###.<ext>và chúng tôi muốn loại trừ chúng.

Nó tìm kiếm tất cả các cảnh đen và ghi thời gian bắt đầu và thời lượng cảnh đen vào một tệp CSV mới có tên <video_name>_cutpoints.txt

Nó cũng tính toán các điểm cắt như hình : cutpoint = black_start + black_duration / 2. Sau đó, video được phân đoạn tại các dấu thời gian này.

Tệp cutpoint.txt cho video mẫu của bạn sẽ hiển thị:

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

Sau khi chạy, bạn có thể thao tác các điểm cắt bằng tay nếu muốn. Nếu bạn chạy lại tập lệnh, tất cả nội dung cũ sẽ bị ghi đè. Hãy cẩn thận khi chỉnh sửa thủ công và lưu công việc của bạn ở nơi khác.

Đối với video mẫu, lệnh ffmpeg để phát hiện cảnh đen là

$ffmpeg -i "Tape_10_3b.mp4" -vf blackdetect=d=4:pic_th=0.98:pix_th=0.15 -an -f null

Có 3 số quan trọng có thể chỉnh sửa trong phần tùy chọn của tập lệnh

  • d=4 có nghĩa là chỉ những cảnh đen dài hơn 4 giây được phát hiện
  • pic_th=0.98 là ngưỡng để xem ảnh là "đen" (tính bằng phần trăm)
  • pix=0.15đặt ngưỡng để xem xét pixel là "đen" (độ chói). Vì bạn có video VHS cũ, bạn không có cảnh đen hoàn toàn trong video của mình. Giá trị mặc định 10 sẽ không hoạt động và tôi phải tăng ngưỡng một chút

Nếu có gì sai, hãy kiểm tra logfile tương ứng được gọi <video_name>__ffmpeg.log. Nếu thiếu các dòng sau, hãy tăng các số được đề cập ở trên cho đến khi bạn phát hiện tất cả các cảnh đen:

[blackdetect @ 0286ec80]
 black_start:236.908 black_end:242.247 black_duration:5.33877



Kịch bản thứ hai để chạy: 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        
}

Làm thế nào nó hoạt động

Tập lệnh thứ hai lặp lại trên tất cả các tệp video theo cùng một cách tập lệnh đầu tiên đã thực hiện. Nó chỉ đọc các dấu thời gian cắt từ tương ứng cutpoints.txtcủa video.

Tiếp theo, nó đặt một tên tệp phù hợp cho các tệp chương và yêu cầu ffmpeg phân đoạn video. Hiện tại các video được cắt mà không cần mã hóa lại (cực nhanh và không mất dữ liệu). Do đó, có thể có 1-2 giây không chính xác với dấu thời gian điểm cắt vì ffmpeg chỉ có thể cắt tại key_frames. Vì chúng tôi chỉ sao chép và không mã hóa lại, chúng tôi không thể tự chèn key_frames.

Lệnh cho video mẫu sẽ là

$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"

Nếu có gì sai, hãy xem ffmpeg.log tương ứng



Người giới thiệu



Làm

  • Hỏi OP nếu định dạng CSV tốt hơn tệp văn bản dưới dạng tệp điểm cắt, để bạn có thể chỉnh sửa chúng bằng Excel dễ dàng hơn một chút
    »Đã triển khai
  • Thực hiện một cách định dạng dấu thời gian là [hh]: [mm]: [ss], [mili giây] thay vì chỉ vài giây
    »Đã triển khai
  • Thực hiện lệnh ffmpeg để tạo các tệp png mosaik cho mỗi chương
    »Đã triển khai
  • Xây dựng nếu -c copyđủ cho kịch bản của OP hoặc chúng ta cần mã hóa lại hoàn toàn.
    Có vẻ như Ryan đã ở trên đó .

Thật là một câu trả lời thấu đáo! Tôi rât cảm kich! Thật không may, tôi đang sử dụng máy Mac ngay bây giờ và trước tiên sẽ cần tìm cách dịch nó sang Bash.
Ryan

Tôi chưa thể có được điều này để hoạt động trong PowerShell. Các tệp nhật ký vẫn trống.
Ryan

Tôi sẽ thấy những gì tôi có thể tải lên để hữu ích. Giữ nguyên. Cảm ơn sự nhiệt tình của bạn!
Ryan

Tôi đã thêm một mẫu mp4 vào câu hỏi. Cảm ơn bạn đã giúp đỡ!
Ryan

Vì mục đích riêng tư, tôi sẽ không tải lên MOV nguồn thực sự. Tôi muốn cắt nó và loại bỏ âm thanh (như tôi đã làm cho mp4). Vì vậy, dù sao nó cũng không phải là một nguồn gốc thực sự. Mặc dù vậy, nó sẽ là quá lớn để tải lên. Và bước phân tách cảnh có thể xảy ra trên các tệp mp4 thay vì các tệp MOV cho mục đích không gian đĩa. Cảm ơn!
Ryan

3

Đây là phần thưởng: Tập lệnh PowerShell thứ 3 này tạo ra hình ảnh thu nhỏ khảm

Bạn sẽ nhận được một hình ảnh cho mỗi video chương, tất cả trông giống như ví dụ dưới đây.

### 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
}

Ý tưởng chính là để có được một luồng hình ảnh liên tục trên video hoàn chỉnh. Chúng tôi làm điều này với tùy chọn chọn của ffmpeg .

Đầu tiên, chúng tôi truy xuất tổng số khung bằng một phương thức khéo léo (ví dụ 2000) và chia nó thông qua số lượng hình thu nhỏ mặc định của chúng tôi (ví dụ 5 x 4 = 20). Vì vậy, chúng tôi muốn tạo một hình ảnh cứ sau 100 khung hình2000 / 20 = 100

Lệnh ffmpeg kết quả để tạo hình thu nhỏ có thể trông giống như

ffmpeg -y -i input.mp4 -vf "mpdecimate,yadif,select=not(mod(n\,100)),scale=384:-1,tile=5x4" -frames:v 1 output.png

Trong đoạn mã trên, bạn thấy 9 -vfkết hợp khác nhau bao gồm

  • select=not(mod(n\,XXX)) Trong đó XXX là tốc độ khung hình được tính toán
  • thumbnail trong đó tự động chọn các khung đại diện nhất
  • select='gt(scene\,XXX)+ -vsync vfrtrong đó XXX là ngưỡng bạn phải chơi
  • mpdecimate- Loại bỏ các khung gần trùng lặp. Tốt chống lại cảnh đen
  • yadif- Khử xen kẽ hình ảnh đầu vào. Không biết tại sao, nhưng nó hoạt động

Phiên bản 3 là sự lựa chọn tốt nhất theo ý kiến ​​của tôi. Tất cả những người khác được nhận xét, nhưng bạn vẫn có thể thử chúng. Tôi đã có thể loại bỏ hầu hết các hình thu nhỏ mờ sử dụng mpdecimate, yadifselect=not(mod(n\,XXX)). Vâng!

Đối với video mẫu của bạn, tôi nhận được các bản xem trước

nhập mô tả hình ảnh ở đây
Nhấn vào đây để phóng to

nhập mô tả hình ảnh ở đây
Nhấn vào đây để phóng to

Tôi đã tải lên tất cả các hình thu nhỏ được tạo bởi các phiên bản đó. Có một cái nhìn vào họ để so sánh đầy đủ.


Rất tuyệt! Tôi sẽ xem xét điều này. Tôi đã lãng phí hàng giờ hôm nay quá cố gắng để đi select='gt(scene\,0.4)'làm. Và tôi cũng không thể fps="fps=1/600"đi làm. Nhân tiện, bạn có ý tưởng gì về superuser.com/questions/725734 không? Cảm ơn rất nhiều vì tất cả sự giúp đỡ của bạn. Tôi rất hào hứng về việc giúp gia đình khám phá hàng tấn ký ức cũ.
Ryan

Bạn đã thử 5 biến thể mã hóa khác mà tôi liệt kê ở phía dưới chưa? Tôi đã thêm một ví dụ làm việc cho tùy chọn "cảnh". Hãy quên bộ lọc FPS đi. Tôi quản lý để loại bỏ hầu hết các ngón tay cái mờ :)
nixda

0

Đánh giá cao cả hai kịch bản, cách tuyệt vời để chia video.

Tuy nhiên, tôi đã gặp một số vấn đề với các video không hiển thị thời gian chính xác sau đó và tôi không thể chuyển sang thời gian cụ thể. Một giải pháp là demux và sau đó mux các luồng bằng mp4Box.

Một cách khác, dễ dàng hơn đối với tôi là sử dụng mkvmerge để chia tách. Do đó, cả hai kịch bản đã phải được thay đổi. Đối với Det_black.ps1, chỉ có "* .mkv" phải được thêm vào tùy chọn bộ lọc, nhưng điều đó không nhất thiết nếu bạn chỉ bắt đầu với các tệp 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
}

Chức năng là như nhau, nhưng cho đến nay không có vấn đề với video. Cảm ơn đã truyền cảm hứng.


-2

Tôi hy vọng tôi sai, nhưng tôi không nghĩ những gì bạn hỏi là có thể. Có thể có thể trong khi mã hóa lại video, nhưng là một "lát cắt đơn giản", tôi không biết cách nào.

Nhiều chương trình chỉnh sửa video sẽ có tính năng tự động phân tích hoặc một cái gì đó tương đương có thể phân tách video của bạn trên các khung màu đen nhưng điều này sẽ yêu cầu mã hóa lại video.

Chúc may mắn!


Điều đó là hoàn toàn có thể. Bạn có thể cắt video một cách dễ dàng mà không gặp vấn đề gì (ví dụ với muxer phân đoạn trong ffmpeg , xem thêm wiki ffmpeg về cắt video ). Bất kỳ chương trình chỉnh sửa video phong nha sẽ có thể làm điều đó. Vấn đề duy nhất là phát hiện thay đổi cảnh.
slhck

1
Thật ra bạn không thể. Các vấn đề lớn nhất có thể là khung hình chính. Tất cả các clip cần bắt đầu với một khung hình chính để có thể được hiển thị chính xác khi phát lại ngay cả trong các công cụ chỉnh sửa. Nếu các khung màu đen không bắt đầu với các khung hình chính thì sẽ không tương thích trong khi sử dụng các phép cắt không mất dữ liệu.
JasonXA
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.