Comment utiliser ffmpeg pour diviser une vidéo MPEG en tranches de 10 minutes?

63

La communauté open source ou les développeurs actifs ont souvent besoin de publier de grands segments vidéo en ligne. (Vidéos de rencontres, camps, discussions techniques, etc.) Etant donné que je suis un développeur et non un vidéographe, je n'ai aucune envie de débourser la note supplémentaire sur un compte Vimeo premium. Comment puis-je prendre une vidéo de conversation technique MPEG de 12,5 Go (1:20:00) et la découper en segments 00:10:00 pour faciliter le téléchargement sur des sites de partage de vidéos?

Gabriel
la source
Un merci spécial à @StevenD pour accueillir les nouvelles balises.
Gabriel

Réponses:

69
$ ffmpeg -i source-file.foo -ss 0 -t 600 first-10-min.m4v
$ ffmpeg -i source-file.foo -ss 600 -t 600 second-10-min.m4v
$ ffmpeg -i source-file.foo -ss 1200 -t 600 third-10-min.m4v
...

Envelopper cela dans un script pour le faire en boucle ne serait pas difficile.

Attention, si vous essayez de calculer le nombre d'itérations en fonction de la durée de sortie d'un ffprobeappel, il est estimé à partir du débit binaire moyen au début du clip et de la taille du fichier du clip, à moins que vous ne donniez l' -count_framesargument, ce qui ralentit considérablement son fonctionnement. .

Une autre chose à prendre en compte est que la position de l' -ssoption sur la ligne de commande est importante . Où je l'ai maintenant est lent mais précis. La première version de cette réponse donnait l' alternative rapide mais inexacte . L'article lié décrit également une alternative généralement rapide mais toujours précise, que vous payez avec un peu de complexité.

Tout cela mis à part, je ne pense pas que vous souhaitiez vraiment couper exactement 10 minutes par clip. Cela mettra les coupes au beau milieu des phrases, même des mots. Je pense que vous devriez utiliser un éditeur ou un lecteur vidéo pour trouver des points de coupure naturels à un peu moins de 10 minutes d'intervalle.

En supposant que votre fichier soit dans un format accepté directement par YouTube, vous n'avez pas à le recoder pour obtenir des segments. Il suffit de transmettre les décalages naturels du point de coupure à ffmpeg, en lui demandant de transmettre le signal A / V codé sans toucher à l’aide du codec "copy":

$ ffmpeg -i source.m4v -ss 0 -t 593.3 -c copy part1.m4v
$ ffmpeg -i source.m4v -ss 593.3 -t 551.64 -c copy part2.m4v
$ ffmpeg -i source.m4v -ss 1144.94 -t 581.25 -c copy part3.m4v
...

