source: gs2-extensions/video-and-audio/trunk/src/perllib/plugins/SimpleVideoPlugin.pm@ 25780

Last change on this file since 25780 was 25780, checked in by jmt12, 12 years ago

A basic video converter that only takes TS files as input, and produces a MP4 file, extracted metadata and keyframes as output

File size: 10.8 KB
Line 
1###########################################################################
2#
3# SimpleVideoPlugin.pm -- Plugin for multimedia with some simple video
4# processing
5#
6# A component of the Greenstone digital library software from the New
7# Zealand Digital Library Project at the University of Waikato, New
8# Zealand.
9#
10# Copyright (C) 2012 New Zealand Digital Library Project
11#
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation; either version 2 of the License, or
15# (at your option) any later version.
16#
17# This program is distributed in the hope that it will be useful, but
18# WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20# General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with this program; if not, write to the Free Software
24# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25#
26###########################################################################
27
28package SimpleVideoPlugin;
29
30use File::Temp qw/ tempdir /;
31
32use BasePlugin;
33use MetadataRead;
34use util;
35
36use strict;
37use warnings;
38no strict 'refs'; # allow filehandles to be variables and viceversa
39
40sub BEGIN
41{
42 @SimpleVideoPlugin::ISA = ('MetadataRead', 'BasePlugin');
43}
44
45my $arguments = [ { 'name' => "process_exp",
46 'desc' => "{BasePlugin.process_exp}",
47 'type' => "regexp",
48 'reqd' => "no",
49 'deft' => &get_default_process_exp() },
50 { 'name' => "streamingHQsize",
51 'desc' => "{VideoPlugin.streamingsize}",
52 'type' => "int",
53 'deft' => "720",
54 'reqd' => "no" },
55 { 'name' => "streamingHQVideoBitrate",
56 'desc' => "{VideoPlugin.streamingbitrate}",
57 'type' => "int",
58 'deft' => "496",
59 'reqd' => "no" },
60 { 'name' => "streamingHQAudioBitrate",
61 'desc' => "{VideoPlugin.streamingbitrate}",
62 'type' => "int",
63 'deft' => "80",
64 'reqd' => "no" },
65 { 'name' => "videoDeinterlacingFilter",
66 'desc' => "Activate a deinterlacing filter to increase the quality of TV footage",
67 'type' => "enum",
68 'list' => [{'name' => "true", 'desc' => "{common.true}"},
69 {'name' => "false", 'desc' => "{common.false}"}],
70 'deft' => "false",
71 'reqd' => "no" },
72 { 'name' => "isParallel",
73 'desc' => "Will the import use parallel processing? (maybe this should be set by parallel-import.pl somehow)",
74 'type' => "enum",
75 'list' => [{'name' => "true", 'desc' => "{common.true}"},
76 {'name' => "false", 'desc' => "{common.false}"}],
77 'deft' => "false",
78 'reqd' => "no" }
79 { 'name' => "isCluster",
80 'desc' => "Will the import be run on a cluster (multiple computers) or not (single computer - possibly multiple processors)",
81 'type' => "enum",
82 'list' => [{'name' => "true", 'desc' => "{common.true}"},
83 {'name' => "false", 'desc' => "{common.false}"}],
84 'deft' => "false",
85 'reqd' => "no" }
86 ];
87
88my $options = { 'name' => "BasicVideoPlugin",
89 'desc' => "",
90 'abstract' => "no",
91 'inherits' => "yes",
92 'args' => $arguments };
93
94sub new
95{
96 my ($class) = shift (@_);
97 my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
98 push(@$pluginlist, $class);
99
100 push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
101 push(@{$hashArgOptLists->{"OptList"}},$options);
102 my $self = new BasePlugin($pluginlist, $inputargs, $hashArgOptLists);
103 return bless $self, $class;
104}
105
106sub get_default_process_exp
107{
108 return '(?i)\.ts$';
109}
110
111sub get_oid_hash_type
112{
113 my $self = shift (@_);
114 return "hash_on_ga_xml";
115}
116
117sub process
118{
119 my $self = shift (@_);
120 my ($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_;
121
122 my $ivideo_path = &util::filename_cat($base_dir, $file);
123 my $topsection = $doc_obj->get_top_section();
124
125 $file =~ /[\/]?(.+)\.(?:ts)$/;
126 my $filename = $1;
127 $filename =~ /(\d\d\d\d)-(\d\d)-(\d\d)/;
128 my $date = $1 . $2 . $3;
129 $filename =~ s/[^a-z0-9]+/_/ig;
130 $filename =~ s/^_+|_+$//g;
131
132 $doc_obj->add_utf8_metadata($topsection,"Date",$date);
133
134 my $logs_dir = &util::filename_cat($ENV{'GSDLCOLLECTDIR'}, "logs");
135 if (!-d $logs_dir)
136 {
137 mkdir($logs_dir, 0775);
138 }
139 my $convert_log_path = &util::filename_cat($logs_dir, 'convert-' . $filename . '.log');
140 my $pass_log_path = &util::filename_cat($logs_dir, 'convert-' . $filename . '-pass');
141 my $tmp_dir = &util::filename_cat($ENV{'GSDLCOLLECTDIR'}, "cached");
142 if (!-d $tmp_dir)
143 {
144 mkdir($tmp_dir, 0775);
145 }
146 $tmp_dir = &util::filename_cat($tmp_dir, $filename);
147 if (!-d $tmp_dir)
148 {
149 mkdir($tmp_dir, 0775);
150 }
151
152 # 1. Use MediaInfo to extract important metadata
153 print " - Extracting metadata using MediaInfo\n";
154 my $mi_metadata = $self->getMetadata($ivideo_path);
155 $doc_obj->add_utf8_metadata($topsection,"Format", 'multimedia (' . $mi_metadata->{'General'}->{'Format'} . ')');
156 $doc_obj->set_metadata_element($topsection,"FileSize",$mi_metadata->{'General'}->{'File_size'});
157 $doc_obj->add_utf8_metadata($topsection,"Duration",$mi_metadata->{'General'}->{'Duration'});
158 if (defined $mi_metadata->{'Video'}->{'Format_Info'} && defined $mi_metadata->{'Video'}->{'Format'})
159 {
160 $doc_obj->add_utf8_metadata($topsection,"VideoFormat",$mi_metadata->{'Video'}->{'Format_Info'} . ' (' . $metadata->{'Video'}->{'Format'} . ')');
161 }
162 if (defined $mi_metadata->{'Audio'}->{'Format_Info'} && defined $mi_metadata->{'Audio'}->{'Format'})
163 {
164 $doc_obj->add_utf8_metadata($topsection,"AudioFormat",$mi_metadata->{'Audio'}->{'Format_Info'} . ' (' . $metadata->{'Audio'}->{'Format'} . ')');
165 }
166 $doc_obj->add_utf8_metadata($topsection,"Width",$mi_metadata->{'Video'}->{'Width'});
167 $doc_obj->add_utf8_metadata($topsection,"Height",$mi_metadata->{'Video'}->{'Height'});
168
169 # 2. Convert into FLV, reprocess to make seekable, and associate
170 # - generate a path for our temporary converted video file
171 my $ovideo_path = &util::filename_cat($tmp_dir, 'gsv.mp4');
172
173 if (-f $ovideo_path)
174 {
175 print " - Found existing converted video in cache\n";
176 }
177 else
178 {
179 # - first conversion pass
180 print " - Convert using Handbrake\n";
181 my $streaming_HQ_size = $self->{'streamingHQsize'};
182 my $streaming_HQ_VideoBitrate = $self->{'streamingHQVideoBitrate'};
183 my $streaming_HQ_AudioBitrate = $self->{'streamingHQAudioBitrate'};
184 my $deinterlace = $self->{'videoDeinterlacingFilter'};
185 my $video_processing_parameters;
186 if (!$streaming_HQ_size || $streaming_HQ_size eq "fullsize")
187 {
188 $video_processing_parameters = "--strict-anamorphic";
189 }
190 else
191 {
192 $video_processing_parameters = "-w $streaming_HQ_size --loose-anamorphic";
193 }
194 if ($deinterlace eq "true")
195 {
196 $video_processing_parameters .= " --decomb";
197 }
198 # Default MenCoder options for x264
199 my $mencoder_options = 'ref=2:bframes=2:subq=6:mixed-refs=0:weightb=0:8x8dct=0:trellis=0';
200 my $is_cluster = $self->{'isCluster'};
201 my $is_parallel = $self->{'isParallel'};
202 # If we are parallel processing on a single (presumably) multicore computer
203 # then we need to limit the number of threads (and hence CPUs) HandBrake
204 # will utilize in order to emulate true parallel processing (otherwise the
205 # first thread to get to HandBrake conversion will take up most the CPUs
206 # causing all other threads to wait anyway). It will interesting to test
207 # whether parallel processing or serial processing (with HandBrake parallel
208 # processing) is faster.
209 if ($is_parallel && !$is_cluster)
210 {
211 $mencoder_options = ':threads=1';
212 }
213 my $cmd = 'HandBrakeCLI -i "' . $ivideo_path . '" -t 1 -c 1 -f mp4 -O -o "' . $ovideo_path . '" ' . $video_processing_parameters . ' -e x264 -b ' . $streaming_HQ_VideoBitrate . ' -a 1 -E faac -6 dpl2 -R Auto -B ' . $streaming_HQ_AudioBitrate . ' -D 0.0 -x ' . $mencoder_options . ' > "' . $convert_log_path . '" 2>&1';
214 print "[DEBUG: " . $cmd . "]\n";
215 `$cmd`;
216 }
217 if (!-f $ovideo_path)
218 {
219 die("Fatal Error! Failed to convert video: " . $ovideo_path . "\nReason:" . $! . "\n");
220 }
221 # - associate
222 $doc_obj->associate_file($ovideo_path,'gsv.mp4','video/mp4',$topsection);
223
224 # 3. Extract keyframes using hive, and associate
225 my $oshots_path = &util::filename_cat($tmp_dir, 'shots.xml');
226 if (-f $oshots_path)
227 {
228 print " - Found existing keyframe images in cache\n";
229 }
230 else
231 {
232 print " - Generating keyframe images using Hive2\n";
233 my $cmd = 'hive2_ffmpegsvn -o "' . $oshots_path . '" -k "' . $tmp_dir . '" "' . $ovideo_path . '" >> "' . $convert_log_path . '" 2>&1';
234 ###print "[cmd: " . $cmd . "]\n";
235 `$cmd`;
236 }
237 if (!-f $oshots_path)
238 {
239 die("Fatal Error! Failed to extract keyframe images: " . $oshots_path . "\nReason:" . $! . "\n");
240 }
241 # - associate all of the JPGs found in the temp directory
242 opendir(my $dh, $tmp_dir);
243 my @shots = readdir($dh);
244 closedir($dh);
245 my $thumbnail = 0;
246 foreach my $shot (sort @shots)
247 {
248 my $shot_path = &util::filename_cat($tmp_dir, $shot);
249 if ($shot =~ /.jpg$/)
250 {
251 if (!$thumbnail)
252 {
253 $doc_obj->add_utf8_metadata($topsection,"Thumbnail",$shot);
254 $thumbnail = 1;
255 }
256 $doc_obj->add_utf8_metadata($topsection,"Keyframe",$shot);
257 $doc_obj->associate_file($shot_path,$shot,"image/jpeg",$topsection);
258 }
259 }
260
261 # - I have to add some text (yay, back to needing dummy text) otherwise the
262 # DocumentText formatting is ignored (?!?)
263 $doc_obj->add_utf8_text($topsection, "This is dummy text");
264
265 # 4. Done! Cleanup.
266
267 return 1;
268}
269
270sub getMetadata
271{
272 my ($self, $ivideo_path) = @_;
273 my $cmd = 'mediainfo --Output=XML "' . $ivideo_path . '" 2>&1';
274 ###print "Cmd: " . $cmd1 . "\n";
275 my $metadata_xml = `$cmd`;
276 my @lines = split(/\r?\n/, $metadata_xml);
277 my $metadata = {'Unknown'=>{}};
278 my $metadata_type = 'Unknown';
279 foreach my $line (@lines)
280 {
281 if ($line =~ /<track type="(.+)">/)
282 {
283 $metadata_type = $1;
284 if (!defined $metadata->{$metadata_type})
285 {
286 $metadata->{$metadata_type} = {};
287 }
288 }
289 elsif ($line =~ /<([^>]+)>(.+)<\/[^>]+>/)
290 {
291 my $field = $1;
292 my $value = $2;
293 $metadata->{$metadata_type}->{$field} = $value;
294 }
295 }
296 return $metadata;
297}
298
2991;
300
301
302
303
304
305
306
307
308
309
310
Note: See TracBrowser for help on using the repository browser.