nikolausdulgeridis
  Video splitten mit ffmpeg
 

Linux: Videobearbeitung mit ffmpeg - Videos splitten / zusammenfügen

Thema: Bash Skript zum Zusammenfügen und Schneiden von Videos unter Linux mit ffmpeg

Videocapture Geräte zeichnen Videoschnipsel in der Größe von 2 GB auf. Diese müssen vor der Weiterverarbeitung zusammengefügt werden. Viele einfache Aufgaben lassen sich über die Konsole effizienter lösen als erst eine Gui zu laden. Ein wichtiger Grund für mich war jedoch dass die Videobearbeitung bei mir unter Linux läuft, VLC und Dateiexplorer hingegen unter Windows.

Das unten wiedergegebene Skript fasst die wichtigsten Routineaufgaben zusammen. So ermöglicht der einfache Befehl splitvideo.sh c alle Videos im Verzeichnis zusammenzufügen.
 
Features
  • Video zusammenfügen
  • Video splitten
  • Werbepausen herausschneiden
  • Anfang oder Ende abschneiden
  • transcode Videos (zu h264 mp4 Container)
  • Videoschnipsel  video, video_1, ... zusammenfügen (speziell für Digitnow Capture device)
Andere Tools:
Wer Tools für die Videobearbeitung sucht, kann sich Handbrake und KDenlive ansehen.

------------------------------------------

Vorgehen:
Alle Dateien muessen im selben Verzeichnis stehen. Die Videos werden in alphabetischer Reihenfolge zusammengefügt: video1, video2, usw.

Verwendung:
Die Arbeitsumgebung benötigt einen komfortablen Dateiexplorer und einen Videoplayer zum Ablesen der Zeitmarken (z. Bsp. VLC und Totalcommander) . Die Verwendung im Netzwerk ist nicht zwingend, ermöglicht aber die Arbeit auf mehrere Computer zu verteilen. 
Der Aufruf des Skripts erfolgt aus dem Verzeichnis in dem sich die Videodateien befinden.

Beispiel 1:
# cd /srv/meineVideos 
# splitvideo.sh c
Der letzte Befehl fügt alle Videos im Verzeichnis /srv/meineVideos zu einem Video zusammen.
In der Datei mylist.tmp wird die Reihenfolge protokolliert.
Ergebniskontrolle und Abräumen erfolgt im Anschluss mit VLC und dem Dateiexplorer.

Beispiel 2:

# cd /srv/meineVideos 
# splitvideo.sh s 00:30:00 
Splittet das Video im Verzeichnis an der Zeitmarke 30 Minuten, nach Beendigung befinden sich in dem Verzeichnis genau 3 Dateien: originaldatei, anfang_video, ende_video, der Zeistempel der Originaldatei bleibt bei allen Operationen erhalten. 
 
Installation:
ffmpeg muss installiert sein: sudo apt update && sudo apt install ffmpeg.
Das u.a. Skript im Verzeichnis /usr/local/bin ablegen und mit chmod +x aufuehrbar machen.
# sudo nano  /usr/local/bin/splitvideo.sh
# sudo chmod +x /usr/local/bin/splitvideo.sh 

Fehlerausgabe:
Bei Fehlern gibt zuweilen das Konsolenfenster wichtige Hinweise. So kann z. Bsp. die Datei video_1_1.mp4 nicht verarbeitet werden, weil _1 den Beginn einer Videokette signalisiert und daher der Name nicht eindeutig ist. Bei Auftreten der Datei video_10 verweigert das Tool ebenfalls den Dienst, weil die Sortierung nicht stimmt. Bitte in diesem Fall die Dateien entsprechend der Anweisung manuell umbenennen.

