2015-05-18 7 views
13

W dokumencie this page Albert Armea udostępnia kod służący do dzielenia wideo według rozdziału za pomocą ffmpeg. Kod jest prosty, ale niezbyt dobrze wyglądający.Czy istnieje elegancki sposób dzielenia plików według rozdziału za pomocą ffmpeg?

ffmpeg -i "$ SOURCE. $ EXT" 2> & 1 | grep Rozdział | sed -E "s/* Rozdział # ([0-9] +. [0-9] +): start ([0-9] +. [0-9] +), koniec ([0-9] + [0-9] +)/- i \ "$ SOURCE. $ EXT \" -vcodec copy -acodec copy -ss \ 2 -to \ 3 \ "$ SOURCE- \ 1. $ EXT \"/"| xargs -n 11 ffmpeg

Czy istnieje elegancki sposób na wykonanie tej pracy?

+0

musiałem zrobić drobna modyfikacja, aby to zadziałało, ponieważ moje rozdziały miały słowo "Chapter" w tytule: '| grep '^ \ s * Chapter' | ' – bmaupin

Odpowiedz

8

(Edit: Ta porada pochodzi https://github.com/phiresky za tym numerze: https://github.com/harryjackson/ffmpeg_split/issues/2)

można dostać rozdziałów za pomocą:

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

Gdybym pisał to jeszcze raz użyję json ffprobe za opcje

(Oryginalna odpowiedź następuje)

Jest to działający skrypt Pythona. Testuję to na kilku filmach i działa dobrze Python nie jest moim pierwszym językiem, ale zauważyłem, że go używasz, więc myślę, że pisanie go w Pythonie może mieć więcej sensu. Dodałem go do Github. Jeśli chcesz to poprawić, zwariuj.

#!/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 __name__ == '__main__': 
    chapters = getChapters() 
    convertChapters(chapters) 
+0

Oto kolejny podobny skrypt Pythona przeznaczony do parsowania książek audio m4b według rozdziałów. https://github.com/valekhz/m4b-converter – davidcondrey

+0

Opublikowałem zmodyfikowaną wersję poniżej, która używa nazwy rozdziału jako nazwy pliku. To nie jest eleganckie, ale działa :) – clifgriffin

+0

i drugi, napisany właśnie teraz dla konwersji rozdziałów AAX na MP3 https://github.com/OndrejSkalicka/aax-to-mp3-python –

2
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 

Twoje polecenie analizuje metadane plików i odczytuje znaczniki kodu czasowego dla każdego rozdziału. Można to zrobić ręcznie dla każdego rozdziału ..

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

lub można napisać znaczników rozdziałów i prowadzony przez nich z tego skryptu bash, który jest tylko trochę łatwiejsze do odczytania ..

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

można też użyć HandbrakeCLI, jak pierwotnie wymienione w this post przykład ten wydobywa rozdział 3 do 3.mkv

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

lub innym narzędziem jest mowa w this post

mkvmerge -o output.mkv --split chapters:all input.mkv 
2

Zmodyfikowałem skrypt Harry'ego, aby użyć nazwy rozdziału dla nazwy pliku. Wyprowadza do nowego katalogu z nazwą pliku wejściowego (minus rozszerzenie). Przedrostek każdego przedrostka nazywa się "1 -", "2 -", itd., Na wypadek gdyby były rozdziały o tej samej nazwie.

#!/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 __name__ == '__main__': 
    chapters = getChapters() 
    convertChapters(chapters) 

Zajęło to sporo pracy, ponieważ zdecydowanie nie jestem facetem z Python. Jest również nieelegancki, ponieważ było wiele kół, przez które można przeskakiwać, ponieważ przetwarza linię metadanych po linii. (Tj. Dane tytułu i rozdziału znajdują się w oddzielnych pętlach za pośrednictwem wyjścia metadanych).

Ale to działa i powinno oszczędzić ci dużo czasu. To zrobiło dla mnie!

+0

To działało świetnie, dzięki! –

+0

@ JP. Miło to słyszeć! – clifgriffin

+0

To działało dobrze, gdy uruchomiłem 'ffmpeg -i' niezależnie, aby określić format metadanych mojego pliku. Musiałem majstrować przy regex, ponieważ moje rozdziały nie były w formacie 'Rozdział #dd: dd'. Byłoby dobrze, aby spróbować uczynić twoje regex mocniejszym :-) – alexw

0

chciałem kilka dodatkowych rzeczy jak:

  • wydobywania pokrywę
  • używając nazwy rozdziału w pliku
  • prefiksacji licznik do nazwy pliku z wiodącymi zerami, więc kolejność alfabetyczna będzie działać poprawnie każde oprogramowanie
  • dokonywania playlistę
  • modyfikując metadane zawierają nazwę rozdziału
  • wyprowadzania wszystkich plików do nowego katalogu w oparciu o metadane (rok autor - tytuł)

Oto mój skrypt (użyłem wskazówkę z wyjściem ffprobe json od Harry'ego)

#!/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 
Powiązane problemy