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