Last active
October 25, 2025 16:43
-
-
Save oxagast/aa4bda692f6a636fa749cec5e2ace85a to your computer and use it in GitHub Desktop.
Video Downsampler in Bash
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/local/bin/bash | |
| # oxagast / [email protected] | |
| # | |
| # | |
| maxres="480p" ## Target Resolution | |
| audiochs=2 ## Max Audio Channels | |
| maxsize=600 ## Max filesize in megabytes | |
| # | |
| cleanup() | |
| { | |
| printf "\nCaught CTRL+C cleaning up... " | |
| rm -f "downsample/*.mp4" | |
| echo "Exiting..." | |
| exit 1 | |
| } | |
| trap cleanup SIGINT | |
| res=$(echo $maxres | tr -d 'p') | |
| if [[ $(pgrep ffmpeg | wc -l) -ne 0 ]]; then | |
| echo "Already encoding!" | |
| exit 1 | |
| fi | |
| mkdir -p orig | |
| mkdir -p downsample | |
| count=0 | |
| totfiles=$(ls -1 *.{mp4,mpg,avi,mkv} 2>/dev/null | wc -l | tr -d ' ') | |
| for file in *.{mp4,mpg,avi,mkv}; do | |
| ((count++)) | |
| filename=$(basename -- "$file") | |
| extension="${filename##*.}" | |
| filename_no_ext="${filename%.*}" | |
| if [[ -f "downsample/${file}" ]]; then | |
| rm -f "downsample/${file}" | |
| fi | |
| if [[ $(echo "${file}" | grep -E "[[:digit:]]{4}") ]]; then | |
| date=$(echo "${file}" | tr '.' ' ' | sed -E 's|.* ([[:digit:]]+) mp4|\1|' || echo "${file}" | grep -oE \ | |
| "[[:digit:]]{4}") | |
| title=$(echo "${file}" | tr '.' ' ' | sed -E 's|(.*) [[:digit:]]{4} mp4|\1|' || echo "${filename_no_ext}") | |
| else | |
| date="" | |
| title="" | |
| fi | |
| if [[ -f "${file}" ]]; then | |
| osize=$(stat -f "%z" "${file}" | tr -d '\n') | |
| osizemb=$(stat -f "%z" "${file}" | gnumfmt --to-unit M --format "%8.0f" | xargs printf "\r%s") | |
| else | |
| osize=0 | |
| osizemb=0 | |
| fi | |
| if [ $osizemb -gt $maxsize ]; then | |
| if [[ $(ffprobe -v fatal -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 "${file}") -gt $res || | |
| $(ffprobe -v fatal -select_streams a:0 -show_entries stream=channels -of default=noprint_wrappers=1:nokey=1 \ | |
| "${file}" 2>/dev/null) -gt 2 ]]; then | |
| if [ ! -s "downsample/${filename_no_ext}.mp4" ] || [[ ! $(ffprobe -v fatal -show_entries format=duration -of \ | |
| default=noprint_wrappers=1:nokey=1 "${file}" 2>&1 >/dev/null) ]]; then | |
| if [ ! -f "orig/${file}" ] || [[ $(ffprobe -ignore_chapters 1 -v fatal -show_entries format=duration -of \ | |
| default=noprint_wrappers=1:nokey=1 "${file}" | cut -d '.' -f 1) -ne $(ffprobe -v \ | |
| error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "orig/${file}" | | |
| cut -d '.' -f 1) ]]; then | |
| #FRAMECOUNT=$(mediainfo --Output="Video;%FrameCount%" "${file}" | tr -d '\n') | |
| pv -s "${osize}" -aepN "Downsampling: ($count/$totfiles) ${title} [$date]" "${file}" | ffmpeg -i pipe:0 -y \ | |
| -nostats -hide_banner -loglevel fatal -ignore_chapters 1 -vf "scale=-2:$res:flags=lanczos+accurate_rnd" \ | |
| -c:v libx264 -preset slow -filter:v fps=24 -crf 31 -maxrate 2M -bufsize 4M -c:a aac -ac $audiochs -b:a \ | |
| 128k -metadata year=$date -metadata title="${title}" "downsample/${filename_no_ext}.mp4" && mv \ | |
| "${file}" "orig/" 2>/dev/null && mv -f "downsample/${filename_no_ext}.mp4" "${filename_no_ext}.mp4" \ | |
| 2>/dev/null || mv -f "orig/${file}" "${file}" 2>/dev/null | |
| fi | |
| fi | |
| fi | |
| fi | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment