├── .gitignore ├── src ├── exe │ ├── total │ ├── cleanup │ ├── timer │ ├── echo-shell │ ├── password-old.sh │ ├── create-archive │ ├── explorer │ ├── m3u-gen │ ├── ffconcat-gen │ ├── scale-to-720 │ ├── hprint │ ├── cuesplit │ ├── crc32.py │ ├── decrud-from-internet │ ├── openexe │ ├── disksize │ ├── password.sh │ ├── denoise-squash-png │ ├── png-zopfli-idat │ ├── pacgit │ ├── make-tumblr-gif │ ├── exsorttar │ ├── crush-image │ ├── tbz-common.sh │ └── clipvideo ├── c │ ├── ecma-encode.h │ ├── utf8.h │ ├── decode-uri.c │ ├── encode-uri.c │ ├── encode-uri-component.c │ ├── crc32.c │ ├── tranep-crc32.c │ ├── tranep-crc32.h │ ├── utf8.c │ ├── slurp.c │ └── ecma-encode.c └── lua │ ├── markchapter.lua │ └── screenshotprocess.lua ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | # C garbage 2 | *.o 3 | 4 | # python garbage 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /src/exe/total: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec awk '{s+=$1} END {printf "%.0f\n", s}' 3 | 4 | -------------------------------------------------------------------------------- /src/exe/cleanup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "${1-}" = "-r" -o "${1-}" = "--recursive" ] ; then 4 | find -type f -name '*~' -or -name '~$*' -exec rm -f -- '{}' '+' 5 | else 6 | rm -f -- *~ \~\$* 7 | fi 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scripts 2 | 3 | Collection of scripts. 4 | Scripts themselves are sorted in src/, and src/exe/ contains executable scripts, so this is the one to add to your PATH environment if necessary. 5 | -------------------------------------------------------------------------------- /src/c/ecma-encode.h: -------------------------------------------------------------------------------- 1 | #ifndef _ECMA_ENCODE_H 2 | #define _ECMA_ENCODE_H 3 | 4 | int ecma_encode_uri_component(char **dest, const char *input); 5 | int ecma_encode_uri(char **dest, const char *input); 6 | int ecma_decode(char **dest, const char *input); 7 | 8 | #endif // _ECMA_ENCODE_H 9 | -------------------------------------------------------------------------------- /src/exe/timer: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | start_time="$(date -u +%s.%N)" 4 | 5 | get_timer() { 6 | elapsed="$(printf '%s %s' "$start_time" "$(date -u +%s.%N)" | awk '{ print $2 - $1 }')" 7 | printf '%.3f\r' "$elapsed" 8 | } 9 | 10 | while get_timer; do 11 | sleep .003 12 | done 13 | -------------------------------------------------------------------------------- /src/c/utf8.h: -------------------------------------------------------------------------------- 1 | #ifndef _UTF8_H 2 | #define _UTF8_H 3 | 4 | #include 5 | #include 6 | 7 | int utf8_read_codepoint(const char *str, size_t bufsize, uint32_t *codepoint); 8 | int utf8_write_codepoint(char *str, size_t bufsize, uint32_t codepoint); 9 | 10 | #endif _UTF8_H 11 | -------------------------------------------------------------------------------- /src/exe/echo-shell: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e; set -f; set -u 3 | 4 | shell_escape() { 5 | local arg; for arg; do 6 | arg="$(printf %s "$arg" | sed "s/'/'\\\\''/g"; printf 'x\n')" 7 | arg="${arg%?}" 8 | printf "'%s' " "$arg" | sed "s/^''//" | sed "s/\([^\\\\]\)''/\1/g" 9 | done 10 | printf '\n' 11 | } 12 | 13 | shell_escape "$@" 14 | -------------------------------------------------------------------------------- /src/exe/password-old.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo -n "Enter domain: " 5 | read -r domain 6 | echo -n "Enter username: " 7 | read -r username 8 | echo -n "Enter index: " 9 | read -r index 10 | echo -n "Enter master password: " 11 | MHASH="$(sha256sum | cut -f1 -d' ' | perl -lne 'print pack "H*", $_' | base64)" 12 | 13 | PHASH=$(printf "%s%s%s%s" "$domain" "$username" "$index" "$MHASH" | sha256sum | cut -f1 -d' ' | perl -lne 'print pack "H*", $_' | base64) 14 | 15 | printf "%s%s\n" "Df!1@2" "$PHASH" 16 | -------------------------------------------------------------------------------- /src/exe/create-archive: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . tbz-common.sh 3 | 4 | if [ $# -lt 1 ] ; then 5 | error "$this_script file_or_directory" 6 | else 7 | input="$1" 8 | fi 9 | 10 | if [ ! -d "$input" ] ; then 11 | exec lz4 -9 -z "$@" "$input".lz4 12 | fi 13 | 14 | readfrom "$input" tr -d '/' 15 | namd="$___" 16 | 17 | if [ -z "$namd" ] ; then 18 | error "Please do not create an archive of / with this command." 19 | fi 20 | 21 | exsorttar --lz4 "$namd".tar.lz4 "$input" 22 | 23 | stat -c%s "${namd}.tar.lz4" "$input" | hprint 24 | -------------------------------------------------------------------------------- /src/exe/explorer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # don't use dumb paths here 4 | explorers=("/mnt/c/Windows/explorer.exe" "/usr/bin/caja") 5 | expl_command="" 6 | 7 | for exp in "${explorers[@]}"; do 8 | if command -v "$exp" >/dev/null 2>/dev/null; then 9 | expl_command="$exp" 10 | break 11 | fi 12 | done 13 | 14 | if [ -z "$expl_command" ] ; then 15 | printf '%s\n' "Error finding explorer." 16 | exit 1 17 | fi 18 | 19 | if [ "$#" -lt 1 ] ; then 20 | set -- '.' 21 | fi 22 | 23 | working_dir="$(pwd)" 24 | 25 | for arg; do 26 | cd "$arg" 27 | "$expl_command" . 28 | cd "$working_dir" 29 | done 30 | -------------------------------------------------------------------------------- /src/exe/m3u-gen: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | printf '%s\n' "#EXTM3U" 4 | for arg; do 5 | ffprobeinfo="$(ffprobe 2>/dev/null -i "$arg" -of flat -show_format -select_streams a -show_streams)" 6 | title="$(printf '%s\n' "$ffprobeinfo" | grep -Ei -e '^(format|streams\.stream\.[0-9]+)\.tags\.title=".*"$' | sed -r 's/^(format|streams\.stream\.[0-9]+)\.tags\.title="(.*)"$/\2/i' | head -n1)" 7 | duration="$(printf '%s\n' "$ffprobeinfo" | grep -Ei -e '^(format|streams\.stream\.[0-9]+)\.duration=".*"$' | sed -r 's/^(format|streams\.stream\.[0-9]+)\.duration="(.*)"$/\2/i' | head -n1)" 8 | printf '#EXTINF:%s,%s\n' "$duration" "$title" 9 | printf '%s\n' "$arg" 10 | done 11 | -------------------------------------------------------------------------------- /src/c/decode-uri.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "ecma-encode.h" 6 | 7 | int main(int argc, char **argv){ 8 | char *dest = NULL; 9 | size_t count = 0; 10 | 11 | if (argc < 2) { 12 | fprintf(stderr, "%s string...\n", argv[0]); 13 | return 1; 14 | } 15 | 16 | while (++count < argc) { 17 | int status = ecma_decode(&dest, argv[count]); 18 | if (status < 0) { 19 | fprintf(stderr, "%s: error: %s: \n", argv[0], argv[count]); 20 | if (dest) 21 | free(dest); 22 | return 2; 23 | } 24 | printf("%s\n", dest); 25 | free(dest); 26 | dest = NULL; 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/c/encode-uri.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "ecma-encode.h" 6 | 7 | int main(int argc, char **argv){ 8 | char *dest = NULL; 9 | size_t count = 0; 10 | 11 | if (argc < 2) { 12 | fprintf(stderr, "%s string...\n", argv[0]); 13 | return 1; 14 | } 15 | 16 | while (++count < argc) { 17 | int status = ecma_encode_uri(&dest, argv[count]); 18 | if (status < 0) { 19 | fprintf(stderr, "%s: error: %s: \n", argv[0], argv[count]); 20 | if (dest) 21 | free(dest); 22 | return 2; 23 | } 24 | printf("%s\n", dest); 25 | free(dest); 26 | dest = NULL; 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/c/encode-uri-component.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "ecma-encode.h" 6 | 7 | int main(int argc, char **argv){ 8 | char *dest = NULL; 9 | size_t count = 0; 10 | 11 | if (argc < 2) { 12 | fprintf(stderr, "%s string...\n", argv[0]); 13 | return 1; 14 | } 15 | 16 | while (++count < argc) { 17 | int status = ecma_encode_uri_component(&dest, argv[count]); 18 | if (status < 0) { 19 | fprintf(stderr, "%s: error: %s: \n", argv[0], argv[count]); 20 | if (dest) 21 | free(dest); 22 | return 2; 23 | } 24 | printf("%s\n", dest); 25 | free(dest); 26 | dest = NULL; 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/exe/ffconcat-gen: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "${1+x}" ] ; then 4 | >&2 printf 'Usage: %s files...\n' "${0##*/}" 5 | exit 1 6 | fi 7 | 8 | shell_escape() { 9 | for arg; do 10 | arg="$(printf %s "$arg" | sed "s/'/'\\\\''/g"; printf 'x')" 11 | arg="${arg%?}" 12 | printf "'%s' " "$arg" | sed "s/^''//" | sed "s/\([^\\\\]\)''/\1/g" 13 | done 14 | printf '\n' 15 | } 16 | 17 | printf '%s\n' "ffconcat version 1.0" 18 | 19 | for arg; do 20 | ffprobeinfo="$(ffprobe 2>/dev/null -i "$arg" -of flat -show_format -show_streams; printf x)" 21 | ffprobeinfo="${ffprobeinfo%?}" 22 | duration="$(printf '%s' "$ffprobeinfo" | grep -Ei -e '^(format|streams\.stream\.[0-9]+)\.duration="[0-9]*\.[0-9]*"$' | sed -r 's/^(format|streams\.stream\.[0-9]+)\.duration="([0-9]*\.[0-9]*)"$/\2/i' | head -n1)" 23 | printf 'file %s\n' "$(shell_escape "$arg")" 24 | printf 'duration %s\n' "$duration" 25 | done 26 | -------------------------------------------------------------------------------- /src/exe/scale-to-720: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$#" -lt 1 ] ; then 4 | printf >&2 'error: Provide a filename.\nUsage: %s \n' "${0##*/}" 5 | fi 6 | 7 | if [ "$1" = "--rotate" ] ; then 8 | argextra="-rotate '-90<'" 9 | else 10 | argextra='' 11 | fi 12 | 13 | input_file="$1" 14 | input_filename="${input_file##*/}" 15 | input_base="${input_filename%.*}" 16 | input_ext="${input_filename#"$input_base"}" 17 | tpng="$(mktemp --suffix=.png)" 18 | tpng2="$(mktemp --suffix=.png)" 19 | ffmpeg -y -i "$input_file" "$tpng2" 20 | width="$(magick identify -format '%w' "$tpng2")" 21 | height="$(magick identify -format '%h' "$tpng2")" 22 | magick "$tpng2" ${argextra} -filter Catrom -resize '1280x1280>' "$tpng" 23 | if [ "$input_ext" = ".jpg" ] ; then 24 | timg="$(mktemp --suffix="$input_ext")" 25 | magick "$tpng" "$timg" 26 | else 27 | timg="$tpng" 28 | fi 29 | xclip -i -selection clipboard -t image/png <"$tpng" 30 | eom "$timg" 31 | sh -c 'sleep 10; rm -f -- "$@"' "$0" "$tpng" "$timg" 32 | -------------------------------------------------------------------------------- /src/exe/hprint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | sub hprint { 4 | my $number = shift; 5 | chomp $number; 6 | 7 | my $human_number; 8 | my $suffix; 9 | 10 | if ($number >= 1099511627776){ 11 | $human_number = sprintf("%.2f", $number / 1099511627776); 12 | $suffix = "Ti"; 13 | } elsif ($number >= 1073741824){ 14 | $human_number = sprintf("%.2f", $number / 1073741824); 15 | $suffix = "Gi"; 16 | } elsif ($number >= 1048576){ 17 | $human_number = sprintf("%.2f", $number / 1048576); 18 | $suffix = "Mi"; 19 | } elsif ($number >= 1024){ 20 | $human_number = sprintf("%.2f", $number / 1024); 21 | $suffix = "Ki"; 22 | } else { 23 | $human_number = $number; 24 | $suffix = ""; 25 | } 26 | 27 | print "$human_number $suffix\n"; 28 | } 29 | 30 | # This disables STDOUT buffering. 31 | # Don't ask. It's Perl. 32 | select STDOUT; 33 | $|=1; 34 | 35 | if ($#ARGV < 0){ 36 | while (<>){ 37 | hprint($_); 38 | } 39 | } else { 40 | foreach (@ARGV){ 41 | hprint($_); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/exe/cuesplit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | usage(){ 5 | echo "Usage: cuesplit cuefile.cue audiofile.audio" 6 | exit 1 7 | } 8 | 9 | if [ ! $# = 2 ]; then 10 | usage 11 | fi 12 | 13 | filename="$2.flac" 14 | 15 | finish(){ 16 | rm -f -- "$filename" 17 | } 18 | 19 | trap finish EXIT 20 | 21 | echo "Creating flac..." 22 | 23 | if [ "$(ffprobe "$2" -show_streams -select_streams a 2>/dev/null | grep -E '^codec_name=.+$')" = 'codec_name=flac' ] ; then 24 | ln -s "$2" "$filename" 25 | else 26 | ffmpeg -y -i "$2" -map a -c flac "$filename" 27 | fi 28 | 29 | echo "Splitting..." 30 | 31 | shnsplit -f "$1" -o flac "$filename" 32 | 33 | echo "Tagging..." 34 | cuetag.sh "$1" split-track??.flac 35 | 36 | echo "Renaming..." 37 | for flac in split-track??.flac ; do 38 | mv "$flac" "$(printf '%02d %s' "$(ffprobe "$flac" 2>&1 | grep -Ei 'track\s+:\s' | sed -r 's/.*?:\s(.*)/\1/' | sed -r 's/^0*([1-9])/\1/' )" "$(ffprobe "$flac" 2>&1 | grep -Ei 'title\s+:\s' | sed -r 's/.*?:\s(.*)/\1/').flac")" 39 | done 40 | 41 | echo "Done!" 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2020 Leo Izen (thebombzen) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/exe/crc32.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import re, sys, zlib 3 | from typing import Iterable 4 | 5 | def calculate_crc(input_iter: Iterable[bytes], png: bool) -> int: 6 | crc = 0xFFFFFFFF if png else 0 7 | for block in input_iter: 8 | crc = zlib.crc32(block, crc) 9 | return 0xFFFFFFFF ^ crc if png else crc 10 | 11 | def calculate_crc_name(filename: str, png: bool = False) -> int: 12 | if filename == '-': 13 | return calculate_crc(sys.stdin.buffer, png) 14 | with open(filename, 'rb') as input_file: 15 | return calculate_crc(input_file, png) 16 | 17 | if len(sys.argv) < 2: 18 | print(f'Usage: {sys.argv[0]} [--png]', file=sys.stderr) 19 | sys.exit(1) 20 | 21 | png_mode = len(sys.argv) >= 3 and sys.argv[2] == '--png' 22 | filename = sys.argv[1] 23 | crc = calculate_crc_name(filename, png_mode) 24 | crc_str = f'{crc:08X}' 25 | print(crc_str) 26 | match = re.search(r'(? [waifu2x opts]\n" "${0##*/}" 5 | exit 1 6 | fi 7 | 8 | URL="$1"; shift 9 | TEMPDL="$(mktemp)" 10 | TEMPFIN="$(mktemp --suffix=.png)" 11 | TEMP_A="$(mktemp --suffix=.png)" 12 | 13 | # Download the file 14 | ffmpeg -y -i "$URL" -map v -vf libplacebo=format=gbrp16le -c png -f image2pipe -update 1 -frames 1 "$TEMPFIN" 15 | 16 | # Here's where we actually decrud it 17 | waifu2x-ncnn-vulkan -i "$TEMPFIN" -o "$TEMP_A" -s 1 -n 3 "$@" 18 | 19 | # We add a small bit of selective blur for compression ratio 20 | # and we turn it back to an 8-bit png 21 | convert "$TEMP_A" -selective-blur 0x1+0.5% -depth 8 "$TEMPFIN" 22 | 23 | # remove the temporary file 24 | rm -f "$TEMP_A" 25 | 26 | # Re-encode the png to reduce filesize a little bit 27 | optipng -zc1 -zm1 -zs0 -f5 "$TEMPFIN" 28 | png-zopfli-idat <"$TEMPFIN" | sponge "$TEMPFIN" 29 | 30 | # Write the filename of the PNG file to stdout 31 | printf 'Finished!\n%s\n' "$TEMPFIN" 32 | 33 | if [ -n "$DISPLAY" ] ; then 34 | # copy it into the Rich X clipboard, so we can just paste it into whatever 35 | # Or upload with another script 36 | xclip -i -selection clipboard -t image/png <"$TEMPFIN" 37 | 38 | # copy the filename into the Plain X clipboard 39 | printf '%s' "$TEMPFIN" | xclip 40 | 41 | # And preview it with Eye of Mate 42 | eom "$TEMPFIN" 43 | fi 44 | -------------------------------------------------------------------------------- /src/exe/openexe: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -n "$DISPLAY" ]; then 4 | if command -v code >/dev/null; then 5 | my_editor=${my_editor-code} 6 | elif command -v subl >/dev/null; then 7 | my_editor=${my_editor-subl} 8 | elif command -v pluma >/dev/null; then 9 | my_editor=${my_editor-pluma} 10 | elif command -v gedit >/dev/null; then 11 | my_editor=${my_editor-gedit} 12 | elif command -v nano >/dev/null; then 13 | my_editor=${my_editor-nano} 14 | elif command -v "$EDITOR" >/dev/null; then 15 | my_editor=${my_editor-${EDITOR}} 16 | fi 17 | fi 18 | 19 | if command -v nano >/dev/null; then 20 | my_editor="${my_editor-nano}" 21 | elif command -v vim >/dev/null; then 22 | my_editor="${my_editor-vim}" 23 | elif command -v emacs >/dev/null; then 24 | printf '%s\n' "...really? emacs?" 25 | my_editor="${my_editor-emacs}" 26 | else 27 | printf 'error: %s: %s\n' "${0##*/}" 'No editor found!' 28 | exit 1 29 | fi 30 | 31 | my_files=() 32 | 33 | for arg; do 34 | if command -v "$arg" >/dev/null; then 35 | myfile=$(command -v "$arg") 36 | else 37 | myfile=$arg 38 | fi 39 | myfile=$(realpath -- "$myfile") 40 | encoding=$(file --brief --mime-encoding "$myfile") 41 | if [ "$encoding" = binary ]; then 42 | printf '%s is a binary file.\n' "$myfile" 43 | else 44 | my_files+=("$myfile") 45 | fi 46 | done 47 | 48 | exec "$my_editor" "${my_files[@]}" 49 | -------------------------------------------------------------------------------- /src/c/crc32.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file crc32.c 3 | * @author Leo Izen (Traneptora) 4 | * 5 | * This file is in the public domain unless this is not possible by law. 6 | * In that case, you may do anything you want with this file for any 7 | * reason with or without permission. 8 | * 9 | * This file is provided as-is. All warranties are waivied, implied or 10 | * otherwise, including but not limited to the implied warranties of 11 | * merchantability and fitness for a particular purpose. 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "tranep-crc32.h" 19 | 20 | 21 | int main(int argc, char **argv) { 22 | uint32_t crc; 23 | int status; 24 | int ret = 0; 25 | const char *error; 26 | 27 | if (argc < 2 || !strncmp("--help", argv[1], 7)){ 28 | fprintf(argc < 2 ? stderr : stdout, "%s filename...\n", argv[0]); 29 | return argc < 2 ? 1 : 0; 30 | } 31 | 32 | if (argc == 2) { 33 | status = tranep_compute_filename_crc32(argv[1], &crc, &error); 34 | if (!status){ 35 | printf("%08X\n", crc); 36 | } else { 37 | fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1], error); 38 | ret = 2; 39 | } 40 | } else { 41 | for (int i = 1; i < argc; i++){ 42 | status = tranep_compute_filename_crc32(argv[i], &crc, &error); 43 | if (!status){ 44 | printf("%08X %s\n", crc, argv[i]); 45 | } else { 46 | fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], error); 47 | ret = 2; 48 | continue; 49 | } 50 | } 51 | } 52 | 53 | return ret; 54 | } 55 | -------------------------------------------------------------------------------- /src/exe/disksize: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # disksize.bash 3 | 4 | simple_mode=false 5 | found_minus=false 6 | arglist=() 7 | this_script="$0" 8 | 9 | usage() { 10 | bname="${this_script##*/}" 11 | printf >&2 '%s: Print size on disk for files or directories\n' "$bname" 12 | printf >&2 'Usage: %s [options] [--] files_or_directories...\n\n' "$bname" 13 | printf >&2 'Options:\n' 14 | printf >&2 ' --help, -h, -? Print this help message\n' 15 | printf >&2 ' -s, --simple-mode Print one entry for all provided arguments combined\n' 16 | exit "${1-1}" 17 | } 18 | 19 | for arg; do 20 | if [[ $found_minus = 'true' ]] ; then 21 | arglist+=("$arg") 22 | elif [[ $arg = '--' ]] ; then 23 | found_minus='true' 24 | elif [[ $arg = @(--simple-mode|-s) ]]; then 25 | simple_mode='true' 26 | elif [[ $arg = @(--help|-h|-?) ]] ; then 27 | usage 0 28 | else 29 | arglist+=("$arg") 30 | fi 31 | done 32 | 33 | if [ "${#arglist}" -lt 1 ] ; then 34 | usage 1 35 | fi 36 | 37 | size_of() { 38 | if [ -d "$1" && ! -L "$1" ] ; then 39 | du -bs -- "$1" | grep -Eo '^[0-9]+' 40 | else 41 | stat -c%s -- "$1" 42 | fi 43 | } 44 | 45 | if [ "$simple_mode" = "true" ] ; then 46 | byteval="$( ( ( du 2>/dev/null -bs -- "${arglist[@]}" | grep -Eo '^[0-9]+') || printf '0' ) | total)" 47 | printf '%s/%s %s\n' "$byteval" "$(printf %s "$byteval" | hprint)" "" | sort -n -t/ | grep -Ev '^0/' | sed 's:^[0-9]*/::' 48 | else 49 | for arg in "${arglist[@]}"; do 50 | byteval="$( ( ( du 2>/dev/null -bs -- "$arg" | grep -Eo '^[0-9]+') || printf '0' ) | total)" 51 | printf '%s/%s %s\n' "$byteval" "$(printf %s "$byteval" | hprint)" "$arg" 52 | done | sort -n -t/ | grep -Ev '^0/' | sed 's:^[0-9]*/::' 53 | fi 54 | -------------------------------------------------------------------------------- /src/lua/markchapter.lua: -------------------------------------------------------------------------------- 1 | -- usage at input.conf, add e.g. "M script_message mark-chapter" 2 | mp.register_script_message("mark-chapter", function() 3 | local time_pos = mp.get_property_number("time-pos") 4 | local curr_chapter = mp.get_property_number("chapter") 5 | local chapter_count = mp.get_property_number("chapter-list/count") 6 | local all_chapters = mp.get_property_native("chapter-list") 7 | 8 | -- the script will bork if we don't already have chapters 9 | -- this special case just creates one chapter 10 | if chapter_count == 0 then 11 | all_chapters[1] = {title=(tostring(time_pos)), time=time_pos} 12 | 13 | -- We just set it to zero here so when we add 1 later it ends up as 1 14 | -- otherwise it's probably "nil" 15 | curr_chapter = 0 16 | 17 | -- note that mpv will treat the beginning of the file as all_chapters[0] when using pageup/pagedown 18 | -- so we don't actually have to worry if the file doesn't start with a chapter 19 | else 20 | 21 | -- to insert a chapter we have to increase the index on all subsequent chapters 22 | -- otherwise we'll end up with duplicate chapter IDs which will confuse mpv 23 | 24 | -- +2 looks weird, but remember mpv indexes at 0 and lua indexes at 1 25 | -- adding two will turn "current chapter" from mpv notation into "next chapter" from lua's notation 26 | 27 | -- count down because these areas of memory overlap 28 | for i=chapter_count,curr_chapter+2,-1 do 29 | 30 | all_chapters[i + 1] = all_chapters[i] 31 | end 32 | 33 | -- again the +2 is because we want to insert the chapter as a new one after the "current one" 34 | all_chapters[curr_chapter+2] = {title=(tostring(time_pos)), time=time_pos} 35 | end 36 | 37 | 38 | mp.set_property_native("chapter-list", all_chapters) 39 | 40 | -- We increment the chatper count so it's now the chapter we just created 41 | mp.set_property_number("chapter", curr_chapter+1) 42 | 43 | end) 44 | -------------------------------------------------------------------------------- /src/exe/password.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | if command -v sha3-256sum >/dev/null 2>/dev/null; then 5 | SHA3_256SUM="sha3-256sum" 6 | elif command -v sha3sum >/dev/null 2>/dev/null; then 7 | SHA3_256SUM="sha3sum -a 256" 8 | else 9 | printf '%s\n' "Cannot find sha3-256sum or sha3sum" 10 | exit 1 11 | fi 12 | 13 | 14 | STORED_MHASH="${STORED_MHASH:-/tmp/stored_mhash}" 15 | 16 | if [ -e "$STORED_MHASH" ] ; then 17 | MHASH_AGE="$(perl -e 'print -M $ARGV[0];' "$STORED_MHASH")" 18 | if [ -n "$(awk -v n1="$MHASH_AGE" -v n2="0.25" 'BEGIN { print(n1 > n2 ? "x" : "" ) }')" ] ; then 19 | rm -f -- "$STORED_MHASH" 20 | else 21 | MHASH="$(xz -cd --format=raw --lzma1=dict=8MiB,lc=1,lp=1,pb=1,mode=normal,nice=64,mf=bt4,depth=0 <"$STORED_MHASH")" 22 | fi 23 | fi 24 | 25 | printf '%s' "Enter domain: " 26 | read -r domain 27 | default_username="leo.izen@gmail.com" 28 | printf 'Enter username [%s]: ' "$default_username" 29 | read -r username 30 | if [ -z "$username" ] ; then 31 | username="$default_username" 32 | fi 33 | default_index="0" 34 | printf 'Enter index [%s]: ' "$default_index" 35 | read -r index 36 | if [ -z "$index" ] ; then 37 | index="$default_index" 38 | fi 39 | default_maxlength="32" 40 | printf 'Enter max password length [%s]: ' "$default_maxlength" 41 | read -r maxlength 42 | if [ -z "$maxlength" ] ; then 43 | maxlength="$default_maxlength" 44 | elif [ ! "$maxlength" -ge 8 -o ! "$maxlength" -le 50 ] ; then 45 | printf 'Invalid maxlength: %s\n' "$maxlength" >&2 46 | exit 2 47 | fi 48 | 49 | if [ -z "${MHASH+x}" ] ; then 50 | printf "Enter master password: " 51 | # The cat pipe here causes the commands to be greedy 52 | # They won't exit until cat has sent EOF 53 | MHASH="$(cat | sh -c "$SHA3_256SUM" | cut -f1 -d' ' | perl -lne 'print pack "H*", $_' | base64)" 54 | printf "%s" "$MHASH" | xz -cz --format=raw --lzma1=dict=8MiB,lc=1,lp=1,pb=1,mode=normal,nice=64,mf=bt4,depth=0 >"$STORED_MHASH" 55 | fi 56 | 57 | 58 | PHASH="$(printf '%s%s%s%s%s' "$domain" "$username" "$index" "$MHASH" "$maxlength" | sh -c "$SHA3_256SUM" | cut -f1 -d' ' | perl -lne 'print pack "H*", $_' | base64)" 59 | 60 | printf '%s%s' "Df!1@2" "$PHASH" | head -c "$maxlength" 61 | printf '\n' 62 | -------------------------------------------------------------------------------- /src/c/tranep-crc32.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file tranep-crc32.c 3 | * @author Leo Izen (Traneptora) 4 | * 5 | * This file is in the public domain unless this is not possible by law. 6 | * In that case, you may do anything you want with this file for any 7 | * reason with or without permission. 8 | * 9 | * This file is provided as-is. All warranties are waivied, implied or 10 | * otherwise, including but not limited to the implied warranties of 11 | * merchantability and fitness for a particular purpose. 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include "tranep-crc32.h" 22 | 23 | TRANEP_EXPORT int tranep_compute_filename_crc32(const char *filename, uint32_t *crc, const char **error) { 24 | FILE *input; 25 | int status; 26 | 27 | input = fopen(filename, "rb"); 28 | if(!input) { 29 | *error = strerror(errno); 30 | return 2; 31 | } 32 | status = tranep_compute_stream_crc32(input, crc, error); 33 | fclose(input); 34 | 35 | return status; 36 | } 37 | 38 | TRANEP_EXPORT int tranep_compute_stream_crc32(FILE *input, uint32_t *crc, const char **error){ 39 | size_t chunk_size = 1 << 12; 40 | uint8_t *buffer; 41 | uint32_t calc_crc = 0; 42 | size_t bytes_read; 43 | 44 | buffer = malloc(chunk_size); 45 | 46 | if(!buffer) { 47 | *error = strerror(ENOMEM); 48 | return 1; 49 | } 50 | 51 | while (1) { 52 | 53 | bytes_read = fread(buffer, 1, chunk_size, input); 54 | 55 | if (bytes_read > 0) 56 | calc_crc = crc32_z(calc_crc, buffer, bytes_read); 57 | 58 | if (bytes_read < chunk_size) { 59 | if (ferror(input)) { 60 | *error = strerror(errno); 61 | free(buffer); 62 | return 1; 63 | } else if (feof(input)) { 64 | break; 65 | } 66 | } 67 | 68 | } 69 | 70 | free(buffer); 71 | *crc = calc_crc; 72 | 73 | return 0; 74 | } 75 | 76 | TRANEP_EXPORT uint32_t tranep_compute_buffer_crc32(const uint8_t *buffer, size_t buflen) { 77 | return crc32_z(0, buffer, buflen); 78 | } 79 | -------------------------------------------------------------------------------- /src/lua/screenshotprocess.lua: -------------------------------------------------------------------------------- 1 | utils = require 'mp.utils' 2 | msg = require 'mp.msg' 3 | lfs = require 'lfs' 4 | 5 | local function get_fname(format, default_name) 6 | local cwd = mp.get_property("working-directory") 7 | if (cwd:find("://") ~= nil) then 8 | cwd = os.getenv('HOME') 9 | end 10 | local path = "" 11 | local screenshot_dir = mp.get_property('screenshot-directory', '') 12 | if not (screenshot_dir == "") then 13 | path = screenshot_dir 14 | else 15 | path = cwd 16 | end 17 | path = path:gsub('^(~)', os.getenv('HOME')) 18 | msg.debug('cwd: '..cwd..' path: '..path) 19 | lfs.mkdir(path) 20 | local basename = default_name 21 | local direntries = utils.readdir(path, "files") 22 | local ftable = {} 23 | for i = 1, #direntries do 24 | ftable[direntries[i]] = 1 25 | end 26 | local fname = "" 27 | for i=1, 9999 do 28 | local f = string.format('%s%04d.%s', basename, i, format) 29 | if ftable[f] == nil then 30 | msg.debug('fname: '..f) 31 | return f, path, cwd 32 | end 33 | end 34 | return nil 35 | end 36 | 37 | mp.register_script_message("screenshot_write_inaccurate", function (stype) 38 | local fname, path = get_fname("png", "mpv-shot") 39 | if not (fname == nil) then 40 | -- local pngfname = fname .. "_tempshot_.png" 41 | local fpath = utils.join_path(path, fname) 42 | -- local pngfpath = utils.join_path(path, pngfname) 43 | -- mp.commandv("screenshot-to-file", pngfpath, stype) 44 | mp.commandv("screenshot-to-file", fpath, stype) 45 | mp.commandv("show-text", string.format("Screenshot: '%s'", fname)) 46 | mp.commandv("run", "/usr/bin/env", "denoise-squash-png", "--quiet", "--gpu=1", "--w2x=w2xc", "--noise-level=2", "--type=anime", fpath); 47 | end 48 | end) 49 | 50 | mp.register_script_message("screenshot_write_accurate", function (stype) 51 | local fname, path = get_fname("png", "mpv-shot") 52 | if not (fname == nil) then 53 | local fpath = utils.join_path(path, fname) 54 | mp.commandv("screenshot-to-file", fpath, stype) 55 | mp.commandv("show-text", string.format("Screenshot: '%s'", fname)) 56 | mp.commandv("run", "/usr/bin/env", "crush-png", fpath, "--quiet"); 57 | end 58 | end) 59 | 60 | -------------------------------------------------------------------------------- /src/c/tranep-crc32.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file tranep-crc32.h 3 | * @author Leo Izen (Traneptora) 4 | * 5 | * This file is in the public domain unless this is not possible by law. 6 | * In that case, you may do anything you want with this file for any 7 | * reason with or without permission. 8 | * 9 | * This file is provided as-is. All warranties are waivied, implied or 10 | * otherwise, including but not limited to the implied warranties of 11 | * merchantability and fitness for a particular purpose. 12 | */ 13 | 14 | #ifndef TRANEP_CRC32_H_ 15 | #define TRANEP_CRC32_H_ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #if defined(_WIN32) 22 | #if defined(SHARED_EXPORTS) 23 | #define TRANEP_EXPORT __declspec(dllexport) 24 | #else 25 | #define TRANEP_EXPORT __declspec(dllimport) 26 | #endif 27 | #elif defined(__GNUC__) || defined(__CLANG__) 28 | #if defined(SHARED_EXPORTS) 29 | #define TRANEP_EXPORT __attribute__((visibility("default"))) 30 | #else 31 | #define TRANEP_EXPORT __attribute__((visibility("hidden"))) 32 | #endif 33 | #else 34 | #define TRANEP_EXPORT 35 | #endif 36 | 37 | /** 38 | * Compute the CRC32 of a FILE* stream. 39 | * @param input The stream for which we want to compute the crc32. 40 | * @param returned_crc The calculated crc32 will be stored inside this pointer. Left untouched upon failure. 41 | * @param error The error string, will be stored inside this pointer. Left unchanged upon success. 42 | * @return Zero upon success, nonzero upon failure. 43 | */ 44 | TRANEP_EXPORT int tranep_compute_stream_crc32(FILE *input_f, uint32_t *crc, const char **error); 45 | 46 | /** 47 | * Compute the CRC32 of an in-memory buffer. 48 | * 49 | * @param buffer The buffer for which we want to compute the crc32. 50 | * @param buflen The size of the buffer. 51 | * @return The calculated crc32 52 | */ 53 | TRANEP_EXPORT uint32_t tranep_compute_buffer_crc32(const uint8_t *buffer, size_t buflen); 54 | 55 | /** 56 | * Compute the CRC32 of a file located on the filesystem. 57 | * 58 | * @param filename The path to the file for which we want to compute the crc32. 59 | * @param crc The calculated crc32 will be stored inside this pointer. Left untouched upon failure. 60 | * @param error The error string, will be stored inside this pointer. Left unchanged upon success. 61 | * @return Zero upon success, nonzero upon failure. 62 | */ 63 | TRANEP_EXPORT int tranep_compute_filename_crc32(const char *filename, uint32_t *crc, const char **error); 64 | 65 | #endif // TRANEP_CRC32_H_ 66 | -------------------------------------------------------------------------------- /src/exe/denoise-squash-png: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | . tbz-common.sh 3 | 4 | if [ "$#" -lt 1 ] ; then 5 | error "Insufficient arguments." "Usage: $this_script [options] filename.png" 6 | fi 7 | 8 | QUIET=false 9 | W2XARGS=() 10 | MULTI=false 11 | INPUT="" 12 | INPUTS=() 13 | 14 | for arg; do 15 | if [ "$arg" = "--quiet" ] ; then 16 | QUIET=true 17 | elif [[ "$arg" =~ ^-- ]] ; then 18 | W2XARGS+=("$arg") 19 | elif [ -z "$INPUT" ] ; then 20 | INPUT="$arg" 21 | INPUTS+=("$arg") 22 | else 23 | INPUTS+=("$arg") 24 | MULTI=true 25 | fi 26 | done 27 | 28 | if check "$MULTI"; then 29 | for INPUT in "${INPUTS[@]}"; do 30 | if ! [ -e "$INPUT" ] ; then 31 | error "Can't find: $INPUT" 32 | fi 33 | done 34 | if check "$QUIET"; then 35 | QUIETARG="--quiet" 36 | else 37 | QUIETARG="" 38 | fi 39 | for INPUT in "${INPUTS[@]}"; do 40 | shecho "$this_script_abs" $QUIETARG "${W2XARGS[@]}" "$INPUT" 41 | done | parallel --lb 42 | exit 0 43 | fi 44 | 45 | if check "$QUIET"; then 46 | STDOUT="/dev/null" 47 | STDERR="/dev/null" 48 | OQUIET="-quiet" 49 | else 50 | STDOUT="/dev/stderr" 51 | STDERR="/dev/stderr" 52 | OQUIET=" " 53 | fi 54 | 55 | clup() { 56 | if check "$FINISHED_OPTIPNG" ; then 57 | if ! $FINISHED_BOTH ; then note "Received signal, cleaning up." ; fi 58 | cp --attributes-only --preserve=all "$BAKPNG" "$INPUT" 59 | rm -f "$TEMPFILE1" 60 | rm -f "$BAKPNG" 61 | else 62 | note "Received signal, restoring from backup." 63 | cp --preserve=all "$BAKPNG" "$INPUT" 64 | rm -f "$TEMPFILE1" 65 | rm -f "$BAKPNG" 66 | rm -f "$INPUT".bak 67 | fi 68 | } 69 | 70 | trap clup EXIT 71 | 72 | BAKPNG="$INPUT".denoisesquashpng_backup.$RANDOM 73 | while [ -e "$BAKPNG" ] ; do 74 | BAKPNG="$BAKPNG".$RANDOM 75 | done 76 | 77 | FINISHED_OPTIPNG=false 78 | FINISHED_BOTH=false 79 | 80 | cp --preserve=all "$INPUT" "$BAKPNG" 81 | 82 | TEMPFILE1="$(mktemp --suffix=.png)" 83 | 84 | waifu2x-denoiser --noise-level=2 "$INPUT" "$TEMPFILE1" "${W2XARGS[@]}" >"$STDOUT" 2>"$STDERR" 85 | 86 | INHEIGHT="$(identify -format '%h' "$INPUT")" 87 | INWIDTH="$(identify -format '%w' "$INPUT")" 88 | 89 | if [ "$INHEIGHT" -gt 720 ] || [ "$INWIDTH" -gt 1280 ]; then 90 | # It maintains aspect and fits it inside 1280x720 91 | convert "$TEMPFILE1" -rotate '90<' -filter Catrom -resize 1280x720 -depth 8 "$INPUT" 92 | else 93 | convert "$TEMPFILE1" -rotate '90<' -depth 8 "$INPUT" 94 | fi 95 | 96 | optipng $OQUIET -preserve -zc9 -zm8 -zs1 -f5 "$INPUT" 97 | FINISHED_OPTIPNG=true 98 | 99 | zopflipng -y -m --filters=04me "$INPUT" "$TEMPFILE1" >"$STDOUT" 2>"$STDERR" 100 | cp "$TEMPFILE1" "$INPUT" 101 | FINISHED_BOTH=true 102 | -------------------------------------------------------------------------------- /src/c/utf8.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int utf8_read_codepoint(const char *str, size_t bufsize, uint32_t *codepoint) { 5 | const uint8_t *uns_str = str; 6 | 7 | if (!bufsize) 8 | return 0; 9 | 10 | if (!(*uns_str & 0x80u)) { 11 | *codepoint = *uns_str; 12 | return 1; 13 | } 14 | 15 | if ((*uns_str & 0xe0u) == 0xc0u) { 16 | if (bufsize < 2) 17 | return 0; 18 | if ((uns_str[1] & 0xc0u) != 0x80u) 19 | return -1; 20 | *codepoint = ((0x1fu & (uint32_t)uns_str[0]) << 6) | (0x3fu & (uint32_t)uns_str[1]); 21 | return 2; 22 | } 23 | 24 | if ((*uns_str & 0xf0u) == 0xe0u) { 25 | if (bufsize < 3) 26 | return 0; 27 | if (((uns_str[1] & 0xc0u) != 0x80u) || ((uns_str[2] & 0xc0u) != 0x80u)) 28 | return -1; 29 | *codepoint = ((0x0fu & (uint32_t)uns_str[0]) << 12) 30 | | ((0x3fu & (uint32_t)uns_str[1]) << 6) 31 | | (0x3fu & (uint32_t)uns_str[2]); 32 | return 3; 33 | } 34 | 35 | if ((*uns_str & 0xf8u) == 0xf0u) { 36 | if (bufsize < 4) 37 | return 0; 38 | if (((uns_str[1] & 0xc0u) != 0x80u) || ((uns_str[2] & 0xc0u) != 0x80u) || ((uns_str[3] & 0xc0u) != 0x80u)) 39 | return -1; 40 | *codepoint = ((0x07u & (uint32_t)uns_str[0]) << 18) 41 | | ((0x3fu & (uint32_t)uns_str[1]) << 12) 42 | | ((0x3fu & (uint32_t)uns_str[2]) << 6) 43 | | (0x3fu & (uint32_t)uns_str[3]); 44 | return 3; 45 | } 46 | 47 | // invalid utf-8 48 | return -1; 49 | } 50 | 51 | int utf8_write_codepoint(char *str, size_t bufsize, uint32_t codepoint) { 52 | if (codepoint < 0x80u) { 53 | if (!bufsize) 54 | return 0; 55 | *str = codepoint; 56 | return 1; 57 | } 58 | 59 | if (codepoint < 0x800u) { 60 | if (bufsize < 2) 61 | return 0; 62 | str[0] = ((codepoint >> 6) & 0x1fu) | 0xc0u; 63 | str[1] = (codepoint & 0x3fu) | 0x80u; 64 | return 2; 65 | } 66 | 67 | if (codepoint < 0x10000u) { 68 | if (bufsize < 3) 69 | return 0; 70 | str[0] = ((codepoint >> 12) & 0x0fu) | 0xe0u; 71 | str[1] = ((codepoint >> 6) & 0x3fu) | 0x80u; 72 | str[2] = (codepoint & 0x3fu) | 0x80u; 73 | return 3; 74 | } 75 | 76 | if (codepoint < 0x110000u) { 77 | if (bufsize < 4) 78 | return 0; 79 | str[0] = ((codepoint >> 18) & 0x07u) | 0xf0u; 80 | str[1] = ((codepoint >> 12) & 0x3fu) | 0x80u; 81 | str[2] = ((codepoint >> 6) & 0x3fu) | 0x80u; 82 | str[3] = (codepoint & 0x3fu) | 0x80u; 83 | return 4; 84 | } 85 | 86 | return -1; 87 | } 88 | -------------------------------------------------------------------------------- /src/exe/png-zopfli-idat: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -n "${1+x}" ] ; then 4 | input_file=$1 5 | if [ -r "$input_file" ] ; then 6 | exec 3<&0 7 | exec <"$input_file" 8 | else 9 | printf >&2 '%s: %s: Cannot read input file\n' "${0##*/}" "$input_file" 10 | exit 1 11 | fi 12 | fi 13 | 14 | png_magic=89504e470d0a1a0a 15 | if ! [ "$(od -t x8 -N 8 --endian=big | head -n1 | awk '{print $2}')" = "$png_magic" ] ; then 16 | printf >&2 'Input is not a png\n' 17 | exit 2 18 | fi 19 | 20 | if [ -n "${2+x}" ] ; then 21 | output_file=$2 22 | fi 23 | 24 | if [ -n "$output_file" ] ; then 25 | if [ -w "$output_file" ] ; then 26 | exec 4>&1 27 | exec >"$output_file" 28 | else 29 | printf >&2 '%s: %s: Cannot write output file' "${0##*/}" "$output_file" 30 | exit 31 | fi 32 | fi 33 | 34 | last_header_type="" 35 | temp_zz="" 36 | sequence_number="" 37 | 38 | dump_chunk() { 39 | printf '%08x' "$1" | xxd -r -p 40 | printf '%s' "$2" 41 | head -c "$1" 42 | head -c4 43 | } 44 | 45 | dump_zlib_chunk_stage1() { 46 | temp_len=$1 47 | if [ "$2" = fdAT ] ; then 48 | temp_sequence_number=$(od --endian=big -t u4 -N 4 | head -n1 | awk '{print $2}') 49 | temp_len=$((temp_len - 4)) 50 | fi 51 | if [ "$last_header_type" != "$2" ] ; then 52 | sequence_number="$temp_sequence_number" 53 | temp_zz="$(mktemp --suffix=.or.zz)" 54 | fi 55 | head -c "$temp_len" >>"$temp_zz" 56 | head -c4 >/dev/null 57 | } 58 | 59 | dump_zlib_chunk_stage2() { 60 | case "$1" in 61 | IDAT|fdAT) 62 | ;; 63 | *) 64 | return 0 65 | esac 66 | pigz -d -f -n -z - <"$temp_zz" >"${temp_zz%.zz}" || exit 2 67 | if [ "$1" = fdAT ] ; then 68 | printf '%08x' "$sequence_number" | xxd -r -p >"$temp_zz" 69 | else 70 | printf '' >"$temp_zz" 71 | fi 72 | pigz -c -f -n -z -11 - <"${temp_zz%.zz}" >>"$temp_zz" 73 | new_len=$(stat -c%s "$temp_zz") 74 | printf '%08x' "$new_len" | xxd -r -p 75 | printf '%s' "$1" 76 | head -c "$new_len" <"$temp_zz" 77 | printf '%s' "$1" | cat - "$temp_zz" | crc32.py - | awk '{print $1}' | xxd -r -p 78 | rm -f -- "$temp_zz" "${temp_zz%.zz}" 79 | } 80 | 81 | while [ "$last_header_type" != IEND ]; do 82 | len=$(od --endian=big -t u4 -N 4 | head -n1 | awk '{print $2}') 83 | read -r -N 4 header_type 84 | if [ "$last_header_type" != "$header_type" ] ; then 85 | dump_zlib_chunk_stage2 "$last_header_type" 86 | fi 87 | case "$header_type" in 88 | IHDR) 89 | printf '%s' "$png_magic" | xxd -r -p 90 | dump_chunk "$len" "$header_type" 91 | ;; 92 | IDAT|fdAT) 93 | dump_zlib_chunk_stage1 "$len" "$header_type" 94 | ;; 95 | *) 96 | dump_chunk "$len" "$header_type" 97 | esac 98 | last_header_type="$header_type" 99 | done 100 | -------------------------------------------------------------------------------- /src/c/slurp.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file slurp.c 3 | * @author Leo Izen (Traneptora) 4 | * 5 | * This file is in the public domain unless this is not possible by law. 6 | * In that case, you may do anything you want with this file for any 7 | * reason with or without permission. 8 | * 9 | * This file is provided as-is. All warranties are waivied, implied or 10 | * otherwise, including but not limited to the implied warranties of 11 | * merchantability and fitness for a particular purpose. 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define SB_INIT_BUFSIZE ((size_t)4096) 21 | #define SB_MAX_CAPACITY ((size_t)1u << 31) 22 | 23 | typedef struct { 24 | uint8_t *buf; 25 | size_t buflen; 26 | size_t capacity; 27 | } SlurpBuffer; 28 | 29 | static void free_slurpbuffer(SlurpBuffer **sb) { 30 | if (!sb) 31 | return; 32 | if (!*sb) 33 | return; 34 | if ((*sb)->buf) 35 | free((*sb)->buf); 36 | (*sb)->buf = NULL; 37 | free(*sb); 38 | *sb = NULL; 39 | } 40 | 41 | static SlurpBuffer *new_slurpbuffer(size_t capacity) { 42 | SlurpBuffer *sb = NULL; 43 | 44 | if (capacity > SB_MAX_CAPACITY) 45 | return NULL; 46 | 47 | sb = malloc(sizeof(SlurpBuffer)); 48 | if (!sb) 49 | goto fail; 50 | 51 | sb->buf = malloc(capacity); 52 | if (!sb->buf) 53 | goto fail; 54 | 55 | sb->capacity = capacity; 56 | sb->buflen = 0; 57 | return sb; 58 | 59 | fail: 60 | free_slurpbuffer(&sb); 61 | return NULL; 62 | } 63 | 64 | static SlurpBuffer *get_larger_slurpbuffer(SlurpBuffer *sb) { 65 | SlurpBuffer *new_sb = NULL; 66 | size_t new_capacity = sb->capacity * 2; 67 | if (new_capacity <= sb->capacity) 68 | /* overrun */ 69 | return NULL; 70 | new_sb = new_slurpbuffer(new_capacity); 71 | if (!new_sb) 72 | return NULL; 73 | memcpy(new_sb->buf, sb->buf, sb->buflen); 74 | new_sb->buflen = sb->buflen; 75 | free_slurpbuffer(&sb); 76 | return new_sb; 77 | } 78 | 79 | int main(int argc, char **argv) { 80 | size_t bytes_read, bytes_written; 81 | size_t remaining; 82 | FILE *fout = stdout; 83 | SlurpBuffer *sb = new_slurpbuffer(SB_INIT_BUFSIZE); 84 | int ret; 85 | 86 | while (1) { 87 | remaining = sb->capacity - sb->buflen; 88 | if (!remaining) { 89 | sb = get_larger_slurpbuffer(sb); 90 | if (!sb) { 91 | fprintf(stderr, "%s: could not allocate buffer\n", argv[0]); 92 | ret = 3; 93 | goto fail; 94 | } 95 | remaining = sb->capacity - sb->buflen; 96 | } 97 | bytes_read = fread(sb->buf + sb->buflen, 1, remaining, stdin); 98 | if (bytes_read < remaining) { 99 | if (ferror(stdin)) { 100 | perror(argv[0]); 101 | ret = 1; 102 | goto fail; 103 | } 104 | } 105 | sb->buflen += bytes_read; 106 | if (feof(stdin)) { 107 | fclose(stdin); 108 | break; 109 | } 110 | } 111 | 112 | remaining = sb->buflen; 113 | 114 | if (argc > 1) { 115 | fout = fopen(argv[1], "wb"); 116 | if (!fout) { 117 | perror(argv[0]); 118 | ret = 1; 119 | goto fail; 120 | } 121 | } 122 | 123 | while (remaining > 0) { 124 | bytes_written = fwrite(sb->buf + sb->buflen - remaining, 1, remaining, fout); 125 | if (bytes_written < remaining) { 126 | if (ferror(fout)) { 127 | perror(argv[0]); 128 | ret = 2; 129 | goto fail; 130 | } 131 | } 132 | remaining -= bytes_written; 133 | } 134 | 135 | fclose(fout); 136 | free_slurpbuffer(&sb); 137 | 138 | return 0; 139 | 140 | fail: 141 | free_slurpbuffer(&sb); 142 | if (fout && fout != stdout) 143 | fclose(fout); 144 | return ret; 145 | } 146 | -------------------------------------------------------------------------------- /src/c/ecma-encode.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "utf8.h" 7 | #include "ecma-encode.h" 8 | 9 | static const uint8_t ecma_uri_component_lut[] = { 10 | 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 11 | 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 12 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13 | 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 15 | }; 16 | 17 | static const uint8_t ecma_uri_lut[] = { 18 | 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 20 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21 | 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 23 | }; 24 | 25 | static inline int hex_to_int(uint8_t hex) { 26 | if (hex >= '0' && hex <= '9') 27 | return hex - '0'; 28 | if (hex >= 'a' && hex <= 'f') 29 | return hex - 'a' + 0xau; 30 | if (hex >= 'A' && hex <= 'F') 31 | return hex - 'A' + 0xau; 32 | return -1; 33 | } 34 | 35 | static inline int int_to_hex(uint8_t input) { 36 | if (input < 10) 37 | return input + '0'; 38 | if (input < 16) 39 | return input + 'a'; 40 | return -1; 41 | } 42 | 43 | // (if deadlink) https://web.archive.org/web/20210622004141/ 44 | // https://tc39.es/ecma262/multipage/global-object.html#sec-uri-handling-functions 45 | static int ecma_encode(char **dest, const char *input, const uint8_t *lut) { 46 | int status = 0; 47 | size_t str_len = strlen(input); 48 | char *ret = calloc(str_len + 1, 3); 49 | 50 | if (!ret) 51 | goto encode_fail; 52 | 53 | size_t ret_offset = 0; 54 | size_t byte_offset = 0; 55 | 56 | while (byte_offset < str_len) { 57 | uint32_t codepoint; 58 | status = utf8_read_codepoint(input + byte_offset, str_len - byte_offset + 1, &codepoint); 59 | if (status <= 0) 60 | goto encode_fail; 61 | if (codepoint >= 32 && codepoint < 127 && lut[codepoint - 32]) { 62 | ret[ret_offset] = codepoint; 63 | ret_offset += 1; 64 | } else { 65 | for (int i = 0; i < status; i++) { 66 | uint32_t byte = input[byte_offset + i]; 67 | ret[ret_offset] = '%'; 68 | ret[ret_offset + 1] = int_to_hex((byte >> 4) & 0xfu); 69 | ret[ret_offset + 2] = int_to_hex(byte & 0xfu); 70 | ret_offset += 3; 71 | } 72 | } 73 | byte_offset += status; 74 | } 75 | 76 | ret[ret_offset] = '\0'; 77 | *dest = ret; 78 | return 0; 79 | 80 | encode_fail: 81 | if (ret) 82 | free(ret); 83 | return -1; 84 | } 85 | 86 | int ecma_encode_uri_component(char **dest, const char *input) { 87 | return ecma_encode(dest, input, ecma_uri_component_lut); 88 | } 89 | 90 | int ecma_encode_uri(char **dest, const char *input) { 91 | return ecma_encode(dest, input, ecma_uri_lut); 92 | } 93 | 94 | int ecma_decode(char **dest, const char *input){ 95 | int status = 0; 96 | const size_t str_len = strlen(input); 97 | char *ret = malloc(str_len + 1); // R, in the spec 98 | 99 | if (!ret) 100 | goto decode_fail; 101 | 102 | size_t ret_offset = 0; 103 | size_t byte_offset = 0; // k, in the spec 104 | 105 | while (byte_offset < str_len) { 106 | uint32_t codepoint; 107 | status = utf8_read_codepoint(input + byte_offset, str_len - byte_offset + 1, &codepoint); 108 | if (status <= 0) 109 | goto decode_fail; 110 | if (codepoint == '%') { 111 | if (byte_offset + 2 > str_len) 112 | goto decode_fail; 113 | int v0 = hex_to_int(input[byte_offset + 1]); 114 | int v1 = hex_to_int(input[byte_offset + 2]); 115 | if (v0 < 0 || v1 < 0) 116 | goto decode_fail; 117 | ret[ret_offset++] = (v0 << 4) | v1; 118 | byte_offset += 3; 119 | } else { 120 | for (int i = 0; i < status; i++) 121 | ret[ret_offset + i] = input[byte_offset + i]; 122 | ret_offset += status; 123 | byte_offset += status; 124 | } 125 | } 126 | 127 | ret[ret_offset++] = '\0'; 128 | *dest = ret; 129 | return 0; 130 | 131 | decode_fail: 132 | if (ret) 133 | free(ret); 134 | return -1; 135 | } 136 | -------------------------------------------------------------------------------- /src/exe/pacgit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # pacgit 4 | 5 | # Copyright 2020 Leo Izen (thebombzen) 6 | # 7 | # Permission is hereby granted, free of charge, to any person 8 | # obtaining a copy of this software and associated documentation 9 | # files (the "Software"), to deal in the Software without 10 | # restriction, including without limitation the rights to use, 11 | # copy, modify, merge, publish, distribute, sublicense, and/or 12 | # sell copies of the Software, and to permit persons to whom the 13 | # Software is furnished to do so, subject to the following 14 | # conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | # OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | # === Start Necessary Boilerplate === 29 | _shell_escape() { 30 | for _shell_escape_arg; do 31 | _shell_escape_arg="$(printf %s "$_shell_escape_arg" | sed "s/'/'\\\\''/g"; printf 'x')" 32 | _shell_escape_arg="${_shell_escape_arg%x}" 33 | printf "'%s' " "$_shell_escape_arg" | sed "s/^''//" | sed "s/\([^\\\\]\)''/\1/g" 34 | done 35 | printf '\n' 36 | } 37 | 38 | normal="$(tput sgr0)" 39 | #bold="$(tput bold)" 40 | red="$(tput setaf 1)" 41 | green="$(tput setaf 2)" 42 | yellow="$(tput setaf 3)" 43 | blue="$(tput setaf 4)" 44 | # ==== End Necessary Boilerplate ==== 45 | 46 | pkgbuild_file="$(dirname "$(mktemp --dry-run)")/_pacgit_pkgbuild" 47 | update_pkgs="false" 48 | 49 | if [ "${1-x}" = "--update" ] && [ "$#" = "1" ]; then 50 | if [ "$(id -u)" = "0" ] ; then 51 | printf 'Must not be root.\n' 52 | exit 1 53 | fi 54 | sudo true || exit 1 55 | update_pkgs="true" 56 | elif [ "$#" -gt 0 ] ; then 57 | printf 'Usage: %s [--update]\n' "$(basename "$0")" 58 | exit 2 59 | fi 60 | 61 | _checkpkg() { 62 | want_pacaur_update=2 63 | package_name="$1" 64 | package_version_local="$(pacman -Q "$package_name" | sed -r 's/^.*?-git\s+//')" 65 | package_version_upstream_hash="" 66 | pkgbuild_url="https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=${package_name}" 67 | package_url="" 68 | if ! curl -s -f -o "$pkgbuild_file" --url "$pkgbuild_url" ; then 69 | _print_version 70 | return $want_pacaur_update 71 | fi 72 | package_url="$(/bin/bash -c 'source '"$(_shell_escape "${pkgbuild_file}")"' && printf %s "$source"' | sed -r 's/^.*?git\+([^ ]+(\.git)?).*$/\1/')" 73 | rm -f -- "$pkgbuild_file" 74 | if ! ( printf '%s' "$package_url" | grep -Eq -e '^[a-z]+://' ) ; then 75 | _print_version 76 | return $want_pacaur_update 77 | fi 78 | if [ "${package_url%\#branch=*}" != "${package_url}" ] ; then 79 | git_branch="refs/heads/${package_url##*\#branch=}" 80 | elif [ "${package_url%\#commit=*}" != "${package_url}" ] ; then 81 | package_version_upstream_hash="${package_url##*\#commit=}" 82 | else 83 | git_branch="HEAD" 84 | fi 85 | if [ -z "$package_version_upstream_hash" ] ; then 86 | if ! ls_remote_out="$(git ls-remote "${package_url%%\#*}" "$git_branch")"; then 87 | _print_version 88 | return $want_pacaur_update 89 | fi 90 | package_version_upstream_hash="$(printf '%s\n' "$ls_remote_out" | head -n1 | sed -r 's:\s+.*$::')" 91 | fi 92 | hash_chunk="$(printf '%s\n' "$package_version_local" | sed -r 's/^([^\.:]*[\.:]+)*//' | sed -r 's/-[0-9a-zA-Z]+$//')" 93 | if printf '%s\n' "$package_version_upstream_hash" | grep -Eq -e "^${hash_chunk}" -e "^${hash_chunk#?}"; then 94 | want_pacaur_update=1 95 | else 96 | want_pacaur_update=0 97 | fi 98 | _print_version 99 | return $want_pacaur_update 100 | } 101 | 102 | _print_version() { 103 | case "$want_pacaur_update" in 104 | 0) 105 | status_color="${red}" 106 | ;; 107 | 1) 108 | status_color="${green}" 109 | ;; 110 | *) 111 | status_color="${yellow}" 112 | ;; 113 | esac 114 | printf '%s\n\tURL: %s\n\tCurrent version: %s\n\tRemote version: %s\n\n' "${status_color}${package_name}${normal}" "${blue}${package_url}${normal}" "${package_version_local}" "${package_version_upstream_hash}" 115 | } 116 | 117 | updatelist="" 118 | updatelistnl="" 119 | pkglist="$(pacman -Qmq | grep -E -e '-git$' | tr '\n' ' ')" 120 | for pkg in $pkglist; do 121 | if _checkpkg "$pkg"; then 122 | updatelist="$(printf '%s %s' "$updatelist" "$pkg")" 123 | updatelistnl="$(printf '%s\n%s' "$pkg" "$updatelistnl")" 124 | fi 125 | done 126 | 127 | printf '%s\n' "${updatelist#?}" 128 | if [ "$update_pkgs" = "true" ] ; then 129 | if [ -z "$updatelist" ] ; then 130 | paru -Syu --noconfirm 131 | else 132 | printf '%s\n' "$updatelistnl" | paru -Syu --noconfirm - 133 | fi 134 | fi 135 | 136 | rm -f -- "$pkgbuild_file" 137 | -------------------------------------------------------------------------------- /src/exe/make-tumblr-gif: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e; set +H 3 | 4 | exename="$0" 5 | 6 | finish() { 7 | if [ $? -ne 0 ] ; then 8 | echo "Some error occurred. :(" 9 | fi 10 | rm -f $nutfile 11 | rm -f $palettefile 12 | rm -f $datafile 13 | rm -f $giffile 14 | } 15 | 16 | trap finish EXIT 17 | 18 | usage(){ 19 | printf "Usage:\n\t%s input_video_file output_gif_file [ --fullrate | --forcerate=N | --scalerate=N | --bayerscale={1-5} | --minsize=N | --maxsize=N | --maxcolors={2-255} ] ... \n" "${exename##*/}" 20 | echo 21 | echo $(basename $0) "converts the input video to a GIF with size between 1.9 MB and 2.0 MB." 22 | echo "2.0 MB is the tumblr GIF size limit, which is why this limit is useful." 23 | echo "It normally halves the framerate but --fullrate tells" $(basename $0) "to use the full frame rate." 24 | } 25 | 26 | if [ "$1" = "-h" ] || [ "$1" = "-?" ] || [ "$2" = "" ] || [ "$1" = "-help" ] || [ "$1" = "--help" ] ; then 27 | usage 28 | exit 1 29 | fi 30 | 31 | 32 | datafile=$(mktemp) 33 | palettefile=$(mktemp) 34 | giffile=$(mktemp) 35 | nutfile=$(mktemp) 36 | 37 | inputfile="$1"; shift 38 | outputfile="$1"; shift 39 | 40 | doforcerate=false 41 | dofullrate=false 42 | doscalerate=false 43 | forcedrate="" 44 | scaledrate="" 45 | bayerscale=3 46 | maxcolors=144 47 | maxsize=2097151 48 | minsize="" 49 | 50 | for i; do 51 | if [[ "$i" =~ ^--fullrate$ ]] ; then 52 | dofullrate=true 53 | doforcerate=false 54 | doscalerate=false 55 | elif [[ "$i" =~ ^--forcerate= ]] ; then 56 | doforcerate=true 57 | dofullrate=false 58 | doscalerate=false 59 | forcedrate="${i#--forcerate=}" 60 | elif [[ "$i" =~ ^--scalerate= ]] ; then 61 | doforcerate=false 62 | dofullrate=false 63 | doscalerate=true 64 | scaledrate="${i#--scalerate=}" 65 | elif [[ "$i" =~ ^--bayerscale= ]] ; then 66 | bayerscale="${i#--bayerscale=}" 67 | elif [[ "$i" =~ ^--maxcolors= ]] ; then 68 | maxcolors="${i#--maxcolors=}" 69 | elif [[ "$i" =~ ^--maxsize= ]] ; then 70 | maxsize="${i#--maxsize=}" 71 | elif [[ "$i" =~ ^--minsize= ]] ; then 72 | minsize="${i#--minsize=}" 73 | else 74 | >&2 echo "Unknown option: $i" 75 | exit 1 76 | fi 77 | done 78 | 79 | if [ -z "$minsize" ] ; then 80 | minsize=$(printf "%.0f" "$(printf '%s*0.9375\n' "$maxsize" | bc -l)") 81 | fi 82 | 83 | ffprobe -select_streams v -of flat -show_streams -show_format -hide_banner >$datafile 2>/dev/null "$inputfile" 84 | 85 | extract() { 86 | value=$(grep streams.stream.0.$1= $datafile | sed "s/streams.stream.0.$1=//") 87 | if [ -z "$value" ] || [ "$value" = '"N/A"' ] ; then 88 | value=$(grep format.$1= $datafile | sed "s/format.$1=//") 89 | fi 90 | echo $value 91 | } 92 | 93 | filesize() { 94 | stat -c%s "$1" 95 | } 96 | 97 | width=$(extract width) 98 | height=$(extract height) 99 | duration=$(extract duration | tr -d '"') 100 | rate=$(extract r_frame_rate | tr '"' ' ' | bc -l) || rate=$(extract avg_frame_rate | tr '"' ' ' | bc -l) 101 | targetrate=$(awk -v rate="$rate" 'BEGIN { if (rate > 30) print 30; else print rate;}') 102 | halfrate=$(echo "($targetrate)*0.5" | bc -l) 103 | if $dofullrate; then 104 | framerate="-r $targetrate" 105 | elif $doforcerate; then 106 | framerate="-r $forcedrate" 107 | elif $doscalerate; then 108 | framerate="-vf setpts=N/$scaledrate/TB -r $scaledrate" 109 | else 110 | framerate="-r $halfrate" 111 | fi 112 | scale=1 113 | scalehigh=1 114 | scalelow=1 115 | foundlowerbound=0 116 | 117 | fractionDone(){ 118 | while read line; do 119 | if [ -n "$duration" ] && ! [ "$duration" = 'N/A' ] ; then 120 | timedone=$(echo $line | tr ' ' '\n' | grep time= | sed 's/time=//' | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }') 121 | printf "%s%%\n" $(echo "scale=4;$timedone/$duration*100" | bc) 122 | fi 123 | done 124 | } 125 | 126 | creategif(){ 127 | newwidth=$(echo "scale=0;" $width \* $scale | bc) 128 | newheight=$(echo "scale=0;" $height \* $scale | bc) 129 | 130 | echo "Testing Size: " ${newwidth%.*}x${newheight%.*} 131 | echo -n "Scaling Video... " 132 | ffmpeg -y -v error -i "$inputfile" -map v -vf "zscale=f=lanczos,format=bgr0,zscale=${newwidth%.*}x${newheight%.*}:f=spline36,format=bgr0" $framerate -c ffv1 -f nut $nutfile 133 | echo -ne 'Done. \r' 134 | # | stdbuf -o0 tr '\r' '\n' | grep --line-buffered "^frame" | fractionDone | while read line; do echo -n "Scaling Video... "; echo $line; done | stdbuf -o0 tr '\n' '\r' 135 | echo -n "Scaling Video... Done. Generating Palette... " 136 | ffmpeg -y -f nut -i $nutfile -vf "palettegen=max_colors=${maxcolors}" -c png -f image2 $palettefile >/dev/null 2>&1 137 | echo -ne 'Done. \r' 138 | ffmpeg -y -f nut -i $nutfile -f png_pipe -i $palettefile -lavfi "paletteuse=dither=bayer:bayer_scale=${bayerscale}" -c gif -f gif $giffile 2>&1 | stdbuf -o0 tr '\r' '\n' | grep --line-buffered "^frame" | fractionDone | while read line; do echo -n "Scaling Video... Done. Generating Palette... Done. Generating GIF... "; echo $line; done | stdbuf -o0 tr '\n' '\r' 139 | echo -n "Scaling Video... Done. Generating Palette... Done. Generating GIF... Done. Crushing GIF... " 140 | gifsicle --batch --unoptimize --optimize=3 "$giffile" 141 | echo 'Done.' 142 | } 143 | 144 | adjustscale(){ 145 | echo -n "Checking Filesize... " 146 | currfilesize=$(filesize $giffile) 147 | if [ $currfilesize -gt $maxsize ] ; then 148 | if [ $foundlowerbound -eq 0 ] ; then 149 | echo -n "Too Big: " 150 | scalehigh=$scale 151 | scalelow=$(echo $scale / 2 | bc -l) 152 | scale=$scalelow 153 | else 154 | echo -n "Too Big: " 155 | scalehigh=$scale 156 | scale=$(echo 0.5 \* $scalelow + 0.5 \* $scalehigh | bc -l) 157 | fi 158 | elif [ $currfilesize -lt $minsize ] && [ $(echo "$scale < 1" | bc) -ne 0 ]; then 159 | echo -n "Too Small: " 160 | foundlowerbound=1 161 | scalelow=$scale 162 | scale=$(echo 0.5 \* $scalelow + 0.5 \* $scalehigh | bc -l) 163 | else 164 | echo -n "Just right: " 165 | fi 166 | echo $currfilesize 167 | echo 168 | } 169 | 170 | while [ $(echo "$scale < 1" | bc) -ne 0 ] && [ $(filesize $giffile) -lt $minsize ] || [ $(filesize $giffile) -gt $maxsize ] || [ $(filesize $giffile) -eq 0 ] ; do 171 | creategif 172 | adjustscale 173 | done 174 | 175 | mv $giffile "$outputfile" 176 | 177 | 178 | -------------------------------------------------------------------------------- /src/exe/exsorttar: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e; set +H 3 | shopt -s expand_aliases 4 | alias echo='>&2 echo' 5 | 6 | minorusage() { 7 | echo "Usage:" "$(basename $0) [options] [archive.tar] file_or_directory..." 8 | } 9 | 10 | usage(){ 11 | minorusage 12 | echo 13 | echo "exsorttar will take a list of files and directories and recurse through with find(1)." 14 | echo 15 | echo "It will create a tar(1) archive with directories first (sorted by name, starting with the top directories), followed by regular files (sorted by file extension, then by filename, then by directory name), followed by everything else (including symbolic links, etc.)" 16 | echo "" 17 | echo "Options:" 18 | echo " --help,-h,-? Print this help message." 19 | echo " --no-tar Do not create an archive." 20 | echo " Instead, print the file list in order to standard out." 21 | echo " --findarg= Add as an argument to the find command." 22 | echo " You may specify --findarg multiple times to add multiple arguments." 23 | echo " --exclude= Ignore any path with the given pattern." 24 | echo " Example: --exclude='*.git*'" 25 | echo " You may specify --exclude multiple times to exclude multiple paths." 26 | echo " --tararg= Add as an argument to the tar command." 27 | echo " You may specify --tararg multiple times to add multiple arguments." 28 | echo " Example: --tararg=-z" 29 | echo " --lz4 Compress the tar archive with lz4 HC" 30 | echo " (Similar efficiency to gzip, fast compression, very fast decompression)" 31 | echo " --force Don't ask questions before making decisions." 32 | echo " This is particularly useful in a script, but not recommended for interactive use." 33 | echo "" 34 | echo "You are required to give the tar archive filename unless --no-tar is specified." 35 | echo "Pass '-' as the archive name to write the archive to standard out." 36 | echo "The tar archive will be uncompressed unless --lz4 is specified. If you want a compressed archive, you can write the archive to standard out and filter using your favorite compression program to create a compressed archive on the fly." 37 | echo "You could also use a tararg, like --tararg=--xz, to compress the archive with xz." 38 | echo "" 39 | echo "Note: Careful with directory names starting with a - or other odd things as this may mess up find(1)." 40 | exit $1 41 | } 42 | 43 | # people like not having to type --help again 44 | if [ $# -eq 0 ] ; then 45 | usage 1 46 | fi 47 | 48 | # check all the options for --help first 49 | for i; do 50 | if [[ "$i" =~ ^--help|-h|-\?$ ]] ; then 51 | usage 0 52 | fi 53 | done 54 | 55 | # our parameters 56 | force=false 57 | lz4=false 58 | notar=false 59 | tarfile="" 60 | tarargs=() 61 | findargs=() 62 | filelist=() 63 | foundfirst=false 64 | 65 | # parse the arguments 66 | for i; do 67 | if ! $foundfirst; then 68 | if [[ "$i" =~ ^--findargs=.+$ ]] ; then 69 | j="$(printf %s "$i" | sed 's/^--findargs=\(.*\)$/\1/')" 70 | findargs+=("$j") 71 | elif [[ "$i" =~ ^--exclude=.+$ ]]; then 72 | j="$(printf %s "$i" | sed 's/^--exclude=\(.*\)$/\1/')" 73 | findargs+=("-not") 74 | findargs+=("-path") 75 | findargs+=("$j") 76 | findargs+=("-and") 77 | elif [[ "$i" =~ ^--tarargs=.+$ ]]; then 78 | j="$(printf %s "$i" | sed 's/^--tarargs=\(.*\)$/\1/')" 79 | tarargs+=("$j") 80 | elif [ "$i" == "--lz4" ] ; then 81 | lz4=true 82 | elif [ "$i" == "--force" ] ; then 83 | force=true 84 | elif [ "$i" == "--no-tar" ] ; then 85 | notar=true 86 | elif [ "$i" == "--" ] ; then 87 | foundfirst=true 88 | continue 89 | else 90 | foundfirst=true 91 | fi 92 | fi 93 | if $foundfirst; then 94 | if ! $notar && [ "$tarfile" == "" ] ; then 95 | tarfile="$i" 96 | elif [ "$i" != "$tarfile" ] ; then 97 | filelist+=("$i") 98 | else 99 | echo "$i is the same as the archive to create." 100 | exit 1 101 | fi 102 | fi 103 | done 104 | 105 | # what if they don't give us anything at all? 106 | if ! $notar && [ -z "$tarfile" ]; then 107 | echo "Error: No archive name on command line, and no --no-tar." 108 | minorusage 109 | exit 1 110 | fi 111 | 112 | # what if they don't give us anything to archive? 113 | if ! $notar && [ -z "${filelist[0]}" ]; then 114 | echo "Error: No list of files provided. Provide '.' to archive the current directory." 115 | minorusage 116 | exit 1 117 | fi 118 | 119 | 120 | # This failsafe works especially well at detecting when people forgot the archive name and jumped right to a list of dirs. 121 | if ! $notar && [ -d "$tarfile" ] ; then 122 | echo "Tar archive, $tarfile, is currently a directory." 123 | echo "Did you forget to add the archive name?" 124 | minorusage 125 | exit 1 126 | fi 127 | 128 | # doublecheck the file arguments 129 | # foundminus=false 130 | for f in "${filelist[@]}"; do 131 | if [[ "$f" =~ ^- ]] ; then 132 | echo "Error: Interpreting $f as a file or directory name. This can break \`find\`. Use ./$f to supress this error." 133 | exit 1 134 | fi 135 | if [ ! -e "$f" ] ; then 136 | echo "Error: $f: No such file or directory." 137 | exit 1 138 | fi 139 | done 140 | 141 | # Allow the user to take back typos. Does not stop on devices because that's just annoying. 142 | if ! $force && ! $notar && [ -f "$tarfile" ]; then 143 | if [ -t 0 ] ; then 144 | read -p "Tar archive, $tarfile, already exists. Overwrite? [y/N]: " line 145 | if ! [[ "$line" =~ ^y|Y ]]; then 146 | echo "Not overwriting $tarfile." 147 | exit 1 148 | fi 149 | else 150 | echo "$tarfile already exists. Use --force to overwrite it." 151 | exit 1 152 | fi 153 | fi 154 | 155 | # tar -I allows you to use an external program for filtering. We use -I 'lz4 -9 -z' 156 | if $lz4; then 157 | if ! lz4 -9 -z - - /dev/null 2>/dev/null; then 158 | echo "lz4 not found in PATH." 159 | exit 2 160 | fi 161 | tarargs+=("-I") 162 | tarargs+=("lz4 -9 -z") 163 | fi 164 | 165 | # alright, let's get to it! 166 | 167 | sortbyext(){ 168 | sed 's_^\(\([^/]*/\)*\)\(.*\)\(\.[^\./]*\)$_\4/\3/\1_' | sed 's_^\(\([^/]*/\)*\)\([^\./]\+\)$_/\3/\1_' | sort -t/ -k1,1 -k2,2 -k3,3 | sed 's_^\([^/]*\)/\([^/]*\)/\(.*\)$_\3\2\1_' 169 | } 170 | 171 | printfilelist(){ 172 | cat <(find "${filelist[@]}" "${findargs[@]}" -type d | sort -t/) <(find "${filelist[@]}" "${findargs[@]}" -type f | sortbyext) <(find "${filelist[@]}" "${findargs[@]}" -not -type d -and -not -type f | sort -t/) 173 | } 174 | 175 | if $notar; then 176 | printfilelist 177 | else 178 | tar --no-recursion -T <(printfilelist) "${tarargs[@]}" -cvf "$tarfile" 179 | fi 180 | 181 | -------------------------------------------------------------------------------- /src/exe/crush-image: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # crush-image 4 | 5 | found_mm=false 6 | input_files=() 7 | 8 | # only used for error messages 9 | bname="${0##*/}" 10 | 11 | check_expand='false' 12 | lossy_png='false' 13 | 14 | usage(){ 15 | exec >&2 16 | printf 'Usage: %s [options...] input.png...\n\n' "$bname" 17 | printf 'Options:\n' 18 | printf ' -h, -?,\n' 19 | printf ' --help Print this help message\n' 20 | printf ' --no-expand Do not check images for extra frames,\n' 21 | printf ' treat file as one image (default)\n' 22 | printf ' --expand Check images for extra frames, and crush as an animation\n' 23 | printf ' --no-lossy-png Recompress PNGs losslessly (default)' 24 | printf ' --lossy-png Use pngquant to lossily preprocess PNGs\n' 25 | printf ' -- Interpret all arguments after\n' 26 | printf ' this one as an input filename\n' 27 | exit "${1-1}" 28 | } 29 | 30 | for arg; do 31 | if [[ $found_mm = 'true' ]] ; then 32 | input_files+=("$arg") 33 | continue 34 | fi 35 | case $arg in 36 | '--') 37 | found_mm='true' 38 | ;; 39 | '--help'|'-h'|'-?') 40 | usage 0 41 | ;; 42 | '--expand') 43 | check_expand='true' 44 | ;; 45 | '--no-expand') 46 | check_expand='false' 47 | ;; 48 | '--lossy-png') 49 | lossy_png='true' 50 | ;; 51 | '--no-lossy-png') 52 | lossy_png='false' 53 | ;; 54 | *) 55 | input_files+=("$arg") 56 | esac 57 | done 58 | 59 | if [[ "${#input_files[@]}" -lt 1 ]] ; then 60 | usage 61 | fi 62 | 63 | if [[ $lossy_png = 'true' ]] && ! command >/dev/null -v pngquant ; then 64 | printf >&2 '%s: could not find pngquant, cannot use --lossy-png\n' "$bname" 65 | exit 1 66 | fi 67 | 68 | if [[ $check_expand = 'true' ]] && ! command >/dev/null -v apngasm ; then 69 | printf >&2 '%s: could not find apngasm, cannot use --expand\n' "$bname" 70 | exit 1 71 | fi 72 | 73 | temp_images=() 74 | backup_images=() 75 | current_images=() 76 | temp_dirs=() 77 | success='true' 78 | 79 | check_validity(){ 80 | fname="$1" 81 | # the redirect is intentionally in the wrong order here, 82 | # if I had wanted to send both streams to /dev/null 83 | # I want to send stderror to stdout and capture it 84 | # but discard actual stdout output to /dev/null 85 | emsg="$(head -c1 2>&1 >/dev/null "$fname")" 86 | if [ -z "$emsg" ] ; then 87 | printf 'true' 88 | else 89 | printf >&2 '%s%s\n' "$bname" "${emsg#head}" 90 | printf 'false' 91 | fi 92 | } 93 | 94 | cleanup() { 95 | while [[ "${#current_images[@]}" -gt 0 ]]; do 96 | current_image="${current_images[0]}" 97 | backup_image="${backup_images[0]}" 98 | if [[ -n "${current_image}" ]] ; then 99 | printf >&2 'Caught signal, restoring from backup\n' 100 | if [[ -f "${backup_image}" ]] ; then 101 | cp --preserve=all -- "$backup_image" "$current_image" 102 | rm -f -- "$backup_image" 103 | else 104 | printf >&2 'Unable to find backup image: %s\n' "$backup_image" 105 | fi 106 | fi 107 | rm -f -- "$backup_image" 108 | current_images=("${current_images[@]:1}") 109 | backup_images=("${backup_images[@]:1}") 110 | done 111 | rm -f -- "${temp_images[@]}" 112 | rm -fr -- "${temp_dirs[@]}" 113 | } 114 | 115 | trap cleanup EXIT 116 | 117 | divide_percent(){ 118 | awk -v num="$1" -v denom="$2" 'BEGIN { printf("%.2f\n", 100.0 * num/denom) }' 119 | } 120 | 121 | _crush_png() { 122 | if [ "$3" = "true" ] ; then 123 | _crush_apng "$@" 124 | return "$?" 125 | fi 126 | printf >&2 'Stripping unnecessary chunks...\n' 127 | umbrielpng --fix-in-place -- "$1" 128 | printf >&2 'Performing optipng optimizations...\n' 129 | optipng -fix -force -preserve -zc1 -zm1 -zs1 -f5 "$1" -out "$4" 130 | if [ "$lossy_png" = "true" ] ; then 131 | printf >&2 'Using pngquant...\n' 132 | pngquant "$4" 133 | fi 134 | printf >&2 'Crushing with zopfli...\n' 135 | png-zopfli-idat <"$4" >"$2" 136 | if ! [ "$(file --brief --no-pad --mime-type "$2")" = image/png ] ; then 137 | printf >&2 "not a png?\n" 138 | exit 2 139 | fi 140 | if [ "$(stat -c%s "$1")" -lt "$(stat -c%s "$2")" ] ; then 141 | printf >&2 'Looks like optipng expanded it, trying without optipng' 142 | png-zopfli-idat <"$1" >"$2" 143 | fi 144 | } 145 | 146 | # This is to catch errors 147 | _apngasm() { 148 | #printf '%s\n' "$*" 149 | apngasm "$@" 150 | } 151 | 152 | _crush_apng() { 153 | printf >&2 'Exploding possible animated png\n' 154 | local input_file="$1" 155 | local temp_file="$2" 156 | local temp_dir="${temp_file}.d" 157 | temp_dirs+=("$temp_dir") 158 | _apngasm --force --output "$temp_dir" --json directive.json --disassemble "$input_file" 159 | local new_png; for new_png in "$temp_dir"/*.png; do 160 | _crush_image "$new_png" "false" 161 | done 162 | _apngasm --force --output "$temp_file" --file "${temp_dir}/directive.json" 163 | rm -rf -- "$temp_dir" 164 | } 165 | 166 | _crush_jpg() { 167 | printf >&2 'Stripping JPEG exifdata (except timestamp) with jhead\n' 168 | jhead -purejpg -mkexif -dsft -autorot "$1" 169 | printf >&2 'Optimizing JPEG with jpegtran\n' 170 | jpegtran -optimize -progressive <"$1" >"$2" 171 | } 172 | 173 | _crush_gif(){ 174 | printf >&2 'Optimizing GIF with gifsicle\n' 175 | gifsicle --unoptimize --optimize=3 --no-comments --no-names --no-extensions --no-interlace -o "$2" "$1" 176 | } 177 | 178 | _crush_image() { 179 | local input_file="$1" 180 | local expand="$2" 181 | if [ "$(check_validity "$input_file")" = "false" ] ; then 182 | return 2 183 | fi 184 | local mime_type 185 | mime_type="$(file --brief --no-pad --mime-type "$input_file")" 186 | case "$mime_type" in 187 | image/png) 188 | printf >&2 'Found PNG image: %s\n' "$input_file" 189 | crush_func=_crush_png 190 | ;; 191 | image/jpeg) 192 | printf >&2 'Found JPEG image: %s\n' "$input_file" 193 | crush_func=_crush_jpg 194 | ;; 195 | image/gif) 196 | printf >&2 'Found GIF image: %s\n' "$input_file" 197 | crush_func=_crush_gif 198 | ;; 199 | *) 200 | printf >&2 '%s: Unrecognized file type: %s\n' "$bname" "$mime_type" 201 | return 2 202 | ;; 203 | esac 204 | local current_image 205 | current_image="$input_file" 206 | current_images+=("$current_image") 207 | local backup_image 208 | backup_image="${input_file}.crush-image.bak" 209 | backup_images+=("$backup_image") 210 | local temp_image 211 | local temp_image2 212 | temp_image="${current_image}.crush-image-tmp.png" 213 | temp_image2="${current_image}.crush-image-tmp2.png" 214 | temp_images+=("$temp_image" "$temp_image2") 215 | cp -f --preserve=all -- "$current_image" "$backup_image" 216 | 217 | local old_size 218 | old_size="$(stat -c%s "$current_image")" 219 | "$crush_func" "$current_image" "$temp_image" "$expand" "$temp_image2" 220 | local new_size 221 | new_size="$(stat -c%s "$temp_image")" 222 | printf >&2 '\nOld size: %d, new size: %d\nNew file %s bytes lower and %s%% size\n' "$old_size" "$new_size" "$((old_size - new_size))" "$(divide_percent "$new_size" "$old_size")" 223 | if [ "$new_size" -ge "$old_size" ] ; then 224 | printf >&2 'New size is larger, so not doing anything\n\n' 225 | cp -f --preserve=all -- "$backup_image" "$current_image" 226 | else 227 | printf >&2 '\n' 228 | mv -f -- "$temp_image" "$current_image" 229 | cp -f --attributes-only --preserve=all -- "$backup_image" "$current_image" 230 | fi 231 | rm -f -- "$temp_image" "$backup_image" "$temp_image2" 232 | 233 | unset "current_images[${#current_images[@]}-1]" 234 | unset "backup_images[${#backup_images[@]}-1]" 235 | 236 | for target in "$temp_image" "$temp_image2"; do 237 | for i in "${!temp_images[@]}"; do 238 | if [ "$i" = "$target" ] ; then 239 | unset 'temp_images[i]' 240 | fi 241 | done 242 | done 243 | } 244 | 245 | for input_file in "${input_files[@]}"; do 246 | if ! _crush_image "$input_file" "$check_expand"; then 247 | success="false" 248 | fi 249 | done 250 | 251 | if [ "$success" = "true" ] ; then 252 | exit 0 253 | else 254 | exit 2 255 | fi 256 | -------------------------------------------------------------------------------- /src/exe/tbz-common.sh: -------------------------------------------------------------------------------- 1 | #!/hint/sh 2 | 3 | # The shebang here is to identify this as an SH script, not a BASH script 4 | # Actually executing this won't do anything interesting. 5 | 6 | if [ -t 2 ] ; then 7 | printf >&2 '%sError: %s%s%s\n' "$(tput bold)$(tput setaf 1)" "$(tput sgr0)$(tput setaf 1)" 'You are calling a script using the deprecated framework.' "$(tput sgr0)" 8 | else 9 | printf >&2 'Error: %s\n' 'You are calling a script using the deprecated framework.' 10 | fi 11 | 12 | shell_is_interactive_=false 13 | 14 | case "$-" in 15 | *i*) 16 | shell_is_interactive_=true 17 | ;; 18 | *) 19 | shell_is_interactive_=false 20 | ;; 21 | esac 22 | 23 | if [ -z "${TBZ_COMMON_H_+x}" ] ; then #ifndef TBZ_COMMON_H_ 24 | TBZ_COMMON_H_="true" #define TBZ_COMMON_H_ 25 | 26 | if [ "$shell_is_interactive_" != "true" ] && [ "${tbz_common_no_set_efu_-}" != "true" ] ; then 27 | set -e 28 | set -f 29 | set -u 30 | printf(){ 31 | /usr/bin/printf "$@" 32 | } 33 | fi 34 | 35 | print_oneline_cli_message_(){ 36 | local color="$1"; shift 37 | local title="$1"; shift 38 | local uncolor="$1"; shift 39 | local message="$*" 40 | if [ -t 2 ] ; then 41 | printf "${color}%s: ${uncolor}%s\e[0m\n" "$title" "$message" >&2 42 | else 43 | printf '%s: %s\n' "$title" "$message" >&2 44 | fi 45 | } 46 | 47 | message_nonfatal_() { 48 | local color="$1"; shift 49 | local title="$1"; shift 50 | local uncolor="$1"; shift 51 | if [ -z "${1+x}" ] ; then 52 | local message="" 53 | else 54 | local message="$1"; shift 55 | fi 56 | print_oneline_cli_message_ "$color" "$title" "$uncolor" "$message" 57 | local arg; for arg; do 58 | printf "${color}%s${uncolor}\e[0m\n" "$arg" 59 | done 60 | } 61 | 62 | error_nonfatal(){ 63 | message_nonfatal_ '\e[31m\e[1m' 'Error' '\e[21m' "$@" 64 | } 65 | 66 | error(){ 67 | error_nonfatal "$@" 68 | if checkvar shell_is_interactive_ ; then 69 | kill -s INT "$$" 70 | else 71 | exit 1 72 | fi 73 | } 74 | 75 | warning(){ 76 | message_nonfatal_ '\e[33m\e[1m' 'Warning' '\e[21m' "$@" 77 | } 78 | 79 | note(){ 80 | message_nonfatal_ '\e[36m\e[1m' 'Note' '\e[21m' "$@" 81 | } 82 | 83 | set_var(){ 84 | eval "$1=$(shell_escape "$2")" 85 | } 86 | 87 | _return_value(){ 88 | local status_="$?" 89 | _return_var="${_return_var:-___}" 90 | set_var "$_return_var" "$*" 91 | return "$status_" 92 | } 93 | 94 | __()( 95 | _return_var="___" "$@" 96 | printf '%s\n' "$___" 97 | ) 98 | 99 | value_of(){ 100 | # THE VALUUUUEEE 101 | eval "_return_value \"\${$1}\"" 102 | } 103 | 104 | tolower() { 105 | _return_value "$(awk 'BEGIN { print(tolower(ARGV[1])) }' "$*")" 106 | } 107 | 108 | 109 | # deprecated 110 | is_unset(){ 111 | local arg; for arg; do 112 | eval "local status=\"\${${arg}+x}\"" 113 | if [ -z "${status-}" ] ; then 114 | return 0 115 | fi 116 | done 117 | return 1 118 | } 119 | 120 | # deprecated 121 | is_set() { 122 | if is_unset "$@"; then 123 | return 1 124 | else 125 | return 0 126 | fi 127 | } 128 | 129 | # deprecated 130 | assert_is_set(){ 131 | if is_unset "$@"; then 132 | error "$* is unset." 133 | fi 134 | } 135 | 136 | check(){ 137 | if [ "$*" = "true" ] || [ "$*" = "yes" ]; then 138 | return 0 139 | else 140 | return 1 141 | fi 142 | } 143 | 144 | # deprecated 145 | checkvar(){ 146 | if is_set "$*"; then 147 | if check "$(__ value_of "$*")" ; then 148 | return 0 149 | else 150 | return 1 151 | fi 152 | else 153 | return 2 154 | fi 155 | } 156 | 157 | readfrom(){ 158 | local read_string="$1"; shift 159 | _return_value "$(printf '%s\n' "$read_string" | "$@")" 160 | } 161 | 162 | matches_grep(){ 163 | local grep_args="$1"; shift 164 | if is_unset matches_grep_read_string; then 165 | local read_string="$1"; shift 166 | else 167 | local read_string="${matches_grep_read_string-}" 168 | fi 169 | 170 | # This is all a hack to get around sh's lack of arrays 171 | local num_args="$#" 172 | local i=1; while [ "$i" -le "$num_args" ] ; do 173 | eval "local arg_num_${i}=\"\${$i}\"" 174 | i=$((1+i)); done 175 | set -- 176 | local i=1; while [ "$i" -le "$num_args" ] ; do 177 | eval "local arg=\"\${arg_num_${i}}\"" 178 | set -- "$@" -e "${arg}" 179 | i=$((1+i)); done 180 | 181 | printf '%s\n' "$read_string" | "${grep_command:-grep}" "$grep_args" "$@" 182 | } 183 | 184 | matches(){ 185 | matches_grep -Eq "$@" 186 | } 187 | 188 | fmatches(){ 189 | matches_grep -Fqx "$@" 190 | } 191 | 192 | num_compare() { 193 | _return_value "$(awk -v n1="$1" -v n2="$2" 'BEGIN {print (n1 < n2 ? "-1" : (n1 > n2 ? "1" : "0")) }')" 194 | } 195 | 196 | subtract(){ 197 | _return_value "$(awk -v n1="$1" -v n2="$2" 'BEGIN { print (n1 - n2) }')" 198 | } 199 | 200 | is_gt() { 201 | num_compare "$1" "$2" 202 | if [ "$___" -gt 0 ] ; then 203 | return 0 204 | else 205 | return 1 206 | fi 207 | } 208 | 209 | is_lt() { 210 | num_compare "$1" "$2" 211 | if [ "$___" -lt 0 ] ; then 212 | return 0 213 | else 214 | return 1 215 | fi 216 | } 217 | 218 | is_ge() { 219 | num_compare "$1" "$2" 220 | if [ "$___" -ge 0 ] ; then 221 | return 0 222 | else 223 | return 1 224 | fi 225 | } 226 | 227 | is_le() { 228 | num_compare "$1" "$2" 229 | if [ "$___" -le 0 ] ; then 230 | return 0 231 | else 232 | return 1 233 | fi 234 | } 235 | 236 | parse_arguments(){ 237 | local finished_parsing_args="false" 238 | local arg; for arg; do 239 | if ! check "${finished_parsing_args-}" && matches "$arg" '^--'; then 240 | if [ "$arg" = "--" ] ; then 241 | finished_parsing_args="true" 242 | elif matches "$arg" "=" ; then 243 | local name; local value 244 | readfrom "$arg" sed 's/^--\([^=]*\)=\(.*\)$/\1/'; name="$___" 245 | readfrom "$arg" sed 's/^--\([^=]*\)=\(.*\)$/\2/'; value="$___" 246 | "${option_processor:?}" "$name" "$value" 247 | else 248 | readfrom "$arg" tail -c +3 249 | "${option_processor:?}" "$___" "$___" 250 | fi 251 | else 252 | "${naked_argument_processor:?}" "$arg" 253 | fi 254 | done 255 | } 256 | 257 | command_exists(){ 258 | command -v "$1" >/dev/null 259 | } 260 | 261 | # Includes a file that doesn't have to be in PATH 262 | # It could also be in the same directory as the file doing the including 263 | # deprecated 264 | include(){ 265 | local included_file="$1"; shift 266 | if [ -e "$this_script_dirname"/"$included_file" ] ; then 267 | . "$this_script_dirname"/"$included_file" 268 | elif [ -e "$this_script_abs_dirname"/"$included_file" ] ; then 269 | . "$this_script_abs_dirname"/"$included_file" 270 | elif [ -e ./"$included_file" ] ; then 271 | . ./"$included_file" 272 | elif command_exists "$included_file"; then 273 | . "$included_file" 274 | else 275 | error "Cannot find: $included_file" 276 | fi 277 | } 278 | 279 | iamroot(){ 280 | test "$(id -u)" = "0" 281 | } 282 | 283 | # NICE MEME 284 | iamgroot(){ 285 | iamroot 286 | } 287 | 288 | shell_escape() { 289 | local arg; for arg; do 290 | arg="$(printf %s "$arg" | sed "s/'/'\\\\''/g"; printf 'x\n')" 291 | arg="${arg%?}" 292 | printf "'%s' " "$arg" | sed "s/^''//" | sed "s/\([^\\\\]\)''/\1/g" 293 | done 294 | printf '\n' 295 | } 296 | 297 | 298 | shecho(){ 299 | local retvalue="" 300 | local arg; for arg; do 301 | readfrom "$arg" sed -r "s/'/'\\\\''/g" 302 | retvalue="${retvalue}'${___}' " 303 | done 304 | readfrom "$retvalue" sed "s/^''//g; s/\([^\\\\]\)''/\\1/g" 305 | } 306 | 307 | _add_to_export_env0(){ 308 | 309 | local add_value="$1" 310 | 311 | eval "local curr_value=\"\${${env_var:?}-}\"" 312 | curr_value=${curr_value%%${separator_char}} 313 | curr_value=${curr_value##${separator_char}} 314 | 315 | local my_sep="" 316 | if [ -n "${curr_value-}" ] ; then 317 | my_sep="${separator_char:?}" 318 | fi 319 | 320 | 321 | if check "${prepend-}"; then 322 | local new_value="${add_value}${my_sep}${curr_value}" 323 | else 324 | local new_value="${curr_value}${my_sep}${add_value}" 325 | fi 326 | eval "export ${env_var}=\"\${new_value}\"" 327 | } 328 | 329 | add_to_export_env() { 330 | local arg; for arg; do 331 | _add_to_export_env0 "$arg" 332 | done 333 | } 334 | 335 | add_dir_to_export_env0_(){ 336 | ___="$1" 337 | ___="${___%%/}" 338 | if [ "${#___}" = 0 ] ; then ___="/"; fi 339 | local new_value="$___" 340 | separator_char=':' add_to_export_env "$new_value" 341 | } 342 | 343 | add_dirs_to_export_env(){ 344 | local arg; for arg; do 345 | add_dir_to_export_env0_ "$arg" 346 | done 347 | } 348 | 349 | add_to_search_path(){ 350 | env_var=PATH add_dirs_to_export_env "$@" 351 | } 352 | 353 | lessfrom(){ 354 | "$@" | less 355 | } 356 | 357 | tbz_cleanup_temp_files_(){ 358 | eval "rm -f -- $tbz_common_temp_files_" 359 | } 360 | 361 | create_temp_file(){ 362 | local temp_file 363 | temp_suffix=${temp_suffix:-"$1"} 364 | if [ -n "${temp_suffix:+x}" ]; then 365 | temp_file="$(mktemp --suffix=".${temp_suffix-}")" 366 | else 367 | temp_file="$(mktemp)" 368 | fi 369 | tbz_common_temp_files_="${tbz_common_temp_files_} $(__ shecho "$temp_file")" 370 | _return_value "$temp_file" 371 | } 372 | 373 | # Don't give it a stupid name 374 | add_cleanup_routine(){ 375 | tbz_cleanup_routines_="$tbz_cleanup_routines_ $1" 376 | } 377 | 378 | tbz_cleanup_(){ 379 | local routine 380 | for routine in $tbz_cleanup_routines_; do 381 | eval "$routine" 382 | done 383 | trap - INT QUIT TERM HUP ALRM EXIT 384 | echo 385 | case "$SIGNAL" in 386 | EXIT) 387 | ;; 388 | *) 389 | kill -s "${SIGNAL}" "$$" 390 | ;; 391 | esac 392 | } 393 | 394 | tbz_cleanup_routines_="" 395 | tbz_common_temp_files_="" 396 | 397 | add_cleanup_routine "tbz_cleanup_temp_files_" 398 | 399 | if ! check "$shell_is_interactive_" ; then 400 | trap 'SIGNAL=INT tbz_cleanup_' INT 401 | trap 'SIGNAL=QUIT tbz_cleanup_' QUIT 402 | trap 'SIGNAL=TERM tbz_cleanup_' TERM 403 | trap 'SIGNAL=HUP tbz_cleanup_' HUP 404 | trap 'SIGNAL=ALRM tbz_cleanup_' ALRM 405 | fi 406 | 407 | trap 'SIGNAL=EXIT tbz_cleanup_' EXIT 408 | 409 | if ! check "$shell_is_interactive_" ; then 410 | this_script_fullname="$0" 411 | this_script="$(basename "$this_script_fullname")" 412 | this_script_dirname="$(dirname "$this_script_fullname")" 413 | this_script_abs="$(readlink -f "$this_script_fullname")" 414 | this_script_abs_dirname="$(dirname "$this_script_abs")" 415 | export this_script_fullname 416 | export this_script 417 | export this_script_dirname 418 | export this_script_abs 419 | export this_script_abs_dirname 420 | fi 421 | 422 | fi #endif TBZ_COMMON_H_ 423 | 424 | -------------------------------------------------------------------------------- /src/exe/clipvideo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # thebombzen's easy video clipping script 4 | # 5 | # Dependencies: These must be in your path 6 | # 1. bash 7 | # 2. ffmpeg 8 | # 3. awk 9 | # 4. mkvmerge (if outputting to matroska) 10 | # 11 | # Usage: 12 | # clipvideo INPUT_FILENAME OUTPUT_FILENAME --start=START_TIMECODE --end=END_TIMECODE [options] 13 | # 14 | # This will create (and overwrite!) output_filename as input_filename but temporally clipped between the timecodes 15 | # Option order does not matter for the most part, and options can be inserted anywhere among the initial filenames, --start, and --end. 16 | # If the same option is specified twice, the second one wins. 17 | # Some options can be specified more than once. See below. 18 | # 19 | # Note: To streamcopy rather than re-encode, it's much faster and there's no quality loss, but the timecodes will be inaccurate. 20 | # You will get more video and audio than you bargained for. 21 | # To do this, use: 22 | # clipvideo INPUT_FILENAME OUTPUT_FILENAME --start=START_TIMECODE --end=END_TIMECODE --codec=copy --acodec=copy --ffopts='-c copy' 23 | # 24 | # If you want to copy always, no matter what, then add this line to ~/.bashrc 25 | # alias clipvideo='clipvideo --codec=copy --acodec=copy --ffopts="-c copy"' 26 | # 27 | # Most options have very sane defaults (that's the point of the script, tbh). 28 | # You should not touch them unless you know why. 29 | # 30 | # Options: 31 | # --subs=VALUE 32 | # VALUE can be hard, to burn the subtitles into the video 33 | # soft, to copy the subtitle stream and any attachments 34 | # image, to burn image subtitles into the video (this is hard to autodetect) 35 | # no (default), do not copy subtitles 36 | # 37 | # --ffmpeg=VALUE 38 | # Path to FFmpeg, if it's not in the environment variable PATH. 39 | # Allows you to select a specific one if more than one is installed. 40 | # Default: ffmpeg 41 | # 42 | # --size=VALUE 43 | # Scale the video to size VALUE. Give it in WxH. (e.g. 1280x720) 44 | # Default: Same as input. (i.e. do nothing) 45 | # 46 | # --fps=VALUE 47 | # Duplicate and drop frames to achieve the framerate VALUE. 48 | # Default: Same as input. (i.e. do nothing) 49 | # 50 | # --pixel-format=VALUE 51 | # Output to the pixel format VALUE. 52 | # Default: Automatically select the appropriate output pixel format. 53 | # 54 | # --container-format=VALUE 55 | # Write to the file format VALUE. 56 | # Default: Autodetect the file format from the filename. 57 | # 58 | # --aid=VALUE 59 | # VALUE is the index of the audio stream you want to clip. 60 | # It starts at 1, not 0. 61 | # Default: 1 62 | # 63 | # --sid=VALUE 64 | # VALUE is the index of the subtitle stream you want to clip. 65 | # It starts at 1, not 0. 66 | # Default: 1 67 | # 68 | # --codec=VALUE 69 | # Set the video codec. 70 | # These use libavcodec names. Most common names are added as aliases. 71 | # Default: libx264. 72 | # 73 | # --bitrate=VALUE 74 | # Set the video bitrate in bits per second. 75 | # Set to "vbr" to use VBR encoding for video. 76 | # supports 'k' and 'M' as suffixes. 77 | # Default: vbr 78 | # 79 | # --quality=VALUE 80 | # Set the VBR quality. For x264, x265, vp8, and vp9, this is the CRF value. For others, it's a constant QP. 81 | # Note: Lower is higher quality. Setting it to 0 is lossless. 82 | # Note: This option does absolutely nothing for lossless codecs. 83 | # Default: CRF 16 for x264, x265, vp8, and vp9. QP 25 for others. 84 | # 85 | # --filter=VALUE 86 | # Add a libavfilter-syntax video filtergraph. 87 | # This option can be specified multiple times. 88 | # Default: No video filter. 89 | # 90 | # --ffopts=VALUE 91 | # Add extra options to the ffmpeg execution. 92 | # This option can be specified multiple times. 93 | # Default: No extra options. 94 | # 95 | # --pause 96 | # Ignored. (mpv compatibility) 97 | # 98 | # --mute 99 | # Ignored. (mpv compatibility) 100 | # 101 | 102 | VIDEOSTREAM="v:0?" 103 | AUDIOSTREAM="a:0?" 104 | VIDEOINPUT="" 105 | VIDEOOUTPUT="" 106 | ARGSDONE="no" 107 | SUBS="no" 108 | SID=0 109 | START="" 110 | END="" 111 | LENGTH="" 112 | VENCODER="libx264" 113 | VBITRATE="vbr" 114 | PIXEL_FORMAT="" 115 | FFOPTS="" 116 | FILTER="copy" 117 | QUALITY="" 118 | SIZE="" 119 | FPS="" 120 | FFMPEG="ffmpeg" 121 | LIBX264SHARED="" 122 | AENCODER="" 123 | SENCODER="" 124 | CONTAINER="" 125 | X265OPTS="aq-mode=3:bframes=8:psy-rd=1.0" 126 | X264OPTS="aq-mode=3:bframes=8" 127 | USE_MKVMERGE="false" 128 | FORCE="false" 129 | FORMATOPTS="" 130 | SUBSLINK=".clipvideo_subsymlink" 131 | REMUX_MOVMP4="false" 132 | SPEED="1.0" 133 | AFILTER="acopy" 134 | SPEEDCHANGER="" 135 | AAC_ENCODER="aac" 136 | STARTOPTS="" 137 | DRY_RUN="false" 138 | NO_ZSCALE="false" 139 | 140 | _message_nonfatal() { 141 | title="$1" 142 | color="$2" 143 | uncolor="$3" 144 | shift 3 145 | if [ -z "${1+x}" ] ; then 146 | message="" 147 | else 148 | message="$1"; shift 149 | fi 150 | if [ -t 2 ] ; then 151 | printf >&2 "${color}%s: ${uncolor}%s\e[0m\n" "$title" "$message" 152 | else 153 | printf '%s: %s\n' "$title" "$message" >&2 154 | fi 155 | while [ -n "${1+x}" ]; do 156 | printf "${color}%s${uncolor}\e[0m\n" "$1" 157 | shift 158 | done 159 | } 160 | 161 | error(){ 162 | _message_nonfatal 'Error' '\e[31m\e[1m' '\e[21m' "$@" 163 | exit 1 164 | } 165 | 166 | warning(){ 167 | _message_nonfatal 'Warning' '\e[33m\e[1m' '\e[21m' "$@" 168 | } 169 | 170 | note(){ 171 | _message_nonfatal 'Note' '\e[36m\e[1m' '\e[21m' "$@" 172 | } 173 | 174 | cleanup(){ 175 | rm -f -- "${SUBSLINK}" 176 | } 177 | 178 | trap cleanup EXIT 179 | 180 | # metadata tags added by mkvmerge or iOS 181 | BAD_METADATA="BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES _STATISTICS_WRITING_APP _STATISTICS_WRITING_DATE_UTC _STATISTICS_TAGS creation_time handler_name" 182 | BAD_GLOBAL_METADATA="major_brand minor_version compatible_brands" 183 | BAD_MD_OPTS="" 184 | 185 | # Strip away mkvmerge metadata tags as they're not going to be relevant anymore 186 | for tag in $BAD_METADATA; do 187 | BAD_MD_OPTS="$BAD_MD_OPTS -metadata:s ${tag}= -metadata:s ${tag}-eng=" 188 | done 189 | 190 | for tag in $BAD_GLOBAL_METADATA; do 191 | BAD_MD_OPTS="$BAD_MD_OPTS -metadata ${tag}=" 192 | done 193 | 194 | process_option(){ 195 | NAME="$1" 196 | VALUE="$2" 197 | NAMEL=$(awk 'BEGIN { print(tolower(ARGV[1])) }' "$NAME") 198 | VALUEL=$(awk 'BEGIN { print(tolower(ARGV[1])) }' "$VALUE") 199 | case "$NAMEL" in 200 | subs) 201 | case "$VALUEL" in 202 | hard|soft|image|no) 203 | SUBS="$VALUEL" 204 | ;; 205 | *) 206 | error "Invalid --subs value." "Accepted values: hard, soft, image, no" 207 | exit 1 208 | ;; 209 | esac 210 | ;; 211 | vid|video) 212 | VIDEOSTREAM="v:$((VALUE-1))?" 213 | ;; 214 | aid|audio) 215 | # Amazingly, this works with FFmpeg 216 | # --aid=no turns into a:-1 217 | # There is never a stream with id -1 218 | # So --aid=no disables audio 219 | if [ "$VALUEL" = "no" ] ; then 220 | AUDIOSTREAM="no" 221 | else 222 | AUDIOSTREAM="a:$((VALUE-1))?" 223 | fi 224 | ;; 225 | end|ab-loop-b) 226 | END="$VALUE" 227 | ;; 228 | length) 229 | LENGTH="$VALUE" 230 | ;; 231 | start|ab-loop-a) 232 | START="$VALUE" 233 | ;; 234 | bitrate|vbitrate|vb) 235 | VBITRATE="$VALUE" 236 | ;; 237 | abitrate|ab) 238 | ABITRATE="$VALUE" 239 | ;; 240 | codec|vcodec) 241 | VENCODER="$VALUEL" 242 | ;; 243 | scodec) 244 | SENCODER="$VALUEL" 245 | ;; 246 | sid) 247 | SID="$((VALUE-1))" 248 | ;; 249 | ffopts) 250 | FFOPTS="$FFOPTS $VALUE" 251 | ;; 252 | ffmpeg|ffmpeg-exe) 253 | FFMPEG="$VALUE" 254 | ;; 255 | format) 256 | error "--format has been removed. Use --pixel-format instead." 257 | exit 1 258 | ;; 259 | pixel-format) 260 | PIXEL_FORMAT="$VALUEL" 261 | ;; 262 | # Geometry and Autofit for mpv compat 263 | size|geometry|autofit) 264 | SIZE="$VALUEL" 265 | ;; 266 | fps) 267 | FPS="$VALUE" 268 | ;; 269 | filter|vf|vf-add|vf-pre) 270 | if ! [ "$VALUE" = "${VALUE#lavfi=}" ] ; then 271 | # remove the lavfi we know is there 272 | VALUE="${VALUE#lavfi=}" 273 | # mpv allows (sometimes requires) you to wrap lavfi in quotes 274 | VALUE="${VALUE#\"}" 275 | VALUE="${VALUE%\"}" 276 | fi 277 | FILTER="$FILTER,$VALUE" 278 | ;; 279 | afilter|af|af-add|af-pre) 280 | if ! [ "$VALUE" = "${VALUE#lavfi=}" ] ; then 281 | # remove the lavfi we know is there 282 | VALUE="${VALUE#lavfi=}" 283 | # mpv allows (sometimes requires) you to wrap lavfi in quotes 284 | VALUE="${VALUE#\"}" 285 | VALUE="${VALUE%\"}" 286 | fi 287 | AFILTER="$AFILTER,$VALUE" 288 | ;; 289 | x265opts) 290 | X265OPTS="${X265OPTS}:$VALUE" 291 | ;; 292 | x264opts) 293 | X264OPTS="${X264OPTS}:$VALUE" 294 | ;; 295 | libx264-shared|libx264-so|libx264-dll|libx264-dylib) 296 | warning "This option has been removed because libx264 supports 10bit in main" 297 | ;; 298 | use-mkvmerge) 299 | # Remux the file together with mkvmerge 300 | # lavf has occasional issues with mkv muxing 301 | USE_MKVMERGE="true" 302 | ;; 303 | remux-movmp4) 304 | # the purpose of this option is to allow previewing during encoding while outputting to mov or mp4 305 | REMUX_MOVMP4="true" 306 | ;; 307 | acodec) 308 | AENCODER="$VALUEL" 309 | ;; 310 | container|container-format) 311 | CONTAINER="$VALUEL" 312 | ;; 313 | qp) 314 | error "--qp is removed. Use --quality instead." 315 | exit 316 | ;; 317 | quality) 318 | QUALITY="$VALUE" 319 | ;; 320 | speed) 321 | SPEED="$VALUE" 322 | ;; 323 | force) 324 | warning "Disabling sanity checks and aliases. Errors are your fault!" 325 | FORCE="true" 326 | ;; 327 | # mpv compatibility 328 | pause|loop-playlist|loop-file) 329 | note "Ignoring --${NAME}" 330 | ;; 331 | mute) 332 | AUDIOSTREAM="no" 333 | ;; 334 | dry-run) 335 | warning "Doing a dry-run." 336 | DRY_RUN="true" 337 | ;; 338 | no-zscale) 339 | NO_ZSCALE="true" 340 | ;; 341 | *) 342 | error "Invalid option: $NAME" 343 | ;; 344 | esac 345 | } 346 | 347 | process_naked_argument(){ 348 | if [ -z "$VIDEOINPUT" ] ; then 349 | VIDEOINPUT="$arg" 350 | elif [ -z "$VIDEOOUTPUT" ] ; then 351 | VIDEOOUTPUT="$arg" 352 | else 353 | error "Invalid trailing argument: $arg" 354 | exit 1 355 | fi 356 | } 357 | 358 | found_mm=false 359 | for arg; do 360 | if [ "$found_mm" = "true" ] ; then 361 | process_naked_argument "$arg" 362 | elif [ "$arg" = "--" ] ; then 363 | found_mm="true" 364 | elif ! [ "${arg#--}" = "${arg}" ]; then 365 | arg=${arg#--} 366 | name=${arg%%=*} 367 | value=${arg#*=} 368 | process_option "$name" "$value" 369 | else 370 | process_naked_argument "$arg" 371 | fi 372 | done 373 | 374 | ffmpeg_v(){ 375 | printf '%s %s\n' "$FFMPEG" "$*" 376 | ffmpeg "$@" 377 | } 378 | 379 | if [ -z "${VIDEOINPUT}" ] || [ -z "${VIDEOOUTPUT}" ] ; then 380 | error "Provide both a video input and output." 381 | exit 382 | fi 383 | 384 | if [ -z "$START" ] && [ -z "$END" ] ; then 385 | warning "Clipping the entire video." 386 | fi 387 | 388 | if [ -z "$START" ] ; then 389 | START=0 390 | fi 391 | 392 | if ( grep -Eq -e image -e hard <<<"$SUBS" ) && [ "$VENCODER" = "copy" ] ; then 393 | error "Cannot codec copy when hardsubbing." 394 | exit 1 395 | fi 396 | 397 | if grep -Eq -e '^nv' -e '^p0' <<<"$PIXEL_FORMAT" ; then 398 | error "Use planar pixel formats." 399 | exit 1 400 | fi 401 | 402 | if [ -z "$CONTAINER" ] ; then 403 | case "$VIDEOOUTPUT" in 404 | *.mkv|*.mka|*.mk3d|*.mks) 405 | CONTAINER='matroska' 406 | ;; 407 | *.ts|*.tsv|*.tsa|*.mpegts|*.m2ts|*.m2t) 408 | CONTAINER='mpegts' 409 | ;; 410 | *.nut) 411 | CONTAINER='nut' 412 | ;; 413 | *.mp4|*.m4a|*.m4v) 414 | CONTAINER='mp4' 415 | ;; 416 | *.ogg|*.oga|*.ogv) 417 | CONTAINER='ogg' 418 | ;; 419 | *.webm) 420 | CONTAINER='webm' 421 | ;; 422 | *.mov) 423 | CONTAINER='mov' 424 | ;; 425 | *) 426 | CONTAINER='auto' 427 | esac 428 | elif ! [ "$FORCE" = "true" ] ; then 429 | case "$CONTAINER" in 430 | mkv|mka|mk3d|mks) 431 | warning "$CONTAINER is an alias for matroska." 432 | CONTAINER="matroska" 433 | ;; 434 | ts|tsa|tsv) 435 | warning "$CONTAINER is an alias for mpegts." 436 | CONTAINER="mpegts" 437 | ;; 438 | m4a|m4v) 439 | warning "$CONTAINER is an alias for mp4." 440 | CONTAINER="mp4" 441 | ;; 442 | ogv|oga) 443 | warning "$CONTAINER is an alias for ogg." 444 | CONTAINER="ogg" 445 | ;; 446 | esac 447 | fi 448 | 449 | if ffmpeg -v panic -f lavfi -i anullsrc -c:a libfdk_aac -t 1 -b:a 128k -f null - ; then 450 | AAC_ENCODER="libfdk_aac" 451 | else 452 | AAC_ENCODER="aac" 453 | fi 454 | 455 | if [ -z "$AENCODER" ] ; then 456 | case "$CONTAINER" in 457 | matroska|mpegts|nut|webm) 458 | AENCODER="libopus" 459 | ;; 460 | mp4|mov) 461 | AENCODER="$AAC_ENCODER" 462 | ;; 463 | ogg) 464 | AENCODER="libvorbis" 465 | ;; 466 | *) 467 | AENCODER="auto" 468 | ;; 469 | esac 470 | fi 471 | 472 | if ! [ "$FORCE" = "true" ] ; then case "$AENCODER" in 473 | mp3) 474 | note "$AENCODER is an alias for libmp3lame." 475 | AENCODER="libmp3lame" 476 | ;; 477 | opus) 478 | note "$AENCODER is an alias for libopus." 479 | AENCODER="libopus" 480 | ;; 481 | libvo-aacenc|libvo_aacenc) 482 | warning "$AENCODER is an alias for '$AAC_ENCODER' because $AENCODER is unbelievably terrible." 483 | AENCODER="$AAC_ENCODER" 484 | ;; 485 | vorbis) 486 | note "$AENCODER is an alias for libvorbis." 487 | AENCODER="libvorbis" 488 | ;; 489 | esac; fi 490 | 491 | case "$AENCODER" in 492 | libopus) 493 | AUDIOOPTS="-c:a $AENCODER -b:a 160k -ac:a 2" 494 | ;; 495 | libvorbis) 496 | AUDIOOPTS="-c:a $AENCODER -q:a 6" 497 | ;; 498 | aac|libfdk_aac) 499 | AUDIOOPTS="-c:a $AENCODER -b:a 160k" 500 | ;; 501 | libmp3lame) 502 | AUDIOOPTS="-c:a $AENCODER -b:a 256k" 503 | ;; 504 | # lossless stuff 505 | copy|ape|pcm*) 506 | AUDIOOPTS="-c:a $AENCODER" 507 | ;; 508 | # more lossless 509 | flac|alac) 510 | AUDIOOPTS="-c:a $AENCODER -compression_level:a 12" 511 | ;; 512 | *) 513 | AUDIOOPTS="-c:a $AENCODER -b:a 128k" 514 | ;; 515 | esac 516 | 517 | FORMATOPTS="$FORMATOPTS -f $CONTAINER" 518 | 519 | if [ -z "$SENCODER" ] ; then case "$CONTAINER" in 520 | matroska) 521 | if [ "$USE_MKVMERGE" = "true" ]; then 522 | VIDEOOUTPUT="$VIDEOOUTPUT".mkv 523 | FORMATOPTS="$FORMATOPTS -live 1" 524 | fi 525 | SENCODER="ass" 526 | ;; 527 | webm) 528 | if [ "$USE_MKVMERGE" = "true" ] ; then 529 | VIDEOOUTPUT="$VIDEOOUTPUT".webm 530 | FORMATOPTS="$FORMATOPTS -live 1" 531 | fi 532 | SENCODER="webvtt" 533 | ;; 534 | mp4) 535 | FORMATOPTS="$FORMATOPTS -movflags +faststart" 536 | if [ "$AENCODER" = "libopus" ] ; then 537 | FORMATOPTS="$FORMATOPTS -strict experimental" 538 | fi 539 | if [ "$REMUX_MOVMP4" = "true" ] ; then 540 | VIDEOOUTPUT="$VIDEOOUTPUT".nut 541 | FORMATOPTS="$FORMATOPTS -f nut" 542 | fi 543 | SENCODER="mov_text" 544 | ;; 545 | mov) 546 | FORMATOPTS="$FORMATOPTS -movflags +faststart" 547 | if [ "$REMUX_MOVMP4" = "true" ] ; then 548 | VIDEOOUTPUT="$VIDEOOUTPUT".nut 549 | FORMATOPTS="$FORMATOPTS -f nut" 550 | fi 551 | SENCODER="mov_text" 552 | ;; 553 | esac; fi 554 | 555 | if ! [ "$FORCE" = "true" ] ; then case "$VENCODER" in 556 | nvenc|nvenc_h264) 557 | warning "$VENCODER is a deprecated alias for h264_nvenc." 558 | VENCODER="h264_nvenc" 559 | ;; 560 | nvenc_hevc) 561 | warning "$VENCODER is a deprecated alias for hevc_nvenc." 562 | VENCODER="hevc_nvenc" 563 | ;; 564 | h264|avc|x264) 565 | note "$VENCODER is an alias for libx264." 566 | VENCODER="libx264" 567 | ;; 568 | h265|hevc|x265) 569 | note "$VENCODER is an alias for libx265." 570 | VENCODER="libx265" 571 | ;; 572 | vp8|libvpx-vp8) 573 | warning "$VENCODER is an alias for libvpx." 574 | VENCODER="libvpx" 575 | ;; 576 | vp9) 577 | note "$VENCODER is an alias for libvpx-vp9." 578 | VENCODER="libvpx-vp9" 579 | ;; 580 | esac; fi 581 | 582 | if [ -z "$QUALITY" ] ; then 583 | case "$VENCODER" in 584 | libx26?|libvpx*) 585 | QUALITY=16 586 | ;; 587 | *) 588 | QUALITY=25 589 | ;; 590 | esac 591 | fi 592 | 593 | if [ "$VBITRATE" = "vbr" ] ; then 594 | case "$VENCODER" in 595 | libx26?) 596 | if [ "$QUALITY" -eq 0 ] ; then 597 | # This is truly lossless for 10-bit avc 598 | VBITRATEOPTS="-qp:v 0" 599 | else 600 | VBITRATEOPTS="-crf:v $QUALITY" 601 | fi 602 | ;; 603 | libvpx*) 604 | VBITRATEOPTS="-b:v 0 -crf:v $QUALITY" 605 | ;; 606 | # These are lossless 607 | copy|ffvhuff|huffyuv|utvideo|ffv1|rawvideo|wrapped_avframe|png|ppm|gif|bmp) 608 | VBITRATEOPTS="" 609 | ;; 610 | *) 611 | VBITRATEOPTS="-qp:v $QUALITY" 612 | ;; 613 | esac 614 | else 615 | VBITRATEOPTS="-b:v $VBITRATE" 616 | fi 617 | 618 | case "$VENCODER" in 619 | h264_nvenc) 620 | VIDEOOPTS="-c:v h264_nvenc -preset:v slow -profile:v high -spatial-aq:v 1 -qmin:v 0 -qmax:v 69" 621 | ;; 622 | libx264) 623 | VIDEOOPTS="-c:v libx264 -preset:v slow -x264opts ${X264OPTS}" 624 | ;; 625 | libx265) 626 | VIDEOOPTS="-c:v libx265 -preset:v slow -x265-params ${X265OPTS}" 627 | ;; 628 | # The _ in spacial_aq is intentional 629 | # Nvenc is weird 630 | hevc_nvenc) 631 | VIDEOOPTS="-c:v hevc_nvenc -preset:v slow -tier:v 1 -rc-lookahead:v 120 -spatial_aq:v 1" 632 | ;; 633 | ffv1) 634 | VIDEOOPTS="-c:v ffv1 -level:v 3 -g:v 1 -slicecrc:v 1 -coder:v range_tab -slices:v 4 -threads:v 4" 635 | ;; 636 | *) 637 | VIDEOOPTS="-c:v $VENCODER" 638 | ;; 639 | esac 640 | 641 | signdiff() { 642 | awk -v n1="$1" -v n2="$2" 'BEGIN {print (n1 < n2 ? "-1" : (n1 > n2 ? "1" : "0")) }' 643 | } 644 | 645 | if [ "$(signdiff "$SPEED" "1.0" )" -ne '0' ] ; then 646 | SPEEDSCALER="atempo=" 647 | if [ "$(signdiff "$SPEED" "2.0")" -gt '0' ] || [ "$(signdiff "$SPEED" "0.5")" -lt '-1' ]; then 648 | if ffmpeg -v error -f lavfi -i anullsrc -af rubberband=tempo="$SPEED" -t 1 -f null -; then 649 | SPEEDSCALER="rubberband=tempo=" 650 | fi 651 | fi 652 | AFILTER="${SPEEDSCALER}${SPEED},${AFILTER}" 653 | FILTER="setpts=PTS/${SPEED},${FILTER}" 654 | fi 655 | 656 | if [ -z "$PIXEL_FORMAT" ] ; then 657 | case "$VENCODER" in 658 | *_nvenc) 659 | if ffmpeg -v error -f lavfi -i yuvtestsrc -vf scale,format=yuv420p10le -pix_fmt yuv420p10le -c "$VENCODER" -frames 1 -f nut - | ffprobe -v error -show_entries stream=pix_fmt -of default=noprint_wrappers=1:nokey=1 -f nut -i - | grep -q -e '10le$'; then 660 | PIXEL_FORMAT=yuv420p10le 661 | else 662 | PIXEL_FORMAT=yuv420p 663 | fi 664 | ;; 665 | libx26?) 666 | if ffmpeg -v error -f lavfi -i yuvtestsrc -vf scale,format=yuv420p10le -pix_fmt yuv420p10le -c "$VENCODER" -frames 1 -f nut - | ffprobe -v error -show_entries stream=pix_fmt -of default=noprint_wrappers=1:nokey=1 -f nut -i - | grep -q -e '10le$'; then 667 | PIXEL_FORMAT=yuv420p10le 668 | else 669 | PIXEL_FORMAT=yuv420p 670 | fi 671 | ;; 672 | *) 673 | PIXEL_FORMAT="auto" 674 | ;; 675 | esac 676 | fi 677 | 678 | if ! [ "$NO_ZSCALE" = "true" ] && ffmpeg -v panic -f lavfi -i yuvtestsrc -vf zscale -frames 1 -f null -; then 679 | scaler_(){ 680 | if grep -Eq -e 'bgr' -e 'rgb' -e 'gbr' -e 'bayer' <<<"$PIXEL_FORMAT" ; then 681 | printf 'zscale=%s:f=spline36:range=pc' "$1" 682 | else 683 | printf 'zscale=%s:f=spline36:range=tv' "$1" 684 | fi 685 | } 686 | else 687 | scaler_(){ 688 | printf 'scale=%s:interl=-1:flags=lanczos' "$(__ readfrom "$1" tr 'x' ':')" 689 | } 690 | fi 691 | 692 | if [ -n "$FPS" ] ; then 693 | FILTER="${FILTER},fps=fps=${FPS}" 694 | if [ -z "$END" ] ; then 695 | FFOPTS="$FFOPTS -shortest" 696 | fi 697 | fi 698 | 699 | if [ -n "$SIZE" ] ; then 700 | FILTER="${FILTER},$(scaler_ $SIZE)" 701 | else 702 | FILTER="${FILTER},$(scaler_ w=iw:h=ih)" 703 | fi 704 | 705 | if [ "$PIXEL_FORMAT" != "auto" ] ; then 706 | FILTER="${FILTER},format=${PIXEL_FORMAT}" 707 | if grep -Eq -e 'bgr' -e 'rgb' -e 'gbr' -e 'bayer' <<<"$PIXEL_FORMAT"; then 708 | FORMATOPTS="$FORMATOPTS -color_range pc" 709 | else 710 | FORMATOPTS="$FORMATOPTS -color_range tv" 711 | fi 712 | fi 713 | 714 | time_duration_toseconds() { 715 | awk -F: '{ n = 1; for (i = NF; i > 0; i--) { out = $i * n + out; n = n * 60 } print out }' <<<"$1" 716 | } 717 | 718 | STARTSECS="$(time_duration_toseconds "$START")" 719 | 720 | if [ -n "$LENGTH" ] ; then 721 | # not dividing by speed here is intentional 722 | DURATION="$(time_duration_toseconds "$LENGTH")" 723 | elif [ -n "$END" ] ; then 724 | ENDSECS="$(time_duration_toseconds "$END")" 725 | DURATION="$(awk "BEGIN { print ( $ENDSECS - $STARTSECS ) / $SPEED }")" 726 | fi 727 | 728 | if [ "$SUBS" = "hard" ] ; then 729 | ln -sf "$VIDEOINPUT" "${SUBSLINK}" 730 | fi 731 | 732 | FILTER="${FILTER##,}" 733 | FILTER="${FILTER%%,}" 734 | FILTER=$(sed -r 's/,+/,/g' <<<"$FILTER") 735 | 736 | FORMATOPTS="$FORMATOPTS $BAD_MD_OPTS" 737 | 738 | case "$SUBS" in 739 | image) 740 | FILTER="copy,[s:${SID}]overlay,setpts=PTS-$STARTSECS/TB,$FILTER" 741 | AFILTER="asetpts=PTS-STARTPTS,$AFILTER" 742 | ;; 743 | hard) 744 | FILTER="subtitles=filename=${SUBSLINK}:si=$SID,setpts=PTS-$STARTSECS/TB,${FILTER}" 745 | AFILTER="asetpts=PTS-STARTPTS,$AFILTER" 746 | ;; 747 | esac 748 | 749 | if [ "$(signdiff "$STARTSECS" '0')" -gt '0' ] ; then 750 | STARTOPTS="-ss $STARTSECS" 751 | if [ "$SUBS" = "image" ] || [ "$SUBS" = "hard" ] ; then 752 | STARTOPTS="$STARTOPTS -copyts -start_at_zero" 753 | fi 754 | fi 755 | 756 | set -- 757 | 758 | if [ -n "$END" ] || [ -n "$LENGTH" ] ; then 759 | set -- "$@" "-t" "$DURATION" 760 | fi 761 | 762 | if [ "$VENCODER" != "copy" ] ; then 763 | set -- "$@" "-vf" "$FILTER" 764 | fi 765 | 766 | if [ "$AENCODER" != "copy" ] ; then 767 | set -- "$@" "-af" "$AFILTER" 768 | fi 769 | 770 | if [ "$SUBS" = "image" ] ; then 771 | if [ "$AUDIOSTREAM" = no ] ; then 772 | set -- -map "$VIDEOSTREAM" -an "$@" 773 | else 774 | set -- -map "$VIDEOSTREAM" -map "$AUDIOSTREAM" "$@" 775 | fi 776 | elif [ "$SUBS" = "soft" ] ; then 777 | if [ "$AUDIOSTREAM" = no ] ; then 778 | set -- -map "$VIDEOSTREAM" -an -map s:"$SID" -map t? -map d? -c copy -c:s "$SENCODER" "$@" 779 | else 780 | set -- -map "$VIDEOSTREAM" -map "$AUDIOSTREAM" -map s:"$SID" -map t? -map d? -c copy -c:s "$SENCODER" "$@" 781 | fi 782 | elif [ "$SUBS" = "hard" ] ; then 783 | if [ -n "$FILTER" ] ; then FILTER=",$FILTER"; fi 784 | if [ "$AUDIOSTREAM" = no ] ; then 785 | set -- -map "$VIDEOSTREAM" -an "$@" 786 | else 787 | set -- -map "$VIDEOSTREAM" -map "$AUDIOSTREAM" "$@" 788 | fi 789 | else 790 | if [ "$AUDIOSTREAM" = no ] ; then 791 | set -- -map "$VIDEOSTREAM" -an -c copy "$@" 792 | else 793 | set -- -map "$VIDEOSTREAM" -map "$AUDIOSTREAM" -c copy "$@" 794 | fi 795 | fi 796 | 797 | set -- "-hide_banner" "-y" $STARTOPTS -i "$VIDEOINPUT" "$@" $VIDEOOPTS $VBITRATEOPTS $AUDIOOPTS $FORMATOPTS $FFOPTS "$VIDEOOUTPUT" 798 | 799 | if [ "$DRY_RUN" = "true" ] ; then 800 | printf '%s %s\n' "$FFMPEG" "$*" 801 | else 802 | ffmpeg_v "$@" 803 | if [ "$USE_MKVMERGE" = "true" ] && grep -Fqx -e 'matroska' -e 'webm' <<<"$CONTAINER" ; then 804 | MKVMERGEOPTS="--verbose" 805 | if [ -n "$FPS" ] ; then 806 | MKVMERGEOPTS="$MKVMERGEOPTS --default-duration 0:${FPS}fps" 807 | fi 808 | mkvmerge --verbose $MKVMERGEOPTS -o "${VIDEOOUTPUT%.*}" "$VIDEOOUTPUT" 809 | rm -f "$VIDEOOUTPUT" 810 | fi 811 | if [ "$REMUX_MOVMP4" = "true" ] && grep -Fqx -e 'mov' -e 'mp4' <<<"$CONTAINER"; then 812 | ffmpeg -hide_banner -y -i "$VIDEOOUTPUT" -map 0 -c copy $FORMATOPTS -f "$CONTAINER" "${VIDEOOUTPUT%.nut}" 813 | rm -f "$VIDEOOUTPUT" 814 | fi 815 | fi 816 | --------------------------------------------------------------------------------