Warnung
Falls die Verabeitung stoppt mit dem Hinweis 'file exists, overwrite?' grundsätzlich nein drücken und die Situation analysieren. Sicherheit geht vor! In den meisten Fällen ist die Datei noch im Videoplayer geöffnet.
Alle Operationen behalten eine Kopie der Ursprungsdateien, es lässt sich also in jedem Fall die Ausgangssituation wiederherstellen. Die Dateien sollten erst gelöscht werden, sobald man sich von der erfolgreichen Bearbeitung überzeugt hat. Die Grösse der Dateien ist dabei ein wichtiger Hinweis, ausserdem sollte man nach dem Zusammenfügen auf korrekte Reihenfolge und Vollständigkeit prüfen, das kann in der Datei mylist.old nachgesehen werden.
Es wird empfohlen auf einem Sicherungslaufwerk eine Kopie der Originaldateien aufzubewahren.

Zusammenfügen von Digitnow Videocapture Dateien (Option f)
Es kann in bestimmten Situationen sinnvoll sein, die genaue Arbeitsweise zu verstehen: Das Tool sucht zuerst nach einer Datei *_1.mp4, das ist der Indikator für die Existenz einer Videokette, ein Beispiel wäre 'video_77_1.mp4'. Im nächsten Schritt werden alle Dateien 'video_77*' in das temporäre Verzeichnis concat1 verschoben und zusammengefügt (alphabetische Reihenfolge). Die Ergebnisdatei erhält den Namen und den Zeitstempel der ersten Datei (mit führendem underliner), in diesem Beispiel also _video_77.mp4. 

In einigen Fällen lassen sich Videos nicht zusammenfügen, dann kann man versuchen, mit der Option r die Container vorher neu zu schreiben. Das sollte man allerdings nur machen wenn es erforderlich ist, da dadurch die Tonspuren an den Rändern 'gesäubert' werden, wodurch hinterher zwei 1/10 Sekunden fehlen. Eine zweite Möglichkeit besteht darin, die Dateien zu transcodieren (Parameter t), 

Hinweise:
- Funktionen: Zusammenfügen, Splitten, Entfernen, Anfang oder Ende abschneiden, Refresh
- Hinweise zur Verwendung erhält man bei Aufruf der Hilfe (-h).
- Rootrechte sind erforderlich für die Operationen f und w
- Bei der Sortierung von Dateien ist zu beachten: Video10 kommt vor Video9 Gross Z kommt vor klein a 
- Wegen der Komprimierung ist ein sekundengenaues Schneiden von h264 Videos nicht moeglich.
- Der Fokus liegt auf der h264 Codierung und mp4 Containern. Bei anderen Formaten wie z. Bsp. mpeg2 funktionieren manche Befehle mit ffmpeg nicht. H265 wird von ffmpeg gut unterstützt. Das default Audio Format ist AAC, auch mp3 wird problemlos verarbeitet. Weitere Kanäle werden i.d.R mitkopiert (keine Gewähr). Bei Fragen die ffmpeg Dokumentation zu Rate ziehen. 
- Die Transcode Funktion ist eher provisorisch implementiert. Ich empfehle stattdessen Handbrake, um die beste Qualität zu erreichen. Darüber hinaus empfiehlt es sich, verschiedene alte Codierer vorzuhalten. Insbesondere mpeg2 wird zuweilen von alten Tools besser verarbeitet.
- Gute Ergebnisse liefert ffmpeg auf aktuellen Arm64 Konsolen, z. Bsp.Odroid N2 etc. Mit ffmpeg auf alten Arm32 Systemen (RasPi 2, Odroid XU4) hatte ich weniger gute Erfahrungen gemacht. 

Temporärer 
Festplatten mögen es nicht gleichzeitig zu lesen und zu schreiben. Deshalb kann die Verwendung einer zweiten Festplatte als Ziel, die Performance verbessern. Das würde aber auch die Bedienung komplizieren,  deshalb wird nur der Operation w ein optionales Temp Laufwerk spendiert, das über ein Konfigurationsfile definiert werden kann. 

  

--- Skript mit Temp Ordner und Konfigfile  ---
---  (oder Download hier) ---
 
#!/bin/bash
echo ======================================================
echo ======= $0 =======
echo Parameter:
echo Param1 $1 Param2 $2 Param3 $3
echo Pfad:
pwd
echo Dateien im Verzeichnis:
ls
maxtime="72:00:00";
TmpVid="./tmp/";
echo maxtime=$maxtime - derzeit festgelegtes Maximum, bei Bedarf einen hoeheren Wert definieren
echo TempDir fuer Operation w: $TmpVid
 
