blib/lib/App/Dthumb.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 22 | 24 | 91.6 |
branch | n/a | ||
condition | n/a | ||
subroutine | 8 | 8 | 100.0 |
pod | n/a | ||
total | 30 | 32 | 93.7 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package App::Dthumb; | ||||||
2 | |||||||
3 | |||||||
4 | =head1 NAME | ||||||
5 | |||||||
6 | App::Dthumb - Generate thumbnail index for a set of images | ||||||
7 | |||||||
8 | =head1 SYNOPSIS | ||||||
9 | |||||||
10 | use App::Dthumb; | ||||||
11 | use Getopt::Long qw(:config no_ignore_case); | ||||||
12 | |||||||
13 | my $opt = {}; | ||||||
14 | |||||||
15 | GetOptions( | ||||||
16 | $opt, | ||||||
17 | qw{ | ||||||
18 | help|h | ||||||
19 | size|d=i | ||||||
20 | spacing|s=f | ||||||
21 | no-lightbox|L | ||||||
22 | no-names|n | ||||||
23 | quality|q=i | ||||||
24 | version|v | ||||||
25 | }, | ||||||
26 | ); | ||||||
27 | |||||||
28 | my $dthumb = App::Dthumb->new($opt); | ||||||
29 | $dthumb->run(); | ||||||
30 | |||||||
31 | =head1 VERSION | ||||||
32 | |||||||
33 | This manual documents App::Dthumb version 0.2 | ||||||
34 | |||||||
35 | =cut | ||||||
36 | |||||||
37 | |||||||
38 | 2 | 2 | 16121 | use strict; | |||
2 | 14 | ||||||
2 | 173 | ||||||
39 | 2 | 2 | 35 | use warnings; | |||
2 | 5 | ||||||
2 | 148 | ||||||
40 | 2 | 2 | 1784 | use autodie; | |||
2 | 66623 | ||||||
2 | 25 | ||||||
41 | 2 | 2 | 14597 | use 5.010; | |||
2 | 19 | ||||||
2 | 107 | ||||||
42 | |||||||
43 | 2 | 2 | 12 | use base 'Exporter'; | |||
2 | 4 | ||||||
2 | 239 | ||||||
44 | |||||||
45 | 2 | 2 | 5089 | use App::Dthumb::Data; | |||
2 | 7 | ||||||
2 | 115 | ||||||
46 | 2 | 2 | 17 | use Cwd; | |||
2 | 4 | ||||||
2 | 152 | ||||||
47 | 2 | 2 | 898 | use Image::Imlib2; | |||
0 | |||||||
0 | |||||||
48 | |||||||
49 | our @EXPORT_OK = (); | ||||||
50 | our $VERSION = '0.2'; | ||||||
51 | |||||||
52 | |||||||
53 | =head1 METHODS | ||||||
54 | |||||||
55 | =head2 new($conf) | ||||||
56 | |||||||
57 | Returns a new B |
||||||
58 | designed so that it can be directly fed by B |
||||||
59 | |||||||
60 | Valid hash keys are: | ||||||
61 | |||||||
62 | =over | ||||||
63 | |||||||
64 | =item B |
||||||
65 | |||||||
66 | Set base directory for image reading, data creation etc. | ||||||
67 | |||||||
68 | Default: F<.> (current working directory) | ||||||
69 | |||||||
70 | =item B |
||||||
71 | |||||||
72 | Set name of the html index file | ||||||
73 | |||||||
74 | Default: F |
||||||
75 | |||||||
76 | =item B |
||||||
77 | |||||||
78 | Include and use javascript lightbox code | ||||||
79 | |||||||
80 | Default: true | ||||||
81 | |||||||
82 | =item B |
||||||
83 | |||||||
84 | If true, unconditionally recreate all thumbnails. | ||||||
85 | |||||||
86 | Default: false | ||||||
87 | |||||||
88 | =item B |
||||||
89 | |||||||
90 | Maximum image size in pixels, either width or height (depending on image | ||||||
91 | orientation) | ||||||
92 | |||||||
93 | Default: 200 | ||||||
94 | |||||||
95 | =item B |
||||||
96 | |||||||
97 | Spacing between image boxes. 1.0 means each box is exactly as wide as the | ||||||
98 | maximum image width (see B |
||||||
99 | |||||||
100 | Default: 1.1 | ||||||
101 | |||||||
102 | =item B |
||||||
103 | |||||||
104 | Show image name below thumbnail | ||||||
105 | |||||||
106 | Default: true | ||||||
107 | |||||||
108 | =item B |
||||||
109 | |||||||
110 | Thumbnail image quality | ||||||
111 | |||||||
112 | Default: 75 | ||||||
113 | |||||||
114 | =back | ||||||
115 | |||||||
116 | =cut | ||||||
117 | |||||||
118 | |||||||
119 | sub new { | ||||||
120 | my ($obj, %conf) = @_; | ||||||
121 | my $ref = {}; | ||||||
122 | |||||||
123 | $conf{quality} //= 75; | ||||||
124 | $conf{recreate} //= 0; | ||||||
125 | $conf{size} //= 200; | ||||||
126 | $conf{spacing} //= 1.1; | ||||||
127 | $conf{title} //= (split(qr{/}, cwd()))[-1]; | ||||||
128 | |||||||
129 | $conf{file_index} //= 'index.xhtml'; | ||||||
130 | $conf{dir_images} //= '.'; | ||||||
131 | |||||||
132 | $conf{dir_data} = "$conf{dir_images}/.dthumb"; | ||||||
133 | $conf{dir_thumbs} = "$conf{dir_images}/.thumbs"; | ||||||
134 | |||||||
135 | # helpers to directly pass GetOptions results | ||||||
136 | $conf{lightbox} //= ( $conf{'no-lightbox'} ? 0 : 1 ); | ||||||
137 | $conf{names} //= ( $conf{'no-names'} ? 0 : 1 ); | ||||||
138 | |||||||
139 | $ref->{config} = \%conf; | ||||||
140 | |||||||
141 | $ref->{data} = App::Dthumb::Data->new(); | ||||||
142 | |||||||
143 | $ref->{data}->set_vars( | ||||||
144 | title => $conf{title}, | ||||||
145 | width => $conf{size} * $conf{spacing} . 'px', | ||||||
146 | height => $conf{size} * $conf{spacing} . 'px', | ||||||
147 | ); | ||||||
148 | |||||||
149 | $ref->{html} = $ref->{data}->get('html_start.dthumb'); | ||||||
150 | |||||||
151 | return bless($ref, $obj); | ||||||
152 | } | ||||||
153 | |||||||
154 | =head2 read_directories | ||||||
155 | |||||||
156 | Read in a list of all image files in the current directory and all files in | ||||||
157 | F<.thumbs> which do not have a corresponding full-size image. | ||||||
158 | |||||||
159 | =cut | ||||||
160 | |||||||
161 | |||||||
162 | sub read_directories { | ||||||
163 | my ($self) = @_; | ||||||
164 | my $thumbdir = $self->{config}->{dir_thumbs}; | ||||||
165 | my $imgdir = $self->{config}->{dir_images}; | ||||||
166 | my $dh; | ||||||
167 | my (@files, @old_thumbs); | ||||||
168 | |||||||
169 | opendir($dh, $imgdir); | ||||||
170 | |||||||
171 | for my $file (readdir($dh)) { | ||||||
172 | if (-f "${imgdir}/${file}" and $file =~ qr{ \. (png | jp e? g) $ }iox) { | ||||||
173 | push(@files, $file); | ||||||
174 | } | ||||||
175 | } | ||||||
176 | closedir($dh); | ||||||
177 | |||||||
178 | if (-d $thumbdir) { | ||||||
179 | opendir($dh, $thumbdir); | ||||||
180 | for my $file (readdir($dh)) { | ||||||
181 | if ($file =~ qr{^ [^.] }ox and not -f "${imgdir}/${file}") { | ||||||
182 | push(@old_thumbs, $file); | ||||||
183 | } | ||||||
184 | } | ||||||
185 | closedir($dh); | ||||||
186 | } | ||||||
187 | |||||||
188 | @{$self->{files}} = sort { lc($a) cmp lc($b) } @files; | ||||||
189 | @{$self->{old_thumbnails}} = @old_thumbs; | ||||||
190 | } | ||||||
191 | |||||||
192 | |||||||
193 | =head2 create_files | ||||||
194 | |||||||
195 | Makes sure the F<.thumbs> directory exists. | ||||||
196 | |||||||
197 | Also, if lightbox is enabled (which is the default), creates the F<.dthumb> | ||||||
198 | directory and fills it with all required files. | ||||||
199 | |||||||
200 | =cut | ||||||
201 | |||||||
202 | |||||||
203 | sub create_files { | ||||||
204 | my ($self) = @_; | ||||||
205 | my $thumbdir = $self->{config}->{dir_thumbs}; | ||||||
206 | my $datadir = $self->{config}->{dir_data}; | ||||||
207 | |||||||
208 | if (not -d $thumbdir) { | ||||||
209 | mkdir($thumbdir); | ||||||
210 | } | ||||||
211 | |||||||
212 | if ($self->{config}->{lightbox}) { | ||||||
213 | |||||||
214 | if (not -d $datadir) { | ||||||
215 | mkdir($datadir); | ||||||
216 | } | ||||||
217 | |||||||
218 | for my $file ($self->{data}->list_archived()) { | ||||||
219 | open(my $fh, '>', "${datadir}/${file}"); | ||||||
220 | print {$fh} $self->{data}->get($file); | ||||||
221 | close($fh); | ||||||
222 | } | ||||||
223 | } | ||||||
224 | } | ||||||
225 | |||||||
226 | |||||||
227 | =head2 delete_old_thumbnails | ||||||
228 | |||||||
229 | Unlink all no longer required thumbnails (as previously found by | ||||||
230 | B |
||||||
231 | |||||||
232 | =cut | ||||||
233 | |||||||
234 | |||||||
235 | sub delete_old_thumbnails { | ||||||
236 | my ($self) = @_; | ||||||
237 | my $thumbdir = $self->{config}->{dir_thumbs}; | ||||||
238 | |||||||
239 | for my $file (@{$self->{old_thumbnails}}) { | ||||||
240 | unlink("${thumbdir}/${file}"); | ||||||
241 | } | ||||||
242 | } | ||||||
243 | |||||||
244 | |||||||
245 | =head2 get_files | ||||||
246 | |||||||
247 | Returns an array of all image files found by B |
||||||
248 | |||||||
249 | =cut | ||||||
250 | |||||||
251 | |||||||
252 | sub get_files { | ||||||
253 | my ($self) = @_; | ||||||
254 | |||||||
255 | return @{$self->{files}}; | ||||||
256 | } | ||||||
257 | |||||||
258 | |||||||
259 | =head2 create_thumbnail_html($file) | ||||||
260 | |||||||
261 | Append the necessary lines for $file to the HTML. | ||||||
262 | |||||||
263 | =cut | ||||||
264 | |||||||
265 | |||||||
266 | sub create_thumbnail_html { | ||||||
267 | my ($self, $file) = @_; | ||||||
268 | my $div_width = $self->{config}->{size} * $self->{config}->{spacing}; | ||||||
269 | my $div_height = $div_width + ($self->{config}->{names} ? 10 : 0); | ||||||
270 | |||||||
271 | $self->{html} .= " \n"; |
||||||
272 | |||||||
273 | $self->{html} .= sprintf( | ||||||
274 | "\t\n" | ||||||
275 | . "\t\t\n", | ||||||
276 | ($file) x 2, | ||||||
277 | $self->{config}->{dir_thumbs}, | ||||||
278 | ($file) x 2, | ||||||
279 | ); | ||||||
280 | |||||||
281 | if ($self->{config}->{names}) { | ||||||
282 | $self->{html} .= sprintf( | ||||||
283 | "\t \n" |
||||||
284 | . "\t%s\n", | ||||||
285 | 'text-decoration: none', | ||||||
286 | ($file) x 2, | ||||||
287 | ); | ||||||
288 | } | ||||||
289 | |||||||
290 | $self->{html} .= "\n"; | ||||||
291 | } | ||||||
292 | |||||||
293 | |||||||
294 | =head2 create_thumbnail_image($file) | ||||||
295 | |||||||
296 | Load F<$file> and save a resized version in F<.thumbs/$file>. Skips thumbnail | ||||||
297 | generation if the thumbnail already exists and has a more recent mtime than | ||||||
298 | the original file. | ||||||
299 | |||||||
300 | =cut | ||||||
301 | |||||||
302 | |||||||
303 | sub create_thumbnail_image { | ||||||
304 | my ($self, $file) = @_; | ||||||
305 | my $thumbdir = $self->{config}->{dir_thumbs}; | ||||||
306 | my $thumb_dim = $self->{config}->{size}; | ||||||
307 | |||||||
308 | if ( | ||||||
309 | -e "${thumbdir}/${file}" | ||||||
310 | and not $self->{config}->{recreate} | ||||||
311 | and (stat($file))[9] <= (stat("${thumbdir}/${file}"))[9] | ||||||
312 | ) { | ||||||
313 | return; | ||||||
314 | } | ||||||
315 | |||||||
316 | my $image = Image::Imlib2->load($file); | ||||||
317 | my ($dx, $dy) = ($image->width(), $image->height()); | ||||||
318 | my $thumb = $image; | ||||||
319 | |||||||
320 | if ($dx > $thumb_dim or $dy > $thumb_dim) { | ||||||
321 | if ($dx > $dy) { | ||||||
322 | $thumb = $image->create_scaled_image($thumb_dim, 0); | ||||||
323 | } | ||||||
324 | else { | ||||||
325 | $thumb = $image->create_scaled_image(0, $thumb_dim); | ||||||
326 | } | ||||||
327 | } | ||||||
328 | |||||||
329 | $thumb->set_quality($self->{config}->{quality}); | ||||||
330 | $thumb->save("${thumbdir}/${file}"); | ||||||
331 | } | ||||||
332 | |||||||
333 | |||||||
334 | =head2 write_out_html | ||||||
335 | |||||||
336 | Write the cached HTML data to F |
||||||
337 | |||||||
338 | =cut | ||||||
339 | |||||||
340 | |||||||
341 | sub write_out_html { | ||||||
342 | my ($self) = @_; | ||||||
343 | |||||||
344 | $self->{html} .= $self->{data}->get('html_end.dthumb'); | ||||||
345 | |||||||
346 | open(my $fh, '>', $self->{config}->{file_index}); | ||||||
347 | print {$fh} $self->{html}; | ||||||
348 | close($fh); | ||||||
349 | } | ||||||
350 | |||||||
351 | sub version { | ||||||
352 | return $VERSION; | ||||||
353 | } | ||||||
354 | |||||||
355 | 1; | ||||||
356 | |||||||
357 | __END__ |