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