File Coverage

blib/lib/Image/TextMode/SAUCE.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Image::TextMode::SAUCE;
2              
3 1     1   2091 use Moose;
  0            
  0            
4              
5             # some SAUCE constants
6             my $SAUCE_ID = 'SAUCE';
7             my $SAUCE_VERSION = '00';
8             my $SAUCE_FILLER = ' ' x 22;
9             my $COMNT_ID = 'COMNT';
10              
11             =head1 NAME
12              
13             Image::TextMode::SAUCE - Create, manipulate and save SAUCE metadata
14              
15             =head1 DESCRIPTION
16              
17             This module reads and writes SAUCE metadata. SAUCE metadata is a 128-byte
18             record stored after an EOF char at the end of a given file.
19              
20             =head1 ACCESSORS
21              
22             =over 4
23              
24             =item * sauce_id - identified at the start of the record (default: SAUCE)
25              
26             =item * version - sauce version (default: 00)
27              
28             =item * title - title of the work
29              
30             =item * author - author name
31              
32             =item * group - group affiliation
33              
34             =item * date - YYYYMMDD date (default: today's date)
35              
36             =item * filesize - the size of the file, less sauce info
37              
38             =item * datatype_id - numeric identifier for the data type
39              
40             =item * filetype_id - numeric identifier for the file sub-type
41              
42             =item * tinfo1 - first slot of filetype-specific info
43              
44             =item * tinfo2 - second slot of filetype-specific info
45              
46             =item * tinfo3 - third slot of filetype-specific info
47              
48             =item * tinfo4 - fourth slot of filetype-specific info
49              
50             =item * comment_count - number of comments stored before the sauce record
51              
52             =item * flags_id - datatype specific flags
53              
54             =item * filler - 22 spaces to fill in the remaining bytes
55              
56             =item * comment_id - identifier for comments section (default: COMNT)
57              
58             =item * comments - array ref of comment lines
59              
60             =item * has_sauce - undef before read; after read: true if file has sauce record
61              
62             =back
63              
64             =cut
65              
66             has 'sauce_id' => ( is => 'rw', isa => 'Str', default => sub { $SAUCE_ID } );
67              
68             has 'version' =>
69             ( is => 'rw', isa => 'Str', default => sub { $SAUCE_VERSION } );
70              
71             has 'title' => ( is => 'rw', isa => 'Str', default => sub { '' } );
72              
73             has 'author' => ( is => 'rw', isa => 'Str', default => sub { '' } );
74              
75             has 'group' => ( is => 'rw', isa => 'Str', default => sub { '' } );
76              
77             has 'date' => (
78             is => 'rw',
79             isa => 'Str',
80             default => sub {
81             my @t = ( localtime )[ 5, 4, 3 ];
82             return sprintf '%4d%02d%02d', 1900 + $t[ 0 ], $t[ 1 ] + 1, $t[ 2 ];
83             }
84             );
85              
86             has 'filesize' => ( is => 'rw', isa => 'Int', default => sub { 0 } );
87              
88             has 'filetype_id' => ( is => 'rw', isa => 'Int', default => sub { 0 } );
89              
90             has 'datatype_id' => ( is => 'rw', isa => 'Int', default => sub { 0 } );
91              
92             has 'tinfo1' => ( is => 'rw', isa => 'Int', default => sub { 0 } );
93              
94             has 'tinfo2' => ( is => 'rw', isa => 'Int', default => sub { 0 } );
95              
96             has 'tinfo3' => ( is => 'rw', isa => 'Int', default => sub { 0 } );
97              
98             has 'tinfo4' => ( is => 'rw', isa => 'Int', default => sub { 0 } );
99              
100             has 'comment_count' => ( is => 'rw', isa => 'Int', default => sub { 0 } );
101              
102             has 'flags_id' => ( is => 'rw', isa => 'Int', default => sub { 0 } );
103              
104             has 'filler' =>
105             ( is => 'rw', isa => 'Str', default => sub { $SAUCE_FILLER } );
106              
107             has 'comment_id' =>
108             ( is => 'rw', isa => 'Str', default => sub { $COMNT_ID } );
109              
110             has 'comments' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] } );
111              
112             has 'has_sauce' => ( is => 'rw', isa => 'Bool' );
113              
114             # define datatypes and filetypes as per SAUCE specs
115             my @datatypes
116             = qw(None Character Graphics Vector Sound BinaryText XBin Archive Executable);
117             my $filetypes = {
118             None => {
119             filetypes => [ 'Undefined' ],
120             flags => [ 'None' ]
121             },
122             Character => {
123             filetypes =>
124             [ qw( ASCII ANSi ANSiMation RIP PCBoard Avatar HTML Source ) ],
125             flags => [ 'None', 'iCE Color' ],
126             tinfo => [
127             ( { tinfo1 => 'Width', tinfo2 => 'Height' } ) x 3,
128             { tinfo1 => 'Width', tinfo2 => 'Height', tinfo3 => 'Colors' },
129             ( { tinfo1 => 'Width', tinfo2 => 'Height' } ) x 2
130             ]
131             },
132             Graphics => {
133             filetypes => [
134             qw( GIF PCX LBM/IFF TGA FLI FLC BMP GL DL WPG PNG JPG MPG AVI )
135             ],
136             flags => [ 'None' ],
137             tinfo => [
138             ( { tinfo1 => 'Width',
139             tinfo2 => 'Height',
140             tinfo3 => 'Bits Per Pixel'
141             }
142             ) x 14
143             ]
144             },
145             Vector => {
146             filetypes => [ qw( DXF DWG WPG 3DS ) ],
147             flags => [ 'None' ],
148             },
149             Sound => {
150             filetypes => [
151             qw( MOD 669 STM S3M MTM FAR ULT AMF DMF OKT ROL CMF MIDI SADT VOC WAV SMP8 SMP8S SMP16 SMP16S PATCH8 PATCH16 XM HSC IT )
152             ],
153             flags => [ 'None' ],
154             tinfo => [ ( {} ) x 16, ( { tinfo1 => 'Sampling Rate' } ) x 4 ]
155             },
156             BinaryText => {
157             filetypes => [ qw( Undefined ) ],
158             flags => [ 'None', 'iCE Color' ],
159             },
160             XBin => {
161             filetypes => [ qw( Undefined ) ],
162             flags => [ 'None' ],
163             tinfo => [ { tinfo1 => 'Width', tinfo2 => 'Height' }, ]
164             },
165             Archive => {
166             filetypes => [ qw( ZIP ARJ LZH ARC TAR ZOO RAR UC2 PAK SQZ ) ],
167             flags => [ 'None' ],
168             },
169             Executable => {
170             filetypes => [ qw( Undefined ) ],
171             flags => [ 'None' ],
172             }
173             };
174              
175             # vars for use with pack() and unpack()
176             my $sauce_template = 'A5 A2 A35 A20 A20 A8 V C C v v v v C C A22';
177             my @sauce_fields
178             = qw( sauce_id version title author group date filesize datatype_id filetype_id tinfo1 tinfo2 tinfo3 tinfo4 comment_count flags_id filler );
179             my $comnt_template = 'A5 A64';
180             my @comnt_fields = qw( comment_id comments );
181              
182             =head1 METHODS
183              
184             =head2 new( %args )
185              
186             Creates a new SAUCE metadata instance.
187              
188             =head2 read( $fh )
189              
190             Read the sauce record from C<$fh>.
191              
192             =cut
193              
194             sub read { ## no critic (Subroutines::ProhibitBuiltinHomonyms)
195             my ( $self, $fh ) = @_;
196              
197             my $buffer;
198             my %info;
199              
200             seek( $fh, 0, 2 );
201             return if tell $fh < 128;
202              
203             seek( $fh, -128, 2 );
204             my $size = read( $fh, $buffer, 128 );
205              
206             if ( substr( $buffer, 0, 5 ) ne $SAUCE_ID ) {
207             $self->has_sauce( 0 );
208             return;
209             }
210              
211             @info{ @sauce_fields } = unpack( $sauce_template, $buffer );
212              
213             # because trailing spaces are stripped....
214             $info{ filler } = $SAUCE_FILLER;
215              
216             # Do we have any comments?
217             my $comment_count = $info{ comment_count };
218              
219             $self->$_( $info{ $_ } ) for keys %info;
220             $self->has_sauce( 1 );
221              
222             if ( $comment_count > 0 ) {
223             seek( $fh, -128 - 5 - $comment_count * 64, 2 );
224             read( $fh, $buffer, 5 + $comment_count * 64 );
225              
226             if ( substr( $buffer, 0, 5 ) eq $COMNT_ID ) {
227             my $template
228             = $comnt_template
229             . ( split( / /s, $comnt_template ) )[ 1 ]
230             x ( $comment_count - 1 );
231             my ( $id, @comments ) = unpack( $template, $buffer );
232             $self->comment_id( $id );
233             $self->comments( \@comments );
234             }
235             }
236             }
237              
238             =head2 write( $fh )
239              
240             Write the sauce record to C<$fh>.
241              
242             =cut
243              
244             sub write { ## no critic (Subroutines::ProhibitBuiltinHomonyms)
245             my ( $self, $fh ) = @_;
246              
247             seek( $fh, 0, 2 );
248             print $fh chr( 26 );
249              
250             # comments...
251             my $comments = scalar @{ $self->comments };
252             if ( $comments ) {
253             print $fh pack(
254             $comnt_template
255             . (
256             ( split( / /s, $comnt_template ) )[ 1 ] x ( $comments - 1 )
257             ),
258             $self->comment_id,
259             @{ $self->comments }
260             );
261             }
262              
263             # SAUCE...
264             my @template = split( / /s, $sauce_template );
265             for ( 0 .. $#sauce_fields ) {
266             my $field = $sauce_fields[ $_ ];
267             my $value = ( $field ne 'comments' ) ? $self->$field : $comments;
268             print $fh pack( $template[ $_ ], $value );
269             }
270              
271             }
272              
273             =head2 record_size( )
274              
275             Return the size of the SAUCE record in bytes.
276              
277             =cut
278              
279             sub record_size {
280             my $self = shift;
281              
282             return 0 unless $self->has_sauce;
283              
284             my $size = 128;
285              
286             if( $self->comment_count ) {
287             $size += 5 + ( 64 * $self->comment_count );
288             }
289              
290             return $size;
291             }
292              
293             =head2 datatype( )
294              
295             The string name of the data represented in datatype_id.
296              
297             =cut
298              
299             sub datatype {
300             return $datatypes[ $_[ 0 ]->datatype_id || 0 ];
301             }
302              
303             =head2 filetype( )
304              
305             The string name of the data represented in filetype_id.
306              
307             =cut
308              
309             sub filetype {
310             return $filetypes->{ $_[ 0 ]->datatype }->{ filetypes }
311             ->[ $_[ 0 ]->filetype_id || 0 ];
312             }
313              
314             =head2 flags( )
315              
316             The string name of the data represented in flags_id.
317              
318             =cut
319              
320             sub flags {
321             return $filetypes->{ $_[ 0 ]->datatype }->{ flags }
322             ->[ $_[ 0 ]->flags_id ];
323             }
324              
325             =head2 tinfo1_name( )
326              
327             The string name of the data represented in tinfo1.
328              
329             =cut
330              
331             sub tinfo1_name {
332             return $filetypes->{ $_[ 0 ]->datatype }->{ tinfo }
333             ->[ $_[ 0 ]->filetype_id ]->{ tinfo1 };
334             }
335              
336             =head2 tinfo2_name( )
337              
338             The string name of the data represented in tinfo2.
339              
340             =cut
341              
342             sub tinfo2_name {
343             return $filetypes->{ $_[ 0 ]->datatype }->{ tinfo }
344             ->[ $_[ 0 ]->filetype_id ]->{ tinfo2 };
345             }
346              
347             =head2 tinfo3_name( )
348              
349             The string name of the data represented in tinfo3.
350              
351             =cut
352              
353             sub tinfo3_name {
354             return $filetypes->{ $_[ 0 ]->datatype }->{ tinfo }
355             ->[ $_[ 0 ]->filetype_id ]->{ tinfo3 };
356             }
357              
358             =head2 tinfo4_name( )
359              
360             The string name of the data represented in tinfo4.
361              
362             =cut
363              
364             sub tinfo4_name {
365             return $filetypes->{ $_[ 0 ]->datatype }->{ tinfo }
366             ->[ $_[ 0 ]->filetype_id ]->{ tinfo4 };
367             }
368              
369             no Moose;
370              
371             __PACKAGE__->meta->make_immutable;
372              
373             =head1 AUTHOR
374              
375             Brian Cassidy E<lt>bricas@cpan.orgE<gt>
376              
377             =head1 COPYRIGHT AND LICENSE
378              
379             Copyright 2008-2013 by Brian Cassidy
380              
381             This library is free software; you can redistribute it and/or modify
382             it under the same terms as Perl itself.
383              
384             =cut
385              
386             1;