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