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__ |