Dans cette page , Albert Armea partage un code permettant de scinder les vidéos par chapitre à l'aide de ffmpeg
. Le code est simple, mais pas très beau.
ffmpeg -i "$ SOURCE. $ EXT" 2> & 1 | chapitre de grep | sed -E "s/* Chapitre # ([0-9] +. [0-9] +): début ([0-9] +. [0-9] +), fin ([0-9] + . [0-9] +)/- i\"$ SOURCE. $ EXT \" -vcodec copy -acodec copy -ss\2 -to\3\"$ SOURCE-\1. $ EXT \"/"| xargs -n 11 ffmpeg
Y a-t-il une manière élégante de faire ce travail?
(Edit: Ce conseil provient de https://github.com/phiresky via ce numéro: https://github.com/harryjackson/ffmpeg_split/issues/2 )
Vous pouvez obtenir des chapitres en utilisant:
ffprobe -i fname -print_format json -show_chapters -loglevel error
Si j’écrivais encore, j’utiliserais les options json de ffprobe
(Réponse originale suit)
Ceci est un script de travail en python. Je l'ai testé sur plusieurs vidéos et cela a bien fonctionné. Python n’est pas ma langue maternelle, mais j’ai remarqué que vous l’utilisiez. J’imagine que l’écrire en Python aurait plus de sens. Je l'ai ajouté à Github . Si vous souhaitez améliorer, veuillez soumettre des demandes d'extraction.
#!/usr/bin/env python
import os
import re
import subprocess as sp
from subprocess import *
from optparse import OptionParser
def parseChapters(filename):
chapters = []
command = [ "ffmpeg", '-i', filename]
output = ""
try:
# ffmpeg requires an output file and so it errors
# when it does not get one so we need to capture stderr,
# not stdout.
output = sp.check_output(command, stderr=sp.STDOUT, universal_newlines=True)
except CalledProcessError, e:
output = e.output
for line in iter(output.splitlines()):
m = re.match(r".*Chapter #(\d+:\d+): start (\d+\.\d+), end (\d+\.\d+).*", line)
num = 0
if m != None:
chapters.append({ "name": m.group(1), "start": m.group(2), "end": m.group(3)})
num += 1
return chapters
def getChapters():
parser = OptionParser(usage="usage: %prog [options] filename", version="%prog 1.0")
parser.add_option("-f", "--file",dest="infile", help="Input File", metavar="FILE")
(options, args) = parser.parse_args()
if not options.infile:
parser.error('Filename required')
chapters = parseChapters(options.infile)
fbase, fext = os.path.splitext(options.infile)
for chap in chapters:
print "start:" + chap['start']
chap['outfile'] = fbase + "-ch-"+ chap['name'] + fext
chap['origfile'] = options.infile
print chap['outfile']
return chapters
def convertChapters(chapters):
for chap in chapters:
print "start:" + chap['start']
print chap
command = [
"ffmpeg", '-i', chap['origfile'],
'-vcodec', 'copy',
'-acodec', 'copy',
'-ss', chap['start'],
'-to', chap['end'],
chap['outfile']]
output = ""
try:
# ffmpeg requires an output file and so it errors
# when it does not get one
output = sp.check_output(command, stderr=sp.STDOUT, universal_newlines=True)
except CalledProcessError, e:
output = e.output
raise RuntimeError("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output))
if __== '__main__':
chapters = getChapters()
convertChapters(chapters)
J'ai modifié le script de Harry pour utiliser le nom du chapitre comme nom de fichier. Il sort dans un nouveau répertoire avec le nom du fichier d'entrée (extension moins). Il préfixe également chaque nom de chapitre avec "1 -", "2 -", etc. au cas où il y aurait des chapitres du même nom.
#!/usr/bin/env python
import os
import re
import pprint
import sys
import subprocess as sp
from os.path import basename
from subprocess import *
from optparse import OptionParser
def parseChapters(filename):
chapters = []
command = [ "ffmpeg", '-i', filename]
output = ""
m = None
title = None
chapter_match = None
try:
# ffmpeg requires an output file and so it errors
# when it does not get one so we need to capture stderr,
# not stdout.
output = sp.check_output(command, stderr=sp.STDOUT, universal_newlines=True)
except CalledProcessError, e:
output = e.output
num = 1
for line in iter(output.splitlines()):
x = re.match(r".*title.*: (.*)", line)
print "x:"
pprint.pprint(x)
print "title:"
pprint.pprint(title)
if x == None:
m1 = re.match(r".*Chapter #(\d+:\d+): start (\d+\.\d+), end (\d+\.\d+).*", line)
title = None
else:
title = x.group(1)
if m1 != None:
chapter_match = m1
print "chapter_match:"
pprint.pprint(chapter_match)
if title != None and chapter_match != None:
m = chapter_match
pprint.pprint(title)
else:
m = None
if m != None:
chapters.append({ "name": `num` + " - " + title, "start": m.group(2), "end": m.group(3)})
num += 1
return chapters
def getChapters():
parser = OptionParser(usage="usage: %prog [options] filename", version="%prog 1.0")
parser.add_option("-f", "--file",dest="infile", help="Input File", metavar="FILE")
(options, args) = parser.parse_args()
if not options.infile:
parser.error('Filename required')
chapters = parseChapters(options.infile)
fbase, fext = os.path.splitext(options.infile)
path, file = os.path.split(options.infile)
newdir, fext = os.path.splitext( basename(options.infile) )
os.mkdir(path + "/" + newdir)
for chap in chapters:
chap['name'] = chap['name'].replace('/',':')
chap['name'] = chap['name'].replace("'","\'")
print "start:" + chap['start']
chap['outfile'] = path + "/" + newdir + "/" + re.sub("[^-a-zA-Z0-9_.():' ]+", '', chap['name']) + fext
chap['origfile'] = options.infile
print chap['outfile']
return chapters
def convertChapters(chapters):
for chap in chapters:
print "start:" + chap['start']
print chap
command = [
"ffmpeg", '-i', chap['origfile'],
'-vcodec', 'copy',
'-acodec', 'copy',
'-ss', chap['start'],
'-to', chap['end'],
chap['outfile']]
output = ""
try:
# ffmpeg requires an output file and so it errors
# when it does not get one
output = sp.check_output(command, stderr=sp.STDOUT, universal_newlines=True)
except CalledProcessError, e:
output = e.output
raise RuntimeError("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output))
if __== '__main__':
chapters = getChapters()
convertChapters(chapters)
Cela a pris un peu de temps à comprendre puisque je ne suis définitivement PAS un gars de Python. Il est également inélégant, car il faut franchir de nombreux obstacles dans la mesure où il traite les métadonnées ligne par ligne. (Par exemple, les données de titre et de chapitre se trouvent dans des boucles séparées dans la sortie de métadonnées)
Mais cela fonctionne et cela devrait vous faire gagner beaucoup de temps. C'est fait pour moi!
ffmpeg -i "$SOURCE.$EXT" 2>&1 \ # get metadata about file
| grep Chapter \ # search for Chapter in metadata and pass the results
| sed -E "s/ *Chapter #([0-9]+.[0-9]+): start ([0-9]+.[0-9]+), end ([0-9]+.[0-9]+)/-i \"$SOURCE.$EXT\" -vcodec copy -acodec copy -ss \2 -to \3 \"$SOURCE-\1.$EXT\"/" \ # filter the results, explicitly defining the timecode markers for each chapter
| xargs -n 11 ffmpeg # construct argument list with maximum of 11 arguments and execute ffmpeg
Votre commande parcourt les métadonnées des fichiers et lit les marqueurs de code temporel de chaque chapitre. Vous pouvez le faire manuellement pour chaque chapitre.
ffmpeg -i ORIGINALFILE.mp4 -acodec copy -vcodec copy -ss 0 -t 00:15:00 OUTFILE-1.mp4
ou vous pouvez écrire les marqueurs de chapitre et les parcourir avec ce script bash qui est un peu plus facile à lire.
#!/bin/bash
# Author: http://crunchbang.org/forums/viewtopic.php?id=38748#p414992
# m4bronto
# Chapter #0:0: start 0.000000, end 1290.013333
# first _ _ start _ end
while [ $# -gt 0 ]; do
ffmpeg -i "$1" 2> tmp.txt
while read -r first _ _ start _ end; do
if [[ $first = Chapter ]]; then
read # discard line with Metadata:
read _ _ chapter
ffmpeg -vsync 2 -i "$1" -ss "${start%?}" -to "$end" -vn -ar 44100 -ac 2 -ab 128 -f mp3 "$chapter.mp3" </dev/null
fi
done <tmp.txt
rm tmp.txt
shift
done
ou vous pouvez utiliser HandbrakeCLI, comme mentionné à l’origine dans cet article , cet exemple extrait les chapitres 3 à 3.mkv
HandBrakeCLI -c 3 -i originalfile.mkv -o 3.mkv
ou un autre outil est mentionné dans cet article
mkvmerge -o output.mkv --split chapters:all input.mkv
Une version du code Shell d'origine avec
ffprobe
au lieu de ffmpeg
,xargs
etDans ma ffprobe
version 4.1, les numéros de chapitre sont séparés par :
et doivent être remplacés par .
pour empêcher ffmpeg
de se plaindre à propos de Protocol not found
.
ffprobe "$INPUT" 2>&1 |
sed -En 's/.*Chapter #([0-9]+)[.:]([0-9]+): start ([0-9]+\.[0-9]+), end ([0-9]+\.[0-9]+).*/\1.\2 \3 \4/p' |
while read chapter start end
do
ffmpeg </dev/null \
-i "$INPUT" \
-vcodec copy -acodec copy \
-ss "$start" -to "$end" \
"${INPUT%.*}-$chapter.${INPUT##*.}"
done
L'entrée de ffmpeg
est redirigée pour l'empêcher d'interférer avec la boucle.
Je voulais quelques petites choses comme:
Voici mon script (j'ai utilisé l'allusion avec ffprobe json sortie de Harry)
#!/bin/bash
input="input.aax"
EXT2="m4a"
json=$(ffprobe -activation_bytes secret -i "$input" -loglevel error -print_format json -show_format -show_chapters)
title=$(echo $json | jq -r ".format.tags.title")
count=$(echo $json | jq ".chapters | length")
target=$(echo $json | jq -r ".format.tags | .date + \" \" + .artist + \" - \" + .title")
mkdir "$target"
ffmpeg -activation_bytes secret -i $input -vframes 1 -f image2 "$target/cover.jpg"
echo "[playlist]
NumberOfEntries=$count" > "$target/0_Playlist.pls"
for i in $(seq -w 1 $count);
do
j=$((10#$i))
n=$(($j-1))
start=$(echo $json | jq -r ".chapters[$n].start_time")
end=$(echo $json | jq -r ".chapters[$n].end_time")
name=$(echo $json | jq -r ".chapters[$n].tags.title")
ffmpeg -activation_bytes secret -i $input -vn -acodec -map_chapters -1 copy -ss $start -to $end -metadata title="$title $name" "$target/$i $name.$EXT2"
echo "File$j=$i $name.$EXT2" >> "$target/0_Playlist.pls"
done