File Coverage

blib/lib/SWF/ForcibleConverter.pm
Criterion Covered Total %
statement 49 258 18.9
branch 10 86 11.6
condition 6 31 19.3
subroutine 15 37 40.5
pod 9 14 64.2
total 89 426 20.8


line stmt bran cond sub pod time code
1             package SWF::ForcibleConverter;
2              
3 3     3   98820 use strict;
  3         9  
  3         133  
4 3     3   19 use warnings;
  3         6  
  3         109  
5 3     3   16 use vars qw($VERSION $DEBUG);
  3         10  
  3         313  
6             $VERSION = '0.02';
7             $DEBUG = $ENV{SWF_FORCIBLECONVERTER_DEBUG};
8              
9 3     3   26 use Carp qw/croak/;
  3         5  
  3         256  
10              
11 3     3   14 use constant HEADER_SIZE => 8;
  3         5  
  3         231  
12 3     3   15 use constant MIN_BUFFER_SIZE => 4096;
  3         7  
  3         157  
13              
14 3     3   17 use vars qw($COMPRESSION_LEVEL);
  3         5  
  3         9491  
15             $COMPRESSION_LEVEL = 6;
16              
17             sub create_io_file {
18 1     1 0 1096 require IO::File; IO::File->new(@_);
  1         11836  
19             }
20              
21             sub create_io_handle {
22 1     1 0 498 require IO::Handle; IO::Handle->new(@_);
  1         7  
23             }
24              
25             sub create_io_uncompress {
26 1     1 0 2634 require IO::Uncompress::Inflate;
27 1 50       64911 IO::Uncompress::Inflate->new(@_)
28             or die "Cannot create IO::Uncompress::Inflate: $IO::Uncompress::Inflate::InflateError";
29             }
30              
31             sub create_io_compress {
32 1     1 0 2658 require IO::Compress::Deflate;
33 1 50       25794 IO::Compress::Deflate->new(@_)
34             or die "Cannot create IO::Compress::Deflate: $IO::Compress::Deflate::DeflateError";
35             }
36              
37             sub new {
38 6     6 0 1315 my $class = shift;
39 6   33     35 $class = ref $class || $class;
40            
41 6   100     31 my $args = shift || {};
42 6         11 my $self = {};
43 6         16 bless $self, $class;
44              
45             # set with validation
46 6 100       28 $self->set_buffer_size( $args->{buffer_size} )
47             if( exists $args->{buffer_size} );
48              
49             # stash
50 4         18 $self->{_r_io} = undef; # handle for reading
51 4         10 $self->{_w_io} = undef; # handle for writing
52 4         7 $self->{_header} = undef; # original header HEADER_SIZE bytes
53              
54             # you have to customize and set these chunks before drain()
55 4         8 $self->{_header_v9} = undef; # header for output editing on
56 4         7 $self->{_first_chunk} = undef; # the first chunk just behind header
57              
58 4         17 return $self;
59             }
60              
61             sub buffer_size {
62 10     10 1 263 my $self = shift;
63 10 100       65 return @_ ? $self->{buffer_size} = shift : $self->{buffer_size};
64             }
65              
66             sub set_buffer_size {
67 8     8 1 6233 my $self = shift;
68 8         11 my $size = shift;
69 8 100 100     40 croak "size of buffer is @{[ MIN_BUFFER_SIZE ]} necessity at least"
  5         654  
70             if( ! $size or $size < MIN_BUFFER_SIZE );
71 3         8 $self->buffer_size( $size );
72             }
73              
74             sub get_buffer_size {
75 4 100   4 1 12 shift->buffer_size || MIN_BUFFER_SIZE;
76             }
77              
78             #-----------------------------------------------------------
79             #
80             #
81              
82             sub _open_r {
83 0     0     my $self = shift;
84 0           my $file = shift; # or STDIN
85 0 0         unless( $self->{_r_io} ){
86 0           my $io;
87 0 0         if( defined $file ){
88 0 0         if( ref($file) ){
89 0           $io = $file; # it sets opend file handle that is a "IO"
90             }else{
91 0           $io = create_io_file;
92 0 0         $io->open($file,"r") or die "Cannot open $file for reading: $!";
93             }
94             }else{
95 0           $io = create_io_handle;
96 0 0         $io->fdopen(fileno(STDIN),"r") or die "Cannot open STDIN: $!";
97             }
98 0           $self->{_r_io} = $io;
99              
100             # clear keeping header because input is reopend,
101             # it may be the other resource
102 0           $self->{_header} = undef;
103 0           $self->{_header_v9} = undef;
104 0           $self->{_first_chunk} = undef;
105             }
106 0           return $self->{_r_io};
107             }
108              
109             sub _open_w {
110 0     0     my $self = shift;
111 0           my $file = shift; # or STDOUT
112 0 0         unless( $self->{_w_io} ){
113 0           my $io;
114 0 0         if( defined $file ){
115 0           $io = create_io_file;
116 0 0         $io->open($file,"w") or die "Cannot open $file for writing: $!";
117             }else{
118 0           $io = create_io_handle;
119 0 0         $io->fdopen(fileno(STDOUT),"w") or die "Cannot open STDOUT: $!";
120             }
121 0           $self->{_w_io} = $io;
122             }
123 0           return $self->{_w_io};
124             }
125              
126             sub _close_r {
127 0     0     my $self = shift;
128 0 0         if( $self->{_r_io} ){
129 0           $self->{_r_io}->close;
130 0           $self->{_r_io} = undef;
131             }
132             }
133              
134             sub _close_w {
135 0     0     my $self = shift;
136 0 0         if( $self->{_w_io} ){
137 0           $self->{_w_io}->close;
138 0           $self->{_w_io} = undef;
139             }
140             }
141              
142             sub _switch_input_handle_to_uncompress {
143 0     0     my $self = shift;
144 0           my $input = shift;
145 0           $self->{_r_io} = create_io_uncompress($input);
146             }
147              
148             sub _switch_output_handle_to_compress {
149 0     0     my $self = shift;
150 0           my $output = shift;
151 0           $self->{_w_io} = create_io_compress($output, Append => 1, -Level => $COMPRESSION_LEVEL );
152             }
153              
154             sub _version {
155 0     0     my $self = shift;
156 0           my $header = shift;
157 0           ord(substr($header, 3, 1));
158             }
159              
160             sub _is_compressed {
161 0     0     my $self = shift;
162 0           my $header = shift;
163 0           substr($header, 0, 1) eq "\x43";
164             }
165              
166             sub _modify_custom_header_to_version_9 {
167 0     0     my $self = shift;
168 0           my $h = $self->{_header_v9};
169 0           substr($h, 3, 1, "\x09");
170 0           $self->{_header_v9} = $h;
171             }
172              
173             sub _modify_custom_header_to_uncompressed {
174 0     0     my $self = shift;
175 0           my $h = $self->{_header_v9};
176 0           substr($h, 0, 1, "\x46"); # "F"WS
177 0           $self->{_header_v9} = $h;
178             }
179              
180             sub _modify_custom_header_to_compressed {
181 0     0     my $self = shift;
182 0           my $h = $self->{_header_v9};
183 0           substr($h, 0, 1, "\x43"); # "C"WS
184 0           $self->{_header_v9} = $h;
185             }
186              
187             #-----------------------------------------------------------
188             # io methods
189             #
190              
191             sub _read_header {
192 0     0     my $self = shift;
193 0           my $input = shift;
194 0           my $r = $self->_open_r($input);
195              
196             # skip if it already read header
197 0 0         unless( $self->{_header} ){
198            
199             # read header, 8 bytes from othe rigin
200 0           my $header;
201 0           my $size = $r->read($header, HEADER_SIZE);
202 0 0 0       die "Failed to read the header" if( ! defined $size or $size != HEADER_SIZE );
203              
204 0           $self->{_header} = $header; # keep for reuse
205 0           $self->{_header_v9} = $header;
206             }
207            
208 0           return $self->{_header};
209             }
210              
211             sub _read_first_chunk {
212 0     0     my $self = shift;
213 0           my $input = shift;
214 0           my $r = $self->_open_r($input);
215 0           my $readsize= $self->get_buffer_size;
216              
217 0 0 0       if( ! $self->{_header} and $self->{_first_chunk} ){
218 0           croak "It tried to read the first chunk although the header was not read";
219             }
220              
221 0 0         if( $self->_is_compressed($self->_read_header($input)) ){
222 0           $r = $self->_switch_input_handle_to_uncompress($r);
223 0           $self->_modify_custom_header_to_uncompressed;
224             }
225              
226 0 0         unless( $self->{_first_chunk} ){
227              
228 0           my $chunk = undef;
229 0           my $size = $r->read($chunk, $readsize);
230 0 0 0       if( ! defined $size or ( $size != $readsize and ! $r->eof ) ){
      0        
231 0 0         die "Failed to read the first chunk (@{[ defined $size ? $size : 'undef' ]})";
  0            
232             }
233 0           $self->{_first_chunk} = $chunk;
234             }
235            
236 0           return $self->{_first_chunk};
237             }
238              
239             sub _drain {
240 0     0     my $self = shift;
241 0           my $input = shift;
242 0           my $output = shift;
243 0   0       my $options = shift || {};
244            
245 0 0 0       my $force_cws = ($options->{cws} || $options->{cws} ? 1 : 0);
246 0 0 0       my $force_fws = ($options->{fws} || $options->{fws} ? 1 : 0);
247              
248             # ready to output
249 0           my $w;
250             my $writer;
251 0 0         if( ref($output) eq 'CODE' ){
252 0           $writer = $output;
253             }else{
254 0           $w = $self->_open_w($output);
255             $writer = sub {
256 0     0     $w->print($_[0]);
257 0           };
258             }
259 0           my $total = 0;
260              
261             # choose format of output as cws or fws
262 0 0         my $to_compress = $self->_is_compressed($self->{_header}) ? 1 : 0;
263 0 0         if( $force_cws != $force_fws ){
264 0 0         $to_compress = 1 if( $force_cws );
265 0 0         $to_compress = 0 if( $force_fws );
266             }
267              
268             # print the header that is always uncompressed 8 bytes
269 0 0         if( $to_compress ){
270 0           $self->_modify_custom_header_to_compressed;
271 0           $writer->($self->{_header_v9});
272 0           $total += length $self->{_header_v9};
273 0           $w = $self->_switch_output_handle_to_compress( $w );
274             }else{
275 0           $writer->($self->{_header_v9});
276 0           $total += length $self->{_header_v9};
277             }
278              
279             # print out buffered data
280 0           for my $chunk ( @{$self->{_first_chunk}} ){
  0            
281 0 0         if( ref $chunk eq 'SCALAR' ){
282 0           $writer->($$chunk); $total += length $$chunk;
  0            
283             }else{
284 0           $writer->( $chunk); $total += length $chunk;
  0            
285             }
286             }
287            
288             # print out unread data
289 0           my $r = $self->_open_r($input);
290 0           my $readsize = $self->get_buffer_size;
291 0           while( ! $r->eof ){
292 0           my $buf;
293 0           my $size = $r->read($buf, $readsize);
294 0 0 0       if( ! defined $size or $size != $readsize ){
295 0 0         if( ! $r->eof ){
296 0           die "Failed to read a chunk";
297             }
298             }
299 0           $writer->($buf);
300 0           $total += length $buf;
301             }
302              
303             # drain() can be called once
304 0           $self->_close_w;
305 0           $self->_close_r;
306              
307 0           return $total;
308             }
309              
310             #-----------------------------------------------------------
311             # main public utilities
312             #
313              
314             sub version {
315 0     0 1   my $self = shift;
316 0           my $input = shift;
317 0           $self->_version($self->_read_header($input));
318             }
319              
320             sub is_compressed {
321 0     0 1   my $self = shift;
322 0           my $input = shift;
323 0           $self->_is_compressed($self->_read_header($input));
324             }
325              
326             #-----------------------------------------------------------
327             # main jobs with drain will close handles
328             #
329              
330             sub uncompress {
331 0     0 1   my $self = shift;
332 0           my $input = shift;
333 0           my $output = shift;
334              
335 0           my $first = $self->_read_first_chunk($input);
336 0           $self->{_first_chunk} = [\$first];
337 0           $self->_drain($input, $output, { fws => 1 });
338             }
339              
340             sub _get_body_position { # function for ->covert9()
341 0     0     my $the_9th = shift; # 9th char
342              
343 0           my $result = 0;
344 0           $result += 3; # "FWS" or "CWS"
345 0           $result += 1; # version
346 0           $result += 4; # length
347              
348 0           my $rectNBits = int( ord($the_9th) >> 3 ); # unsigned right shift
349 0           $result += int( (5 + $rectNBits * 4) / 8 ); # stage(rect)
350 0           $result += 2; # ?
351 0           $result += 1; # frame rate
352 0           $result += 2; # total frames
353            
354 0           return $result;
355             }
356              
357             sub convert9 {
358 0     0 1   my $self = shift;
359 0           my $input = shift;
360 0           my $output = shift;
361 0           my $options;
362 0 0         if( scalar @_ == 1 ){
363 0           $options = shift @_;
364             }else{
365 0           my %opts = @_;
366 0           $options = \%opts;
367             }
368              
369             # prepare
370 0           my $header = $self->_read_header($input);
371 0           my $buf_size = $self->get_buffer_size;
372              
373             # read first chunk that includes info for body position
374 0           my $first = $self->_read_first_chunk($input);
375 0           my $pos = _get_body_position(substr($first, 0, 1));
376              
377             # read and write header with updating the version to 9
378 0           my $version = $self->_version($header);
379              
380 0 0         if( $version < 9 ){
381 0           $self->_modify_custom_header_to_version_9;
382             }
383              
384 0           my $total = 0;
385 0 0         if( 9 <= $version ){
386             # simply, copy (but uncompressed)
387 0           $self->{_first_chunk} = [\$first];
388 0           $total += $self->_drain($input, $output, $options);
389              
390             }else{
391            
392 0           my $result = undef;
393 0           my $offset = $pos - HEADER_SIZE;
394 0 0         if( 8 == $version ){
395             # find file attributes position
396              
397             # require Config;
398 0           my $shortsize = 2; # $Config::Config{shortsize};
399 0           my $intsize = 4; # $Config::Config{intsize};
400              
401 0           my $currentp = $offset;
402 0           while( 1 ){
403 0 0         last if( length $first < $currentp - HEADER_SIZE );
404 0           my $short = unpack "x${currentp}s", $first;
405 0           my $tag = $short >> 6;
406 0 0         if( $tag == 69 ){
407 0           $result = $currentp;
408 0           last;
409             }
410 0           $currentp += 2;
411            
412 0           my $len = $short & 0x3f;
413 0 0         if( $len == 0x3f ){
414 0           $len = unpack "x${currentp}i", $first;
415 0           $currentp += $intsize;
416             }
417 0           $currentp += $len;
418             }
419             }
420              
421 0 0         if( defined $result ){
422            
423 0           my $attr_pos = $result + 2 - HEADER_SIZE;
424            
425 0           my $target = unpack('C', substr($first, $attr_pos, 1));
426 0           $target |= 0x08;
427 0           substr($first, $attr_pos, 1, pack('C',$target));
428              
429 0           $self->{_first_chunk} = [\$first];
430 0           $total += $self->_drain($input, $output, $options);
431              
432             }else{
433              
434 0           $self->{_first_chunk} = [
435             substr($first, 0, $offset),
436             "\x44\x11\x08\x00\x00\x00",
437             substr($first, $offset),
438             ];
439 0           $total += $self->_drain($input, $output, $options);
440             }
441             }
442            
443 0           return $total;
444             }
445              
446             sub convert9_compress {
447 0     0 1   my $self = shift;
448 0           my $input = shift;
449 0           my $output = shift;
450 0           $self->convert9($input, $output, { cws => 1 });
451             }
452              
453             sub convert9_uncompress {
454 0     0 1   my $self = shift;
455 0           my $input = shift;
456 0           my $output = shift;
457 0           $self->convert9($input, $output, { fws => 1 });
458             }
459              
460             1;
461             __END__