## optionale Parameter für ffmpeg
## map="-map 0:0 -map 0:1 -map 0:2 -ignore_unknown" 
 
if [ "$1" == "-h" -o "$1" == "" ]; then 
 echo Features:
 echo - Videos zusammenfügen
 echo - Video splitten
 echo - Werbepausen herausschneiden
 echo - Anfang oder Ende abschneiden
 echo - Videosplitter  video, video_1, ... zusammenfügen - speziell für Digitnow Capture device
 echo - transcode Videos zu .h264 mp4 Container
 echo ### help ###
 echo Hilfe:
 echo splitvideo.sh a[nfang] time
 echo splitvideo.sh b{ende}  time [dauer]
 echo splitvideo.sh c[oncat] 
 echo splitvideo.sh s[plit]   time [time]
 echo splitvideo.sh w[ithout] time time
 echo splitvideo.sh f[ull] 
 echo splitvideo.sh t[ranscode] [transcodeparameter]
 echo Beispiel:
 echo splitvideo.sh a 01:00:00 - liefert das Video von 0 bis 1 Stunde 
 echo splitvideo.sh b 01:00:00 - liefert das Video von 1 Std bis Ende 
 echo splitvideo.sh b 01:00:00 00:30:00 - liefert  von 1 Std bis 1:30h 
 echo splitvideo.sh s 01:00:00          - split bei 1 Std 
 echo splitvideo.sh s 01:00:00 01:30:00 - split bis 1 Std und ab 1:30h 
 echo splitvideo.sh c - verbindet alle Videos in alphabet. Reihenfolge  
 echo splitvideo.sh w 01:00:00 01:30:00 - entf. 1 Std bis 1:30h
 echo splitvideo.sh r - refresh - schreibt alle Container neu, Originaldateien erhalten die Endung _old
 echo splitvideo.sh f - verbindet alle Videos nach dem Muster file.mp4 file_1.mp4 .. nach _file.mp4
 echo splitvideo.sh t - transcode alle Videocontainer mkv, wmv, mpeg2, h265, h264 zu video.Mp4
 echo splitvideo.sh t \"-preset slow -crf 19 -vf scale=960:-2\" - transcode mit Parametern
 echo ###
 echo warning: option f is especially designed for the sake of video capture devices, which split videos at 2GB
 echo keep cautious, there is much room for unexpected side effects, backup your data before use!!
 echo no root permission required, if you run into problems you can try with sudo -i though
fi
 
# Hinweis: temporaere Verzeichnisse werden mit 'sudo chown nobody:nogroup concat*; sudo chmod 777 concat*;' zugaenglich gemacht
# bei Nutzung im Netzwerk, kann es sonst Zugriffsfehler geben. 
 
#Rootrechte 
#sudo -i
 
#without
if [[ "$1" == "w"* ]]; then 
 if [ "$2" != "" ]; then 
 if [ "$3" != "" ]; then 
  # make tempfolder (if not exists)
  mkdir "$TmpVid";
  # read config
  if test -f "$(dirname $0)"/splitvideoconfig.sh; then   
    sudo chmod +x "$(dirname $0)"/splitvideoconfig.sh;
