aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README1
-rwxr-xr-xbsgrep512
l---------bsjoin1
-rwxr-xr-xfind_key48
-rw-r--r--image_diff.pl58
-rwxr-xr-xlitterbox465
-rwxr-xr-xtermbin44
-rwxr-xr-xtolatin8
-rwxr-xr-xtunebass83
-rwxr-xr-xuleft24
-rw-r--r--unifmt.pl3
-rwxr-xr-xvol110
-rwxr-xr-xztunekey76
-rw-r--r--ztunekey.desktop9
-rw-r--r--ztunekey.pngbin0 -> 55391 bytes
15 files changed, 1357 insertions, 85 deletions
diff --git a/README b/README
index 0215e2a..2e69696 100644
--- a/README
+++ b/README
@@ -21,6 +21,7 @@ gammatrip.sh - randomly change gamma in X, crude simulation of an acid trip
imagebin - pastebin an image
lddsafe - https://github.com/rg3/lddsafe/ (local copy here)
lddtree - show library dependencies as a tree
+litterbox - upload files/directories to litter.catbox.moe
mkclick.pl - generate a click track at a given tempo
netqiv - wget and display an image file
noobfarm2fortune.sh - scrape noobfarm.org, create fortune(6) file (no longer works!)
diff --git a/bsgrep b/bsgrep
new file mode 100755
index 0000000..19ca3aa
--- /dev/null
+++ b/bsgrep
@@ -0,0 +1,512 @@
+#!/usr/bin/perl -w
+
+$VERSION = "0.0.1";
+
+use Getopt::Std;
+use File::Find;
+
+($self = $0) =~ s,.*/,,;
+
+%printed = ();
+
+$SIG{__WARN__} = sub {
+ my $m = shift;
+
+ # don't include the line number in warnings.
+ $m =~ s/ at \S+ line \d+\.$//;
+
+ # File::Find seems to use double newlines for its warnings..
+ $m =~ s/\n\n+/\n/;
+
+ # warnings that don't start with $self: are e.g. file access errors
+ # from the 'while(<>)' or File::Find.
+ if($m !~ /^$self:/) {
+ $m = "$self: $m";
+ $ret = 2 unless $opt{q};
+ }
+
+ print STDERR $m unless $opt{s};
+};
+
+sub grep_options {
+ my @nargv;
+ my $was_e;
+
+ # first, grab all the -e options and remove them from @ARGV,
+ # because Getopt::Std doesn't support multiple occurrences of
+ # a flag with different args. probably it would be better to
+ # use Getopt::Long, but for now this works.
+ for(@ARGV) {
+ if($was_e) {
+ push @patterns, $_;
+ $was_e = 0;
+ } elsif($_ eq '-e') {
+ $was_e = 1;
+ } elsif($_ =~ /-e(.*)/) {
+ push @patterns, $1;
+ } else {
+ push @nargv, $_;
+ }
+ }
+ @ARGV = @nargv;
+
+ getopts('d:FiklnNqrsvwz', \%opt) || exit 1;
+}
+
+sub print_line {
+ print "$ARGV:" if $filecount > 1;
+ print "$start_line:" if $opt{n};
+ print $_[0];
+ print $opt{z} ? "\0" : "\n";
+}
+
+sub join_options {
+ getopts('d:knwz', \%opt) || exit 1;
+}
+
+sub handle_line {
+ my $match = 0;
+
+ for my $pat (@patterns) {
+ if($opt{v}) {
+ $match++ if $out !~ /$pat/;
+ } else {
+ $match++ if $out =~ /$pat/;
+ }
+ }
+
+ return unless $match;
+
+ if($opt{N}) {
+ return unless $match == @patterns;
+ }
+
+ $ret = 0 if $ret == 1;
+ return if $opt{q};
+
+ if($opt{l}) {
+ if(!$printed{$ARGV}++) {
+ print "$ARGV\n";
+ }
+ } else {
+ print_line($out);
+ }
+}
+
+### main()
+# TODO: do we need 'use locale'?
+# also, why does reading iso-8859-1 text auto-convert to utf-8?
+for (qw/LANG LC_CTYPE LC_ALL/) {
+ if(($ENV{$_} // "") =~ /utf-?8/i) {
+ binmode(\*STDIN, ':utf8');
+ binmode(\*STDOUT, ':utf8');
+ last;
+ }
+}
+
+if(defined($ARGV[0])) {
+ if($ARGV[0] =~ /-help/) {
+ exec "perldoc $0";
+ exit(1);
+ } elsif($ARGV[0] eq '--man') {
+ exec "pod2man --stderr -s1 -cUrchlaysStuff -r$VERSION -u $0";
+ exit(1);
+ } elsif($ARGV[0] eq '--version') {
+ print "bsgrep $VERSION\n";
+ exit(0);
+ }
+}
+
+if($self =~ /join/) {
+ join_options();
+ push @patterns, '^'; # every string has a beginning...
+} else {
+ grep_options();
+
+ if(!@patterns) {
+ if(!($patterns[0] = shift)) {
+ grep_usage();
+ die("$self: missing required pattern argument\n");
+ }
+ }
+
+ map { $_ = quotemeta } @patterns if $opt{F};
+ map { $_ = "(?i)$_" } @patterns if $opt{i};
+}
+
+if($opt{r}) {
+ @ARGV = (".") unless @ARGV;
+ for(@ARGV) {
+ if(-d $_) {
+ find({
+ wanted => sub { push @nargv, $_ if -f _; },
+ follow => 0,
+ no_chdir => 1 },
+ $_);
+ } else {
+ push @nargv, $_;
+ }
+ }
+
+ @ARGV = @nargv;
+}
+
+$ret = 1; # return value from main(), set to 0 if anything matched.
+
+$filecount = @ARGV; # used to decide whether to print filename prefixes.
+
+$cont = quotemeta($opt{d} // '\\');
+
+$/ = "\0" if $opt{z};
+
+while(<>) {
+ chomp;
+ if(s/\r//) {
+ if(!$cr_warning) {
+ warn "$self: $ARGV: stripping carriage returns\n" unless $opt{s};
+ $cr_warning = 1;
+ }
+ }
+ if(/$cont\s+$/) {
+ warn "$self: $ARGV:$.: whitespace after continuation, malformed input?\n" unless $opt{s};
+ }
+ s/^\s+// if $out && $opt{w};
+ $start_line = $. unless defined $out;
+ $out .= $_;
+ if(/$cont$/) {
+ if(!$opt{k}) {
+ $out =~ s/$cont$//;
+ }
+ } else {
+ handle_line();
+ undef $out;
+ }
+} continue {
+ # reset $. on each new file (perldoc -f eof)
+ if(eof) {
+ if($out) {
+ warn "$self: $ARGV:$.: last line ends with continuation\n" unless $opt{s};
+ handle_line();
+ undef $out;
+ }
+ close ARGV;
+ $cr_warning = 0;
+ }
+}
+
+exit $ret;
+
+### rest of file is the docs
+
+=pod
+
+=head1 NAME
+
+bsgrep - search for strings in files with backslash continuation
+
+bsjoin - join lines with backslash continuation
+
+=head1 SYNOPSIS
+
+bsgrep [B<[-FiklnNqrsvwz]> B<-d> I<char> I<...>] [B<-e> I<pattern> ... | I<pattern>] [I<file> I<...>]
+
+bsjoin [B<[-knwz]> B<-d> I<char> I<...>] [I<file> I<...>]
+
+=head1 DESCRIPTION
+
+B<bsgrep> (backslash grep) uses a regular expression to search for
+strings in a file, much like B<grep>(1). The main difference is,
+B<bsgrep> joins together lines that use the backslash for continuation
+(e.g. as B<sh>(1) does).
+
+Other differences: B<bsgrep> doesn't support the full set of B<grep>
+options, and it uses Perl regular expressions rather than POSIX.
+
+Input is read from one or more files, or standard input if no files
+are given. Output goes to standard output.
+
+The search is done after lines are joined together, so the regex can
+match text split across continuation lines.
+
+If B<bsgrep> is run as B<bsjoin> (via symbolic or hard link, or just
+copying the executable), it will simply join together continued lines
+without searching for anything. In this mode, only the B<-k>, B<-n>,
+B<-w>, B<--version>, and B<--help> options are supported.
+
+=head1 OPTIONS
+
+These options work with both B<bsgrep> and B<bsjoin>:
+
+=over 4
+
+=item -d I<char>
+
+Use I<char> as the continuation character, rather than a backslash.
+Actually, there's no law that says it has to be a single character,
+if you can think of a use for a string here... though it's treated as
+a fixed string, not a regular expression. This option does not exist
+in B<grep>.
+
+=item -k
+
+Keep the continuation characters when joining continued lines together.
+This option does not exist in B<grep>.
+
+=item -n
+
+Prefix output lines with line numbers (same as B<grep>). For lines
+that are split with continuation characters, the line number will be
+that of the first line in the set. Same as B<grep>.
+
+=item -w
+
+For continuation lines, remove any leading whitespace. This option is
+specific to B<bsgrep>. The B<grep> B<-w> option can be simulated with
+the Perl B<\b> syntax in the regex.
+
+=item -z
+
+Use zero bytes (ASCII NUL) rather than newlines for line terminators,
+for both input and output. Same as B<grep>.
+
+=item --version
+
+Print the version of B<bsgrep> and exit.
+
+=item --help
+
+Prints this help text, via B<perldoc>(1).
+
+=item --man
+
+Prints this help text as a man page, via B<pod2man>(1). Suggested use:
+
+ bsgrep --man > bsgrep.1
+
+=back
+
+These options are only supported by B<bsgrep>:
+
+=over 4
+
+=item -e I<pattern>
+
+Use I<pattern> as the pattern. May be used multiple times, in which case
+they are ORed together (a line that matches any I<pattern> is a match)... unless
+the B<-N> option is used, q.v. Same as B<grep>.
+
+=item -F
+
+Treat pattern(s) as fixed strings, not regular expression(s). Same as B<grep>.
+
+=item -i
+
+Case-insensitive search (same as B<grep>).
+
+=item -l
+
+Instead of printing lines that match, print only the names of files
+that contain matches (same as B<grep>).
+
+=item -N
+
+When multiple patterns are given with multiple B<-e> options, only
+select lines that match all of the patterns; the default is to select
+lines that match any of the patterns. This option doesn't exist
+in B<grep>.
+
+=item -q
+
+Quiet: don't write to standard output. Exit status will be zero if
+a match was found, even if there were errors. This doesn't prevent
+warnings/errors being printed to standard error; use B<-s> to silence
+those. Same as B<grep>.
+
+=item -r
+
+Recursively read all files under each directory, following symlinks
+only if they're on the command line. If no files or directories are
+given, reads the current directory. Same as B<grep>.
+
+=item -s
+
+Silence warnings (same as B<grep>). This includes error messages
+about unreadable files as well as warnings about the input (see
+B<DIAGNOSTICS>, below).
+
+=item -v
+
+Print only lines that do I<not> match (same as B<grep>).
+
+=back
+
+=head1 EXAMPLE
+
+Given the file B<trs80-roms.info> (which comes from SlackBuilds.org), containing:
+
+ PRGNAM="trs80-roms"
+ VERSION="20230516"
+ HOMEPAGE="https://sdltrs.sourceforge.net/docs/index.html"
+ DOWNLOAD="https://www.filfre.net/misc/trs_roms.zip \
+ http://cpmarchives.classiccmp.org/trs80/mirrors/www.discover-net.net/~dmkeil/trs80/files/trs80-62.zip \
+ https://www.tim-mann.org/trs80/ld4-631.zip \
+ https://archive.org/download/mame-0.250-roms-split_202212/MAME%200.250%20ROMs%20%28split%29/trs80m4p.zip \
+ http://www.tim-mann.org/trs80/xtrs-4.9d.tar.gz \
+ https://www.classic-computers.org.nz/system-80/disks/NEWDOS_80sssd_jv1.DSK"
+ MD5SUM="ecd2c47c0624885fbcfb17889241f0ed \
+ 9b342f4401801bbc947e303cbeb9902f \
+ f2678aa45b76d935a34a0cd2b108925d \
+ 8a0f1567df8f166f4056a6a71ef7dce5 \
+ 8bb7cf88a3bc1da890f1f29398120bf3 \
+ 6f624bdbf4b410cfbe8603fa3bef44fa"
+ DOWNLOAD_x86_64=""
+ MD5SUM_x86_64=""
+ REQUIRES=""
+ MAINTAINER="B. Watson"
+ EMAIL="urchlay@slackware.uk"
+
+We can extract all the download URLs from the file with:
+
+ $ bsgrep '^DOWNLOAD=' trs80-roms
+
+ DOWNLOAD="https://www.filfre.net/misc/trs_roms.zip http://cpmarchives.classiccmp.org/trs80/mirrors/www.discover-net.net/~dmkeil/trs80/files/trs80-62.zip https://www.tim-mann.org/trs80/ld4-631.zip https://archive.org/download/mame-0.250-roms-split_202212/MAME%200.250%20ROMs%20%28split%29/trs80m4p.zip http://www.tim-mann.org/trs80/xtrs-4.9d.tar.gz https://www.classic-computers.org.nz/system-80/disks/NEWDOS_80sssd_jv1.DSK"
+ DOWNLOAD_x86_64=""
+
+All the URLs are listed as one long line (apologies for the ugly formatting).
+Note that the whitespace that indents the continuation lines is
+preserved. In this case, the whitespace is all spaces, but tabs would
+be treated the same way. To compress the whitespace into a single space,
+use the B<-w> option.
+
+=head1 DIAGNOSTICS
+
+Unless disabled with the B<-s> option, B<bsgrep> may print these messages
+on standard error:
+
+ bsgrep: <file>: stripping carriage returns
+
+The input file has MS-DOS/Windows CRLF line endings. B<bsgrep>'s
+output will have these removed. Note that other Unix-flavored tools
+that understand continuation lines will generally fail when fed CRLF
+files.
+
+ bsgrep: <file>, line <line>: whitespace after continuation, malformed input?
+
+In shell scripts (and most other uses of backslash continuation), a
+line that ends with whitespace after the backslash is not treated as a
+continuation line. This is a very easy error to create, when manually
+editing files. The above warning will help you avoid this. As usual,
+it can be ignored if you know exactly what you're doing.
+
+ bsgrep: <file>: last line ends with continuation
+
+This warning is self-explanatory. There's nothing for the last line
+to continue onto, so this is almost certainly an error.
+
+The above warnings don't affect the exit status.
+
+=head1 ENVIRONMENT
+
+B<bsgrep> doesn't define any environment variables of its own, but
+it does pay attention to B<LANG>, B<LC_ALL>, and B<LC_CTYPE>. If any
+of these contain the string I<UTF-8>, the input and output will be
+read/written as Unicode, encoded as UTF-8. If the input turns out not
+to be Unicode, it will be assumed ISO-8859-1, and converted to Unicode.
+
+=head1 EXIT STATUS
+
+0 if there were any matches, 1 if there were none, or 2 if there
+were errors (e.g. nonexistent file). However, with B<-q>, the exit
+status will be 0 or 1 even if there were errors. This is the same as
+B<grep>'s exit status.
+
+=head1 LIMITATIONS
+
+B<bsgrep> doesn't detect binary files like B<grep> does. It can and
+will print them to your terminal instead of "binary file matches".
+
+Not all B<grep> options are supported. Options that aren't implemented
+but might be someday include B<--color>, B<-a>, B<-A>, B<-B>, B<-C>, B<-o>.
+I don't intend to implement every single option B<grep> has, there are
+too many of them.
+
+There are no long options other than B<--help> and B<--version>.
+
+B<bsgrep> does not comply with the POSIX (or any other) standard for
+B<grep>, and does not intend do.
+
+Locale support isn't quite the same as B<grep>: in a UTF-8 locale,
+if the input isn't plain ASCII or valid UTF-8, it will be treated
+as ISO-8859-1, internally converted to Unicode, and output will be
+UTF-8. This isn't intended; it's a side-effect of how Perl UTF-8
+filehandles work. In non-UTF-8 locales, things should work as
+expected. I hope.
+
+=head1 AUTHOR
+
+B<bsgrep> was written by B. Watson <urchlay@slackware.uk> and released
+under the WTFPL: Do WTF you want with this.
+
+=head1 SEE ALSO
+
+B<grep>(1), B<perl>(1)
+
+=cut
+
+__END__
+
+implemented:
+ --help
+ -V, --version
+ -F, --fixed-strings
+ -e PATTERNS, --regexp=PATTERNS
+ -i, --ignore-case
+ -v, --invert-match
+ -q, --quiet, --silent
+ -s, --no-messages
+ -n, --line-number
+ -z, --null-data
+ -r, --recursive
+ -l, --files-with-matches
+
+todo:
+ -f FILE, --file=FILE
+ -y Obsolete synonym for -i.
+ -c, --count
+ -R, --dereference-recursive
+ -L, --files-without-match
+ -Z, --null
+ -A NUM, --after-context=NUM
+ -B NUM, --before-context=NUM
+ -C NUM, -NUM, --context=NUM
+ -H, --with-filename
+ -h, --no-filename
+ -w, --word-regexp
+ -x, --line-regexp
+
+do not implement:
+ -E, --extended-regexp
+ -G, --basic-regexp
+ -P, --perl-regexp
+ --no-ignore-case
+
+undecided:
+ --color[=WHEN], --colour[=WHEN]
+ -m NUM, --max-count=NUM
+ -o, --only-matching
+ -b, --byte-offset
+ --label=LABEL
+ -T, --initial-tab
+ --group-separator=SEP
+ --no-group-separator
+ -a, --text
+ --binary-files=TYPE
+ -D ACTION, --devices=ACTION
+ -d ACTION, --directories=ACTION
+ --exclude=GLOB
+ --exclude-from=FILE
+ --exclude-dir=GLOB
+ -I
+ --include=GLOB
+ --line-buffered
+ -U, --binary
diff --git a/bsjoin b/bsjoin
new file mode 120000
index 0000000..ef6d065
--- /dev/null
+++ b/bsjoin
@@ -0,0 +1 @@
+bsgrep \ No newline at end of file
diff --git a/find_key b/find_key
index 5e5e035..c391e65 100755
--- a/find_key
+++ b/find_key
@@ -55,8 +55,9 @@ $0: Find key of music in audio (or maybe video) files.
Usage: $0 [-m|-M] file.wav [file.wav ...]
--m: Force major keys to relative minor
--M: Force minor keys to relative major
+-q: Quiet, only output the most likely key.
+-m: Force major keys to relative minor.
+-M: Force minor keys to relative major.
EOF
exit 0;
}
@@ -70,6 +71,9 @@ for(@ARGV) {
$fold_major = 0;
$fold_minor = 1;
next;
+ } elsif(/^-q/) {
+ $quiet++;
+ next;
}
my $sec;
@@ -79,22 +83,23 @@ for(@ARGV) {
$sf = Audio::SndFile->open("<", $_);
};
if($@) {
- warn "Extracting to tmp.wav\n";
- system("mplayer -vo null -ao pcm:fast:file=tmp.wav \"$_\"");
+ my $rq = $quiet ? "-really-quiet" : "";
+ warn "Extracting to tmp.wav\n" unless $quiet;
+ system("mplayer $rq -vo null -ao pcm:fast:file=tmp.wav \"$_\"");
$sf = Audio::SndFile->open("<", "tmp.wav");
$_ = "tmp.wav";
}
$sec = ($sf->frames) * (1 / $sf->samplerate);
}
- printf "file is %03.2f sec\n", $sec;
+ printf "file is %03.2f sec\n", $sec unless $quiet;
my $oldstamp = -1;
my $oldkey = -1;
- my $got = `vamp-simple-host qm-vamp-plugins:qm-keydetector "$_" 2`;
+ my $got = `vamp-simple-host qm-vamp-plugins:qm-keydetector "$_" 2 2>/dev/null`;
die "Analysis failed\n" unless defined $got;
- print "\n";
+ print "\n" unless $quiet;
my @got = split "\n", $got;
## for my $line (@got) {
@@ -122,24 +127,25 @@ for(@ARGV) {
} elsif($fold_minor) {
$key = get_relative_minor($key);
}
- if($key =~ /m$/) {
- $relkey = get_relative_major($key);
- } else {
- $relkey = get_relative_minor($key);
- }
+ $relkey = get_relative_key($key);
if($oldstamp != -1) {
$times{$oldkey} += ($stamp - $oldstamp);
}
$oldstamp = $stamp;
$oldkey = $key;
- print "$stamp: $key ($relkey)\n";
+ print "$stamp: $key ($relkey)\n" unless $quiet;
}
$times{$oldkey} += ($sec - $oldstamp);
- print "\n";
- for(sort { $times{$b} <=> $times{$a} } keys %times) {
- printf "%3s: %4.2f sec, %4.2f%%\n", $_, $times{$_}, $times{$_} / $sec * 100;
+ print "\n" unless $quiet;
+ my @sorted = sort { $times{$b} <=> $times{$a} } keys %times;
+ if($quiet) {
+ print $sorted[0] . " (" . get_relative_key($sorted[0]) . ")\n";
+ } else {
+ for(@sorted) {
+ printf "%3s: %4.2f sec, %4.2f%%\n", $_, $times{$_}, $times{$_} / $sec * 100;
+ }
}
}
@@ -153,6 +159,16 @@ sub get_relative_minor {
return $relative_minors{$key} || $key;
}
+sub get_relative_key {
+ my $key = shift || die "missing key";
+ if($key =~ /m$/) {
+ $relkey = get_relative_major($key);
+ } else {
+ $relkey = get_relative_minor($key);
+ }
+ return $relkey;
+}
+
__END__
Output of plugin:
- Estimated key (from C major = 1 to B major = 12 and C minor = 13 to B minor = 24)
diff --git a/image_diff.pl b/image_diff.pl
new file mode 100644
index 0000000..9199f61
--- /dev/null
+++ b/image_diff.pl
@@ -0,0 +1,58 @@
+#!/usr/bin/perl -w
+
+# given 2 images of the same size, create a 3rd image, with the pixel
+# at each (x,y) transparent if it's the same RGB color in both images,
+# or set to the color in the 2nd image if they're different.
+
+# the 2nd image should be an edited version of the 1st image, and the
+# output image can be displayed overlaid on the 1st image to show what
+# the 2nd image looked like.
+
+use Image::Magick;
+
+sub die_usage {
+ die "usage: $0 <input-image1> <input-image2> <output-image>]\n";
+}
+
+sub readimage {
+ my $file = shift;
+ my $i = new Image::Magick;
+ my $r = $i->Read($file);
+ die "$r\n" if $r;
+ warn "read $file, OK\n";
+ return $i;
+}
+
+die_usage() if @ARGV != 3;
+$a = readimage(shift);
+$b = readimage(shift);
+
+$w = $a->Get('width');
+$h = $a->Get('height');
+
+if($w != $b->Get('width') || $h != $b->Get('height')) {
+ die "input images are not the same pixel size\n";
+}
+
+$out = Image::Magick->new(size => $w . 'x' . $h);
+$r = $out->ReadImage('xc:transparent');
+die "$r\n" if $r;
+
+# this is rather slow...
+for($y = 0; $y < $h; $y++) {
+ for($x = 0; $x < $w; $x++) {
+ my @ap = $a->GetPixel(map => "RGB", x => $x, y => $y);
+ my @bp = $b->GetPixel(map => "RGB", x => $x, y => $y);
+ if($ap[0] != $bp[0] || $ap[1] != $bp[1] || $ap[2] != $bp[2]) {
+ push @bp, 65535; # fully opaque
+ } else {
+ @bp = (0, 0, 0, 0); # transparent (and black)
+ }
+ $r = $out->SetPixel(map => "RGBA", x => $x, y => $y, color => \@bp);
+ die "$r\n" if $r;
+ }
+}
+
+$r = $out->Write($ARGV[0]);
+die "$r\n" if $r;
+warn "wrote to $ARGV[0]\n";
diff --git a/litterbox b/litterbox
new file mode 100755
index 0000000..ad730ab
--- /dev/null
+++ b/litterbox
@@ -0,0 +1,465 @@
+#!/bin/bash
+
+# based on the API spec: https://litterbox.catbox.moe/tools.php
+
+VERSION="0.0.1"
+SELF="$( basename $0 )"
+APIURL="https://litterbox.catbox.moe/resources/internals/api.php"
+URLS=""
+EXPIRE="1"
+DELAY="${DELAY:-2}"
+UPLOAD_COUNT=0
+TMP="$( mktemp -t -d $SELF.XXXXXXXXXX )"
+trap cleanup EXIT
+
+die() {
+ echo "$SELF: fatal: $@" 1>&2
+ exit 1
+}
+
+warn() {
+ echo "$SELF: warning: $@" 1>&2
+}
+
+info() {
+ [ "$VERBOSE" = "1" ] && echo "$SELF: info: $@" 1>&2
+}
+
+check_path() {
+ type -p "$1" &>/dev/null
+}
+
+cleanup() {
+ [ "$TMP" != "" ] && rm -rf "$TMP"
+}
+
+usage() {
+ if ! check_path perldoc; then
+ warn "can't find perldoc on PATH, printing raw POD."
+ exec sed -n '/^=pod/,/^=cut/p' $0
+ fi
+
+ exec perldoc "$0"
+}
+
+manpage() {
+ exec pod2man --stderr -s1 -c"UrchlaysStuff" -r$VERSION "$0"
+}
+
+pluralize() {
+ echo -n "$1 $2"
+ [ "$1" != 1 ] && echo -n "s"
+}
+
+delay() {
+ info "sleeping $DELAY sec between $1"
+ sleep "$DELAY"
+}
+
+set_expiration() {
+ case "$1" in
+ 1|12|24|72) ;; # OK
+ *) die "invalid expiration time, allowed values are: 1 12 24 72" ;;
+ esac
+ EXPIRE="$1"
+}
+
+add_url() {
+ if [ "$URLS" = "" ]; then
+ URLS="$1"
+ else
+ URLS+=" $1"
+ fi
+ : $(( UPLOAD_COUNT++ ))
+}
+
+copy_urls() {
+ if ! check_path xsel; then
+ warn "xsel not found in PATH, not copying URL(s) to clipboard"
+ return
+ fi
+
+ if [ "$DISPLAY" = "" ]; then
+ warn "DISPLAY not set in env, not copying URL(s) to clipboard"
+ return
+ fi
+
+ echo -n "$URLS" | xsel -i
+}
+
+# compress_file() gets called on both files and directories.
+compress_file() {
+ local ext
+ local basefile
+ local is_dir
+
+ [ -d "$FILE" ] && is_dir=1
+
+ if [ "$ZIP" = "1" ]; then
+ ext=.zip
+ else
+ ext=.gz
+ fi
+
+ basefile="$( basename "$FILE" )"
+
+ # TODO: create a random tmp dir, don't use /tmp!
+ TMPFILE="$TMP/$basefile$ext"
+ rm -f "$TMPFILE"
+ if [ "$ZIP" = "1" ]; then
+ zip -qr "$TMPFILE" "$FILE"
+ info "zipped $FILE as $TMPFILE"
+ else
+ if [ "$is_dir" = 1 ]; then
+ tar cfz "$TMPFILE" "$FILE"
+ info "tarred up $FILE as $TMPFILE"
+ else
+ gzip -9 < "$FILE" > "$TMPFILE"
+ info "gzipped $FILE as $TMPFILE"
+ fi
+ fi
+ FILE="$TMPFILE"
+}
+
+# TODO: curl options:
+# --max-time --retry --retry-max-time
+upload_file() {
+ local url
+ local use_compression
+
+ info "checking $FILE"
+
+ case "$FILE" in
+ *.exe|*.scr|*.cpl|*.doc*|*.jar)
+ warn "forbidden file extension, compression forced"
+ use_compression=1 ;;
+ *.zip|*.gz)
+ use_compression=0 ;;
+ *)
+ use_compression="$COMPRESS" ;;
+ esac
+
+ if [ "$use_compression" = 1 ]; then
+ # can't easily zip stdin, write it to a file
+ if [ "$FILE" = "-" ]; then
+ FILE="$TMP/$SELF.stdin"
+ cat > "$FILE"
+ if [ "$?" != 0 ]; then
+ rm -f "$FILE"
+ return
+ fi
+ fi
+ compress_file # resets FILE, sets TMPFILE
+ fi
+
+ # at this point, we have an actual file, even if input was stdin,
+ # so we can encrypt it.
+ if [ "$ENC" = "1" ]; then
+ CRYPTFILE="$FILE.aes"
+ openssl enc -pbkdf2 -aes-256-cbc < "$FILE" > "$CRYPTFILE"
+ if [ "$?" != "0" ]; then
+ warn "encryption failed, skipping file"
+ return
+ fi
+ FILE="$CRYPTFILE"
+ fi
+
+ if [ "$URLS" != "" ]; then
+ delay "uploads"
+ fi
+
+ info "uploading $FILE"
+
+ url="$(
+ cat "$FILE" | \
+ curl --silent \
+ -F "reqtype=fileupload" \
+ -F "time=${EXPIRE}h" \
+ -F "fileToUpload=@-" \
+ "$APIURL"
+ )"
+ # TODO: check curl's exit status!
+
+ case "$url" in
+ https://litter.catbox.moe/*) add_url "$url" ;;
+ *) warn "upload failed" ;;
+ esac
+}
+
+download_and_decrypt() {
+ local url="$1"
+ info "downloading and decrypting $url"
+ curl --silent "$url" | openssl enc -d -pbkdf2 -aes-256-cbc
+ [ "$?" != 0 ] && warn "download/decrypt failed"
+}
+
+# main()
+if [ "$1" = "--man" ]; then
+ manpage ; exit 0
+fi
+
+if [ "$1" = "--help" ]; then
+ usage ; exit 0
+fi
+
+if ! check_path curl; then
+ die "can't find curl on PATH (PATH is \"$PATH\")"
+fi
+
+# option string starts with a :, meaning we're responsible for
+# printing our own error messages.
+while getopts ":e:gzcvdo:" OPT; do
+ case "$OPT" in
+ e) set_expiration "$OPTARG"; opt_e=1 ;;
+ o) DLOUT="$OPTARG" ;;
+ g) GZIP=1 ;;
+ z) ZIP=1 ;;
+ c) ENC=1 ;;
+ v) VERBOSE=1 ;;
+ d) DOWNLOAD=1 ;;
+ *) die "invalid option -$OPTARG, try --help" ;;
+ esac
+done
+
+# getopts doesn't remove the args/options for us, so:
+shift $(($OPTIND - 1))
+
+# sanity check options and external binaries' existence.
+[ "$GZIP" = 1 -a "$ZIP" = 1 ] && die "can't give both -g and -z"
+
+if [ "$ZIP" = 1 ] && ! check_path zip; then
+ warn "can't find zip on PATH, disabling -z option"
+ ZIP=0
+fi
+
+if [ "$GZIP" = 1 ] && ! check_path gzip; then
+ warn "can't find gzip on PATH, disabling -g option"
+ GZIP=0
+fi
+
+[ "$GZIP" = 1 -o "$ZIP" = 1 ] && COMPRESS=1
+
+if [ "$DOWNLOAD" = 1 -o "$ENC" = 1 ]; then
+ check_path openssl || die "can't find openssl on path, -c/-d won't work"
+fi
+
+# download mode (-d)
+if [ "$DOWNLOAD" = 1 ]; then
+ [ "$GZIP" = 1 ] && warn "-g option ignored when using -d"
+ [ "$ZIP" = 1 ] && warn "-z option ignored when using -d"
+ [ "$ENC" = 1 ] && warn "-c option ignored when using -d"
+ [ "$opt_e" = 1 ] && warn "-e option ignored when using -d"
+ [ "$DLOUT" != "" ] && exec 1>"$DLOUT" || die "can't redirect stdout"
+ for arg; do
+ [ "$need_delay" = "1" ] && delay "downloads"
+ download_and_decrypt "$arg"
+ need_delay=1
+ done
+ exit 0
+else
+ [ "$DLOUT" != "" ] && warn "-o option ignored when not using -d"
+fi
+
+info "expire time set to $( pluralize $EXPIRE hour )"
+
+# upload mode
+[ "$1" = "" ] && set -- -
+for arg; do
+ TMPFILE=""
+ CRYPTFILE=""
+
+ if [ "$arg" = "-" -o -f "$arg" ]; then
+ FILE="$arg"
+ elif [ -d "$arg" ]; then
+ FILE="$arg"
+ compress_file # sets FILE
+ else
+ # broken symlink, device node, FIFO, ???
+ warn "$arg not a regular file, ignoring"
+ continue
+ fi
+
+ upload_file
+
+ # clean up if needed
+ if [ "$TMPFILE" != "" ]; then
+ info "removing temp file $TMPFILE"
+ rm -f "$TMPFILE"
+ fi
+
+ if [ "$TMPFILE" != "" ]; then
+ info "removing temp encrypted file $CRYPTFILE"
+ rm -f "$CRYPTFILE"
+ fi
+done
+
+if [ "$UPLOAD_COUNT" = "0" ]; then
+ die "no files uploaded"
+fi
+
+echo "$URLS"
+copy_urls
+
+info "$( pluralize $UPLOAD_COUNT file ) uploaded"
+
+exit 0
+
+# Rest of the file is perldoc.
+: <<EOF
+=pod
+
+=head1 NAME
+
+B<litterbox> - upload files to litterbox.catbox.moe
+
+=head1 SYNOPSIS
+
+B<litterbox> [-e I<hours>] [-g] [-z] [-c] [-v] I<file> <I<file ...>>
+
+B<litterbox> -d [-o I<output>] I<url> <I<url ...>>
+
+=head1 DESCRIPTION
+
+B<litterbox> uploads files to (or downloads files from) the temporary
+file storage site B<https://litterbox.catbox.moe>. The site is
+similar to a "pastebin" site, except that it allows any type of
+file (not just text), and uploads can be up to 1GB in size.
+
+=head2 Uploading
+
+Each I<file> can be a filename, a directory name, or "-" for standard
+input. If no files are given, litterbox reads from standard input.
+Directories are uploaded as B<.tar.gz> archives, by default. Files
+can be optionally encrypted with AES-256.
+
+Once a file is uploaded, its URL is printed on stdout (one per
+line). If B<xsel>(1) is installed and X is running, URLs are also
+copied to the X paste buffer (separated by spaces).
+
+When uploading multiple files, a delay of 2 seconds is added between
+uploads, to avoid hammering the site.
+
+=head2 Downloading
+
+Download mode is only for downloading AES-256 encrypted files that
+were previously uploaded with B<litterbox>. Non-encrypted files can
+simply be downloaded with B<wget>(1), B<curl>(1), a web browser, etc.
+You will, of course, have to provide the correct decryption key
+(password).
+
+=head1 OPTIONS
+
+=head2 Upload Options
+
+=over 4
+
+=item B<-e> I<hours>
+
+Set expiration time in hours. Allowed values are B<1>, B<12>,
+B<24>, or B<72>. Default is B<1>.
+
+=item B<-g>
+
+Upload gzipped files. This is automatically enabled
+when uploading files whose names end in .exe, .scr,
+.cpl, .doc*, .jar (unless B<-z> is given). Also, directories
+are automatically tarred/gzipped (unless B<-z> is given).
+
+=item B<-z>
+
+Upload zipped files or dirs, rather than .gz or .tar.gz.
+
+=item B<-c>
+
+Upload encrypted file. Currently this means the file
+is encrypted with 'openssl enc -pbkdf2 -aes-256-cbc', and
+you're prompted for an encryption key. Whoever downloads
+the encrypted file will have to decrypt it with the B<-d>
+option, or else do it manually.
+
+=back
+
+=head2 Download Options
+
+=over 4
+
+=item B<-d>
+
+Download and decrypt previously uploaded file(s) from URL(s).
+Equivalent to downloading the file normally, then piping it
+through:
+
+ openssl enc -d -pbkdf2 -aes-256-cbc
+
+...which means the file will be printed to your terminal, unless you
+use shell redirection or the B<-o> option. The upload options above (B<-e>
+B<-g> B<-z> B<-t> B<-c>) are ignored in download mode.
+
+=item B<-o> I<file>
+
+Set output file for B<-d> option. Note that giving multiple URLs
+will result in all the data being written to the same file. If you need
+separate files, run B<litterbox> multiple times.
+
+The B<-o> option is ignored in upload mode (when B<-d> is not used).
+
+=back
+
+=head2 General Options
+
+=over 4
+
+=item B<-v>
+
+Verbose operation.
+
+=item B<--help>
+
+Print this help to standard output and exit. Since the help is long,
+it will be passed through your pager.
+
+=item B<--man>
+
+Print this help in man page (troff) format to standard output and exit.
+
+=back
+
+=head1 NOTES
+
+=over 2
+
+=item B<->
+
+B<litter.catbox.moe> is an amazingly useful service. B<Please> be
+respectful of the site's owner, B<follow> the site's rules, and don't
+abuse the service with excessive requests. You should also consider
+supporting the site financially, by buying merch from its store, or
+simply making a donation.
+
+=item B<->
+
+With the B<-c> and B<-d> options, you will be prompted interactively
+for the encryption key (password), on the process's controlling tty.
+This could pose a problem if there's no controlling tty. With
+multiple files, you will be prompted separately for each.
+
+=item B<->
+
+Copying URLs to the copy/paste buffer only works under X11. It might
+be possible to support gpm(8) for the console. If Wayland has an
+xsel-like utility, it could be supported too (the author doesn't use
+Wayland so it's not high on the priority list).
+
+=back
+
+=head1 AUTHOR
+
+B. Watson <urchlay@slackware.uk>
+
+=head1 COPYRIGHT
+
+B<litterbox> is licensed under the WTFPL: do WTF you want to with this.
+See http://www.wtfpl.net/txt/copying/ for details.
+
+=cut
+EOF
diff --git a/termbin b/termbin
index 441be32..6a9f304 100755
--- a/termbin
+++ b/termbin
@@ -1,16 +1,25 @@
-#!/bin/sh
+#!/bin/bash
+
+VERSION="0.1.0"
+SELF="$( basename $0 )"
# 20200424 bkw: This used to be a one-line script:
# cat "${1:--}" | nc termbin.com 9999
# ...but I really wanted the "copy link to X clipboard" feature.
-if [ "$1" = "--help" ]; then
+if [ "$1" = "--help" -o "$1" = "-h" ]; then
cat <<EOF
-Usage: $( basename $0 ) <filename>
+termbin v$VERSION
+
+Usage: $SELF <filename> [<filename> ...]
+
+Uses nc (netcat) to paste stdin to termbin.com, or pastes one or more
+files if <filename(s)> are given. Spits out paste URL on stdout. Also
+copies URL to the X clipboard if X is running and either xsel or xclip
+is installed.
-Uses nc (NetCat) to paste stdin to termbin.com, or pastes a file if
-<filename> is given. Spits out paste URL on stdout. Also copies URL to
-the X clipboard if X is running and either xsel or xclip is installed.
+Only one paste is created. If you give multiple filenames, they are
+concatenated together.
Written by B. Watson <urchlay@slackware.uk>, released under the WTFPL:
do WTF you want with this.
@@ -18,20 +27,21 @@ EOF
exit 0
fi
-if ! which nc &>/dev/null; then
- echo "$( basename $0 ): can't find nc on path, install nc or netcat package"
- exit 1
-fi
+# 20251226 bkw: the sed is because termbin.com sends us e.g.
+# https://termbin.com/XXXX\n\0
+# and we get "warning: ignoring null byte" from bash.
+url="$( cat "${1:--}" | nc termbin.com 9999 | sed 's,\x00,,' )"
-url="$( cat "${1:--}" | nc termbin.com 9999 )"
-[ -z "$url" ] && exit "$?"
+# 20251227 bkw: don't print an error message if nc fails (nc will print
+# its own, or the shell will if it's "command not found").
+err="$?"
+[ -z "$url" ] && exit "$err"
echo "$url"
if [ -n "$DISPLAY" ]; then
- if which xsel &>/dev/null; then
- echo -n "$url" | xsel -i
- elif which xclip &>/dev/null; then
- echo -n "$url" | xclip
- fi
+ echo -n "$url" | xsel -i >/dev/null 2>&1 || \
+ echo -n "$url" | xclip >/dev/null 2>&1
fi
+
+exit 0
diff --git a/tolatin b/tolatin
new file mode 100755
index 0000000..44b04c1
--- /dev/null
+++ b/tolatin
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+case "$@" in
+ -u) PIPE="| unaccent utf8" ;;
+ *) ;;
+esac
+
+eval exec uconv -x Any-Latin $PIPE
diff --git a/tunebass b/tunebass
new file mode 100755
index 0000000..5394abc
--- /dev/null
+++ b/tunebass
@@ -0,0 +1,83 @@
+#!/bin/bash
+
+# Ear tuner for guitar and bass. Install tunebass on your $PATH, and
+# symlink it to tunegtr.
+
+# this used to be a one-liner:
+#play -n synth 2 pluck ${1:-A2} repeat 99999
+
+case "$0" in
+ *gtr*|*guit*)
+ notes="E4 B3 G3 D3 A2 E2"
+ note=6
+ lastnote=6
+ ;;
+ *)
+ notes="G2 D2 A1 E1"
+ note=4
+ lastnote=4
+ ;;
+esac
+
+prompt="Space: next string, BkSpc: prev string, 1-$lastnote: strings, q: quit, ?: help"
+
+# these seem like they ought to be the arrow keys, but they're actually
+# \n and \b... kcud1 and kcub1 are the 'application mode' versions, which
+# doesn't do us any good here.
+#down="$( tput cud1 )"
+#left="$( tput cub1 )"
+
+# these are correct for xterm and derivatives, and for the linux console.
+down="$( printf "\x1b\x5bB" )"
+left="$( printf "\x1b\x5bD" )"
+
+up="$( tput cuu1 )"
+right="$( tput cuf1 )"
+pageup="$( tput kpp )"
+pagedown="$( tput knp )"
+
+esc="$( printf "\x1b" )"
+enter='
+'
+
+oldnote=
+pid=
+echo "$prompt"
+while true; do
+ narg="$( echo $notes | cut -d' ' -f $note )"
+ if [ "$oldnote" != "$note" ]; then
+ [ "$pid" != "" ] && kill "$pid"
+ echo -e -n "\rString: $note Note: $narg"
+ tput el
+ play --buffer 1024 -qn synth 2 pluck $narg repeat - &>/dev/null &
+ pid="$!"
+ oldnote="$note"
+ fi
+ key=
+ read -rs -N5 -t 0.1 key
+ if [ "$key" != "" ]; then
+ case "$key" in
+ [1-9])
+ [ "$key" -le "$lastnote" ] && note=$key ;;
+ a|A|h|H|||$left|$pageup)
+ note=$(( note + 1 )); [ $note -gt $lastnote ] && note=1 ;;
+ d|D|l|L|' '|' '|$right|$enter|$pagedown)
+ note=$(( note - 1 )); [ $note = 0 ] && note=$lastnote ;;
+ s|S|j|J|$down)
+ note=$lastnote ;;
+ w|W|k|K|$up)
+ note=1 ;;
+ \?)
+ echo
+ cat <<EOF
+Use space + backspace, arrow keys, pageup/down, WASD, HJKL, or numbers
+1 to $lastnote to pick a string. Tune your strings to the notes you hear.
+Press Q or Escape to exit.
+EOF
+ ;;
+ $esc|q|Q) break ;;
+ esac
+ fi
+done
+kill "$pid"
+echo
diff --git a/uleft b/uleft
index eac3856..f9f46d7 100755
--- a/uleft
+++ b/uleft
@@ -25,9 +25,19 @@
#GLEFT="104x51+1280+0"
#GRIGHT="101x51+2222+0"
#GFULL="205x51-71+0"
-GLEFT="101x49+1280+0"
-GRIGHT="90x49+2295+0"
-GFULL="191x49+1280+0"
+
+# Before my Dell widescreen monitor died:
+#GLEFT="101x49+1280+0"
+#GRIGHT="90x49+2295+0"
+#GFULL="191x49+1280+0"
+
+GLEFT="80x57+1920+0"
+GRIGHT="80x57+2720+0"
+GFULL="160x57+1920+0"
+
+CLEFT="-bg #000a00"
+CRIGHT="-bg #0a0000"
+CFULL=""
murder_shell() {
# kill the shell that called the shell that's running this script
@@ -50,11 +60,11 @@ get_next_geom() {
case "$0" in
*kill*) murder_shell ;;
- *left*) G=$GLEFT ;;
- *full*) G=$GFULL ;;
+ *left*) G=$GLEFT; C="$CLEFT" ;;
+ *full*) G=$GFULL; C="$CFULL" ;;
*next*) G=$( get_next_geom ) ;;
- *) G=$GRIGHT ;;
+ *) G=$GRIGHT; C="$CRIGHT" ;;
esac
-urxvt -g "$G" "$@" -e sh -c "while true; do bash -login; done" &>/dev/null &
+urxvt -g "$G" $C "$@" -e sh -c "while true; do bash -login; done" &>/dev/null &
disown
diff --git a/unifmt.pl b/unifmt.pl
index 39f3c12..7622c0f 100644
--- a/unifmt.pl
+++ b/unifmt.pl
@@ -1,5 +1,8 @@
#!/usr/bin/perl
+# TODO:
+# Braille.
+
# to read the docs outside of irssi: perldoc /path/to/unifmt.pl
# in irssi, "/script load unifmt.pl", then "/unifmt_help"
diff --git a/vol b/vol
index 14425d7..7e29d41 100755
--- a/vol
+++ b/vol
@@ -5,7 +5,7 @@
# (c) 2020 B. Watson <urchlay@slackware.uk>
# Released under the WTFPL, see http://www.wtfpl.net/txt/copying/
-# Requires xosd.
+# Requires aosd_cat (on Slackware, libaosd from SBo).
# Intended for use with xbindkeys, like so:
#$ cat ~/.xbindkeysrc
@@ -20,7 +20,8 @@
# "Next" and "Prior" are PageDown and PageUp. Replace with "Up" and "Down"
# to use arrow keys. If you have multimedia keys, you can probably use
-# them. See xbindkeys(1).
+# them (try XF86AudioRaiseVolume, XF86AudioLowerVolume, and
+# XF86AudioMute). See xbindkeys(1).
# Can also be used from the command line. Run with no args for help.
@@ -33,27 +34,23 @@
CHANNEL=Master
# Each up/down adjustment is by this much. Use the dB suffix if you
-# prefer decibels, or leave it off for percentage.
-ADJ=3dB
+# prefer decibels, % percentage, no suffix for raw units (on one of my
+# sound cards, the raw unit range is 0 to 87; on another, 0-127).
+ADJ=1%
-# osd_cat seems a bit picky about what fonts it'll use. If you give a
-# nonexistent font, it won't run at all. If you give a font it "doesn't
-# like", you'll see the percentage bar, but no "Volume" text.
-FONT=12x24
+# Pango font string, see:
+# https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html
+# The size is in points; 30px would be pixels.
+FONT="Mono Normal 30"
-# What color is the OSD bar and text? See /usr/share/X11/rgb.txt.
+# What color is the OSD bar and text? See /usr/share/X11/rgb.txt,
+# or use hex escapes (with #).
# COLOR is for when we're unmuted, MUTECOLOR is for muted.
-COLOR=orange
-MUTECOLOR=red
+COLOR='#00ff00'
+MUTECOLOR='#ff0000'
-# Text drop shadow, 0 to disable.
-SHADOW=3
-
-# Where does the OSD show up on screen? One of: top middle bottom
-POSITION=bottom
-
-# How long does the OSD persist? In seconds, integer only.
-DELAY=2
+# How long does the OSD persist? In milliseconds.
+DELAY=2000
# amixer options. Could use -c, -D here. Don't use -q.
# default is "-M", see amixer(1).
@@ -67,23 +64,43 @@ SELF="$( basename $0 )"
PIDFILE="$HOME/.$SELF.osd.pid"
-# osd() will kill any previously-spawned osd_cat process. Ideally this
+BLOCKS="██████████████████████████████████████████████████"
+BLANKS="──────────────────────────────────────────────────"
+PARTIAL="▌"
+
+# progress() prints a UTF-8 progress bar with 50 characters.
+# takes a percentage, 0 to 100
+progress() {
+ local text="$1"
+ local count="$2"
+ local solidblocks
+ local partial
+ local trailing
+
+ solidblocks="$(( $count / 2 ))"
+ partial="$(( $count % 2 ))"
+ trailing="$(( 50 - ( $solidblocks + $partial) ))"
+
+ echo -n "$text${BLOCKS:0:$solidblocks}"
+ [ "$partial" = "1" ] && echo -n $PARTIAL
+ echo -n "${BLANKS:0:$trailing}"
+}
+
+# osd() will kill any previously-spawned aosd_cat process. Ideally this
# means rapid multiple keypresses won't "step on" each other. In practice
# it seems to work OK, but if you press the key really fast on a slow
# system, or hold it down with your key-repeat rate cranked up (on any
# system), the OSD bar will never get a chance to display.
-
osd() {
local got="$( amixer $AMIXER_OPTS get $CHANNEL | tail -1 )"
local muted="$( echo "$got" | grep '\[off\]$' )"
local volpct="$( echo "$got" | cut -d'[' -f2 | cut -d% -f1 )"
local db="$( echo "$got" | cut -d'[' -f3 | cut -d']' -f1 )"
- local text="Volume $db ($volpct%)"
local color="$COLOR"
+ local text="$( printf "%8s" $db) $( printf "%-3s" $volpct% ) "
local oldpid
if [ -n "$muted" ]; then
- text="$text [Muted]"
color="$MUTECOLOR"
fi
@@ -91,18 +108,29 @@ osd() {
# files whose PIDs have been recycled.
if [ -e $PIDFILE ]; then
oldpid="$( cat $PIDFILE )"
- grep -q '^osd_cat' /proc/$oldpid/cmdline 2>/dev/null && \
- kill "$oldpid" 2>/dev/null
+ grep -q '^aosd_cat' /proc/$oldpid/cmdline 2>/dev/null && \
+ kill -9 "$oldpid" 2>/dev/null
+ rm -f $PIDFILE
fi
- osd_cat -b percentage \
- -d $DELAY \
- -P $volpct \
- -p $POSITION \
- -c $color \
- -s $SHADOW \
- -f $FONT \
- -T "$text" &
+ # TODO: make these variables in the config section rather than
+ # hardcoding them.
+ progress "$text" "$volpct" | \
+ aosd_cat \
+ --fore-color $color \
+ --back-color '#808080' \
+ --font "$FONT" \
+ --output 1 \
+ --x-offset 159 \
+ --y-offset 0 \
+ --position 7 \
+ --transparency 1 \
+ --back-opacity 128 \
+ --padding 7 \
+ --alignment 0 \
+ --fade-in 0 \
+ --fade-out 0 \
+ --fade-full $DELAY &
echo "$!" > $PIDFILE
}
@@ -121,17 +149,17 @@ mute() {
usage() {
cat <<EOF
-$SELF [up|down|mute|<nnn>]
+$SELF [up|down|mute|<nn>]
-<nnn> is a numeric volume, possibly followed by "dB" and/or "+" or "-".
- It will be passed to 'amixer $AMIXER_OPTS set $CHANNEL' as-is.
+<nn> is a numeric volume, possibly followed by "dB" or "%", and/or
+ "+" or "-". It will be passed to 'amixer $AMIXER_OPTS set $CHANNEL' as-is.
EOF
exit 1
}
check_deps() {
local missing="no"
- for dep in osd_cat amixer; do
+ for dep in aosd_cat amixer; do
if ! type -p "$dep" > /dev/null; then
echo "$SELF: missing required executable '$dep'" 1>&2
missing="yes"
@@ -157,11 +185,3 @@ esac
osd
exit 0
-
-# For reference:
-#$ amixer get Master
-#Simple mixer control 'Master',0
-# Capabilities: pvolume pvolume-joined pswitch pswitch-joined
-# Playback channels: Mono
-# Limits: Playback 0 - 87
-# Mono: Playback 31 [36%] [-42.00dB] [on]
diff --git a/ztunekey b/ztunekey
new file mode 100755
index 0000000..61eb9e6
--- /dev/null
+++ b/ztunekey
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+# icon from:
+# https://vectorified.com/images/guitar-tuner-icon-28.jpg
+# ...installed via:
+# convert guitar-tuner-icon-28.jpg .local/share/icons/hicolor/256x256/apps/ztunekey.png
+
+if [ "$1" = "--help" ]; then
+ cat <<EOF
+ztunekey: show tuning and key of an audio file, using a zenity-based UI.
+
+Usage: ztunekey [file]
+
+[file] is an audio file, in any format supported by mplayer (since
+mplayer is used to convert it to a .wav file for processing).
+
+With no [file], ztunekey queries audacious (using audtool) to get the
+currently playing file. This will fail if audacious isn't running.
+
+The tuning (in cents relative to A440) and the detected key (and
+relative key, e.g. G (Em) or A (F#m)) are shown in a window, after the
+file is analyzed.
+EOF
+ exit 0
+fi
+
+ICON=ztunekey
+ZOPT='--width 300 --height 100 --title Tuning/Key'
+file="$1"
+
+if [ "$file" = "" ]; then
+ file="$( audtool --current-song-filename )"
+ if [ "$file" = "" ]; then
+ zenity --error $ZOPT --text="Audacious is not running, or no song is playing."
+ exit 1
+ fi
+fi
+
+if [ ! -e "$file" ]; then
+ zenity --error $ZOPT --text="No such file:\\n$file"
+ exit 1
+fi
+
+bn="$( basename "$file" )"
+tmpfile=/tmp/ztunekey.$$.wav
+
+keyfile=/tmp/key.$$
+tunefile=/tmp/tuning.$$
+( mplayer -really-quiet -benchmark -vo null -ao pcm:fast:file=$tmpfile "$file"
+ find_tuning -q "$tmpfile" > $tunefile &
+ find_key -q "$tmpfile" > $keyfile &
+ wait ) | zenity --progress --no-cancel $ZOPT --icon-name=ztunekey --text="$bn" --pulsate --auto-close
+
+TUNING="$( cat $tunefile )"
+KEY="$( cat $keyfile )"
+rm -f $keyfile $tunefile
+
+zenity --question $ZOPT --icon-name=ztunekey --text="$bn\\nTuning: $TUNING cents\\nKey: $KEY )\\n\\nRetune to standard?"
+[ "$?" = 1 ] && exit 1
+
+zenity --scale $ZOPT --icon-name=ztunekey --text="Semitones to transpose?\\n0 = original key." --min-value=-12 --max-value=12 --value=0 > /tmp/semi.$$
+
+S="$( cat /tmp/semi.$$ )"
+rm -f /tmp/semi.$$
+P="$( perl -MPOSIX=round -e "printf '%+2.2f', round(100 * ($S - $TUNING / 100)) / 100" )"
+newfile="$( echo "$bn" | sed 's,\.[^.]*$,'$P'.wav,' )"
+
+mkdir -p ~/retuned
+cd ~/retuned
+rubberband --pitch $P $tmpfile "$newfile" | zenity --progress --no-cancel $ZOPT --icon-name=ztunekey --text "Retuning $bn by $P semitones." --pulsate --auto-close
+flacfile="$( basename "$newfile" .wav )".flac
+flac -f --delete-input-file -o "$flacfile" "$newfile" | zenity --progress --no-cancel $ZOPT --icon-name=ztunekey --text "Encoding $flacfile" --pulsate --auto-close
+id3cp "$file" "$flacfile"
+id3 -2 -t "%|%t||%_f|? (Retuned $P)" "$flacfile"
+audtool --playlist-addurl-to-new-playlist ~/retuned/"$flacfile"
+
diff --git a/ztunekey.desktop b/ztunekey.desktop
new file mode 100644
index 0000000..184792e
--- /dev/null
+++ b/ztunekey.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Version=1.0
+Name=Tuning/Key
+Type=Application
+Exec=/home/urchlay/bin/ztunekey %f
+Icon=ztunekey
+Terminal=false
+StartupNotify=false
+Hidden=false
diff --git a/ztunekey.png b/ztunekey.png
new file mode 100644
index 0000000..d152c37
--- /dev/null
+++ b/ztunekey.png
Binary files differ