File Coverage

blib/lib/IO/SWF/Editor.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package IO::SWF::Editor;
2              
3 1     1   25811 use strict;
  1         2  
  1         38  
4 1     1   5 use warnings;
  1         3  
  1         51  
5              
6             our ( $VERSION );
7             $VERSION = '0.04_01';
8              
9 1     1   5 use base 'IO::SWF';
  1         2  
  1         607  
10             use IO::SWF::Bit;
11             use IO::SWF::Tag::Shape;
12             use IO::SWF::Tag::Action;
13             use IO::SWF::Tag::Sprite;
14             use IO::SWF::Bitmap;
15             use IO::SWF::Lossless;
16             use Compress::Zlib;
17              
18             =head1
19              
20             IO::SWF::Editor - Parse and edit SWF binary by Perl.
21              
22             =head1 SYNOPSIS
23              
24             use IO::SWF::Editor;
25              
26             my $swf = IO::SWF::Editor->new();
27             $swf->parse($swf_binary);
28              
29             print $swf->build();
30              
31             =head1 VERSION
32              
33             This document references version 0.04_01 of IO::SWF::Editor, released
34             to CPAN on Aug 10, 2011.
35              
36             =head1 DESCRIPTION
37              
38             IO::SWF::Editor provides to parse and edit SWF binary.
39              
40             USAGE
41             If you want to replace buried image in SWF to another image,
42             you can do it easily.
43              
44             my $swf = IO::SWF::Editor->new();
45             $swf->parse($swf_binary);
46             $swf->setCharacterId();
47             $swf->replaceBitmapData($character_id, $blob);
48              
49             print $swf->build();
50              
51             For more details, look at each methods' document.
52              
53             =cut
54              
55             =head1 METHODS
56              
57             =item $swf->setCharacterId()
58              
59             Set characterId for specify tag.
60              
61             =cut
62              
63             use constant {
64             SHAPE_BITMAP_NONE => 0,
65             SHAPE_BITMAP_MATRIX_RESCALE => 1,
66             SHAPE_BITMAP_RECT_RESIZE => 2,
67             SHAPE_BITMAP_TYPE_TILED => 4,
68             };
69              
70             sub new {
71             my ($class, $args) = @_;
72             my $self;
73             if(ref $args eq 'HASH') {
74             $self = $class->SUPER::new($args);
75             }else{
76             $self = $class->SUPER::new();
77             }
78             $self->shape_adjust_mode(SHAPE_BITMAP_NONE);
79             return $self;
80             }
81              
82             sub rebuild {
83             my $self = shift;
84             foreach my $tag (@{$self->_tags}) {
85             if ($tag->parseTagContent()) {
86             $tag->content('');
87             $tag->buildTagContent();
88             }
89             }
90             }
91              
92             sub setCharacterId {
93             my $self = shift;
94             foreach my $tag (@{$self->_tags}) {
95             my $content_reader = IO::SWF::Bit->new();
96             $content_reader->input($tag->content);
97             if (
98             $tag->code == 6 || # DefineBits
99             $tag->code == 21 || # DefineBitsJPEG2
100             $tag->code == 35 || # DefineBitsJPEG3
101             $tag->code == 20 || # DefineBitsLossless
102             $tag->code == 36 || # DefineBitsLossless2
103             $tag->code == 46 || # DefineMorphShape
104             $tag->code == 2 || # DefineShape (ShapeId)
105             $tag->code == 22 || # DefineShape2 (ShapeId)
106             $tag->code == 32 || # DefineShape3 (ShapeId)
107             $tag->code == 11 || # DefineText
108             $tag->code == 33 || # DefineText2
109             $tag->code == 37 || # DefineTextEdit
110             $tag->code == 39 # DefineSprite
111             ) {
112             $tag->characterId($content_reader->getUI16LE());
113             }
114             }
115             }
116              
117             sub setReferenceId {
118             my $self = shift;
119             foreach my $tag (@{$self->_tags}) {
120             my $content_reader = IO::SWF::Bit->new();
121             $content_reader->input($tag->content);
122             if ($tag->code == 4 || # 4: // PlaceObject
123             $tag->code == 5 # 5: // RemoveObject
124             ) {
125             $tag->referenceId($content_reader->getUI16LE());
126             }
127             elsif ($tag->code == 26) { # 26: // PlaceObject2 (Shape Reference)
128             $tag->placeFlag($content_reader->getUI8());
129             if ($tag->placeFlag & 0x02) {
130             $tag->referenceId($content_reader->getUI16LE());
131             }
132             }
133             elsif ($tag->code == 2 || # 2: // DefineShape (Bitmap ReferenceId)
134             $tag->code == 22 || # 22: // DefineShape2  (Bitmap ReferenceId)
135             $tag->code == 32 || # 32: // DefineShape3 (Bitmap ReferenceId)
136             $tag->code == 46 # 46: // DefineMorphShape (Bitmap ReferenceId)
137             ) {
138             die "setReferenceId DefineShape not implemented yet.";
139             }
140              
141             }
142             }
143              
144             =item $swf->replaceTagContent()
145              
146             Replace content by tagCode.
147              
148             =cut
149             sub replaceTagContent {
150             my ($self, $tagCode, $content, $limit) = @_;
151             $limit ||= 1;
152             my $count = 0;
153             foreach my $tag (@{$self->_tags}) {
154             if ($tag->code == $tagCode) {
155             $tag->content($content);
156             $count += 1;
157             if ($limit <= $count) {
158             last;
159             }
160             }
161             }
162             return $count;
163             }
164              
165             =item $swf->getTagContent()
166              
167             Get content by tagCode.
168              
169             =cut
170             sub getTagContent {
171             my ($self, $tagCode) = @_;
172             foreach my $tag (@{$self->_tags}) {
173             if ($tag->code == $tagCode) {
174             return $tag->content;
175             }
176             }
177             return '';
178             }
179              
180             =item $swf->replaceTagContentByCharacterId()
181              
182             Replace content by tagCode and characterId.
183             You must call setCharacterId() before call this method.
184              
185             =cut
186             sub replaceTagContentByCharacterId {
187             my ($self, $tagCode, $characterId, $content_after_character_id) = @_;
188             if (ref($tagCode) ne 'ARRAY') {
189             $tagCode = [$tagCode];
190             }
191             my $ret = 0;
192             foreach my $tag (@{$self->_tags}) {
193             my $code = $tag->code;
194             if (grep(/^$code\z/, @{$tagCode}) && $tag->characterId) {
195             if ($tag->characterId == $characterId) {
196             $tag->content(pack('v', $characterId).$content_after_character_id);
197             $ret = 1;
198             last;
199             }
200             }
201             }
202             return $ret;
203             }
204              
205             =item $swf->replaceTagByCharacterId()
206              
207             Replace Tag by tagCode and characterId.
208             You must call setCharacterId() before call this method.
209              
210             =cut
211             sub replaceTagByCharacterId {
212             my ($self, $tagCode, $characterId, $replaceTag_href) = @_;
213             my %replaceTag = ref($replaceTag_href) ? %{$replaceTag_href} : ();
214             if (ref($tagCode) ne 'ARRAY') {
215             $tagCode = [$tagCode];
216             }
217             my $ret = 0;
218             foreach my $tag (@{$self->_tags}) {
219             my $code = $tag->code;
220             if (grep(/^$code\z/, @{$tagCode}) && $tag->characterId) {
221             if ($tag->characterId == $characterId) {
222             if ($replaceTag{'Code'}) {
223             $tag->code($replaceTag{'Code'});
224             }
225             $tag->length(length($replaceTag{'Content'}));
226             $tag->content($replaceTag{'Content'});
227             $ret = 1;
228             last;
229             }
230             }
231             }
232             return $ret;
233             }
234              
235             sub replaceBitmapTagByCharacterId {
236             my ($self, $tagCode, $characterId, $replaceTag_href) = @_;
237             my %replaceTag = ref($replaceTag_href) ? %{$replaceTag_href} : ();
238             if (ref($tagCode) ne 'ARRAY') {
239             $tagCode = [$tagCode];
240             }
241             my $ret = 0;
242             foreach my $tag (@{$self->_tags}) {
243             my $code = $tag->code;
244             if (grep(/^$code\z/, @{$tagCode}) && $tag->characterId) {
245             if ($tag->characterId == $characterId) {
246             if ($replaceTag{'Code'}) {
247             $tag->code($replaceTag{'Code'});
248             }
249             $tag->length(length($replaceTag{'Content'}));
250             $tag->content($replaceTag{'Content'});
251             $ret = 1;
252             last;
253             }
254             }
255             }
256             return $ret;
257             }
258              
259             =item $swf->getTagContentByCharacterId()
260              
261             Get content by tagCode and characterId.
262             You must call setCharacterId() before call this method.
263              
264             =cut
265             sub getTagContentByCharacterId {
266             my ($self, $tagCode, $characterId) = @_;
267             foreach my $tag (@{$self->_tags}) {
268             if (($tag->code == $tagCode) && $tag->characterId) {
269             if ($tag->characterId == $characterId) {
270             return $tag->content;
271             last;
272             }
273             }
274             }
275             return '';
276             }
277              
278             =item $swf->deformeShape()
279              
280             Decrease Shape's edges.
281              
282             =cut
283             sub deformeShape {
284             my ($self, $threshold) = @_;
285             foreach my $tag (@{$self->_tags}) {
286             my $code = $tag->code;
287             if ($code == 2 || $code == 22 || $code == 32) {
288             # 2: // DefineShape
289             # 22: // DefineShape2
290             # 32: // DefineShape3
291             my $shape = IO::SWF::Tag::Shape->new();
292             $shape->parseContent($code, $tag->content);
293             $shape->deforme($threshold);
294             $tag->content($shape->buildContent($code));
295             }
296             }
297             }
298              
299             sub setActionVariables {
300             my ($self, $trans_table_or_key_str, $value_str) = @_;
301             my (%trans_table, $action, $tag, $code);
302             if (ref($trans_table_or_key_str) eq 'HASH') {
303             %trans_table = %{$trans_table_or_key_str};
304             }
305             else {
306             %trans_table = ( $trans_table_or_key_str => $value_str );
307             }
308              
309             my $tagidx = 0;
310             foreach my $tag_local (@{$self->_tags}) {
311             $code = $tag_local->code;
312             if ($code == 12 || # 12: // DoAction
313             $code == 59 # 59: // DoInitAction
314             ) {
315             $action = IO::SWF::Tag::Action->new();
316             $action->parseContent($code, $tag_local->content);
317             $tag = $tag_local;
318             last;
319             }
320             if ($code == 1) {
321             $tag = $tag_local;
322             last;
323             }
324             $tagidx++;
325             }
326             if (!$action) {
327             # create new ActionTag at first frame
328             my $bytecode = '';
329             foreach my $key_str (keys %trans_table) {
330             my $value_str = $trans_table{$key_str};
331             my @key_strs = split("\0", $key_str); # delete \0
332             my @value_strs = split("\0", $value_str); # delete \0
333             my $key_data = chr(0).$key_strs[0]."\0";
334             my $value_data = chr(0).$value_strs[0]."\0";
335             # Push
336             $bytecode .= chr(0x96).pack('v', length($key_data)).$key_data;
337             # Push
338             $bytecode .= chr(0x96).pack('v', length($value_data)).$value_data;
339             # SetVarables
340             $bytecode .= chr(0x1d);
341             # End
342             $bytecode .= chr(0);
343             }
344             my $tag_action = IO::SWF::Tag->new();
345             $tag_action->code(12); # DoAction
346             $tag_action->content($bytecode);
347             # insert new ActionTag
348             my @tags = @{$self->_tags};
349             my @new_tags = @tags[0 .. $tagidx];
350             my @sufix = @tags[$tagidx+1 .. $#tags];
351             push @new_tags, $tag_action;
352             push @new_tags, @sufix;
353             $self->_tags(\@new_tags);
354             }
355             else {
356             # create new_tag
357             my @let_action;
358             foreach my $key_str (keys %trans_table) {
359             my $value_str = $trans_table{$key_str};
360             push @let_action, {'Code' => 0x96, # Push
361             'Values' => [
362             { 'Type' => 0, 'String' => $key_str },
363             ],
364             };
365             push @let_action, {'Code' => 0x96, # Push
366             'Values' => [
367             { 'Type' => 0, 'String' => $value_str },
368             ],
369             };
370             push @let_action, {'Code' => 0x1d }; # SetVariable
371             }
372             push @let_action, @{$action->_actions};
373             $action->_actions(\@let_action);
374              
375             $tag->content($action->buildContent($code));
376             }
377             }
378              
379             =item $swf->replaceActionStrings()
380              
381             =cut
382             sub replaceActionStrings {
383             my ($self, $trans_table_or_from_str, $value_str) = @_;
384             my %trans_table;
385             if (ref($trans_table_or_from_str) eq 'HASH') {
386             %trans_table = %{$trans_table_or_from_str};
387             }
388             else {
389             %trans_table = ( $trans_table_or_from_str => $value_str );
390             }
391             foreach my $tag (@{$self->_tags}) {
392             my $code = $tag->code;
393             if ($code == 12) {
394             # 12: // DoInitAction
395             my $action = IO::SWF::Tag::Action->new();
396             $action->parseContent($code, $tag->content);
397             $action->replaceActionStrings(\%trans_table);
398             $tag->content($action->buildContent($code));
399             }
400             elsif ($code == 39) {
401             # 39: // Sprite
402             my $sprite = IO::SWF::Tag::Sprite->new();
403             $sprite->parseContent($code, $tag->content);
404             foreach my $tag_in_sprite (@{$sprite->_controlTags}) {
405             my $code_in_sprite = $tag_in_sprite->code;
406             if ($code_in_sprite == 12) {
407             # 12: // DoInitAction
408             my $action_in_sprite = IO::SWF::Tag::Action->new();
409             $action_in_sprite->parseContent($code_in_sprite, $tag_in_sprite->content);
410             $action_in_sprite->replaceActionStrings(\%trans_table);
411             $tag_in_sprite->content($action_in_sprite->buildContent($code_in_sprite));
412             }
413             }
414             $tag->content($sprite->buildContent($code));
415             }
416             }
417             }
418              
419             =item $swf->replaceBitmapData()
420              
421             =cut
422             sub replaceBitmapData {
423             my ($self, $bitmap_id, $bitmap_data, $jpeg_alphadata) = @_;
424             my (%tag, $new_width, $new_height, $ret);
425             if ($bitmap_data =~ /^GIF/ ||
426             $bitmap_data =~ /^\x89PNG/) {
427             %tag = IO::SWF::Lossless::BitmapData2Lossless($bitmap_id, $bitmap_data);
428             $new_width = $tag{width};
429             $new_height = $tag{height};
430             } elsif ($bitmap_data =~ /\xff\xd8\xff/) {
431             my $erroneous_header = pack('CCCC', 0xFF, 0xD9, 0xFF, 0xD8);
432             if (!defined $jpeg_alphadata) {
433             # 21: DefineBitsJPEG2
434             my $content = pack('v', $bitmap_id).$erroneous_header.$bitmap_data;
435             %tag = ('Code' => 21,
436             'Content' => $content);
437             }
438             else {
439             # 35: DefineBitsJPEG3
440             my $jpeg_data = $erroneous_header.$bitmap_data;
441             my $compressed_alphadata = Compress::Zlib::memGzip($jpeg_alphadata);
442             my $content = pack('v', $bitmap_id).pack('V', length($jpeg_data)).$jpeg_data.$compressed_alphadata;
443             %tag = ('Code' => 35,
444             'Content' => $content);
445             }
446             ($new_width, $new_height) = IO::SWF::Bitmap::get_jpegsize($bitmap_data);
447             }
448             else {
449             # Unknown Bitmap Format
450             die "Unknown Bitmap Format: ".(pack('h', substr($bitmap_data, 0, 4)));
451             }
452             if ($self->shape_adjust_mode > 0) {
453             $ret = $self->applyShapeAdjustModeByRefId($bitmap_id, $new_width, $new_height);
454             }
455             # DefineBits,DefineBitsJPEG2,3, DefineBitsLossless,DefineBitsLossless2
456             my @tag_code = (6, 21, 35, 20, 36);
457             if ($self->shape_adjust_mode > 0) {
458             $tag{'shape_adjust_mode'} = $self->shape_adjust_mode;
459             }
460             $ret = $self->replaceBitmapTagByCharacterId(\@tag_code, $bitmap_id, \%tag);
461             # $ret = $self->replaceTagByCharacterId(\@tag_code, $bitmap_id, \%tag);
462             return $ret;
463             }
464              
465             sub applyShapeAdjustModeByRefId {
466             my ($self, $bitmap_id, $new_height, $old_height) = @_;
467             my $shape_adjust_mode = $self->shape_adjust_mode;
468             if ($shape_adjust_mode == SHAPE_BITMAP_NONE) {
469             return 0;
470             }
471             elsif ($shape_adjust_mode == SHAPE_BITMAP_MATRIX_RESCALE ||
472             $shape_adjust_mode == SHAPE_BITMAP_RECT_RESIZE ||
473             $shape_adjust_mode == SHAPE_BITMAP_TYPE_TILED
474             ) {
475             #
476             }
477             else {
478             # "Illegal shape_adjust_mode($shape_adjust_mode)"
479             return 0;
480             }
481              
482             if ($shape_adjust_mode == SHAPE_BITMAP_MATRIX_RESCALE) {
483             }
484             elsif ($shape_adjust_mode == SHAPE_BITMAP_RECT_RESIZE) {
485             }
486             elsif ($shape_adjust_mode == SHAPE_BITMAP_TYPE_TILED) {
487             }
488             else {
489             # "Illegal shape_adjust_mode($shape_adjust_mode)"
490             return 0;
491             }
492             return 1;
493             }
494              
495             =item $swf->countShapeEdges()
496            
497             =cut
498             sub countShapeEdges {
499             my ($self, $opts_href) = @_;
500             my %count_table = ();
501             foreach my $tag (@{$self->_tags}) {
502             my $code = $tag->code;
503             if ($code == 2 || $code == 22 || $code == 32 || $code == 46) {
504             # 2: // DefineShape
505             # 22: // DefineShape2
506             # 32: // DefineShape3
507             # 46: // DefineMorphShape
508             my $shape = IO::SWF::Tag::Shape->new();
509             $shape->parseContent($code, $tag->content);
510             my ($shape_id, $edges_count) = $shape->countEdges();
511             $count_table{$shape_id} = $edges_count;
512             }
513             }
514             return %count_table;
515             }
516              
517             sub setShapeAdjustMode {
518             my ($self, $mode) = @_;
519             $self->shape_adjust_mode($mode);
520             }
521              
522             1;