"$(dirname $0)"/splitvideoconfig.sh;
  fi
  sudo chown nobody:nogroup "$TmpVid"; sudo chmod 777 "$TmpVid";
  rm "$TmpVid"anfang.mp4 "$TmpVid"anfang_bak.mp4 "$TmpVid"bisende.mp4 "$TmpVid"bisende_bak.mp4
  mv "$TmpVid"anfang.mp4 "$TmpVid"anfang_bak.mp4; mv "$TmpVid"bisende.mp4 "$TmpVid"bisende_bak.mp4
  #maxtime=06:00:00;
  #write beginning
  cuttime=$2; for i in *.*; do infile="$i"; done; echo $cuttime $infile; 
  ffmpeg -ss 00:00:00 -t "$cuttime" -i "$infile"   -c copy "$TmpVid"anfang.mp4; 
  touch -r "$infile" "$TmpVid"anfang.mp4; 
  #write middle 
  ffmpeg -ss $cuttime -t 555 -i "$infile"   -c copy "$TmpVid"mitte.mp4;
  touch -r "$infile" "$TmpVid"mitte.mp4; 
  rm "$TmpVid"mitte_"$infile"
  mv "$TmpVid"mitte.mp4 "$TmpVid"mitte_"$infile"
  #write end
  if [ "$3" != "" ]; then cuttime=$3; fi
  ffmpeg -ss $cuttime -t "$maxtime" -i "$infile"   -c copy "$TmpVid"bisende.mp4;
  touch -r "$infile" "$TmpVid"bisende.mp4; 
  echo mv "$infile" "$infile"_bak.ts
  mv "$infile" "$infile"_bak.ts
 
  rm mylist.txt; rm mylist.old; 
  for f in "$TmpVid"anfang.mp4 "$TmpVid"bisende.mp4; do  lastfile="$f"; echo "file '$f'"; echo "file '$f'" >> mylist.txt; done; 
  ffmpeg -f concat -safe 0 -i mylist.txt -c copy "$infile"; 
  touch -r "$lastfile" "$infile"; 
  cp mylist.txt mylist.old; 
  rm mylist.txt; rm mylist.old; 
  #rm anfang.mp4 bisende.mp4
  mv "$TmpVid"anfang.mp4 "$TmpVid"anfang_bak.mp4
  mv "$TmpVid"bisende.mp4 "$TmpVid"bisende_bak.mp4
  #mv outfile.mp4 "$infile"   
 else 
  echo parameter fehlt
 fi
 fi
fi
 
#split
if [[ "$1" == "s"* ]]; then 
 if [ "$2" != "" ]; then 
  mv anfang.mp4 anfang_bak.mp4; mv bisende.mp4 bisende_bak.mp4
  #maxtime=24:00:00;
  cuttime=$2; for i in *.*; do infile="$i"; done; echo $cuttime $infile; 
  ffmpeg -ss 00:00:00 -t "$cuttime" -i "$infile" -c copy anfang_"$infile";
  touch -r "$infile" anfang_"$infile"; 
  if [ "$3" != "" ]; then cuttime=$3; fi
  ffmpeg -ss $cuttime -t "$maxtime" -i "$infile" -c copy bisende_"$infile";
  touch -r "$infile" bisende_"$infile"; 
 else 
  echo parameter fehlt
 fi
fi
 
#anfang -> liefert den Anfang -> ~anfang.mp4
if [[ "$1" == "a"* ]]; then 
 if [ "$2" != "" ]; then 
  rm anfang.mp4;
  mv anfang.mp4 anfang_bak.mp4; mv bisende.mp4 bisende_bak.mp4
  #maxtime=24:00:00;
  cuttime=$2; for i in *.*; do infile="$i"; done; echo $cuttime $infile; 
  ffmpeg -ss 00:00:00 -t "$cuttime" -i "$infile"   -c copy anfang_"$infile";
  ##ffmpeg -ss $cuttime -t "$maxtime" -i "$infile" -c copy bisende.mp4;
  touch -r "$infile"  anfang_"$infile"; 
 else 
  echo parameter fehlt
 fi
fi
 
#bis -> liefert das Ende -> bisende.mp4
if [[ "$1" == "b"* ]]; then 
 if [ "$2" != "" ]; then 
  rm bisende.mp4;
  mv anfang.mp4 anfang_bak.mp4; mv bisende.mp4 bisende_bak.mp4
  #maxtime=24:00:00;
  cuttime=$2; for i in *.*; do infile="$i"; done; echo $cuttime $infile; 
  if [ "$3" != "" ]; then maxtime=$3; fi
  ##ffmpeg -ss 00:00:00 -t "$cuttime" -i "$infile"   -c copy anfang.mp4;
  ffmpeg -ss $cuttime -t "$maxtime" -i "$infile"   -c copy bisende_"$infile";
  touch -r "$infile" bisende_"$infile"; 
 else 
  echo parameter fehlt
 fi
