Diviser automatiquement les gros fichiers vidéo .mov en fichiers plus petits avec des images noires (changements de scène)?

11

J'ai 65 fichiers vidéo .mov nommés tape_01.mov, tape_02.mov, ..., tape_65.mov. Chacun dure environ 2,5 heures et prend de nombreux gigaoctets. Ce sont des copies numériques de ce qui était des cassettes VHS (les "films à la maison" de ma famille).

Chaque fichier vidéo .mov de 2,5 heures contient de nombreux "chapitres" (dans le sens où parfois la scène se termine par un fondu sur un écran noir, puis une nouvelle scène apparaît en fondu).

Mon objectif principal est de découper les 65 gros fichiers en petits fichiers par chapitre. Et je veux que cela se fasse automatiquement via la détection des cadres noirs entre les "scènes".

Puis-je utiliser ffmpeg (ou quoi que ce soit) pour passer les 65 noms de fichiers originaux et le faire itérer automatiquement sur chaque vidéo et le couper, en nommant les morceaux résultants tape_01-ch_01.mov, tape_01-ch_02.mov, tape_01-ch_03.mov, etc? Et je veux qu'il le fasse sans ré-encodage (je veux que ce soit une simple tranche sans perte).

Comment puis-je faire ceci?

Voici les étapes que je souhaite:

  1. Parcourez un dossier de fichiers .mov (nommé tape_01.mov, tape_02.mov, ..., tape_65.mov) pour les exporter en tant que fichiers mp4 (nommés tape_01.mp4, tape_02.mp4, ..., tape_65.mp4) . Je veux que les paramètres de compression soient faciles à configurer pour cette étape. Les fichiers .mov d'origine doivent toujours exister après la création des fichiers .mp4.
  2. Itérer sur les fichiers mp4: pour chaque fichier mp4, générer un fichier texte qui spécifie l'heure de début et la durée des segments noirs entre les "scènes". Chaque mp4 peut avoir 0 ou plusieurs de ces sauts de scène noirs. L'heure de début et la durée doivent être précises à une fraction de seconde. Le format HH: MM: SS n'est pas assez précis.
  3. Je pourrai ensuite revérifier manuellement ces fichiers texte pour voir s'ils identifient correctement les changements de scène (segments noirs). Je peux ajuster les horaires si je le souhaite.
  4. Un autre script lira chaque fichier mp4 et son fichier txt et divisera (de manière super rapide et sans perte) le mp4 en autant de morceaux que nécessaire en fonction des lignes du fichier texte (les horaires indiqués ici). Les séparations doivent se produire au milieu de chaque segment noir. Voici un exemple de fichier mp4 (avec audio supprimé à des fins de confidentialité) qui a des changements de scène en fondu. Les fichiers mp4 d'origine ne doivent pas être modifiés lors de la création des fichiers mp4 plus petits. Les fichiers mp4 plus petits doivent avoir le schéma de dénomination de tape_01_001.mp4, tape_01_002.mp4, tape_01_003.mp4, etc.
  5. Prime! Ce serait génial si un autre script pouvait ensuite parcourir les petits fichiers mp4 et générer une image .png pour chacun qui est une mosaïque de captures d'écran comme celle que cette personne essaie de: Des miniatures significatives pour une vidéo utilisant FFmpeg

J'aimerais que ces scripts soient des scripts shell que je puisse exécuter sur Mac, Ubuntu et Windows Git Bash.

Ryan
la source

Réponses:

11

Voici deux scripts PowerShell pour diviser de longues vidéos en petits chapitres par scènes noires.

Enregistrez-les sous Detect_black.ps1 et Cut_black.ps1. Téléchargez ffmpeg pour Windows et indiquez au script le chemin d'accès à votre ffmpeg.exe et à votre dossier vidéo dans la section des options.

entrez la description de l'image ici

Les deux scripts ne toucheront pas les fichiers vidéo existants, ils restent intacts.
Cependant, vous obtiendrez quelques nouveaux fichiers au même endroit où vos vidéos d'entrée sont

  • Un fichier journal par vidéo avec la sortie de la console pour les deux commandes ffmpeg utilisées
  • Un fichier CSV par vidéo avec tous les horodatages des scènes noires pour un réglage fin manuel
  • Quelques nouvelles vidéos en fonction du nombre de scènes noires détectées précédemment

entrez la description de l'image ici


Premier script à exécuter: 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
}

Comment ça marche

