#!/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) = Nil, #| formats to use for furaffinity/weasyl :f(:$furaffinity) = Nil, #| formats to use for gallery.n.w :g(:$gallery) = Nil, #| formats to use for bluesky :b(:$bluesky) = Nil, #| webp/jpg quality setting (default 98) Int :Q(:$quality) = 98, #| only run conversions explicitly mentioned Bool :O(:$only) = False, #| don't actually do anything (implies -v) Bool :n(:$dry-run) = False, #| print every action Bool :v(:$verbose) = False, #| out dir (default ./out) IO(Str) :o(:$outdir) = 'out'.IO, #| output as e.g. thing.gallery.webp instead of gallery/thing.webp Bool :F(:$flat) = False, #| background colour to use for jpg Str :B(:$background) = 'white', #| files to resize (default *.jpg *.png *.webp) *@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' { @default } when Nil { $only ?? Empty !! @default } when Format { $_ } default { .lc.split(',').map: { Format::{$_} // die "unrecognised format '$_'" } } } my @mastodon = to-formats $mastodon, webp; # my @itaku = to-formats $itaku, webp; my @furaffinity = to-formats $furaffinity, png; my @gallery = to-formats $gallery, png,webp; my @bluesky = to-formats $bluesky, jpg; die "furaffinity/weasyl don't know what webp are" if webp ∈ @furaffinity; die "at the time of writing, bsky converts everything to jpg" if @bluesky and @bluesky !eqv [jpg]; if @files { @files .= map(*.IO) } else { @files = $*CWD.dir: test => /:i \.[jpe?g|png|webp]$/ && *.IO.f } for @files { die "$_ is not a regular file" unless .f; } # running imagemagick (and oxipng sometimes) 🪄 sub make-filename($in, $base, $format) { $flat ?? $outdir.child($in.basename).extension("$base.$format") !! $outdir.child($base).child($in.basename).extension("$format") } multi convert($in, @names where .elems >= 1, $size, $format) { my $name = @names[0]; my $out = make-filename($in, $name, $format); my @bg-flags = «-background $background -alpha remove» if $format == jpg or $name eq 'bluesky'; cmd , $out.dirname; cmd , $in, '-resize', $size, '-quality', $quality, @bg-flags, $out; cmd , $out if $format == png; for @names[1..*] { my $link = make-filename($in, $_, $format); cmd , $link.dirname; cmd , $out, $link; } $out } multi convert($in, $name, $size, $format) { convert($in, [$name], $size, $format); } sub square($s) { "{$s}x{$s}>" } sub pixels($s) { "{$s}@" } my @sites = [ {:name«fedi itaku», formats => @mastodon, size => square 2800}, {:name, formats => @furaffinity, size => square 1920}, {:name, formats => @furaffinity, size => square 2800}, {:name, formats => @gallery, size => square 3000}, {:name, formats => @bluesky, size => square 2000}, ]; # 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 { ENTER $sem.acquire; LEAVE $sem.release; body } @tasks.push: $task; $task } constant &task = $dry-run ?? &task-serial !! &task-parallel; my @todo = (@files X @sites).map: { hash :file(.[0]), |.[1] } 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;