fi
 
#concat -> vebindet alle videos im Verzeichnis -> _<firstfile>
if [[ "$1" == "c"* ]]; then 
 rm mylist.txt; 
 rm mylist.old; 
 for f in *.*; do firstfile="$f"; break; done; 
 for f in *.*; do lastfile="$f"; echo "file '$f'"; echo "file '$f'" >> mylist.txt; done; 
 ffmpeg -f concat -safe 0 -i mylist.txt -c copy outfile.mp4; 
 touch -r "$firstfile" outfile.mp4; 
 mv outfile.mp4 _"$firstfile"
 mv mylist.txt mylist.old; 
fi
 
#refresh -> schreibt alle files neu -> outfiletemp.mp4 -> $infile
if [[ "$1" == "r"* ]]; then 
 rm mylist.txt; rm mylist.old; 
 for infile in *.*; do  
  ffmpeg -ss 00:00:00 -t 09:00:00 -i "$infile" -c copy outfiletemp.mp4;
  touch -r "$infile" outfiletemp.mp4; 
  #touch -r outfiletemp.mp4 "$infile"; 
  mv "$infile" "$infile"_old
  mv outfiletemp.mp4 "$infile"  
 done; 
fi
 
#full -> verbindet alle files nach dem Muster file.mp4 file_1.mp4 .. -> _file.mp4
#(fuer Videosschnipsel vom DigitNow Videograbber, andere Geraete haben evtl. ein anderes Namensschema)
if [[ "$1" == "f"* ]]; then 
 if ls *P_1.mp4 1> /dev/null 2>&1; then
echo Konflikt festgestellt, Dateiname nicht verarbeitbar
ls *P_1.mp4
ls *P_1_1.mp4
echo bitte Dateinamen aendern von datei_1.mp4 zu datei_01.mp4 bzw. datei_01_1.mp4 etc.
exit
 fi
 if ls *_10.mp4 1> /dev/null 2>&1; then
echo Konflikt festgestellt, Datei nicht verarbeitbar
ls *_10.mp4 # oder ls *_1[0,1-9].mp4
echo bitte Dateinamen aendern von datei_10.mp4 zu dateix10.mp4
 else
