/[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

Diff of /amv.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 5 by dpavlin, Thu Jul 19 20:53:33 2007 UTC revision 25 by dpavlin, Sat Aug 18 11:20:25 2007 UTC
# Line 3  Line 3 
3  # amv.pl  # amv.pl
4  #  #
5  # 07/19/07 19:21:39 CEST Dobrica Pavlinusic <dpavlin@rot13.org>  # 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;  use strict;
15    
16  use Data::Dump qw/dump/;  use Data::Dump qw/dump/;
17  use Carp qw/confess/;  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";  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: $!";  open(my $fh, '<', $path) || die "can't open $path: $!";
54    
55  # offset in file  # offset in file
# Line 20  my $o = 0; Line 59  my $o = 0;
59  my $d;  my $d;
60    
61  sub hex_dump {  sub hex_dump {
62          my $bytes = shift || return;          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;          my $ascii = $bytes;
72          $ascii =~ s/\W/./gs;          $ascii =~ s/\W/./gs;
73          my $hex = unpack('h*', $bytes);          my $hex = uc( unpack('h*', $bytes) );
74          $hex =~ s/(..)/$1 /g;          $hex =~ s/(..)/$1 /g;
75          # calculate number of characters for offset          # calculate number of characters for offset
76          #my $d = length( sprintf("%x",length($bytes)) );          #my $d = length( sprintf("%x",length($bytes)) );
77          my $d = 4;          my $d = 4;
78            my $prefix = '#.';
79          while ( $hex =~ s/^((?:\w\w\s){1,16})// ) {          while ( $hex =~ s/^((?:\w\w\s){1,16})// ) {
80                  printf "## %0${d}x | %-48s| %s\n", $o, $1, substr( $ascii, 0, 16 );                  printf "$prefix %0${d}x | %-48s| %s\n", $o, $1, substr( $ascii, 0, 16 );
81                    $prefix = '##';
82                  if ( length($ascii) >= 16 ) {                  if ( length($ascii) >= 16 ) {
83                          $ascii = substr( $ascii, 16 );                          $ascii = substr( $ascii, 16 );
84                          $o += 16;                          $o += 16;
# Line 39  sub hex_dump { Line 87  sub hex_dump {
87                          last;                          last;
88                  }                  }
89          }          }
90    
91            $o = $old_o if $old_o;
92  }  }
93    
94  sub x {  sub x {
# Line 50  sub x { Line 100  sub x {
100          my $r_len = length($bytes);          my $r_len = length($bytes);
101          confess "read $r_len bytes, expected $len" if $len != $r_len;          confess "read $r_len bytes, expected $len" if $len != $r_len;
102    
103          hex_dump( $bytes );          if ( $dump_amv ) {
104                    print "## raw $len bytes\n";
105                    hex_dump( $bytes );
106            }
107    
108          if ( $bytes eq 'AMV_END_' ) {          if ( $bytes eq 'AMV_END_' ) {
109                  warn "> end of file marker AMV_END_\n";                  print "> end of file marker AMV_END_\n" if $dump_video;
110                  $d->{eof}++;                  $d->{eof}++;
111                  return;                  return;
112          }          }
113    
114          if ( $format ) {          if ( $format ) {
115                  my @data = unpack($format, $bytes);                  my @data = unpack($format, $bytes);
116                  warn "## unpacked = ",dump(@data),"\n";                  print "## unpacked = ",dump(@data),"\n" if $debug;
117                  return @data;                  return @data;
118          } else {          } else {
119                  return $bytes;                  return $bytes;
# Line 75  sub next_part { Line 128  sub next_part {
128          if ( $expected_len ) {          if ( $expected_len ) {
129                  confess "expected $expected_len bytes for $part got $len" if $len != $expected_len;                  confess "expected $expected_len bytes for $part got $len" if $len != $expected_len;
130          }          }
131          printf ">> %s - %d 0x%x bytes\n", $part, $len, $len;          printf "## next_part %s - %d 0x%x bytes\n", $part, $len, $len if $debug;
132          x($len) if $skip;          x($len) if $skip;
133          return $len;          return $len;
134  }  }
135    
136  my ( $riff, $amv ) = x(12, 'Z8Z4');  sub quality {
137  die "not RIFF but $riff" if $riff ne 'RIFF';          my @table = @_;
138  die "not AMV but $amv" if $amv ne 'AMV ';          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}) ) {  while ( ! defined($d->{eof}) ) {
432          my ( $list, $name ) = x(12,'A4x4A4');          my ( $list, $name ) = x(12,'A4x4A4');
433          die "not LIST but $list" if $list ne 'LIST';          die "not LIST but $list" if $list ne 'LIST';
434          print "> $list .. $name\n";          print "< $list * $name\n" if $verbose;
435    
436          if ( $name eq 'hdrl' ) {          if ( $name eq 'hdrl' ) {
437    
# Line 102  while ( ! defined($d->{eof}) ) { Line 446  while ( ! defined($d->{eof}) ) {
446                  } x($len, 'Vx28VVVx8CCv');                  } x($len, 'Vx28VVVx8CCv');
447    
448                  printf "## %s %d*%d %s fps (%d ms/frame) %02d:%02d:%02d\n",                  printf "## %s %d*%d %s fps (%d ms/frame) %02d:%02d:%02d\n",
449                          $h->{path},                          $path,
450                          $h->{width}, $h->{height}, $h->{fps}, $h->{ms_per_frame},                          $h->{width}, $h->{height}, $h->{fps}, $h->{ms_per_frame},
451                          $h->{hh}, $h->{mm}, $h->{ss};                          $h->{hh}, $h->{mm}, $h->{ss};
452    
# Line 118  while ( ! defined($d->{eof}) ) { Line 462  while ( ! defined($d->{eof}) ) {
462                  while (1) {                  while (1) {
463                          my $frame = $d->{movi}++;                          my $frame = $d->{movi}++;
464                                    
465                          my $len = next_part( '00dc', 0, 1 );                          my $len = next_part( '00dc' );
466                          last unless $len;                          last unless $len;
467                          printf ">> %s 00dc - frame %d jpeg %d 0x%x bytes\n", $name, $frame, $len, $len;                          printf "<< %s 00dc - part %d jpeg %d 0x%x bytes\n", $name, $frame, $len, $len if $verbose;
468                            mkjpg( x($len) );
469    
470                          my $len = next_part( '01wb', 0, 1 );                          $len = next_part( '01wb' );
471                          printf ">> %s 01wb - frame %d audio %d 0x%x bytes\n", $name, $frame, $len, $len;                          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 {          } else {
495                  die "unknown $list $name";                  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";

Legend:
Removed from v.5  
changed lines
  Added in v.25

  ViewVC Help
Powered by ViewVC 1.1.26