commit 49e8af42716d63d912ccfb134d24bd020239024e Author: Mark Hoekveen Date: Fri Nov 22 15:47:38 2024 +0100 initial scripts diff --git a/indeel b/indeel new file mode 100755 index 0000000..403eee9 --- /dev/null +++ b/indeel @@ -0,0 +1,87 @@ +#!/bin/bash +# Watch this folder for incoming files +# Rename each file to a hash of some kind +# Put each file into a folder of the old name + +# Check for .known-files which tracks hash history to prevent duplicates +# Will create if -f flag is used, otherwise exits to prevent accidental runs +if [[ ! -f .known-files ]]; then + if [[ "$1" != "-f" ]]; then + echo ".known-files not found. Use -f to override." + exit 1 + fi + touch .known-files +fi + +command -v file >/dev/null 2>&1 || { echo "file command not found"; exit 1; } +command -v magick >/dev/null 2>&1 || { echo "imagemagick not found"; exit 1; } +command -v dctfilename >/dev/null 2>&1 || { echo "dctfilename not found"; exit 1; } +command -v webpmux >/dev/null 2>&1 || { echo "webpmux not found"; exit 1; } +command -v gifski >/dev/null 2>&1 || { echo "gifski not found"; exit 1; } + +while true; do + for f in *.*; do + # Check if we need to skip + [ -e "$f" ] || continue + [ -e "$f.part" ] && continue + [ ${f##*.} == "part" ] && continue + [ ${f##*.} == "sh" ] && continue + + # Print some file info, and check file validity + FILEINFO=`file $f` + echo $FILEINFO + if echo "$FILEINFO" | grep -q empty; then + continue + fi + if ! echo "$FILEINFO" | grep -iq "image\|video"; then + continue + echo "skip" + fi + + # Handle webp as a special case, and defer to next iteration + if [ ${f##*.} == "webp" ] + then + if [ `magick identify $f | wc -l` -gt 1 ] + then + echo "Animated webp. Converting to gif..." + TMPDIR=$(mktemp -d) + magick "$f" "$TMPDIR/frame-%d.png" + DELAYS=$(webpmux -info "$f" | tail -n+6 | awk '{print $7}') + FPS=$(echo "$DELAYS" | head -n1 | awk '{printf "%.0f", 1000/$1}') + gifski --fps $FPS "$TMPDIR"/frame-*.png -o "${f%.*}.gif" + rm -rf "$TMPDIR" + rm "$f" + continue + else + echo "Picture webp. Converting to jpg..." + magick "$f" "${f%.*}.jpg" + rm "$f" + continue + fi + fi + + # Determine phash and destination folder + if command -v dctfilename >/dev/null 2>&1 && ! echo "$FILEINFO" | grep -iq "video"; then + NAME=`dctfilename $f` + else + NAME=`md5sum $f | awk '{print $1}'` + fi + FOLDER=${f%.*} + FOLDER=${FOLDER%\(*} + + # Check if the file's hash already exists in .known-files + if grep -F -q $NAME .known-files; then + echo "$NAME is a duplicate file" + mkdir -p duplicates + mv -f $f "duplicates/$NAME.${f##*.}" + else + # For new files, add hash to .known-files and move to destination folder + echo $NAME >> .known-files + mkdir -p $FOLDER + echo "Moving '$f' to '$FOLDER/$NAME.${f##*.}'" + mv -f $f "$FOLDER/$NAME.${f##*.}" + fi + echo '------' + done + sleep 1 +done diff --git a/pushover b/pushover new file mode 100755 index 0000000..a8e0f01 --- /dev/null +++ b/pushover @@ -0,0 +1,66 @@ +#!/bin/sh + +# Script to send push notifications via Pushover API +# Usage: pushover -m|--message [-t|--title ] [-a|--attachment <attachment>] +# Requires PUSHOVER_APP_TOKEN and PUSHOVER_USER_TOKEN environment variables + +if [ -z "$PUSHOVER_APP_TOKEN" ] || [ -z "$PUSHOVER_USER_TOKEN" ]; then + echo "Error: Missing required environment variables" + echo "Please set PUSHOVER_APP_TOKEN and PUSHOVER_USER_TOKEN" + exit 1 +fi + +MESSAGE="" +TITLE=$HOSTNAME +ATTACHMENT="" + +while [ $# -gt 0 ]; do + case $1 in + -m|--message) + MESSAGE="$2" + shift 2 + ;; + -t|--title) + TITLE="$2" + shift 2 + ;; + -a|--attachment) + ATTACHMENT="$2" + shift 2 + ;; + *) + echo "Invalid option: $1" + exit 1 + ;; + esac +done + +if [ -z "$MESSAGE" ]; then + echo "Error: Message is required" + echo + echo "Usage: pushover -m|--message <message> [-t|--title <title>] [-a|--attachment <attachment>]" + echo "Options:" + echo " -m, --message <message> Message text to send (required)" + echo " -t, --title <title> Notification title (optional, defaults to hostname)" + echo " -a, --attachment <attachment> File to attach (optional, supports images, PDFs etc)" + echo + echo "Example:" + echo " pushover --message \"Backup complete\" --title \"Server Status\" --attachment /path/to/image.png" + exit 1 +fi + +if [ -n "$ATTACHMENT" ]; then + FILESIZE=$(stat -f%z "$ATTACHMENT" 2>/dev/null || stat -c%s "$ATTACHMENT" 2>/dev/null) + if [ "$FILESIZE" -gt 5242880 ]; then + echo "Error: Attachment size exceeds Pushover's 5MB limit" + exit 1 + fi +fi + +curl -s \ + --form-string "token=${PUSHOVER_APP_TOKEN}" \ + --form-string "user=${PUSHOVER_USER_TOKEN}" \ + --form-string "message=${MESSAGE}" \ + --form-string "title=${TITLE}" \ + ${ATTACHMENT:+ -F "attachment=@$ATTACHMENT"} \ + https://api.pushover.net/1/messages.json diff --git a/vc b/vc new file mode 100755 index 0000000..5ad599a --- /dev/null +++ b/vc @@ -0,0 +1,119 @@ +#! /usr/bin/python +"""Call ffmpeg with youtube-dl to convert files or youtube videos to mp4 or webp files.""" + +import subprocess, argparse + +def get_input_args(input_path: str) -> list[str]: + """Generate ffmpeg input arguments for either local files or YouTube URLs.""" + f_args: list[str] = [] + if "https" in input_path: + urls = subprocess.run(["yt-dlp", "--get-url", input_path], capture_output=True, encoding='utf-8') + for url in urls.stdout.split('\n'): + if len(url) > 10: + f_args += ["-i", url] + else: + f_args += ["-i", input_path] + return f_args + + +def get_webp_args(quality: str | None) -> list[str]: + """Generate ffmpeg arguments for WebP output format. + + Args: + quality (str): Output quality percentage, defaults to 70 if not specified + """ + args: list[str] = [] + args += ["-vcodec", "libwebp"] + args += ["-preset", "default"] + args += ["-loop", "0", "-an", "-vsync", "0"] + args += ["-qscale", quality if quality else "70"] + return args + +def get_mp4_args() -> list[str]: + """Generate ffmpeg arguments for MP4 output format.""" + args: list[str] = [] + args += ["-c:v", "libx264"] + args += ["-preset", "veryfast"] + args += ["-crf", "26"] + args += ["-c:a", "aac"] + args += ["-b:a", "128k"] + args += ["-pix_fmt", "yuv420p"] + return args + +def get_video_filter(crop_arg: str | None) -> str: + """Generate ffmpeg video filter string for rescaling and optional cropping. + + Rescales video height to 720p or original height if smaller, maintaining aspect ratio. + Supports standard, percentage-based, and edge-based cropping formats.""" + vfilter = "scale=-2:'min(720,ih)'" + if crop_arg: + if crop_arg.endswith('%'): + # Percentage-based centered cropping (single percentage value) + percent = float(crop_arg.replace('%', '')) + vfilter += f",crop='in_w*{percent/100}:in_h*{percent/100}:(in_w-in_w*{percent/100})/2:(in_h-in_h*{percent/100})/2'" + elif crop_arg.startswith('edge:'): + # Edge-based cropping (edge:left:right:top:bottom or edge:width:height) + parts = crop_arg[5:].split(':') + if len(parts) == 1: + l = r = t = b = int(parts[0]) + vfilter += f",crop='in_w-{l+r}:in_h-{t+b}:{l}:{t}'" + elif len(parts) == 2: + w, h = map(int, parts) + vfilter += f",crop='{w}:{h}:(in_w-{w})/2:(in_h-{h})/2'" + else: + l, r, t, b = map(int, parts) + vfilter += f",crop='in_w-{l+r}:in_h-{t+b}:{l}:{t}'" + else: + # Standard cropping (out_w:out_h:x:y or out_w:out_h for centered) + parts = crop_arg.split(':') + if len(parts) == 2: + w, h = parts + vfilter += f",crop={w}:{h}:(in_w-{w})/2:(in_h-{h})/2" + else: + vfilter += f",crop={crop_arg}" + return vfilter + +def main(): + parser = argparse.ArgumentParser(description='Call ffmpeg for converting small video files to mp4 or webp') + parser.add_argument('input', help='input file or url') + parser.add_argument('output', help='output filename') + parser.add_argument('-s', '--start', help='start time, if not 00:00') + parser.add_argument('-t', '--to', help='end time, if not end of file') + parser.add_argument('-q', '--quality', help='quality of output, as a percentage') + parser.add_argument('-S', '--silent', action='store_true', help='no audio') + parser.add_argument('-c', '--crop', help=""" + Crop formats supported: + - Standard: "out_w:out_h:x:y" or "out_w:out_h" for centered + - Percentage: "NN%" for centered crop to NN percent + - Edge: "edge:left:right:top:bottom" or "edge:width:height" or single value for all""") + args = parser.parse_args() + + f_args = get_input_args(args.input) + + f_args += ["-vf", get_video_filter(args.crop)] + + if args.start: + f_args += ["-ss", args.start] # -ss specifies the start timestamp + if args.to: + f_args += ["-to", args.to] # -to specifies the end timestamp + if args.silent: + f_args += ["-an"] # -an disables audio output + f_args += ["-sn"] # -sn removes subtitles + f_args += ["-dn"] # -dn removes data streams + f_args += ["-map_metadata:c", "-1"] # Remove metadata from the output + + output_ext = args.output.split('.')[-1] + if output_ext == 'webp': + f_args += get_webp_args(args.quality) + elif output_ext == 'mp4': + f_args += get_mp4_args() + else: + print(f"Error: Output file must have either .mp4 or .webp extension, found: .{output_ext}") + exit(1) + + print(f_args) + + subprocess.run(["ffmpeg", "-y"] + f_args + [args.output if args.output else "output.mp4"]) + +if __name__ == "__main__": + main()