/[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 3 by dpavlin, Thu Jul 19 20:17:37 2007 UTC revision 27 by dpavlin, Sat Aug 18 11:21:40 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    # http://wiki.multimedia.cx/index.php?title=IMA_ADPCM
14    
15  use strict;  use strict;
16    
17  use Data::Dump qw/dump/;  use Data::Dump qw/dump/;
18  use Carp qw/confess/;  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";  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: $!";  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 {  sub hex_dump {
63          my $bytes = shift || return;          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;          my $ascii = $bytes;
73          $ascii =~ s/\W/./gs;          $ascii =~ s/\W/./gs;
74          my $hex = unpack('h*', $bytes);          my $hex = uc( unpack('h*', $bytes) );
75          $hex =~ s/(..)/$1 /g;          $hex =~ s/(..)/$1 /g;
         my $o = 0;  
76          # calculate number of characters for offset          # calculate number of characters for offset
77          my $d = length( sprintf("%x",length($bytes)) );          #my $d = length( sprintf("%x",length($bytes)) );
78            my $d = 4;
79            my $prefix = '#.';
80          while ( $hex =~ s/^((?:\w\w\s){1,16})// ) {          while ( $hex =~ s/^((?:\w\w\s){1,16})// ) {
81                  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 );
82                    $prefix = '##';
83                  if ( length($ascii) >= 16 ) {                  if ( length($ascii) >= 16 ) {
84                          $ascii = substr( $ascii, 16 );                          $ascii = substr( $ascii, 16 );
85                            $o += 16;
86                  } else {                  } else {
87                            $o += length($ascii);
88                          last;                          last;
89                  }                  }
                 $o += 16;  
90          }          }
91    
92            $o = $old_o if $old_o;
93  }  }
94    
95  sub x {  sub x {
# Line 43  sub x { Line 101  sub x {
101          my $r_len = length($bytes);          my $r_len = length($bytes);
102          confess "read $r_len bytes, expected $len" if $len != $r_len;          confess "read $r_len bytes, expected $len" if $len != $r_len;
103    
104          hex_dump( $bytes );          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 ) {          if ( $format ) {
116                  my @data = unpack($format, $bytes);                  my @data = unpack($format, $bytes);
117                  dump(@data);                  print "## unpacked = ",dump(@data),"\n" if $debug;
118                  return @data;                  return @data;
119          } else {          } else {
120                  return $bytes;                  return $bytes;
# Line 57  sub x { Line 124  sub x {
124  sub next_part {  sub next_part {
125          my ( $expected_part, $expected_len, $skip ) = @_;          my ( $expected_part, $expected_len, $skip ) = @_;
126          my ( $part, $len ) = x(8,'A4V');          my ( $part, $len ) = x(8,'A4V');
127            return unless $len;
128          confess "not $expected_part but $part" if $expected_part ne $part;          confess "not $expected_part but $part" if $expected_part ne $part;
129          if ( $expected_len ) {          if ( $expected_len ) {
130                  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;
131          }          }
132          printf ">> %s | %d 0x%x bytes\n", $part, $len, $len;          printf "## next_part %s - %d 0x%x bytes\n", $part, $len, $len if $debug;
133          x($len) if $skip;          x($len) if $skip;
134          return $len;          return $len;
135  }  }
136    
137  my ( $riff, $amv ) = x(12, 'Z8Z4');  sub quality {
138  die "not RIFF but $riff" if $riff ne 'RIFF';          my @table = @_;
139  die "not AMV but $amv" if $amv ne 'AMV ';          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  my $d;          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  while ( 1 ) {          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');          my ( $list, $name ) = x(12,'A4x4A4');
434          die "not LIST but $list" if $list ne 'LIST';          die "not LIST but $list" if $list ne 'LIST';
435          print "> $list .. $name\n";          print "< $list * $name\n" if $verbose;
436    
437          if ( $name eq 'hdrl' ) {          if ( $name eq 'hdrl' ) {
438    
439                  my $len = next_part( 'amvh', hex(38) );                  my $len = next_part( 'amvh', hex(38) );
           
                 print ">> $name $len\n";  
440    
                 my (  
                         $ms_per_frame,  
                         $width,  
                         $height,  
                         $fps,  
                         $ss,  
                         $mm,  
                         $hh,  
                 ) =  
441                  my @names = ( qw/ms_per_frame width height fps ss mm hh/ );                  my @names = ( qw/ms_per_frame width height fps ss mm hh/ );
442                  my $h;                  my $h;
443                  map {                  map {
# Line 101  while ( 1 ) { Line 447  while ( 1 ) {
447                  } x($len, 'Vx28VVVx8CCv');                  } x($len, 'Vx28VVVx8CCv');
448    
449                  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",
450                          $h->{path},                          $path,
451                          $h->{width}, $h->{height}, $h->{fps}, $h->{ms_per_frame},                          $h->{width}, $h->{height}, $h->{fps}, $h->{ms_per_frame},
452                          $h->{hh}, $h->{mm}, $h->{ss};                          $h->{hh}, $h->{mm}, $h->{ss};
453    
# Line 112  while ( 1 ) { Line 458  while ( 1 ) {
458                  next_part( 'strh', 0, 1 );                  next_part( 'strh', 0, 1 );
459                  next_part( 'strf', 0, 1 );                  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 {          } else {
496                  die "unknown $list $name";                  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";

Legend:
Removed from v.3  
changed lines
  Added in v.27

  ViewVC Help
Powered by ViewVC 1.1.26