Updated for Ubuntu 22.04. Also fixed merge videos cmin check
[videoscripts/.git] / mkv_extract_chapter
1 #!/usr/bin/perl
2 # Author: Alan J. Pippin
3 # Description: Extract the given chapter(s) from an mkv file into separate video files
4 # Requires: Newer version of ffmpeg to be installed that supports MKV chapters
5
6 # Howto compile the latest FFMPEG from src under Linux
7 # FFMPEG - http://ubuntuforums.org/showthread.php?t=786095
8
9 # Howto eliminate the non-monotonically increasing dts to muxer error
10 # Comment out the error message in ffmpeg src tree in libavformat/utils.c
11 # http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=7&t=49
12
13 # MOV:
14 # major_brand : qt
15 # Video: h264 (avc1 / 0x31637661) 
16 # Audio: pcm_s16le
17
18 # MTS:
19 # Video: h264 (High) (HDMV / 0x564D4448)
20 # Audio: ac3 (AC-3 / 0x332D4341)
21
22 # MP4:
23 # Video: h264 (Baseline) (avc1 / 0x31637661)
24 # Audio: aac (mp4a / 0x6134706D)
25
26 # 3GP:
27 # Video: h264 (Baseline) (avc1 / 0x31637661)
28 # Audio: aac (mp4a / 0x6134706D)
29
30 # FFMPEG EXAMPLE CMD:
31 #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
32
33 ####################################################################################################
34 # Includes
35 use File::Copy;
36 use File::Basename;
37 use Getopt::Std;
38
39 ####################################################################################################
40 # Configuration parameters - CHANGE THESE TO SUITE YOUR NEEDS
41 my $ffmpeg=`which ffmpeg`; chomp($ffmpeg);
42 my $tmpfile = `mktemp`; chomp($tmpfile);
43 ####################################################################################################
44
45 ####################################################################################################
46 # Command Line Options
47 getopts("c:i:o:kse:");
48
49 if(! -x $ffmpeg) { die "-E- Unable to find required program: ffmpeg\n"; }
50 if(! defined $opt_i) { &usage(); die "-E- Missing required argument input video name: -i <input.mkv>\n"; }
51 if(! defined $opt_c) { &usage(); die "-E- Missing required chapter: -c <all|1,2,...>"; }
52 if(! -r $opt_i) { &usage(); die "-E- Unable to open/find the input mkv file: $opt_i\n"; }
53 if(! defined $opt_o) {
54     if($opt_i =~ /^(\d+-\d+-\d+)/) { $opt_o = $1; }
55     elsif($opt_i =~ /^(.*?)\./) { $opt_o = $1; }
56 }
57
58 sub usage {
59     print "usage: $0 -i <input.mkv> -o <output_base_name> -c <chapter,chapter,...>\n";
60     print "  -c <chapter,chapter,...>  Specify which chapter from the mkv file to extract (all or 1,2,3, ...)\n";
61     print "  -i <input.mkv>            Specify the name of the input mkv file\n";
62     print "  -o <output_base_name>     Sets the base name to use when naming the output videos extracted from the chapters\n";
63     print "                            If not specified, the base name will be taken from the name of your input video\n";
64     print "  -e <ext>                  Specify the extension that should be used on the output files.\n";
65     print "                            This is automatically determined. You only need to specify it if autodetection fails.\n";
66     print "  -s                        Simulate mode. Don't actually make the video, but tell us what you will do\n";
67     print "  -k                        Keep intermediate mkv file created during the extraction/conversion of mp4 clips\n";
68     print "\n";
69     return 1;
70 }
71
72 ####################################################################################################
73 # SUBROUTINES
74
75 sub detect_ext {
76     my ($ffmpeg_info, $progressive) = @_;
77
78     my $h264 = 0;
79     my $h264_high = 0;
80     my $ac3 = 0;
81     my $aac = 0;
82     my $pcm_s16le = 0;
83     
84     foreach $line (@{$ffmpeg_info}) {
85         if($line =~ /Stream/) {
86             if($line =~ /h264/i) { $h264 = 1; }
87             if($line =~ /h264.*High/i) { $h264_high = 1; }
88             if($line =~ /ac3/i) { $ac3 = 1; }
89             if($line =~ /aac/i) { $aac = 1; }
90             if($line =~ /pcm_s16le/i) { $pcm_s16le = 1; }
91         }
92     }
93
94     # Quicktime/MOV
95     if($h264 && $pcm_s16le) { return "mov"; }
96     # MTS
97     if($h264_high && $ac3) { if(!$progressive) { return "mkv"; } else { return "mp4"; } }
98     # 3GP/MP4
99     if($h264 && $aac) { if(!$progressive) { return "mkv"; } else { return "mp4"; } }
100
101     return "UNKNOWN";
102 }
103
104 sub export_chapter {
105     my ($chapters, $chapter) = @_;
106     foreach $ch (@{$chapters}) {
107         if($ch == $chapter) { return 1; }
108     }
109     return 0;
110 }
111
112 ####################################################################################################
113 # MAIN
114
115 # Turn our list of chapters into an ordered array
116 my @chapters = sort(split(/,/, $opt_c));
117 my $all_chapters = 0;
118 if($opt_c =~ /all/) { $all_chapters = 1; }
119 print "-> Extracting the following chapters from $opt_i: @chapters\n";
120
121 my $progressive = system('ffmpeg -i "$opt_i" 2>&1 | grep -q "frame rate differs"');
122 if(!$progressive) {
123     print "   Detected interlaced video content\n";
124 } else {
125     print "   Detected progressive video content\n";
126 }
127
128 # Use ffmpeg to extract the list of chapters available to rip
129 # For each chapter specified on the command line, use ffmpeg to extract a video clip from that chapter
130 my @ffmpeg_info = `$ffmpeg -i $opt_i 2>&1`;
131 foreach $line (@ffmpeg_info) {
132     if($line =~ /Chapter #\d+\:(\d+): start (\S+), end (\S+)/) {
133         $chapter = $1;
134         $start = $2;
135         if($start > 0) { $start += 1; } # Add some margin to prevent taking a piece of the previous clip
136         $end = $3;
137         $duration = $end - $start;
138         if($duration < 0) { die "-E- Unexpected negative duration detected for chapter $chapter\n"; }
139         if($all_chapters || &export_chapter(\@chapters,$chapter)) {
140             $ext = "UNKNOWN"; 
141             if(defined $opt_e) { $ext = $opt_e; }
142             else { $ext = &detect_ext(\@ffmpeg_info, $progressive); } 
143             if($ext =~ /UNKNOWN/) { die "-E- Unable to determine the file type/extension to use for the output videos. Specify it with the -e <ext> option.\n"; }
144             $dstfile = $opt_o . ".c" . sprintf("%03d",$chapter) . "." . $ext;
145             print "-> Exporting $chapter to $dstfile:\n";
146             $cmd = "ffmpeg -ss $start -t $duration -i $opt_i -map 0 -vcodec copy -acodec copy $dstfile 2>&1";
147             if(defined $opt_s) {
148                 print "$cmd\n";
149             } else {
150                 print "\n";
151                 print "$cmd\n";
152                 $errno = system("$cmd");
153                 $errno = $errno >> 8;
154                 if($errno > 0) { die "-E- ffmpeg encountered some errors with exit code $errno\n"; }
155             }
156             if(!$progressive && $ext =~ /mkv/i) {
157                 $ext = "mp4";
158                 $srcfile = $dstfile;
159                 $dstfile = $dstfile = $opt_o . ".c" . sprintf("%03d",$chapter) . "." . $ext;
160                 $cmd = "mencoder -oac copy -ovc copy -of lavf -lavfopts format=mp4 -o $dstfile $srcfile";
161                 if(defined $opt_s) {
162                     print "$cmd\n";
163                 } else {
164                     print "\n";
165                     print "$cmd\n";
166                     $errno = system("$cmd");
167                     $errno = $errno >> 8;
168                     if(! defined $opt_k) { unlink "$srcfile"; }
169                     if($errno > 0) { die "-E- mencoder encountered some errors with exit code $errno\n"; }
170                 }
171             }
172         }
173     }
174 }
175
176 ####################################################################################################