#!/bin/sh # mp3me # author: voidraven # https://sr.ht/~voidraven/mp3me/ # license: BSD 3-clause # # A portable shell script for converting audio files to mp3. # It works on directories of music in parallel. # # Dependencies: ffmpeg, gnu parallel # VERSION="0.3.1" # default bitrate and jobs BITRATE=320000 # Portable way to get the number of cores available to the system (Linux, BSD, and MacOS) CORES_AVAIL=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu || echo 2) # Default number of threads to use (half available cores) JOBS=$(( CORES_AVAIL / 2 )) SHIFT=0 # check if the file provided is a has a valid music extension # cuts off extension and compares it fun_is_music () { L_FILE="$1" # ignore directories if [ -f "$L_FILE" ] ; then # get extension L_FILE_EXT="${L_FILE##*.}" # get lowercase version L_FILE_EXT=$(echo "$L_FILE_EXT" | tr '[:upper:]' '[:lower:]') if [ "$L_FILE_EXT" = "flac" ] || [ "$L_FILE_EXT" = "aac" ] || [ "$L_FILE_EXT" = "ogg" ] || [ "$L_FILE_EXT" = "wav" ] || [ "$L_FILE_EXT" = "m4a" ] ; then unset L_FILE L_FILE_EXT return 0 fi unset L_FILE_EXT fi unset L_FILE return 1 } # echoes out all the valid music files in a directory fun_echo_files () { L_LWD=$(pwd) cd "$1" || fun_error "failed to enter directory: $1" for FILE in * ; do if fun_is_music "$FILE" ; then echo "$FILE" fi done cd "$L_LWD" || fun_error "failed to enter directory: $1" unset L_LWD } # take user input as kbps and return bps fun_get_bitrate () { L_BR="$1" if [ "$L_BR" -gt 0 ] && [ "$L_BR" -lt 321 ] ; then BITRATE=$((L_BR*1000)) SHIFT=$((SHIFT+2)) else unset L_BR fun_error "bad bitrate value passed in" fi unset L_BR } fun_check_jobs () { if [ "$1" -gt 0 ] && [ "$1" -lt 256 ] ; then JOBS="$1" SHIFT=$((SHIFT+2)) else fun_error "bad number of jobs passed in (0 < j < 256)" fi } # call this function to start the script fun_run () { echo "mp3me - beginning conversion $1 with $JOBS jobs." # SC2086 - yes, we want to split on spaces since we are building a command line # SC1083 - yes, the '{}' char are literal parallel uses them # shellcheck disable=SC2086,SC1083 fun_echo_files "$1" | parallel -j $JOBS ffmpeg -hide_banner -nostats -loglevel fatal -i "'$1'"/{} -b:a $BITRATE "'$1'"/{.}.mp3 } # run when ctrl-c is pressed fun_cleanup () { echo "caught sigint, exiting.." exit 0 } # print help/version info fun_print_info () { echo "mp3me - parallel mp3 converter script" fun_print_ver echo "license: bsd 3-clause" echo "author: voidraven" fun_print_usage exit 0 } # print usage info fun_print_usage () { echo "-- usage: --------------------" echo " mp3me ./musicdir # just use defaults" echo " mp3me -j 8 ./musicdir # jobs (i.e. threads to start)" echo " mp3me -b 192 ./musicdir # bitrate - default is 320" echo " mp3me -h # shows this help message" echo " mp3me -v # show the version info" } # print version info fun_print_ver () { echo "mp3me version: $VERSION" } # print error message fun_error () { echo "mp3me - error detected" echo "$1" fun_print_usage exit 1 } while getopts 'b:j:hvi:' OPT ; do case "$OPT" in b) fun_get_bitrate "$OPTARG";; j) fun_check_jobs "$OPTARG";; h) fun_print_info;; v) fun_print_ver && exit 0;; [?]) fun_error "invalid argument passed in by user";; esac done shift $SHIFT DIR=$* if [ -d "$DIR" ] ; then fun_run "$DIR" else fun_error "directory not found: $DIR" fi exit 0