web-dev-qa-db-de.com

Gibt es eine elegante Möglichkeit, eine Datei mit ffmpeg nach Kapiteln zu teilen?

In dieser Seite gibt Albert Armea einen Code zum Teilen von Videos nach Kapitel mit ffmpeg frei. Der Code ist unkompliziert, sieht aber nicht gut aus. 

ffmpeg -i "$ SOURCE. $ EXT" 2> & 1 | grep Kapitel | sed -E "s/* Kapitel # ([0-9] +. [0-9] +): Start ([0-9] +. [0-9] +), Ende ([0-9] + [0-9] +)/- i\"$ SOURCE. $ EXT \" -vcodec kopieren -acodec-Kopie -ss\2 -to\3\"$ SOURCE-\1. $ EXT \"/"| xargs -n 11 ffmpeg

Gibt es eine elegante Möglichkeit, diesen Job zu erledigen?

20
Kattern

(Bearbeiten: Dieser Tipp stammt von https://github.com/phiresky über diese Ausgabe: https://github.com/harryjackson/ffmpeg_split/issues/2 )

Sie können Kapitel erhalten mit: 

ffprobe -i fname -print_format json -show_chapters -loglevel error

Wenn ich das noch einmal schreibe, würde ich die Json-Optionen von ffprobe verwenden

(Ursprüngliche Antwort folgt)

Dies ist ein funktionierendes Python-Skript. Ich habe es in mehreren Videos getestet und es hat gut funktioniert. Python ist nicht meine Muttersprache, aber ich habe bemerkt, dass Sie es verwenden. Ich denke, das Schreiben in Python macht vielleicht mehr Sinn. Ich habe es zu Github hinzugefügt. Wenn Sie sich verbessern möchten, reichen Sie bitte Pull-Anfragen ein.

#!/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)
18
Harry

Ich habe Harrys Skript geändert, um den Kapitelnamen für den Dateinamen zu verwenden. Es wird in ein neues Verzeichnis mit dem Namen der Eingabedatei (ohne Erweiterung) ausgegeben. Außerdem werden jedem Kapitelnamen "1 -", "2 -" usw. vorangestellt, falls Kapitel mit demselben Namen vorhanden sind.

#!/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)

Das hat einiges herausgefunden, da ich definitiv KEIN Python-Typ bin. Es ist auch unelegant, da es viele Ränder gab, durch die man springen konnte, da die Metadaten Zeile für Zeile verarbeitet werden. (Dh die Titel- und Kapiteldaten befinden sich in separaten Schleifen durch die Metadaten-Ausgabe.)

Aber es funktioniert und es sollte Ihnen viel Zeit sparen. Es hat für mich getan!

3
clifgriffin
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

Ihr Befehl analysiert die Metadaten der Dateien und liest die Timecode-Marker für jedes Kapitel aus. Sie können dies manuell für jedes Kapitel tun.

ffmpeg -i ORIGINALFILE.mp4 -acodec copy -vcodec copy -ss 0 -t 00:15:00 OUTFILE-1.mp4

oder Sie können die Kapitelmarkierungen ausschreiben und sie mit diesem Bash-Skript durchlaufen, das nur ein wenig leichter zu lesen ist.

#!/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

oder Sie können HandbrakeCLI verwenden, wie ursprünglich in this post erwähnt. Dieses Beispiel extrahiert Kapitel 3 bis 3.mkv

HandBrakeCLI -c 3 -i originalfile.mkv -o 3.mkv

oder ein anderes Werkzeug wird in diesem Beitrag erwähnt

mkvmerge -o output.mkv --split chapters:all input.mkv
3
davidcondrey

in Python

#!/usr/bin/env python3

import sys
import os
import subprocess
import shlex

def split_video(pathToInputVideo):
  command="ffprobe -v quiet -print_format csv -show_chapters "
  args=shlex.split(command)
  args.append(pathToInputVideo)
  output = subprocess.check_output(args, stderr=subprocess.STDOUT, universal_newlines=True)

  cpt=0
  for line in iter(output.splitlines()):
    dec=line.split(",")
    st_time=dec[4]
    end_time=dec[6]
    name=dec[7]

    command="ffmpeg -i _VIDEO_ -ss _START_ -to _STOP_ -vcodec copy -acodec copy"
    args=shlex.split(command)
    args[args.index("_VIDEO_")]=pathToInputVideo
    args[args.index("_START_")]=st_time
    args[args.index("_STOP_")]=end_time

    filename=os.path.basename(pathToInputVideo)
    words=filename.split(".");
    l=len(words)
    ext=words[l-1]

    cpt+=1
    filename=" ".join(words[0:l-1])+" - "+str(cpt)+" - "+name+"."+ext

    args.append(filename)
    subprocess.call(args)

for video in sys.argv[1:]:
  split_video(video)
1
Pepin55i5

Eine Version des ursprünglichen Shell-Codes mit

  • verbesserte Effizienz durch Verwendung von ffprobe anstelle von ffmpeg,
  • vereinfachter regulärer Ausdruck,
  • verbesserte Zuverlässigkeit durch Vermeidung von xargs und
  • verbesserte Lesbarkeit durch Verwendung mehrerer Zeilen.

In meiner ffprobe-Version 4.1 sind die Kapitelnummern durch : getrennt, die durch . ersetzt werden müssen, um zu verhindern, dass ffmpeg sich über Protocol not found beschwert.

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

Die Eingabe von ffmpeg wird umgeleitet, um zu verhindern, dass die Schleife beeinträchtigt wird.

0
joki

Ich wollte ein paar zusätzliche Dinge wie:

  • die Abdeckung herausziehen
  • verwenden Sie den Kapitelnamen als Dateinamen
  • wenn Sie dem Dateinamen einen Zähler mit führenden Nullen voranstellen, funktioniert die alphabetische Reihenfolge in jeder Software korrekt
  • eine Playlist erstellen
  • Ändern der Metadaten, um den Kapitelnamen aufzunehmen
  • ausgabe aller Dateien in ein neues Verzeichnis basierend auf Metadaten (Jahr Autor - Titel)

Hier ist mein Skript (ich habe den Hinweis mit der ffprobe json-Ausgabe von Harry verwendet)

#!/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
0
kaefert