L' -c copyargument lui dit de copier tous les flux d'entrée (audio, vidéo et éventuellement d'autres, tels que les sous-titres) dans la sortie telle quelle. Pour les programmes audiovisuels simples, cela équivaut aux indicateurs plus détaillés -c:v copy -c:a copyou aux indicateurs de style ancien -vcodec copy -acodec copy. Vous utiliseriez le style le plus détaillé lorsque vous souhaitez copier un seul des flux, mais recoder l'autre. Par exemple, il y a de nombreuses années, il était courant avec les fichiers QuickTime de compresser la vidéo avec la vidéo H.264 tout en laissant l'audio au format PCM non compressé ; Si vous rencontriez un tel fichier aujourd'hui, vous pourriez le moderniser -c:v copy -c:a aacpour ne traiter que le flux audio, en laissant la vidéo intacte.

Le point de départ pour chaque commande ci-dessus après la première est le point de départ de la commande précédente plus la durée de la commande précédente.

Warren Young
la source
1
"couper à exactement 10 minutes pour chaque clip" est un bon point.
Chris
peut-être en utilisant le paramètre -show_packets, vous pourrez le rendre plus précis.
rogerdpack
comment mettre dessus en boucle?
kRazzy R
J'utilise cette cmd, y a-t-il divisé en droit mais maintenant la vidéo et l'audio ne sont pas sur SYNC aucune aide
Sunil Chaudhary
@SunilChaudhary: les formats de fichier A / V sont complexes et tous les logiciels ne les implémentent pas de la même manière, de sorte que les informations ffmpegnécessaires pour maintenir la synchronisation entre les flux audio et vidéo risquent de ne pas être présentes dans le fichier source. Si cela vous arrive, il existe des méthodes plus complexes de fractionnement qui évitent le problème.
Warren Young
53

Voici la solution en une ligne :

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment output%03d.mp4

Veuillez noter que cela ne vous donne pas des scissions précises, mais devrait répondre à vos besoins. Au lieu de cela, il coupera à la première image après l'heure spécifiée après segment_time, dans le code ci-dessus, ce serait après la marque des 20 minutes.

Si vous constatez que seul le premier morceau est jouable, essayez d’ajouter -reset_timestamps 1comme indiqué dans les commentaires.

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment -reset_timestamps 1 output%03d.mp4
Jon
la source
2
En fait, cela vous donne des divisions très précises si vous accordez de la valeur à la qualité vidéo. Plutôt que de se scinder en fonction d'une heure particulière, il se scinde à l'image clé la plus proche après l'heure demandée, de sorte que chaque nouveau segment commence toujours par une image clé.
Malvineous
3
quelles sont les unités? 8s? 8min? 8h?
user1133275
1
@ user1133275 sa deuxième
Jon
2
Sur Mac, j’ai constaté que cela entraînait la production de N morceaux de sortie vidéo, mais que seul le premier d’entre eux était un MP4 visible et lisible. Les autres morceaux N-1 étaient des vidéos vierges (toutes noires) sans audio. Pour que cela fonctionne, je devais ajouter le drapeau reset_timestamps comme suit: ffmpeg -i input.mp4 -c copie -map 0 -segment_time 8 -f segment -reset_timestamps 1 sortie% 03d.mp4.
Jarmod
3
a constaté que l'ajout -reset_timestamps 1corrige le problème pour moi
Jlarsch
6

A fait face au même problème plus tôt et mis en place un script Python simple pour faire exactement cela (en utilisant FFMpeg). Disponible ici: https://github.com/c0decracker/video-splitter , et collé ci-dessous:

#!/usr/bin/env python
import subprocess
import re
import math
from optparse import OptionParser
length_regexp = 'Duration: (\d{2}):(\d{2}):(\d{2})\.\d+,'
re_length = re.compile(length_regexp)
def main():
    (filename, split_length) = parse_options()
    if split_length <= 0:
        print "Split length can't be 0"
        raise SystemExit
    output = subprocess.Popen("ffmpeg -i '"+filename+"' 2>&1 | grep 'Duration'",
                              shell = True,
                              stdout = subprocess.PIPE
    ).stdout.read()
    print output
    matches = re_length.search(output)
    if matches:
        video_length = int(matches.group(1)) * 3600 + \
                       int(matches.group(2)) * 60 + \
                       int(matches.group(3))
        print "Video length in seconds: "+str(video_length)
    else:
        print "Can't determine video length."
        raise SystemExit
    split_count = int(math.ceil(video_length/float(split_length)))
    if(split_count == 1):
        print "Video length is less then the target split length."
        raise SystemExit
    split_cmd = "ffmpeg -i '"+filename+"' -vcodec copy "
    for n in range(0, split_count):
        split_str = ""
        if n == 0:
            split_start = 0
        else:
            split_start = split_length * n
            split_str += " -ss "+str(split_start)+" -t "+str(split_length) + \
                         " '"+filename[:-4] + "-" + str(n) + "." + filename[-3:] + \
                         "'"
    print "About to run: "+split_cmd+split_str
    output = subprocess.Popen(split_cmd+split_str, shell = True, stdout =
                              subprocess.PIPE).stdout.read()
def parse_options():
    parser = OptionParser()
    parser.add_option("-f", "--file",
                      dest = "filename",
                      help = "file to split, for example sample.avi",
                      type = "string",
                      action = "store"
    )
    parser.add_option("-s", "--split-size",
                      dest = "split_size",
                      help = "split or chunk size in seconds, for example 10",
                      type = "int",
                      action = "store"
    )
    (options, args) = parser.parse_args()
    if options.filename and options.split_size:
        return (options.filename, options.split_size)
    else:
        parser.print_help()
        raise SystemExit
if __name__ == '__main__':
    try:
        main()
    except Exception, e:
        print "Exception occured running main():"
        print str(e)
c0decracker
la source
1
La prochaine fois faites ceci s'il vous plaît dans un commentaire. Les réponses avec lien uniquement ne sont pas vraiment appréciées ici, et les mêmes si vous publiez votre site. S'il s'agit d'un projet opensource avec du code source, il s'agit peut-être d'une exception, mais je risque maintenant de perdre mes privilèges de révision en ne votant pas pour la suppression de votre réponse. Et oui, vous ne pouvez pas poster de commentaires, mais après avoir recueilli 5 votes positifs (ce qui semble très rapide dans votre cas), vous le ferez.
user259412
2
Bonjour et bienvenue sur le site. S'il vous plaît ne postez pas de lien seulement des réponses. Bien que votre script lui-même soit probablement une excellente réponse, un lien vers ce dernier n’est pas une réponse. C'est un panneau indiquant une réponse. Plus à ce sujet ici . Puisque vous avez gentiment donné le lien, je suis allé de l'avant et j'ai inclus le script dans le corps de votre réponse. Si vous vous y opposez, veuillez supprimer la réponse.
terdon
4

Si vous voulez créer vraiment les mêmes morceaux, vous devez forcer ffmpeg à créer un cadre i sur le premier cadre de chaque morceau afin que vous puissiez utiliser cette commande pour créer un morceau de 0,5 seconde.

ffmpeg -hide_banner  -err_detect ignore_err -i input.mp4 -r 24 -codec:v libx264  -vsync 1  -codec:a aac  -ac 2  -ar 48k  -f segment   -preset fast  -segment_format mpegts  -segment_time 0.5 -force_key_frames  "expr: gte(t, n_forced * 0.5)" out%d.mkv
alireza akbaribayat
la source
Cela devrait être la réponse acceptée. Merci d'avoir partagé mon pote!
Stephan Ahlf le
4

Un autre moyen plus lisible serait

ffmpeg -i input.mp4 -ss 00:00:00 -to 00:10:00 -c copy output1.mp4
ffmpeg -i input.mp4 -ss 00:10:00 -to 00:20:00 -c copy output2.mp4

/**
* -i  input file
* -ss start time in seconds or in hh:mm:ss
* -to end time in seconds or in hh:mm:ss
* -c codec to use
*/

Voici la source et la liste des commandes FFmpeg couramment utilisées.

Niket Pathak
la source
3

Notez que la ponctuation exacte du format alternatif est -ss mm:ss.xxx. J'ai lutté pendant des heures pour essayer d'utiliser l'intuitif, mais mm:ss:xxinutilement.

$ man ffmpeg | grep -C1 position

-ss position
Recherche la position temporelle donnée en secondes. La syntaxe "hh: mm: ss [.xxx]" est également prise en charge.

Références ici et ici .

Mark Hudson
la source
0
#!/bin/bash

if [ "X$1" == "X" ]; then
    echo "No file name for split, exiting ..."
    exit 1
fi

if [ ! -f "$1" ]; then
    echo "The file '$1' doesn't exist. exiting ..."
    exit 1
fi

duration=$(ffmpeg -i "$1" 2>&1 | grep Duration | sed 's/^.*Duration: \(.*\)\..., start.*$/\1/' | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }')      #'
split_time=${split_time:-55}
time=0
part=1

filename=${file%%.*}
postfix=${file##*.}

while [ ${time} -le ${duration} ]; do

echo    ffmpeg -i "$1" -vcodec copy -ss ${time} -t ${split_time} "${filename}-${part}.${postfix}"
    (( part++ ))
    (( time = time + split_time ))

done
Alex_Shilli
la source
0

Utilisez simplement ce qui est construit dans ffmpeg pour faire exactement cela.

ffmpeg -i invid.mp4 -threads 3 -vcodec copy -f segment -segment_time 2 cam_out_h264%04d.mp4

Cela divisera le fichier en mandrins de 2 secondes environ, fractionné aux images clés appropriées, et sera exporté dans les fichiers cam_out_h2640001.mp4, cam_out_h2640002.mp4, etc.

John Allard
la source
Je ne suis pas sûr de comprendre pourquoi cette réponse a été rejetée. Cela semble marcher correctement.
Costin