File Coverage

blib/lib/File/Properties/Media.pm
Criterion Covered Total %
statement 21 100 21.0
branch 0 36 0.0
condition 0 9 0.0
subroutine 7 18 38.8
pod 7 7 100.0
total 35 170 20.5


line stmt bran cond sub pod time code
1             # ----------------------------------------------------------------------------
2             #
3             # This module provides cached access to file SHA-2 digests and mime
4             # types. Additional information is available for bzip2 and gzip
5             # compressed files, and digital media files.
6             #
7             # Copyright © 2010,2011 Brendt Wohlberg
8             # See distribution LICENSE file for license details.
9             #
10             # Most recent modification: 18 December 2011
11             #
12             # ----------------------------------------------------------------------------
13              
14             package File::Properties::Media;
15             our $VERSION = 0.02;
16              
17 2     2   3478 use File::Properties::Compressed;
  2         9  
  2         84  
18 2     2   13 use base qw(File::Properties::Compressed);
  2         4  
  2         1451  
19              
20             require 5.005;
21 2     2   15 use strict;
  2         5  
  2         71  
22 2     2   12 use warnings;
  2         4  
  2         67  
23 2     2   12 use Error qw(:try);
  2         4  
  2         20  
24 2     2   14449 use Storable qw(freeze thaw);
  2         17612  
  2         215  
25 2     2   8426 use Image::ExifTool;
  2         175733  
  2         3413  