Le premier script parcourt tous les fichiers vidéo qui correspondent à une extension spécifiée et ne correspondent pas au modèle *_???.*, car de nouveaux chapitres vidéo ont été nommés <filename>_###.<ext>et nous voulons les exclure.

Il recherche toutes les scènes noires et écrit l'horodatage de début et la durée de la scène noire dans un nouveau fichier CSV nommé <video_name>_cutpoints.txt

Il calcule également des points de coupe comme indiqué: cutpoint = black_start + black_duration / 2. Plus tard, la vidéo est segmentée à ces horodatages.

Le fichier cutpoints.txt de votre exemple de vidéo afficherait:

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

Après une analyse, vous pouvez manipuler les points de coupe manuellement si vous le souhaitez. Si vous réexécutez le script, tout l'ancien contenu est écrasé. Soyez prudent lorsque vous modifiez manuellement et enregistrez votre travail ailleurs.

Pour l'exemple de vidéo, la commande ffmpeg pour détecter les scènes noires est

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

Il y a 3 nombres importants qui sont modifiables dans la section des options du script

  • d=4 signifie que seules les scènes noires de plus de 4 secondes sont détectées
  • pic_th=0.98 est le seuil pour considérer une image comme "noire" (en pourcentage)
  • pix=0.15définit le seuil pour considérer un pixel comme "noir" (en luminance). Étant donné que vous avez d'anciennes vidéos VHS, vous n'avez pas de scènes complètement noires dans vos vidéos. La valeur par défaut 10 ne fonctionnera pas et j'ai dû augmenter légèrement le seuil

En cas de problème, vérifiez le fichier journal correspondant appelé <video_name>__ffmpeg.log. Si les lignes suivantes sont manquantes, augmentez les nombres mentionnés ci-dessus jusqu'à ce que vous détectiez toutes les scènes noires:

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



Deuxième script à exécuter: 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        
}

Comment ça marche

Le deuxième script parcourt tous les fichiers vidéo de la même manière que le premier script. Il lit uniquement les horodatages coupés du correspondant cutpoints.txtd'une vidéo.

Ensuite, il rassemble un nom de fichier approprié pour les fichiers de chapitre et indique à ffmpeg de segmenter la vidéo. Actuellement, les vidéos sont découpées sans ré-encodage (ultra-rapide et sans perte). Pour cette raison, il peut y avoir une imprécision de 1 à 2 avec les horodatages des points de coupure car ffmpeg ne peut couper que sur les images clés. Comme nous copions et ne réencodons pas, nous ne pouvons pas insérer les images clés par nous-mêmes.

La commande pour l'exemple de vidéo serait

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

En cas de problème, consultez le ffmpeg.log correspondant



Références



Faire

  • Demandez à OP si le format CSV est meilleur qu'un fichier texte comme fichier de point de coupure, afin que vous puissiez les modifier avec Excel un peu plus facilement
    »
  • Implémentez un moyen de formater les horodatages comme [hh]: [mm]: [ss], [millisecondes] plutôt que seulement quelques secondes
    »
  • Implémentez une commande ffmpeg pour créer des fichiers png mosaik pour chaque chapitre
    »Implémenté
  • Élaborez si cela -c copysuffit pour le scénario OP ou si nous devons ré-encoder complètement.
    On dirait que Ryan est déjà dessus .
nixda
la source
Quelle réponse approfondie! Je vous en suis reconnaissant! Malheureusement, je suis sur un mac en ce moment et devra d'abord trouver comment traduire cela en Bash.
Ryan
Je n'ai pas du tout réussi à faire fonctionner cela dans PowerShell. Les fichiers journaux restent vides.
Ryan
Je vais voir ce que je peux télécharger pour être utile. Restez à l'écoute. Merci de votre intérêt!
Ryan
J'ai ajouté un échantillon mp4 à la question. Merci de votre aide!
Ryan
Pour des raisons de confidentialité, je ne téléchargerai pas le véritable MOV source. Je voudrais le couper et supprimer l'audio (comme je l'ai fait pour mp4). Ce ne serait donc pas une véritable source originale de toute façon. Même ainsi, il serait beaucoup trop volumineux à télécharger. Et l'étape de division de la scène devrait probablement se produire sur les fichiers mp4 au lieu des fichiers MOV à des fins d'espace disque de toute façon. Merci!
Ryan
3

Voici le bonus: ce 3e script PowerShell génère une image miniature de mosaïque

Vous recevrez une image par chapitre vidéo qui ressemble à l'exemple ci-dessous.

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

