#!/usr/bin/perl # Author: Alan J. Pippin # Description: Extract the given chapter(s) from an mkv file into separate video files # Requires: Newer version of ffmpeg to be installed that supports MKV chapters # Howto compile the latest FFMPEG from src under Linux # FFMPEG - http://ubuntuforums.org/showthread.php?t=786095 # Howto eliminate the non-monotonically increasing dts to muxer error # Comment out the error message in ffmpeg src tree in libavformat/utils.c # http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=7&t=49 # MOV: # major_brand : qt # Video: h264 (avc1 / 0x31637661) # Audio: pcm_s16le # MTS: # Video: h264 (High) (HDMV / 0x564D4448) # Audio: ac3 (AC-3 / 0x332D4341) # MP4: # Video: h264 (Baseline) (avc1 / 0x31637661) # Audio: aac (mp4a / 0x6134706D) # 3GP: # Video: h264 (Baseline) (avc1 / 0x31637661) # Audio: aac (mp4a / 0x6134706D) # FFMPEG EXAMPLE CMD: #ffmpeg -ss 00:00:00.000 -t 00:00:38.299 -i 2010-12-04.000.mkv -map 0 -vcodec copy -acodec copy test.mov #################################################################################################### # Includes use File::Copy; use File::Basename; use Getopt::Std; #################################################################################################### # Configuration parameters - CHANGE THESE TO SUITE YOUR NEEDS my $ffmpeg=`which ffmpeg`; chomp($ffmpeg); my $tmpfile = `mktemp`; chomp($tmpfile); #################################################################################################### #################################################################################################### # Command Line Options getopts("c:i:o:kse:"); if(! -x $ffmpeg) { die "-E- Unable to find required program: ffmpeg\n"; } if(! defined $opt_i) { &usage(); die "-E- Missing required argument input video name: -i \n"; } if(! defined $opt_c) { &usage(); die "-E- Missing required chapter: -c "; } if(! -r $opt_i) { &usage(); die "-E- Unable to open/find the input mkv file: $opt_i\n"; } if(! defined $opt_o) { if($opt_i =~ /^(\d+-\d+-\d+)/) { $opt_o = $1; } elsif($opt_i =~ /^(.*?)\./) { $opt_o = $1; } } sub usage { print "usage: $0 -i -o -c \n"; print " -c Specify which chapter from the mkv file to extract (all or 1,2,3, ...)\n"; print " -i Specify the name of the input mkv file\n"; print " -o Sets the base name to use when naming the output videos extracted from the chapters\n"; print " If not specified, the base name will be taken from the name of your input video\n"; print " -e Specify the extension that should be used on the output files.\n"; print " This is automatically determined. You only need to specify it if autodetection fails.\n"; print " -s Simulate mode. Don't actually make the video, but tell us what you will do\n"; print " -k Keep intermediate mkv file created during the extraction/conversion of mp4 clips\n"; print "\n"; return 1; } #################################################################################################### # SUBROUTINES sub detect_ext { my ($ffmpeg_info, $progressive) = @_; my $h264 = 0; my $h264_high = 0; my $ac3 = 0; my $aac = 0; my $pcm_s16le = 0; foreach $line (@{$ffmpeg_info}) { if($line =~ /Stream/) { if($line =~ /h264/i) { $h264 = 1; } if($line =~ /h264.*High/i) { $h264_high = 1; } if($line =~ /ac3/i) { $ac3 = 1; } if($line =~ /aac/i) { $aac = 1; } if($line =~ /pcm_s16le/i) { $pcm_s16le = 1; } } } # Quicktime/MOV if($h264 && $pcm_s16le) { return "mov"; } # MTS if($h264_high && $ac3) { if(!$progressive) { return "mkv"; } else { return "mp4"; } } # 3GP/MP4 if($h264 && $aac) { if(!$progressive) { return "mkv"; } else { return "mp4"; } } return "UNKNOWN"; } sub export_chapter { my ($chapters, $chapter) = @_; foreach $ch (@{$chapters}) { if($ch == $chapter) { return 1; } } return 0; } #################################################################################################### # MAIN # Turn our list of chapters into an ordered array my @chapters = sort(split(/,/, $opt_c)); my $all_chapters = 0; if($opt_c =~ /all/) { $all_chapters = 1; } print "-> Extracting the following chapters from $opt_i: @chapters\n"; my $progressive = system('ffmpeg -i "$opt_i" 2>&1 | grep -q "frame rate differs"'); if(!$progressive) { print " Detected interlaced video content\n"; } else { print " Detected progressive video content\n"; } # Use ffmpeg to extract the list of chapters available to rip # For each chapter specified on the command line, use ffmpeg to extract a video clip from that chapter my @ffmpeg_info = `$ffmpeg -i $opt_i 2>&1`; foreach $line (@ffmpeg_info) { if($line =~ /Chapter #\d+\:(\d+): start (\S+), end (\S+)/) { $chapter = $1; $start = $2; if($start > 0) { $start += 1; } # Add some margin to prevent taking a piece of the previous clip $end = $3; $duration = $end - $start; if($duration < 0) { die "-E- Unexpected negative duration detected for chapter $chapter\n"; } if($all_chapters || &export_chapter(\@chapters,$chapter)) { $ext = "UNKNOWN"; if(defined $opt_e) { $ext = $opt_e; } else { $ext = &detect_ext(\@ffmpeg_info, $progressive); } if($ext =~ /UNKNOWN/) { die "-E- Unable to determine the file type/extension to use for the output videos. Specify it with the -e option.\n"; } $dstfile = $opt_o . ".c" . sprintf("%03d",$chapter) . "." . $ext; print "-> Exporting $chapter to $dstfile:\n"; $cmd = "ffmpeg -ss $start -t $duration -i $opt_i -map 0 -vcodec copy -acodec copy $dstfile 2>&1"; if(defined $opt_s) { print "$cmd\n"; } else { print "\n"; print "$cmd\n"; $errno = system("$cmd"); $errno = $errno >> 8; if($errno > 0) { die "-E- ffmpeg encountered some errors with exit code $errno\n"; } } if(!$progressive && $ext =~ /mkv/i) { $ext = "mp4"; $srcfile = $dstfile; $dstfile = $dstfile = $opt_o . ".c" . sprintf("%03d",$chapter) . "." . $ext; $cmd = "mencoder -oac copy -ovc copy -of lavf -lavfopts format=mp4 -o $dstfile $srcfile"; if(defined $opt_s) { print "$cmd\n"; } else { print "\n"; print "$cmd\n"; $errno = system("$cmd"); $errno = $errno >> 8; if(! defined $opt_k) { unlink "$srcfile"; } if($errno > 0) { die "-E- mencoder encountered some errors with exit code $errno\n"; } } } } } } ####################################################################################################