#!/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}"; ⁉ [|, $thumb-geometry, $!source, $thumb]; my $blur = "$!dist-dir/blur/{$.basename}"; ⁉ [|, $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 ?? %exif !! ''; 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) { "" } } class Tag { has Str $.name; has Str $.text; has Str %.params; has Tag @.succ; method has-text returns Bool { defined $.text } method has-succ returns Bool { defined $.succ } method open returns Str {"<{$.name}{self.params}>" } method close returns Str { "" } method params returns Str { return '' unless defined %!params; my @params; for %!params.kv -> $key, $val { push @params, " $key='$val'"; } return @params.join; } } sub recurse-tags(@open-stack) { my @close-stack; for @open-stack -> $tag { say $tag.open; say $tag.text if $tag.has-text; unshift @close-stack, $tag; } for @close-stack -> $tag { say $tag.close; recurse-tags $tag.succ if $tag.has-succ; } } sub dist-dirs(Str \dist-dir --> List) { dist-dir <<~>> } 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 = 'Katana album', #= Album title Int :$degree = 4, #= Degree of parallelism ) { my @html-elems; push @html-elems, Tag.new: name => 'html', params => ( xmlns => 'http://www.w3.org/1999/xhtml', lang => 'en', 'xml:lang' => 'en' ); push @html-elems, Tag.new: name => 'header', succ => [Tag.new: name => 'body']; push @html-elems, Tag.new: name => 'title', text => $title; recurse-tags @html-elems; exit; 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; }