#!/usr/bin/env raku #| resize images for uploading to gallery sites unit sub MAIN( #| number of concurrent jobs (default min{10, # cores}) Int :j(:$jobs) = min(10, $*KERNEL.cpu-cores), #| formats to use for mastodon (png, webp, jpg, default, skip) :m(:$mastodon) = 'default', #| formats to use for itaku :i(:$itaku) = 'default', #| formats to use for furaffinity/weasyl :f(:$furaffinity) = 'default', #| formats to use for gallery.n.w :g(:$gallery) = 'default', #| webp/jpg quality setting (default 98) Int :Q(:$quality) = 98, #| only run conversions explicitly mentioned Bool :O(:$only) = False, Bool :n(:$dry-run) = False, Bool :v(:$verbose) = False, #| out dir, default ./out IO(Str) :o(:$outdir) = 'out'.IO, #| use a subdirectory for each site Bool :d(:$subdirs) = False, #| defaults to all *.png & *.jpg files in the current dir *@files, ); sub cmd(*@args) { say "> @args[]" if $dry-run || $verbose; run @args unless $dry-run; } sub verbose(*@args) { say "> @args[]" if $verbose } # fixup args enum Format ; sub to-formats($_, *@default) { when 'skip' { Empty } when 'default' { $only ?? Empty !! @default } when Format { $_ } .lc.split(',').map: { Format::{$_} // die qq/unrecognised format "$_"/ } } die "furaffinity/weasyl don't know what webp are" if $furaffinity ~~ /:i webp/; my @mastodon = to-formats $mastodon, webp; my @itaku = to-formats $itaku, webp; my @furaffinity = to-formats $furaffinity, png, jpg; my @gallery = to-formats $gallery, webp; unless @files.=map(*.IO) { @files = $*CWD.dir: test => /:i \.[jpe?g|png]$/ && *.IO.f; } for @files { die "$_ is not a regular file" unless .f; } # running imagemagick (and oxipng sometimes) 🪄 sub convert($in, $name, $size, $format) { my $out = $subdirs ?? $outdir.child($name).child($in.basename).extension("$format") !! $outdir.child($in.basename).extension("$name.$format"); cmd , $out.dirname; cmd , $in, '-resize', $size, '-quality', $quality, $out; cmd , $out if $format == png; $out } constant $SIZE = 2800; constant $SQUARE = "{$SIZE}x{$SIZE}>"; constant $MASTO-SIZE = "{1280²}@"; constant $GALLERY-SIZE = 3000; constant $GALLERY-SQUARE = "{$GALLERY-SIZE}x{$GALLERY-SIZE}>"; my @sites = [ {:name, :formats(@mastodon), :size($MASTO-SIZE)}, {:name, :formats(@itaku), :size($SQUARE)}, {:name, :formats(@furaffinity), :size($SQUARE)}, {:name, :formats(@gallery), :size($GALLERY-SQUARE)}, ]; # go go go my @tasks; sub task-serial(&body, :$if = True) { body if $if; } sub task-parallel(&body, :$if = True) { state $sem = Semaphore.new: $jobs; return unless $if; my $task = start { $sem.acquire; my $res = body; $sem.release; $res } @tasks.push: $task; $task } constant &task = $dry-run ?? &task-serial !! &task-parallel; my @todo = (@files X @sites).map: -> ($file, %rest) { hash :$file, %rest }; my $len = @todo.map(*.).sum; my $index; for @todo -> (:$file, :$name, :@formats, :$size) { my @outputs = @formats.map: -> $format { my $i = ++$index; my $base = $file.basename; task { say "[$i/$len] $name $base ($format)"; convert $file, $name, $size, $format; } } task if => !$dry-run && @outputs > 1, { my $keep = @outputs».=result.map({$_ => .s}).min(*.value).key; verbose "# keeping $keep"; for @outputs { next when $keep; cmd "rm", $_ } } } @tasks».result;