if test -d concat1; then
echo "directory exists"
else
if ls *_1.mp4 1> /dev/null 2>&1; then
#ls *[0-9]_1.mp4; ls *_1.mp4
## !! Selbstaufruf des Skripts mit $0 c !!
unset fg fh fi fk; fcount=0; for fg in *_1.mp4; do echo file: $fg ; fh=${fg//_1.mp4/}; fcount=$((fcount+1)); mkdir concat$fcount; sudo chown nobody:nogroup concat$fcount; sudo chmod 777 concat$fcount; echo $fcount; for fi in "$fh"*; do echo $fi; mv -n "$fi" ./concat$fcount/; done; cd concat$fcount; for fk in *.*; do firstfile="$fk"; break; done; $0 c; mv -n _"$fk" ../; cd ..; done;
else
  echo no files found for concat 
fi
fi
 fi
fi
 
#transcode -> wandelt alle Videocontainer (mkv, wmv, mpeg2, h265, h264) im Verzeichnis zu -> video.Mp4
#Optional kann Qualität und Aufloesung mitgegeben werden "-preset slow -crf 19 -vf scale=960:540"
#Hinweis: crf (constant rate factor): 18-20 Spielfilmqualität, 22-26 Dokumentationen, unter 18 bringt nichts, ueber 30 gibt Artefakte
#crf - konstante qualitet, cbr - constant bitrate (wird nicht mehr verwendet)
#cbr ist nur bei Leitungen oder Geraeten mit konstanter Bitrate zu empfehlen. Wenn ausgerechnet die rasante Verfolgungsjagd 
#verpixelt wird, kann man das Video wegwerfen. Deshalb wird heutzutage kommerziell eine Mischform
#verwendet, mit konstanter Qualitaet aber festgelegtem Limit und einem Vorlaufpuffer.
#### noch nicht getestet
if [[ "$1" == "t"* ]]; then 
 #transcodeparam="-preset slow -crf 19 -vf scale=960:540"
 #transcodeparam="-preset slow -crf 19 -vf scale=960:-2"
 transcodeparam="-preset slow -crf 20"
 if [ "$2" != "" ]; then 
  transcodeparam="$2"
 fi
 for i in *.*; do 
   date; echo "$i"; begin=$(date +%s); 
   #ffmpeg -i "$i" -c:v libx264 -threads 0 -preset slow -crf 19 -vf scale=960:540 "$i".Mp4; 
   ffmpeg -i "$i" -c:v libx264 -threads 0 $transcodeparam "$i".Mp4; 
   now=$(date +%s); diff=$(($now - $begin)); mins=$(($diff / 60)); secs=$(($diff % 60)); touch -r "$i"  "$i".Mp4; 
   echo "Time $mins:$(printf %02d $secs) $i $diff"; 
 done;
fi
 
#-----------------------------------------
#Letztes Update. 
#17.10.2019 - Parameter f(ull) hinzugefügt
# Vorüberlegung: 
#    Videoaufnahmen werden vom Capture device gesplittet nach dem Muster: video.mp4, video_1.mp4, ...
#    jede gesplittete Videoaufnahme enthält daher genau einen Kandidaten: video_1.mp4
# Algorithmus: 
# - Suche nach einer Datei mit dem Muster <file>_1.mp4
# - Verschiebe alle Dateien mit der Maske <file>* in das temporaere Verzeichnis Concat1
# - Zusammenfügen der Dateien im temporaeren Verzeichnis
# - Wiederhole 
#17.12.2019 Aenderungen:
#- chmod 777 fuer temporaere Verzeichnisse, um den Zugriff mit Samba im Netz zu erlauben,
#  Hintergrund ist, wenn aus Performance Gruenden eine lokale schnelle SSD benutzt wird, ist der Anwendung 
#  der Netzwerkkontext zunächst einmal nicht bekannt, dafuer gibt es auch keine allgemeingueltige Loesung. 
#  Allerdings ist das Thema auf einem Board, das der Videobearbeitung gewidmet ist, wenig relevant..
#  Die Berechtigungsstruktur wird in diesem Fall durch die Netzwerkkonfiguration abgebildet..
#  Bei Bedarf kann der Zugriff z. Bsp.mit chmod 664 und chown Netzuser Netzgruppe angepasst werden.
#- Ausgabedateien heissen nun anfang_<firstfile> bzw bisende<firstfile> statt einfach anfang.mp4 bzw bisende.mp4
#- Die concat Ausgabedatei erhaelt nun den Zeitstempel von firstfile statt den der zuletzt hinzugefuegten Datei.
#19.12.2019 Parameter t(ranscode) hinzugefügt
#5.1.2019 Korrektur bei Parameter f:  ## !! Selbstaufruf des Skripts mit $0 c !! (statt absoluter Pfad)
#18.3.20 Temp Folder für Operation w eingeführt
# Hintergrund ist, dass es möglich ist den Temp Ordner auf einem separaten LW anzulegen, 
# dadurch ergibt sich eine bessere Performance
#18.3.20 sudo wurde den Kommandos chmod und chown vorangestellt (Operationen f und w)
#18.3.20 Optionales Config File hinzugefuegt
#  Pfad: selbes Verzeichnis wie Skript
#  Name: splitvideoconfig.sh
#  Inhalt: TmpVid="./tmp/"; # oder alternatives Verzeichnis angeben
#  das Konfigurationsfile gilt derzeit nur fuer w[ithout], falls nicht vorhanden gilt der Standardwert
# 4.3.2020 Korrektur bei r[efresh]
#   alt: mv $infile "$infile"_old
#   alt: mv outfiletemp.mp4 $infile 
#   neu: mv "$infile" "$infile"_old
#   neu: mv outfiletemp.mp4 "$infile" 


 
  1 Besucher