1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
#!/usr/bin/env raku
sub prefix:<❱>(*@args) {
say "❱ {@args}";
my \proc = run @args, :out, :err;
.say if .chars > 0
for proc.out.slurp(:close), proc.err.slurp(:close);
}
sub prefix:<⁉>(*@args) { ❱ @args unless @args[*-1].IO.f }
sub read-exif-info(Str $file) {
my %exif;
my \proc = run 'exiftool', $file, :out;
for proc.out.slurp(:close).split("\n") -> $line {
my ($key, $val) = $line.split(':')[0,1];
next unless defined $val;
%exif{$key.trim} = $val.trim;
}
return %exif;
}
class Image {
has Str $.basename;
has Str $!source;
has Str $!dist-dir;
has Str $.camera;
submethod BUILD(IO::Path :$source, Str :$dist-dir) {
$!basename = $source.basename;
$!source = $source.path;
$!dist-dir = $dist-dir;
}
method generate(Int :$thumb-geometry, Str :$bg-blur) {
my $thumb = "$!dist-dir/thumb/{$.basename}";
⁉ [|<convert -auto-orient -geometry>, $thumb-geometry, $!source, $thumb];
my $blur = "$!dist-dir/blur/{$.basename}";
⁉ [|<convert -flip -geometry>, $thumb-geometry/4, '-blur', $bg-blur, $thumb, $blur];
my $large = "$!dist-dir/large/{$.basename}";
⁉ ['cp', $!source, $large];
}
method get-camera-model {
my %exif = read-exif-info "$!dist-dir/large/{$.basename}";
my $make = %exif<Make> ?? %exif<Make> !! '';
my $model = %exif{'Camera Model Name'} ?? %exif{'Camera Model Name'} !! '';
$model ~~ s/$make //; # Don't stotter
$model = 'Unknown camera' if $model eq '';
$!camera = "$make $model".trim;
}
method tag(Str $class) {
"<img class='{$class}' src='./{$class}/{$.basename}' />"
}
}
sub dist-dirs(Str \dist-dir --> List) { dist-dir <<~>> </large /blur /thumb> }
sub ensure-directories(Str \dist-dir) {
mkdir dist-dir unless dist-dir.IO.d;
mkdir $_ unless .IO.d for dist-dirs dist-dir;
}
sub walk-dir(Str \dir, :&onFile, :&onDir = sub (\dir) {} ) {
return unless dir.IO.d;
for dir(dir) -> $fh {
given $fh {
when .d { walk-dir $fh.path, :&onFile, :&onDir }
default { &onFile($fh.path) }
}
}
&onDir(dir);
}
sub cleanup-nonexistent (Str \dist-dir, @images) {
my $basenames = set @images.map:{ $_.basename };
walk-dir dist-dir,
onFile => sub (\file) {
unlink file if file.IO.basename ∉ $basenames
};
}
sub make-mr-proper(Str \dist-dir) {
walk-dir dist-dir,
onFile => sub (\file) { unlink file },
onDir => sub (\dir) { rmdir dir };
}
sub camera-stats(@images) {
my %cameras;
%cameras{.camera}++ for @images;
return %cameras.sort(*.value).reverse;
}
multi MAIN(
Bool :$mr-proper, #= Clean output dir
Str :$in-dir = './in', #= Input dir
Str :$dist-dir = './dist', #= Output dir
Int :$thumb-geometry = 800, #= Thumbnail geometry
Str :$bg-blur = '0x8', #= Background blur factor
Bool :$randomize = True, #= Randomize order of images
Str :$title = 'Yay', #= Album title
Int :$degree = 4, #= Degree of parallelism
) {
my @images = dir($in-dir, test => { "$in-dir/$_".IO.f }).map:{
Image.new: source => $_, :$dist-dir
};
say "Found {@images.elems} images";
@images = @images.pick: * if $randomize;
cleanup-nonexistent $dist-dir, @images;
make-mr-proper $dist-dir if $mr-proper;
ensure-directories $dist-dir;
@images.hyper(:$degree).map:{
.get-camera-model;
.generate: :$thumb-geometry, :$bg-blur;
};
.say for camera-stats @images;
}
|