/[amv]/amv.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /amv.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 25 - (show annotations)
Sat Aug 18 11:20:25 2007 UTC (16 years, 8 months ago) by dpavlin
File MIME type: text/plain
File size: 13313 byte(s)
options --dump-avm, --dump-video, --dump-jpeg and --dump-audio to support
selectable dumping from input and/or output stream
1 #!/usr/bin/perl -w
2
3 # amv.pl
4 #
5 # 07/19/07 19:21:39 CEST Dobrica Pavlinusic <dpavlin@rot13.org>
6 #
7 # Various useful links used to produce this:
8 # http://www.moviecodec.com/topics/15431p1.html
9 # http://en.wikipedia.org/wiki/RIFF_(File_format)
10 # http://www.obrador.com/essentialjpeg/HeaderInfo.htm
11 # http://lists.helixcommunity.org/pipermail/datatype-dev/2005-January/001886.html
12 # http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
13
14 use strict;
15
16 use Data::Dump qw/dump/;
17 use Carp qw/confess/;
18 use File::Path;
19 use Getopt::Long;
20
21 my $dump_amv = 0;
22 my $dump_video = 0;
23 my $dump_jpeg = 0;
24 my $dump_audio = 0;
25 my $debug = 0;
26 my $verbose = 0;
27 my $dump_dir = '/tmp/dump/';
28 my $dump_avi = "dump.avi";
29 my $no_jpeg_header = 0;
30 my $jpeg_q = 100;
31 my $jpegtran;
32
33 GetOptions(
34 "dump-amv!" => \$dump_amv,
35 "dump-video!" => \$dump_video,
36 "dump-jpeg!" => \$dump_jpeg,
37 "dump-audio!" => \$dump_audio,
38 "debug!" => \$debug,
39 "dump-dir=s" => \$dump_dir,
40 "no-jpeg-headers!" => \$no_jpeg_header,
41 "jpegtran=s" => \$jpegtran,
42 "verbose!" => \$verbose,
43 );
44
45 my $path = shift @ARGV || die "usage: $0 movie.amv\n";
46
47 # by default, flip frames
48 #$jpegtran = '-flip vertical' unless defined($jpegtran);
49
50 rmtree $dump_dir if -e $dump_dir;
51 mkpath $dump_dir || die "can't create $dump_dir: $!";
52
53 open(my $fh, '<', $path) || die "can't open $path: $!";
54
55 # offset in file
56 my $o = 0;
57
58 # shared data hash
59 my $d;
60
61 sub hex_dump {
62 my ( $bytes, $offset ) = @_;
63 return unless $bytes;
64
65 my $old_o;
66 if (defined($offset)) {
67 $old_o = $o;
68 $o = $offset;
69 }
70
71 my $ascii = $bytes;
72 $ascii =~ s/\W/./gs;
73 my $hex = uc( unpack('h*', $bytes) );
74 $hex =~ s/(..)/$1 /g;
75 # calculate number of characters for offset
76 #my $d = length( sprintf("%x",length($bytes)) );
77 my $d = 4;
78 my $prefix = '#.';
79 while ( $hex =~ s/^((?:\w\w\s){1,16})// ) {
80 printf "$prefix %0${d}x | %-48s| %s\n", $o, $1, substr( $ascii, 0, 16 );
81 $prefix = '##';
82 if ( length($ascii) >= 16 ) {
83 $ascii = substr( $ascii, 16 );
84 $o += 16;
85 } else {
86 $o += length($ascii);
87 last;
88 }
89 }
90
91 $o = $old_o if $old_o;
92 }
93
94 sub x {
95 my ($len,$format) = @_;
96
97 my $bytes;
98 read($fh, $bytes, $len);
99
100 my $r_len = length($bytes);
101 confess "read $r_len bytes, expected $len" if $len != $r_len;
102
103 if ( $dump_amv ) {
104 print "## raw $len bytes\n";
105 hex_dump( $bytes );
106 }
107
108 if ( $bytes eq 'AMV_END_' ) {
109 print "> end of file marker AMV_END_\n" if $dump_video;
110 $d->{eof}++;
111 return;
112 }
113
114 if ( $format ) {
115 my @data = unpack($format, $bytes);
116 print "## unpacked = ",dump(@data),"\n" if $debug;
117 return @data;
118 } else {
119 return $bytes;
120 }
121 }
122
123 sub next_part {
124 my ( $expected_part, $expected_len, $skip ) = @_;
125 my ( $part, $len ) = x(8,'A4V');
126 return unless $len;
127 confess "not $expected_part but $part" if $expected_part ne $part;
128 if ( $expected_len ) {
129 confess "expected $expected_len bytes for $part got $len" if $len != $expected_len;
130 }
131 printf "## next_part %s - %d 0x%x bytes\n", $part, $len, $len if $debug;
132 x($len) if $skip;
133 return $len;
134 }
135
136 sub quality {
137 my @table = @_;
138 die "quantization matrice needs to have 64 bytes!" if $#table != 63;
139
140 my $in = join('', map { chr($_) } @table );
141 my $out;
142
143 foreach my $t ( @table ) {
144 $t = int( ( $t * $jpeg_q ) / 100 );
145 $t = 255 if $t > 255;
146 $out .= chr($t);
147 }
148
149 if ( $dump_video ) {
150 print "## quantization table original\n";
151 hex_dump( $in );
152 print "## quantization table for $jpeg_q %\n";
153 hex_dump( $out );
154 }
155
156 return $out;
157 }
158
159 sub mp3_frame {
160 my $frame = join('',
161 # Frame sync (all bits set)
162 1 x 11 .
163 # MPEG Audio version ID
164 # 00 - MPEG Version 2.5 (unofficial)
165 # 01 - reserved
166 # 10 - MPEG Version 2 (ISO/IEC 13818-3)
167 # 11 - MPEG Version 1 (ISO/IEC 11172-3)
168 1,0,
169 # Layer description
170 # 00 - reserved
171 # 01 - Layer III
172 # 10 - Layer II
173 # 11 - Layer I
174 0,1,
175 # Protection bit
176 # 0 - Protected by CRC (16bit crc follows header)
177 # 1 - Not protected
178 0,
179 # Bitrate index
180 0,0,0,0,
181 # Sampling rate frequency index (22050)
182 0,0,
183 # Padding bit
184 # 0 - frame is not padded
185 # 1 - frame is padded with one extra slot
186 0,
187 # Private bit
188 0,
189 # Channel Mode
190 # 00 - Stereo
191 # 01 - Joint stereo (Stereo)
192 # 10 - Dual channel (2 mono channels)
193 # 11 - Single channel (Mono)
194 1,1,
195 # Mode extension (Only if Joint stereo)
196 0,0,
197 # Copyright
198 0,
199 # Original
200 0,
201 # Emphasis
202 # 00 - none
203 # 01 - 50/15 ms
204 # 10 - reserved
205 # 11 - CCIT J.17
206 0,0,
207 );
208
209 die "frame must have 32 bits, not ", length($frame), " for $frame" if length($frame) != 32;
210
211 my $bits = pack("b32", $frame);
212
213 die "packed bits must be 4 bytes, not $bits" if length($bits) != 4;
214
215 my $t = $frame;
216 $t =~ s/(.{8})/$1 /g;
217 warn "## mp3 frame frame = $t\n";
218
219 return $bits;
220 }
221
222 my @subframes;
223 my $frame_nr = 1;
224
225 # how many subframes to join into single frame?
226 my $join_subframes = 0;
227
228 sub mkjpg {
229 my ($data) = @_;
230
231 confess "no SOI marker in data" if substr($data,0,2) ne "\xFF\xD8";
232 confess "no EOI marker in data" if substr($data,-2,2) ne "\xFF\xD9";
233 $data = substr($data,2,-2);
234
235 if ( $#subframes < ( $join_subframes - 1 ) ) {
236 push @subframes, $data;
237 print "## saved $frame_nr/", $#subframes + 1, " subframe of ", length($data), " bytes\n" if $debug;
238 return;
239 }
240
241 my $w = $d->{amvh}->{width} || die "no width?";
242 my $h = $d->{amvh}->{height} || confess "no height?";
243
244 my $header =
245 # Start of Image (SOI) marker
246 "\xFF\xD8".
247 # JFIF marker
248 "\xFF\xE0".
249 pack("nZ5CCCnnCC",
250 16, # length
251 'JFIF', # identifier (JFIF)
252 1,1, # version
253 0, # units (none)
254 1,1, # X,Y density
255 0,0, # X,Y thumbnail
256 ).
257 "\xFF\xFE".
258 "\x00\x3CCREATOR: amv dumper (compat. IJG JPEG v62), quality = 100\n".
259 # quantization table (quaility=100%)
260 "\xFF\xDB".
261 "\x00\x43".
262 # 8 bit values, table 1
263 "\x00".
264 quality(
265 0x10, 0x0B, 0x0C, 0x0E, 0x0C, 0x0A, 0x10, 0x0E,
266 0x0D, 0x0E, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28,
267 0x1A, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25,
268 0x1D, 0x28, 0x3A, 0x33, 0x3D, 0x3C, 0x39, 0x33,
269 0x38, 0x37, 0x40, 0x48, 0x5C, 0x4E, 0x40, 0x44,
270 0x57, 0x45, 0x37, 0x38, 0x50, 0x6D, 0x51, 0x57,
271 0x5F, 0x62, 0x67, 0x68, 0x67, 0x3E, 0x4D, 0x71,
272 0x79, 0x70, 0x64, 0x78, 0x5C, 0x65, 0x67, 0x63,
273 ).
274 "\xFF\xDB".
275 "\x00\x43".
276 # 8 bit values, table 1
277 "\x01".
278 quality(
279 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2F, 0x1A,
280 0x1A, 0x2F, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63,
281 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
282 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
283 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
284 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
285 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
286 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
287 ).
288 # start of frame
289 "\xFF\xC0".
290 pack("ncnncc9",
291 17, # len
292 8, # sample precision in bits
293 $h,$w, # X,Y size
294 3, # number of components
295 1,0x22,0, # Component ID, H+V sampling factors, Quantization table number
296 2,0x11,1,
297 3,0x11,1,
298 ).
299 # Define huffman table (section B.2.4.1)
300 "\xFF\xC4". # Marker
301 "\x00\x1F". # Length (31 bytes)
302 "\x00". # DC luminance, table 0
303 "\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00".
304 "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B".
305 # Define huffman table (section B.2.4.1)
306 "\xFF\xC4". # Marker
307 "\x00\xB5". # Length (181 bytes)
308 "\x10". # AC luminance, table 0
309 "\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01\x7D".
310 "\x01\x02\x03\x00\x04\x11\x05\x12".
311 "\x21\x31\x41\x06\x13\x51\x61\x07\x22\x71\x14\x32".
312 "\x81\x91\xA1\x08\x23\x42\xB1\xC1\x15\x52\xD1\xF0".
313 "\x24\x33\x62\x72\x82\x09\x0A\x16\x17\x18\x19\x1A".
314 "\x25\x26\x27\x28\x29\x2A\x34\x35\x36\x37\x38\x39".
315 "\x3A\x43\x44\x45\x46\x47\x48\x49\x4A\x53\x54\x55".
316 "\x56\x57\x58\x59\x5A\x63\x64\x65\x66\x67\x68\x69".
317 "\x6A\x73\x74\x75\x76\x77\x78\x79\x7A\x83\x84\x85".
318 "\x86\x87\x88\x89\x8A\x92\x93\x94\x95\x96\x97\x98".
319 "\x99\x9A\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xB2".
320 "\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xC2\xC3\xC4\xC5".
321 "\xC6\xC7\xC8\xC9\xCA\xD2\xD3\xD4\xD5\xD6\xD7\xD8".
322 "\xD9\xDA\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA".
323 "\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA".
324 # Define huffman table (section B.2.4.1)
325 "\xFF\xC4". # Marker
326 "\x00\x1F". # Length (31 bytes)
327 "\x01". # DC chrominance, table 1
328 "\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00".
329 "\x00\x00\x00\x00".
330 "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B".
331 #/* Define huffman table (section B.2.4.1) */
332 "\xFF\xC4". # Marker
333 "\x00\xB5". # Length (181 bytes)
334 "\x11". # AC chrominance, table 1
335 "\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04".
336 "\x00\x01\x02\x77".
337 "\x00\x01\x02\x03\x11\x04\x05\x21".
338 "\x31\x06\x12\x41\x51\x07\x61\x71\x13\x22\x32\x81".
339 "\x08\x14\x42\x91\xA1\xB1\xC1\x09\x23\x33\x52\xF0".
340 "\x15\x62\x72\xD1\x0A\x16\x24\x34\xE1\x25\xF1\x17".
341 "\x18\x19\x1A\x26\x27\x28\x29\x2A\x35\x36\x37\x38".
342 "\x39\x3A\x43\x44\x45\x46\x47\x48\x49\x4A\x53\x54".
343 "\x55\x56\x57\x58\x59\x5A\x63\x64\x65\x66\x67\x68".
344 "\x69\x6A\x73\x74\x75\x76\x77\x78\x79\x7A\x82\x83".
345 "\x84\x85\x86\x87\x88\x89\x8A\x92\x93\x94\x95\x96".
346 "\x97\x98\x99\x9A\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9".
347 "\xAA\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xC2\xC3".
348 "\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xD2\xD3\xD4\xD5\xD6".
349 "\xD7\xD8\xD9\xDA\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9".
350 "\xEA\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA".
351 # Start of Scan marker
352 "\xFF\xDA".
353 pack("nC10",
354 12, # length
355 3, # number of components
356 1,0x00, # Scan 1: use DC/AC huff tables 0/0
357 2,0x11, # Scan 2: use DC/AC huff tables 1/1
358 3,0x11, # Scan 3: use DC/AC huff tables 1/1
359 0,0x3f, # Ss, Se
360 0, # Ah, Ai (not used)
361 );
362
363 if ( $dump_jpeg ) {
364 print "## created JPEG header...\n";
365 hex_dump( $header, 0 );
366 }
367
368 my $frame = join('', @subframes ) . $data;
369 @subframes = ();
370
371 my $path = sprintf("$dump_dir/%04d.jpg", $frame_nr );
372
373 my $fh;
374 if ( $jpegtran ) {
375 open($fh, '|-', "jpegtran $jpegtran > $path") || die "can't create $path: $!";
376 } else {
377 open($fh, '>', $path) || die "can't create $path: $!";
378 }
379
380 if ( ! $no_jpeg_header ) {
381 print $fh $header . $frame . "\xFF\xD9" || die "can't write jpeg $path: $!";
382 } else {
383 print $fh $frame || die "can't write raw jpeg $path: $!";
384 }
385 close $fh || die "can't close $path: $!";
386 print ">> created $frame_nr ", $no_jpeg_header ? 'raw' : '', " jpeg $path ", -s $path, " bytes\n" if $verbose;
387 }
388
389 my ( $riff, $amv ) = x(12, 'Z4x4Z4');
390 die "$path not RIFF but $riff" if $riff ne 'RIFF';
391 die "$path not AMV but $amv" if $amv ne 'AMV ';
392
393 my $apath = "$dump_dir/audio.wav";
394 open(my $audio_fh, '>', $apath) || die "can't open audio file $apath: $!";
395
396 print $audio_fh pack 'a4Va4a4VvvVVv4', (
397 # header 'RIFF', size
398 'RIFF',-1,
399 # type: 'WAVE'
400 'WAVE',
401 'fmt ',0x14,
402 # format: DVI (IMA) ADPCM Wave Type
403 0x11,
404 # channels
405 1,
406 # samples/sec
407 22050,
408 # avg. bytes/sec (for esimation)
409 11567,
410 # block align (size of block)
411 0x800,
412 # bits per sample (mono data)
413 4,
414 # cbSize (ADPCM with 7 soefficient pairs)
415 2,
416 # nSamplesPerBlock
417 # (((nBlockAlign - (7 * nChannels)) * 8) / (wBitsPerSample * nChannels)) + 2
418 0x0ff9,
419 );
420
421 print $audio_fh pack 'a4VVa4V', (
422 # time length of the data in samples
423 'fact',4,
424 220500,
425 #
426 'data',-1,
427 );
428
429 my $riff_header_len = tell($audio_fh);
430
431 while ( ! defined($d->{eof}) ) {
432 my ( $list, $name ) = x(12,'A4x4A4');
433 die "not LIST but $list" if $list ne 'LIST';
434 print "< $list * $name\n" if $verbose;
435
436 if ( $name eq 'hdrl' ) {
437
438 my $len = next_part( 'amvh', hex(38) );
439
440 my @names = ( qw/ms_per_frame width height fps ss mm hh/ );
441 my $h;
442 map {
443 my $v = $_;
444 my $n = shift @names || die "no more names?";
445 $h->{$n} = $v;
446 } x($len, 'Vx28VVVx8CCv');
447
448 printf "## %s %d*%d %s fps (%d ms/frame) %02d:%02d:%02d\n",
449 $path,
450 $h->{width}, $h->{height}, $h->{fps}, $h->{ms_per_frame},
451 $h->{hh}, $h->{mm}, $h->{ss};
452
453 $d->{amvh} = $h;
454
455 } elsif ( $name eq 'strl' ) {
456
457 next_part( 'strh', 0, 1 );
458 next_part( 'strf', 0, 1 );
459
460 } elsif ( $name eq 'movi' ) {
461
462 while (1) {
463 my $frame = $d->{movi}++;
464
465 my $len = next_part( '00dc' );
466 last unless $len;
467 printf "<< %s 00dc - part %d jpeg %d 0x%x bytes\n", $name, $frame, $len, $len if $verbose;
468 mkjpg( x($len) );
469
470 $len = next_part( '01wb' );
471 printf "<< %s 01wb - part %d audio %d 0x%x bytes\n", $name, $frame, $len, $len if $verbose;
472
473 my $audio_frame = x( $len );
474
475 if ( $dump_audio ) {
476 printf "#### dumping audio frame %d 0x%x bytes\n", length($audio_frame), length($audio_frame);
477 hex_dump( $audio_frame );
478 }
479
480 # remove 8 bytes of something
481 $audio_frame = substr( $audio_frame, 8 );
482
483 if ( length($audio_frame) % 2 == 0 ) {
484 print "#### even sized frame!";
485 # $audio_frame = substr( $audio_frame, 0, -1 );
486 }
487
488 # print $audio_fh mp3_frame;
489 print $audio_fh $audio_frame || die "can't write audio frame in $apath: $!";
490
491 $frame_nr++;
492 };
493
494 } else {
495 die "unknown $list $name";
496 }
497 }
498
499 my $cmd = "ffmpeg -i $dump_dir/%04d.jpg -r 16 -y $dump_avi";
500 system($cmd) == 0 || die "can't convert frames to avi using $cmd: $!";
501
502 my $size = tell($audio_fh);
503 warn "## wav file size: $size\n";
504
505 seek( $audio_fh, 4, 0 );
506 print $audio_fh pack("V", $size - 8);
507 seek( $audio_fh, $riff_header_len - 4, 0 );
508 print $audio_fh pack("V", $size - $riff_header_len);
509
510 close($audio_fh) || die "can't close audio file $apath: $!";
511
512 print ">>>> created $frame_nr frames $dump_avi ", -s $dump_avi, " and $apath ", -s $apath, "\n";

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26