L'idée principale est d'obtenir un flux continu d'images sur la vidéo complète. Nous le faisons avec l' option de sélection de ffmpeg .

Tout d'abord, nous récupérons le nombre total de trames avec une méthode ingénieuse (par exemple 2000) et le divisons par le biais de notre nombre de vignettes par défaut (par exemple 5 x 4 = 20). Nous voulons donc générer une image toutes les 100 images depuis2000 / 20 = 100

La commande ffmpeg résultante pour générer la miniature pourrait ressembler à

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

Dans le code ci-dessus, vous voyez 9 -vfcombinaisons différentes comprenant

  • select=not(mod(n\,XXX)) où XXX est une fréquence d'images calculée
  • thumbnail qui sélectionne automatiquement les cadres les plus représentatifs
  • select='gt(scene\,XXX)+ -vsync vfroù XXX est un seuil avec lequel vous devez jouer
  • mpdecimate- Supprimez les cadres presque dupliqués. Bon contre les scènes noires
  • yadif- Désentrelacer l'image d'entrée. Je ne sais pas pourquoi, mais ça marche

La version 3 est le meilleur choix à mon avis. Tous les autres sont commentés, mais vous pouvez toujours les essayer. J'ai pu éliminer la plupart des vignettes floues à l' aide mpdecimate, yadifet select=not(mod(n\,XXX)). Ouais!

Pour votre exemple de vidéo, je reçois ces aperçus

entrez la description de l'image ici
Cliquez pour agrandir

entrez la description de l'image ici
Cliquez pour agrandir

J'ai téléchargé toutes les vignettes créées par ces versions. Jetez-y un œil pour une comparaison complète.

nixda
la source
Très cool! Je vais regarder ça. J'ai aussi perdu des heures aujourd'hui à essayer de me rendre select='gt(scene\,0.4)'au travail. Et je ne pouvais pas non plus me mettre fps="fps=1/600"au travail. Au fait, avez-vous une idée de superuser.com/questions/725734 ? Merci beaucoup pour toute votre aide. J'ai hâte d'aider ma famille à découvrir des tonnes de vieux souvenirs.
Ryan
Avez-vous essayé ces 5 autres variantes d'encodage que j'ai répertoriées en bas ? J'ai ajouté un exemple de travail pour l'option "scène". Oubliez le filtre FPS. J'ai réussi à supprimer la plupart des pouces flous :)
nixda
0

Appréciez les deux scripts, un excellent moyen de diviser des vidéos.

Pourtant, j'ai eu quelques problèmes avec des vidéos ne montrant pas l'heure correcte par la suite et je n'ai pas pu sauter à une heure précise. Une solution consistait à démultiplexer puis à multiplexer les flux à l'aide de Mp4Box.

Un autre moyen plus simple pour moi était d'utiliser mkvmerge pour le fractionnement. Par conséquent, les deux scripts ont dû être modifiés. Pour detect_black.ps1, seul le "* .mkv" devait être ajouté à l'option de filtrage, mais ce n'est pas nécessairement si vous ne commencez qu'avec des fichiers 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
}

La fonction est la même, mais aucun problème avec les temps vidéo jusqu'à présent. Merci pour l'inspiration.

Andy Gebauer
la source
-2

J'espère que je me trompe, mais je ne pense pas que ce que vous demandez soit possible. Cela pourrait être possible lors du réencodage de la vidéo, mais en tant que "simple tranche sans perte", je ne connais aucun moyen.

De nombreux programmes de montage vidéo auront une fonction d'analyse automatique ou quelque chose d'équivalent qui pourrait diviser vos vidéos sur les cadres noirs, mais cela nécessitera un ré-encodage de la vidéo.

Bonne chance!

tbenz9
la source
C'est absolument possible. Vous pouvez découper la vidéo sans perte sans problème (par exemple avec le multiplexeur de segments dans ffmpeg , voir aussi le wiki ffmpeg sur la découpe de vidéo ). Tout programme de montage vidéo décent devrait être en mesure de le faire. Le seul problème est de détecter les changements de scène.
slhck
1
En fait, vous ne pouvez pas. Les plus gros problèmes pourraient être les images clés. Tous les clips doivent commencer par une image clé pour pouvoir être rendus correctement lors de la lecture, même dans les outils d'édition. Si les images noires ne commencent pas par des images clés, il y aura incompatibilité lors de l'utilisation de coupes sans perte.
JasonXA