commit a10c8f0e74bd658b22dc49b43ebe582f57810ca4 Author: anon Date: Tue Mar 14 20:28:23 2023 +0100 init diff --git a/webmify.sh b/webmify.sh new file mode 100644 index 0000000..c77353f --- /dev/null +++ b/webmify.sh @@ -0,0 +1,853 @@ +#!/bin/sh + +printf " + _ _ ______________ ________________ __ + | | | | ___| ___ \ \/ |_ _| ___\ \ / / + | | | | |__ | |_/ / . . | | | | |_ \ V / + | |/\| | __|| ___ \ |\/| | | | | _| \ / + \ /\ / |___| |_/ / | | |_| |_| | | | + \/ \/\____/\____/\_| |_/\___/\_| \_/ + + VERSION: 2.0 CREATED: 1/1/2021 BY: ANON + Use '-h' for help, or type 'help' at any time +" + +# I would highly reccomend placing this script in ~/scripts +# and adding the following alias to your .bashrc +# +# alias webmify='~/scripts/webmify.sh' +# +# You can also place this script in your file browser's scripts folder (eg. Nemo: ~/.local/share/nemo/scripts) to add it as a context menu item + +################################## +# Change this option to where you store your converted webms. +# +default_path="$HOME/Videos/webms/" +# +# If you leave it blank, it will output converted webms to the same directory as this script +################################## + +# Before anything else, let's make sure the user actually has ffmpeg and bc installed +bc_installed=true +ffmpeg_installed=true +command -v ffmpeg >/dev/null 2>&1 || { $'\n '"Requirement \"ffmpeg\" is not installed. Aborting."; bc_installed=false; } +command -v bc >/dev/null 2>&1 || { $'\n '"Requirement \"bc\" is not installed. Aborting."; ffmpeg_installed=false; } + +if [ ! $bc_installed ] || [ ! $ffmpeg_installed ]; then + exit 1 +fi + +# Set up our user variables +SETTINGS=(input_file output_directory filename fps resolution start end max_file_size audio_set keyframe title codec debug help flag fails input OPTARG OPTIND) +for i in ${SETTINGS[@]}; do + unset $i +done + +prompt=true + +# You can change this to output the debug log to a file, if required +debug_echo() { + if [ "$debug" ]; then + echo "$@" + fi +} + +trap ctrl_c INT + +function ctrl_c() { + echo + echo "Interrupt signal received! Aborting..." + for i in ${SETTINGS[@]}; do + unset $i + done + killall ffmpeg > /dev/null 2>&1 + exit +} + +show_hint() { + hints=(" + + Usage: + webmify [OPTIONS] + webmify + + List of available options: + -i Input file (file) + -o Output path (path or file) + -n Output filename (string) + -f Output fps (integer) + -r Output vertical resolution (integer) + -s Encoding start (integer) + -e Encoding end (integer) + -m Max file size (integer) + -a Audio enable (y/n) + -t Set the metadata title (string) + -c Specify VP8 or VP9 codec (8/9) + -v View debug messages + -h Display help + -y Skip all prompts" + " + Please type the full path to the file you wish to convert. + Example: /home/user/Videos/my_movie.mp4 + You can press 'TAB' to autocomplete a path." + " + Destination path is the FOLDER you want the converted video + to. If the folder doesn't exist it will be created. + Output filename is the name of the FILE that will be created." + " + Specify the FPS (Frames Per Second) you want for the output + video. The most common FPS values are 24, 30 (or 29) and 60." + " + Specify the VERTICAL resolution of the video. The width will + be automatically calculated by ffmpeg. Common values are + 480, 720, 1080, 2160. Bear in mind most 4chan boards have a + resolution limit of 2000x2000, although /HR/ has a MINIMUM + resolution of 2560x2520." + " + The START value will determine how many seconds will be cut off + from the beginning of the video. + Th END value determines how long the clip will run for, from + the specified START value. + Eg. a START value of 5 and an END value of 20 will result in a + video that is 20 seconds long, starting from the 5 second mark + of the original video." + " + WSG and GIF both allow for files up to around 5MB in size, as + well as an audio stream. Most other boards limit files to 3MB + in size, and disallow audio. Selecting 'YES' will enable the + 5MB limit and audio, or typing 'C' will allow you to set your + own limit and enable/disable audio." + " + This sets the metadata title value. Most video players will + display this title when playing the video, and many people + on media sharing sites will use this tag to embed useful info + about the video, ie the name of the tv show or muscian in the + video." + " + 4chan allows only 2 codecs for webm, VP8 and VP9. VP8 is older + and will usually encode faster than VP9, but VP9 results in + much higher quality for the same file size. VP9 is now the default + for WebRTC video across the internet, but there are still some + applictions that have limited support. If you have issues with + VP9 webms, try using VP8 instead. + This script defaults to VP9 if you don't specify a codec." + ) + + echo "${hints[$1]}" + + sleep 0.5 + +} + +# Option flags, in case the user wants to skip the prompts +while getopts :i:o:n:f:r:s:e:m:a:t:c:vhy flag +do + case "${flag}" in + i) input_file=${OPTARG} + debug_echo Input file set: ${OPTARG};; + o) output_directory=${OPTARG} + debug_echo Output directory set: ${OPTARG};; + n) filename=${OPTARG} + debug_echo Filename set: ${OPTARG};; + f) fps=${OPTARG} + debug_echo FPS set: ${OPTARG};; + r) resolution=${OPTARG} + debug_echo Resolution set: ${OPTARG};; + s) start=${OPTARG} + debug_echo Start set: ${OPTARG};; + e) end=${OPTARG} + debug_echo End set: ${OPTARG};; + m) max_file_size=${OPTARG} + debug_echo Max file size set: ${OPTARG};; + a) audio_set=${OPTARG} + debug_echo Audio set: ${OPTARG};; + t) title=${OPTARG} + debug_echo Title set: ${OPTARG};; + c) codec=${OPTARG} + debug_echo Codec set: ${OPTARG};; + v) debug="true";; + h) help="true";; + y) prompt=false;; + :) printf $'\n '"Invalid option: -$OPTARG requires an argument" 1>&2; show_hint 0 >&2 && exit 1;; + *) printf $'\n '"Invalid option!" 1>&2; show_hint 0 >&2 && exit 1;; + esac +done + +# Check that the user has correctly set the default path to a directory ending in / +check_default_path() { + if ! [[ "${default_path: -1}" == "/" ]]; then default_path="$default_path/" + debug_echo + debug_echo "Default path does not end in \"/\" ! Adding..." + debug_echo Default path: $default_path; + fi +} + +# Checking that we set both a file size and options for audio, because these are set at the same time for simplicity +check_valid_arguments() { + if ([ -z "$max_file_size" ] && [ -z "$audio_set" ]) || ([ -n "$max_file_size" ] && [ -n "$audio_set" ]) + then + true + else + false + fi +} + +get_input_file() { + # If we haven't specified a file with -i we need to ask the user for one now + if [ -z "$input_file" ]; then + read -erp $'\n '"Path to input file: " input_file + case "$input_file" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 1 >&2 + read -erp $'\n '"Path to input file: " input_file + ;; + *) + break + ;; + esac + # Determine if the file exists (note: this doesn't check if the file is WRITEABLE) + while [[ ! -f "$input_file" ]]; do + read -erp $'\n '"Input file must be a real file!"$'\n '"Path to input file: " input_file + done + else # Check that the -i argument is a file that exists + while [[ ! -f "$input_file" ]]; do + read -erp $'\n '"Input file must be a real file!"$'\n '"Path to input file: " input_file + done + printf $'\n '"Input File: $input_file"$'\n' + fi + # Grab the file size - this is useful for a comparison later + input_file=$input_file + input_filesize=$(stat -c%s "$input_file") + input_filesize="$(echo "$input_filesize/1000" | bc -l)" + + # Get the source video information + source_resolution=$(ffprobe -i "$input_file" -v quiet -show_entries stream=height -hide_banner -of default=noprint_wrappers=1:nokey=1) + source_length=$(ffprobe -i "$input_file" -v quiet -show_entries format=duration -hide_banner -of default=noprint_wrappers=1:nokey=1) + # Wrapping this in brackets prevent it from getting more than one value for FPS + source_fps=($(ffprobe -i "$input_file" -v quiet -show_entries stream=r_frame_rate -hide_banner -of default=noprint_wrappers=1:nokey=1)) + source_fps=$(echo $source_fps | bc) + # Total frame count is used to make a progress bar + source_frame_count=$(ffprobe -i "$input_file" -v quiet -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0) + + # Split up the full path to extract just the filename, sans extension + xpath=${input_file%/*} + xbase=${input_file##*/} + input_filename=${xbase%.*} +} + +# We set both the audio and the file size at the same time. +# This is because 4chan uses the same filesize/audio settings across multiple boards. +get_max_filesize() { + # If we set a max file size with -m... + if [ $max_file_size ]; then + # ...And we said we want audio.... + if [ $audioset == "y" ]; then + audioset="-c:a libvorbis " + else + audioset="-an " + fi + else + if [ "$prompt" = true ]; then + # Ask the user for target filesize based on 4chan limits + # They are slightly below the limit to account for ffmpeg using average bitrates and usually being slightly over-size. + # WSG and GIF have higher file size limits, and uniquely allow audio. + read -rp $'\n '"Posting to WSG/GIF (audio and higher filesize)? [y/N]"$'\n '"Or type 'c' for a custom value"$'\n '"Enable larger file size: " response + case "$response" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 6 >&2 + read -rp $'\n '"Enable larger file size: " response + ;; + esac + case "$response" in + # WSG/GIFs file size limit is ~5MB + [yY][eE][sS]|[yY]) + max_file_size=4900 + audioset="-c:a libvorbis " + ;; + # A custom value needs both... + [cC]) + # ... A file size... + read -rp $'\n '"Enter max filesize in kilobytes: " max_file_size + read -rp $'\n '"Would you like audio? " response + case "$response" in + # ...And an audio setting. + [yY][eE][sS]|[yY]) + audioset="-c:a libvorbis " + ;; + *) + audioset="-an " + ;; + esac + ;; + # If they said no to WSG/GIF, we can just set a "blue board" default + *) + max_file_size=2900 + audioset="-an " + ;; + esac + # If we didn't set a max file size, and we skiped prompts, set a default that works on most "blue boards" + else + max_file_size=2950; audioset="-an " + fi + fi + + # We don't want to make the webm bigger than the source file, ffempeg cannot "add more quality" + if [ ${input_filesize%.*} -lt $max_file_size ]; then + max_file_size=$input_filesize + fi +} + +get_codec() { + if [ -z $codec ]; then + # Ask the user for the codec version (vp8 or vp9), or use default (vp9) if not provided + if [ "$prompt" = true ]; then + read -rp $'\n '"Would you like to use VP[8] or VP[9] encoding?"$'\n '"Leave Blank: Default (VP9)"$'\n '"Codec: " codec + fi + fi + case "$codec" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 8 >&2 + read -rp $'\n '"webm version: " codec + ;; + esac + case "$codec" in + #VP8 is the default setting for libvpx + [8]) + codec="-c:v libvpx " + ;; + [9]) + codec="-c:v libvpx-vp9 " + ;; + esac + if [ -z $codec ]; then codec="-c:v libvpx-vp9 "; fi +} + +get_output_file() { + # Ask the user for the output path if they didn't specify one with -o + if [ -z $output_directory ]; then + if [ "$prompt" = true ]; then + while read -erp $'\n '"Please enter the destination path."$'\n '"Example: '$HOME/Videos/'"$'\n '"Leave Blank: Default path ($default_path)"$'\n '"Destination path: " output_directory; do + case "$output_directory" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 2 >&2 + read -erp $'\n '"Destination path: " output_directory; + break + ;; + *) + break + ;; + esac + done + fi + # Use the defaults if no path provided + if [ -z "$output_directory" ]; then output_directory="$default_path"; fi + + # Ask the user for the output filename + if [ "$prompt" = true ]; then + while read -erp $'\n '"Please enter the output filename."$'\n '"Example: 'My_Movie'"$'\n '"Leave Blank: Default filename ($input_filename)"$'\n '"Output filename: " filename; do + case "$filename" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 2 >&2 + read -erp $'\n '"Output filename: " filename + break + ;; + *) + break + ;; + esac + done + fi + # Use the input filename if no filename provided + if [ -z "$filename" ]; then filename="$input_filename"; fi + else + # If the user specified a full path and filename when running the script, we use that instead + if [ "${output_directory: -5}" == ".webm" ]; then + temp_filename="$output_directory" + fi + fi + # temp_filename is used as a placeholder while we figure out what to do with it + if [ -z $temp_filename ]; then + # Once again making sure our output directory ends in / + debug_echo + if ! [[ "${output_directory: -1}" == "/" ]]; then + output_directory="$output_directory/" + debug_echo "Output directory does not end in \"/\" ! Adding..." + fi + output_filename="$output_directory$filename.webm" + # Check to see if the output directory exists, and if not, create it + if [ ! -d "$output_directory" ]; then + debug_echo "$output_directory" not found! creating... + mkdir -p "$output_directory" + fi + else + output_filename="$temp_filename" + fi + output_basename=${output_filename##*/} + output_basename=${output_basename%.*} +} + +get_frames() { + # + if [ -z "$fps" ]; then + if [ "$prompt" = true ]; then + while read -rp $'\n '"Please enter webm framerate '(fps)'."$'\n '"Example: 30"$'\n '"Leave Blank: Source video framerate"$'\n '"Framerate: " fps; do + case "$fps" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 3 >&2 + read -rp $'\n '"Framerate: " fps; + break + ;; + *) + break + ;; + esac + done + fi + if [ -z $fps ]; then fps="$source_fps"; fi; + elif [ $fps == "d" ]; then + fps="$source_fps" + fi +} + +get_resolution() { + # Prompt the user for the resolution + if [ -z "$resolution" ]; then + if [ "$prompt" = true ]; then + while read -rp $'\n '"Please enter webm vertical render resolution."$'\n '"Example: 720"$'\n '"Leave Blank: Source video resolution."$'\n '"Resolution: " resolution; do + case "$resolution" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 4 >&2 + read -rp $'\n '"Resolution: " resolution + break + ;; + *) + break + ;; + esac + done + fi + if [ -z "$resolution" ]; then resolution="$source_resolution"; fi; + # The -r option accepts the argument "d" to use the default (source) resolution + elif [ "$resolution" == "d" ]; then + resolution="$source_resolution" + fi +} + +get_clip_start() { + source_length=$1 + # Ask the user where the encode should start (in seconds offset from the beginning of the source) + if [ "$prompt" = true ]; then + while read -rp $'\n '"Please enter webm rendering offset in SECONDS."$'\n '"Example: 31"$'\n '"Leave Blank: Start of source video."$'\n '"Offset: " input; do + case "$input" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 5 >&2 + read -rp $'\n '"Offset: " input + break + ;; + *) + break + ;; + esac + done + fi + if [ -z "$input" ]; then input=0; fi + + check_valid_clip "start" "$input" "$source_length" +} + +get_clip_end() { + allowed_length=$1 + start=$2 + # Ask the user where the encode should end (in seconds offset from the beginning of the SOURCE, not from the beginning we just set) + if [ "$prompt" = true ]; then + while read -rp $'\n '"Please enter webm rendering length in SECONDS."$'\n '"Example: 15"$'\n '"Leave Blank: Entire source video."$'\n '"Length: " input; do + case "$input" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 5 >&2 + read -rp $'\n '"Offset: " input + break + ;; + *) + break + ;; + esac + done + fi + if [ -z "$input" ]; then input=$allowed_length; fi + + check_valid_clip "end" "$input" "$allowed_length" +} + +check_valid_clip() { + input=$2 + allowed_length=$3 + while (( $(echo "$input>$allowed_length" | bc -l) )); do + if [ $1 = "start" ]; then + read -rp $'\n '"Rendering offset cannot be longer than the legnth of the source video! (${source_length%.*} seconds)"$'\n '"Please enter webm rendering offset in SECONDS."$'\n '"Offset: " input + elif [ $1 = "end" ]; then + read -rp $'\n '"Rendering length cannot be longer than the remainder of the video! (${allowed_length%.*} seconds)"$'\n '"Please enter webm rendering length in SECONDS."$'\n '"Length: " input + fi + fail_counter + done + echo $input +} + +fail_counter() { + fails=$(($fails+1)) + if (( $(echo "$fails>3" | bc -l) )); then + printf $'\n '"I'm getting really sick of this you know."; + fi +} + +# Allow the user to add a title in the metadata +get_title() { + if [ "$prompt" = true ]; then + if [ -z $title ]; then + printf ""$'\n '"Add a title to be used in metadata tags"$'\n '"If left blank, the output filename ($output_basename) will be used." + while read -rp $'\n '"Title: " title; do + case "$title" in + [hH][eE][lL][pP]|[hH]|[-][hH]) + show_hint 7 >&2 + read -rp $'\n '"Title: " title + break + ;; + *) + break + ;; + esac + done + fi + fi + if [ -z $title ]; then title="$output_basename"; fi + title=$title +} + +# I totally nabbed this from github +# https://github.com/fearside/ProgressBar/blob/master/progressbar.sh +ProgressBar() { + # Process data + progress=$(echo "((${1}*100/${2}*100)/100)/2" | bc) + done=$(echo "(${progress}*4)/10" | bc) + left=$(echo "40-$done" | bc) + # Build progressbar string lengths + done=$(printf "%${done}s") + left=$(printf "%${left}s") + + printf "\rProgress: [${done// /#}${left// /-}] ${progress}%%" +} + +ffmpeg_progress() { + # Make it execute a subprocess with the same ID, so both are killed at the same time + pid=$! + trap "kill $pid 2> /dev/null" EXIT + current_frame=0 + # Drawing the progress bar for as long as the ffmpeg process is running + while kill -0 $pid 2> /dev/null; do + # So that we aren't flooding the CPU with curren frame checks + sleep 0.3; + current_frame=$(cat .ffmpeg-log | grep frame= | tail -1) + current_frame=${current_frame:6} + if [ -z $current_frame ]; then current_frame=1; fi + # Sometimes frames will get re-rendered or something idk but we don't want the % to go over 100 even if it renders more frames + if [ $current_frame -gt $source_frame_count ]; then current_frame=$source_frame_count; fi + ProgressBar ${current_frame} ${source_frame_count}; + done +} + +run_conversion() { + debug_echo + debug_echo Source frame count: $source_frame_count + printf "\nBeginning 1st pass...\n" + eval ffmpeg ${complete_command[@]} -pass 1 -passlogfile .ffmpeg-2pass-log -y null -nostdin -progress .ffmpeg-log 2>&1 > /dev/null & + ffmpeg_progress + # Make sure the progress bar actually finishes + ProgressBar 200 100 + printf "\n1st pass: done" + printf "\nBeginning 2nd pass...\n" + # Second verse, same as the first, could get better but it's gonna get... uh, an output that isn't null + eval ffmpeg ${complete_command[@]} -pass 2 -passlogfile .ffmpeg-2pass-log -y \""$output_filename"\" -nostdin -progress .ffmpeg-log 2>&1 > /dev/null & + ffmpeg_progress + ProgressBar 200 100 + printf "\n2nd pass: done" + sleep 0.1 + + # Let the user know just how small their webm is + output_filesize=$(stat -c%s "$output_filename") + output_filesize="$(echo "$output_filesize/1000" | bc -l)" + + bytes_saved="$(echo "$input_filesize-$output_filesize" | bc -l)" + + printf " + All done! + + You can find the finished file at: '$output_filename' + Total filesize: ${output_filesize%.*} Kilobytes + You saved: ${bytes_saved%.*} Kilobytes! +" +} + +if [ -z $help ]; then + + check_default_path +# if ! check_valid_arguments; then printf $'\n '"Error! if -a is specified, -m must also be specified, and vice versa!"$'\n' && exit 1; fi + get_input_file + + debug_echo + debug_echo Input file: "$input_file" + debug_echo Source resolution: "$source_resolution" + debug_echo Source length: "$source_length" + debug_echo Source fps: "$source_fps" + + get_output_file + + debug_echo + debug_echo Output directory: "$output_directory" + debug_echo Output filename: "$output_filename" + + get_max_filesize + + if [ $audioset = "-c:a libvorbis " ]; then audio="Yes"; else audio="No"; fi + + debug_echo + debug_echo Max file size: "$max_file_size" + debug_echo Audio: $audio + + get_codec + + debug_echo + debug_echo Codec: "${codec:6}" + + get_frames + + debug_echo + debug_echo FPS: "$fps" + + get_resolution + + debug_echo + debug_echo Resolution "$resolution" + + if [ $start ]; then + start=$(check_valid_clip "start" "$start" "$source_length") + else + start=$(get_clip_start "$source_length") + fi + + allowed_length=$(echo "$source_length - $start" | bc -l) + + debug_echo + debug_echo Start: "$start" + debug_echo Allowed length: "$allowed_length" + + if [ $end ]; then + end=$(check_valid_clip "end" "$end" "$allowed_length") + else + end=$(get_clip_end "$allowed_length" "$start") + fi + + length=$(echo "$allowed_length-$start" | bc) + + debug_echo + debug_echo Source length: "$source_length" + debug_echo End: "$end" + debug_echo Length: "$length" + + bitrate=$(echo "8*$max_file_size/$length" | bc)'K' + + debug_echo + debug_echo Bitrate: "$bitrate" + + get_title + + debug_echo + debug_echo Title: "$title" + + # Tell the user what we've just done, and ask them if these settings are ok + printf $'\n '"Creating webm with the following settings: + Codec: ${codec#*v} + Audio: $audio + Resolution: $resolution + Framerate: $fps + Start at: $start + End at: $end + Duration: $length + Target File Size: "$max_file_size"MB + Bitrate: $bitrate + Title: $title + Output to: $output_filename + " + + ####################THE PART WHERE THE MAGIC HAPPENS##################### + +# complete_command="-i $input_file -metadata thanks-to=Ruzakai -metadata date-encoded=$(date +'%m-%d-%Y') -metadata title=$title -ss $start -t $length $codec -b:v $bitrate -vf fps=$fps,scale=-1:$resolution $audioset-sn -loglevel error -f webm" + + complete_command=("-i \"$input_file\"" "-metadata thanks-to=Ruzakai" "-metadata date-encoded=$(date +'%m-%d-%Y')" "-metadata title=\"$title\"" "-ss $start" "-t $length" "$codec" "-b:v $bitrate" "-vf fps=$fps,scale=-1:$resolution" "$audioset" "-sn" "-loglevel error" -f webm) + + ######################################################################### + + if [ "$prompt" = true ]; then + read -rp $'\n '"Start the conversion? [Y/n]: " continue + + else + continue=y + fi + case "$continue" in + [yY][eE][sS]|[yY]) + run_conversion $complete_command $output_filename + ;; + *) + exit + ;; + esac + printf $'\n '"Would you like to view the created webm?" + read -rp $'\n '"View webm? [y/N]: " view + case "$view" in + [yY][eE][sS]|[yY]) + xdg-open "$output_filename" + ;; + *) + exit + ;; + esac + +else + cat <