26              
27              
28             our $CacheTableName = 'MediaFileCache';
29             our $CacheTableCols = ['ContentDigest TEXT','MediaMimeType TEXT',
30             'MediaFileType TEXT','MediaType TEXT',
31             'DateModified DATE', 'ExifHash BLOB'];
32              
33              
34             # ----------------------------------------------------------------------------
35             # Initialiser
36             # ----------------------------------------------------------------------------
37             sub _init {
38 0     0     my $self = shift;
39 0           my $path = shift; # File path or File::Properties::Generic reference
40 0           my $fpcr = shift; # File::Properties::Cache reference
41              
42             # Initialisation for base
43 0           $self->SUPER::_init($path, $fpcr);
44             ## Remainder of initialisation only necessary for regular files (in
45             ## particular, it should not be performed for directories)
46 0 0         if ($self->isreg) {
47             ## Initialisation is complicated because it is only possible to
48             ## reliably determine whether a file is a media file *after* using
49             ## Image::ExifTool to determine its mime type (the mime type
50             ## returned by Properties::Regular is less reliable for this
51             ## purpose). The strategy to avoid inefficient multiple uses of
52             ## Image::ExifTool (potentially resulting in uncompressing a
53             ## compressed file more than once) is as follows. If the
54             ## already-initialised base part of the object is marked as having
55             ## been retrieved from the cache, assume that the file has been
56             ## previously seen, and would therefore already have a media file
57             ## cache entry if it were a media file: try to retrieve the media
58             ## file details from the cache, and assume it is not a media file
59             ## if the retrieval fails. If the already-initialised base part
60             ## was not retrieved from the cache, assume the file has not been
61             ## previously seen, and use Image::ExifTool to determine its
62             ## medial file properties, which are then inserted into the media
63             ## file cache.
64 0 0         if ($self->_fromcache($File::Properties::Regular::CacheTableName)) {
65 0 0         if (my $cent = $fpcr->cretrieve($CacheTableName,
66             {'ContentDigest' => $self->SUPER::cdigest})) {
67 0           $self->mmimetype($cent->{'MediaMimeType'});
68 0           $self->mfiletype($cent->{'MediaFileType'});
69 0           $self->mediatype($cent->{'MediaType'});
70 0           $self->datemod($cent->{'DateModified'});
71 0           $self->exifhash(thaw $cent->{'ExifHash'});
72             # Set flag indicating that this entry was obtained from the cache
73 0           $self->_fromcache($CacheTableName, 1);
74             }
75             } else {
76             ## Attempt to extract EXIF properties from file content
77 0           my $exft = new Image::ExifTool;
78 0           my $info = $exft->ImageInfo($self->cfilehandle, qw(*),
79             {PrintConv => 1,
80             DateFormat => "%Y-%m-%d %H:%M:%S",
81             CoordFormat => "%.8f"});
82 0           my $ierr = $exft->GetValue('Error');
83             ## If attempt to extract EXIF properties fails with error 'Unknown
84             ## file type', then the file is not a media file and the general
85             ## file properties are returned. If the attempt fails with any
86             ## other error, throw an exception.
87 0 0 0       throw File::Properties::Error("ExifTool error: ".$ierr, $exft)
88             if (defined $ierr and $ierr ne 'Unknown file type');
89              
90 0 0         if (not defined $ierr) {
91             ## Determine media mime type, file type, and media type from EXIF data
92 0           $self->mmimetype($exft->GetValue('MIMEType'));
93 0           $self->mfiletype($exft->GetValue('FileType'));
94 0           my $mtyp = $self->mmimetype;
95 0           $mtyp =~ s+\/.*$++;
96 0           $self->mediatype($mtyp);
97 0           $self->datemod(_fixdatestr($info->{'ModifyDate'}));
98             ## Construct hash of EXIF tag data and freeze it for storage in cache
99 0           my $exfh = {};
100 0           my ($tag, $group, $val);
101 0           foreach $tag ($exft->GetFoundTags('Group0')) {
102 0           $group = $exft->GetGroup($tag);
103 0           $val = $info->{$tag};
104 0 0 0       $exfh->{"$group:".Image::ExifTool::GetTagName($tag)} = $val
105             if (defined $val and not ref($val));
106             }
107 0           $self->exifhash($exfh);
108              
109             # Set flag indicating that this entry was not obtained from the cache
110 0           $self->_fromcache($CacheTableName, 0);
111              
112 0 0         if (defined $fpcr) {
113 0           my $row = {'ContentDigest' => $self->SUPER::cdigest,
114             'MediaMimeType' => $self->mmimetype,
115             'MediaFileType' => $self->mfiletype,
116             'MediaType' => $self->mediatype,
117             'DateModified' => $self->datemod,
118 0           'ExifHash' => freeze \%{$self->exifhash}};
119 0           $fpcr->cinsert($CacheTableName, $row);
120             }
121             }
122             }
123             }
124             }
125              
126              
127             # ----------------------------------------------------------------------------
128             # Get (or set) media mime type
129             # ----------------------------------------------------------------------------
130             sub mmimetype {
131 0     0 1   my $self = shift;
132              
133 0 0         $self->{'mmtp'} = shift if (@_);
134 0 0         return (defined $self->{'mmtp'})?$self->{'mmtp'}:$self->cmimetype;
135             }
136              
137              
138             # ----------------------------------------------------------------------------
139             # Get (or set) media file type
140             # ----------------------------------------------------------------------------
141             sub mfiletype {
142 0     0 1   my $self = shift;
143              
144 0 0         $self->{'mftp'} = shift if (@_);
145 0           return $self->{'mftp'};
146             }
147              
148              
149             # ----------------------------------------------------------------------------
150             # Get (or set) media type (initial part of mime type, e.g. 'image')
151             # ----------------------------------------------------------------------------
152             sub mediatype {
153 0     0 1   my $self = shift;
154              
155 0 0         $self->{'mtyp'} = shift if (@_);
156 0           return $self->{'mtyp'};
157             }
158              
159              
160             # ----------------------------------------------------------------------------
161             # Get (or set) EXIF modification date
162             # ----------------------------------------------------------------------------
163             sub datemod {
164 0     0 1   my $self = shift;
165              
166 0 0         $self->{'mddt'} = shift if (@_);
167 0           return $self->{'mddt'};
168             }
169              
170              
171             # ----------------------------------------------------------------------------
172             # Get (or set) hash of EXIF tags and values
173             # ----------------------------------------------------------------------------
174             sub exifhash {
175 0     0 1   my $self = shift;
176              
177 0 0         $self->{'exif'} = shift if (@_);
178 0           return $self->{'exif'};
179             }
180              
181              
182             # ----------------------------------------------------------------------------
183             # Determine whether file properties represent a media file
184             # ----------------------------------------------------------------------------
185             sub ismedia {
186 0     0 1   my $self = shift;
187              
188 0   0       return (defined $self->mediatype and ($self->mediatype eq 'image' or
189             $self->mediatype eq 'video' or
190             $self->mediatype eq 'audio'));
191             }
192              
193              
194             # ----------------------------------------------------------------------------
195             # Construct string representing properties hash
196             # ----------------------------------------------------------------------------
197             sub string {
198 0     0 1   my $self = shift;
199 0           my $levl = shift;
200              
201 0 0         $levl = 0 if (!defined $levl);
202 0           my $lpfx = ' ' x (2*$levl);
203 0           my $s = $self->SUPER::string($levl);
204 0 0         if ($self->ismedia) {
205 0           $s .= $lpfx . " Media Mime Type: ".$self->mmimetype."\n";
206 0           $s .= $lpfx . " Media File Type: ".$self->mfiletype."\n";
207 0           $s .= $lpfx . " Media Type: ".$self->mediatype."\n";
208 0 0         $s .= $lpfx .
209             " Date Modified: ".((defined $self->datemod)?$self->datemod:'')."\n";
210             }
211 0           return $s;
212             }
213              
214              
215             # ----------------------------------------------------------------------------
216             # Initialise cache table for File::Properties::Media data
217             # ----------------------------------------------------------------------------
218             sub _cacheinit {
219 0     0     my $self = shift;
220 0           my $fpcr = shift; # File::Properties::Cache reference
221 0           my $opts = shift; # Options hash
222              
223 0           $self->SUPER::_cacheinit($fpcr, $opts);
224 0           $fpcr->define($CacheTableName, $CacheTableCols,
225             {'TableVersion' => [__PACKAGE__.'::Version', $VERSION]});
226             }
227              
228              
229             # ----------------------------------------------------------------------------
230             # Clear invalid entries in cache table for File::Properties::Media data
231             # ----------------------------------------------------------------------------
232             sub _cacheclean {
233 0     0     my $self = shift;
234 0           my $fpcr = shift; # File::Properties::Cache reference
235              
236 0           my $mtbl = $CacheTableName;
237 0           my $ctbl = $File::Properties::Compressed::CacheTableName;
238             # Remove any entries in the File::Properties::Media cache table
239             # for which there is not a corresponding entry with the same content
240             # digest in the File::Properties::Compressed cache table
241 0           $fpcr->remove($mtbl, {'Where' => "NOT EXISTS (SELECT * FROM $ctbl " .
242             "WHERE ContentDigest = $mtbl.ContentDigest)"});
243             }
244              
245              
246             # ----------------------------------------------------------------------------
247             # Standardise date format from EXIF data
248             # ----------------------------------------------------------------------------
249             sub _fixdatestr {
250 0     0     my $dstr = shift;
251              
252 0 0         return undef if (not defined $dstr);
253 0 0         if ($dstr =~ /(\d+)[^\d](\d+)[^\d](\d+)[^\d](\d+)[^\d](\d+)[^\d](\d+)/) {
254 0           $dstr = "$1-$2-$3 $4:$5:$6";
255             }
256              
257 0           return $dstr;
258             }
259              
260              
261             # ----------------------------------------------------------------------------
262             # End of method definitions
263             # ----------------------------------------------------------------------------
264              
265              
266             1;
267             __END__