121 lines
3.5 KiB
Raku
Executable file
121 lines
3.5 KiB
Raku
Executable file
#!/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,
|
|
#| output as e.g. thing.mastodon.webp instead of mastodon/thing.webp
|
|
Bool :F(:$flat) = False,
|
|
Str :B(:$background) = 'white',
|
|
#| 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 <png webp jpg>;
|
|
|
|
sub to-formats($_, *@default) {
|
|
when 'skip' { Empty }
|
|
when 'default' { $only ?? Empty !! @default }
|
|
when Format { $_ }
|
|
.lc.split(',').map: { Format::{$_} // die qq/unrecognised format "$_"/ }
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
die "furaffinity/weasyl don't know what webp are" if @furaffinity.any == 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 = $flat
|
|
?? $outdir.child($in.basename).extension("$name.$format")
|
|
!! $outdir.child($name).child($in.basename).extension("$format");
|
|
my @bg-flags = «-background $background -alpha remove» if $format == jpg;
|
|
cmd <mkdir -p>, $out.dirname;
|
|
cmd <convert>, $in, '-resize', $size, '-quality', $quality, @bg-flags, $out;
|
|
cmd <oxipng -q>, $out if $format == png;
|
|
$out
|
|
}
|
|
|
|
sub square($s) { "{$s}x{$s}>" }
|
|
|
|
my @sites = [
|
|
{:name<mastodon>, :formats(@mastodon), :size("{1280²}@")},
|
|
{:name<itaku>, :formats(@itaku), :size(square 2800)},
|
|
{:name<furaffinity>, :formats(@furaffinity), :size(square 1500)},
|
|
{:name<gallery>, :formats(@gallery), :size(square 3000)},
|
|
];
|
|
|
|
|
|
# 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(*.<formats>).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;
|