Created
September 2, 2026 07:29
-
-
Save KernelGhost/5bc7188d09d7b44b79cfd0e3ce297791 to your computer and use it in GitHub Desktop.
A multithreaded bash script that produces MP3 files containing track metadata and embedded album art in a format compatible with iPod Classics running Rockbox.
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/bin/env bash | |
| ################################################################################ | |
| # Name: rockbox_converter_mt.sh | |
| # Authors: Rohan Barar | |
| # Revision: 02/09/2026 (DD/MM/YYYY) | |
| # Purpose: Recursively traverse a given input directory to convert all supported | |
| # audio files to Rockbox compatible MP3 files, with a focus on | |
| # ensuring both track metadata (e.g. Title, Artist, Album, etc.) as | |
| # well as embedded album/cover are displayed correctly by Rockbox. | |
| # Usage: ./rockbox_converter_mt.sh <input_dir> <output_dir> | |
| # Example: ./rockbox_converter_mt.sh ~/Desktop/INPUT_DIR/ ~/Desktop/OUTPUT_DIR/ | |
| # References: | |
| # - https://www.reddit.com/r/rockbox/comments/gs8e0p/trouble_with_album_art_heres_a_tip/ | |
| # - https://askubuntu.com/questions/442997/how-can-i-convert-audio-from-ogg-to-mp3 | |
| ################################################################################ | |
| # Ensure 'nullglob' is enabled for safe wildcard expansion | |
| # Ensure 'nocaseglob' is enabled for case-insensitive wildcard matching | |
| shopt -s nullglob nocaseglob | |
| # Constants | |
| ANSI_RED="\033[1;31m" | |
| ANSI_GREEN="\033[1;32m" | |
| ANSI_GREY="\033[90m" | |
| ANSI_ORANGE="\033[1;38;5;214m" | |
| ANSI_CLEAR="\033[0m" | |
| SUPPORTED_AUDIO_EXTENSIONS=("*.mp3" "*.ogg" "*.flac" "*.wav" "*.aac" "*.m4a" "*.opus") | |
| MAX_ART_DIMENSION="300" | |
| readonly ANSI_RED ANSI_GREEN ANSI_GREY ANSI_ORANGE ANSI_CLEAR SUPPORTED_AUDIO_EXTENSIONS MAX_ART_DIMENSION | |
| # shellcheck disable=SC2329 | |
| function cleanup() { | |
| # Unbind trap to prevent infinite loop | |
| trap - EXIT | |
| # Wait for background job subshell | |
| if [[ -n "$jobsub_pid" ]] && kill -0 "$jobsub_pid" 2>/dev/null; then | |
| wait "$jobsub_pid" 2>/dev/null | |
| fi | |
| # Wait for all background jobs | |
| while [[ -n "$(jobs -rp)" ]]; do | |
| wait 2>/dev/null | |
| done | |
| # Delete temporary directory | |
| [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]] && rm -rf -- "$TEMP_DIR" | |
| } | |
| # shellcheck disable=SC2329 | |
| function on_interrupt() { | |
| # Ignore repeated Ctrl+C | |
| trap '' SIGINT | |
| # Print feedback | |
| echo -e "\n\n${ANSI_ORANGE}WARNING:${ANSI_CLEAR} SIGINT received!" | |
| echo "> Running jobs will be terminated." | |
| echo "> No new jobs will be started." | |
| echo "" | |
| # Set global halt variable | |
| halt_requested=1 | |
| # Send SIGUSR1 to job submission background loop | |
| if [[ -n "$jobsub_pid" ]]; then | |
| kill -USR1 "$jobsub_pid" 2>/dev/null | |
| fi | |
| } | |
| function is_valid_mp3() { | |
| local file="$1" | |
| [[ -s "$file" ]] || return 1 | |
| ffprobe \ | |
| -v error \ | |
| -select_streams a:0 \ | |
| -show_entries stream=codec_name \ | |
| -of default=noprint_wrappers=1:nokey=1 \ | |
| "$file" 2>/dev/null | | |
| grep -qx 'mp3' | |
| } | |
| function get_thread_count() { | |
| # Linux --> nproc | |
| # macOS --> sysctl | |
| nproc --all 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo "4" | |
| } | |
| function move_output_file() { | |
| # 'input_audio_file_name' inherited via dynamic scoping since only ever called from 'convert_file' | |
| local source="$1" | |
| local destination="$2" | |
| if ! mv -- "$source" "$destination"; then | |
| send_status "FAILED" "$input_audio_file_name" "WRITE_MOVE_FAIL" | |
| exit 1 | |
| fi | |
| } | |
| function convert_file() { | |
| # Return Codes | |
| # > SIGINT_FAIL --> SIGINT-Triggered Conversion Failure | |
| # > TEMP_DIR_CREATE_FAIL --> Temporary Directory Creation Failure | |
| # > OUTPUT_DIR_CREATE_FAIL --> Output Directory Creation Failure | |
| # > MP3_CONVERT_FAIL --> MP3 Conversion Failure | |
| # > WRITE_MOVE_FAIL --> Output MP3 File Write/Move Failure | |
| # > INVALID_MP3_FAIL --> Invalid Output MP3 Failure | |
| # > COVER_EMBED_WARN --> Cover Art Embedding Failure | |
| # > COVER_MOVE_WARN --> Cover Art Replacement Failure | |
| # > COVER_RESIZE_WARN --> Cover Art Resize Failure | |
| # > COVER_NONE_WARN --> No Album Art | |
| # Store input | |
| local input_audio_file_path="$1" | |
| # Manipulate paths | |
| local input_audio_file_name=${input_audio_file_path##*/} # Strip leading path | |
| input_audio_file_name=${input_audio_file_name%.*} # Strip file extension | |
| local relative_input_audio_file_path=${input_audio_file_path#"$I_DIR"/} | |
| local output_audio_file_path="${O_DIR}/${relative_input_audio_file_path%.*}.mp3" | |
| # Create temporary subdirectory | |
| local job_tmp_dir | |
| job_tmp_dir=$(mktemp -d "${TEMP_DIR}/job.XXXXXX") || { | |
| # Temporary Directory Creation Failure | |
| send_status "FAILED" "$input_audio_file_name" "TEMP_DIR_CREATE_FAIL" | |
| exit 1 | |
| } | |
| # Remove temporary subdirectory when function returns | |
| trap 'rm -rf -- "$job_tmp_dir"' EXIT | |
| # Catch SIGINT locally for a clean abort | |
| trap 'send_status "FAILED" "$input_audio_file_name" "SIGINT_FAIL"; exit 130' SIGINT | |
| # Skip if output_audio_file_path already exists | |
| if ! is_valid_mp3 "$output_audio_file_path"; then | |
| # Define temporary file paths | |
| local temp_output_audio_file_path="${job_tmp_dir}/nocover.mp3" # Temporary Output MP3 Path | |
| local temp_album_art_path="${job_tmp_dir}/cover.jpg" # Extracted Album/Cover Art | |
| local temp_album_art_processed_path="${job_tmp_dir}/cover_processed.jpg" # Processed Album/Cover Art | |
| # Create the output directory if it does not already exist | |
| if ! mkdir -p -- "$(dirname -- "$output_audio_file_path")"; then | |
| # Output Directory Creation Failure | |
| send_status "FAILED" "$input_audio_file_name" "OUTPUT_DIR_CREATE_FAIL" | |
| exit 1 | |
| fi | |
| # Convert input audio file to MP3 | |
| # 1. Extreme Quality (Highest Quality VBR) | |
| # 2. Ignore Video (Album/Cover Art) | |
| # 3. Include Track Metadata | |
| # Note: Metadata is mapped from both the global metadata (-map_metadata 0) and the first subtitle stream (-map_metadata 0:s:0) | |
| # Note: '-map_metadata 0' seems to apply to '.mp3' and '.flac' files. | |
| # Note: '-map_metadata 0:s:0' seems to apply to '.ogg' files. | |
| # Note: FFmpeg will automatically pick the applicable metadata input for the given input audio file type | |
| if ! ffmpeg -nostdin -i "$input_audio_file_path" -vn -q:a 0 -map_metadata 0 -map_metadata 0:s:0 -id3v2_version 3 -write_id3v1 1 -write_id3v2 1 -y "$temp_output_audio_file_path" &>/dev/null; then | |
| # MP3 Conversion Failure | |
| send_status "FAILED" "$input_audio_file_name" "MP3_CONVERT_FAIL" | |
| exit 1 | |
| fi | |
| # Extract the album/cover art to a temporary file | |
| # Note: Use pixel format 'yuvj420p' with 'full' colour range and 'bt470bg' colour space for compatibility with outdated media players | |
| if ffmpeg -nostdin -i "$input_audio_file_path" -an -update 1 -frames:v 1 -pix_fmt yuvj420p -color_range full -colorspace bt470bg -y "$temp_album_art_path" &>/dev/null; then | |
| # 1. Resize Image (Max 300px Length OR Width) + Preserve Aspect Ratio | |
| # 2. Ensure File Size <500KB | |
| # 3. Remove Image Metadata | |
| # 4. Ensure Standard (Baseline) JPEG Output (i.e. Not Progressive) | |
| if magick "$temp_album_art_path" -resize "${MAX_ART_DIMENSION}x${MAX_ART_DIMENSION}>" -strip -interlace none -define jpeg:extent=500KB "$temp_album_art_processed_path" &>/dev/null; then | |
| if mv -- "$temp_album_art_processed_path" "$temp_album_art_path"; then | |
| # Embed the scaled album/cover art into the output audio file | |
| if ! ffmpeg -nostdin -y -i "$temp_output_audio_file_path" \ | |
| -i "$temp_album_art_path" \ | |
| -map 0:0 \ | |
| -map 1:0 \ | |
| -c copy \ | |
| -id3v2_version 3 \ | |
| -metadata:s:v title="Album cover" \ | |
| -metadata:s:v comment="Cover (front)" \ | |
| -y "$output_audio_file_path" &>/dev/null; then | |
| # Cover Art Embedding Failure | |
| send_status "WARNING" "$input_audio_file_name" "COVER_EMBED_WARN" | |
| move_output_file "$temp_output_audio_file_path" "$output_audio_file_path" | |
| fi | |
| else | |
| # Cover Art Replacement Failure | |
| send_status "WARNING" "$input_audio_file_name" "COVER_MOVE_WARN" | |
| move_output_file "$temp_output_audio_file_path" "$output_audio_file_path" | |
| fi | |
| else | |
| # Cover Art Resize Failure | |
| send_status "WARNING" "$input_audio_file_name" "COVER_RESIZE_WARN" | |
| move_output_file "$temp_output_audio_file_path" "$output_audio_file_path" | |
| fi | |
| else | |
| # No Album Art | |
| send_status "WARNING" "$input_audio_file_name" "COVER_NONE_WARN" | |
| move_output_file "$temp_output_audio_file_path" "$output_audio_file_path" | |
| fi | |
| # Check for a valid output MP3 | |
| if ! is_valid_mp3 "$output_audio_file_path"; then | |
| rm -f -- "$output_audio_file_path" | |
| send_status "FAILED" "$input_audio_file_name" "INVALID_MP3_FAIL" | |
| exit 1 | |
| fi | |
| # Return success | |
| send_status "CONVERTED" "$input_audio_file_name" | |
| else | |
| # Return skipped | |
| send_status "SKIPPED" "$input_audio_file_name" | |
| fi | |
| } | |
| function send_status() { | |
| # Examples: | |
| # send_status "CONVERTED" "$relative_input_audio_file_path" | |
| # send_status "SKIPPED" "$relative_input_audio_file_path" | |
| # send_status "WARNING" "$relative_input_audio_file_path" "COVER_NONE_WARN" | |
| # send_status "FAILED" "$relative_input_audio_file_path" "MP3_CONVERT_FAIL" | |
| # send_status "DONE" | |
| local status="$1" | |
| local file="$2" | |
| local reason="${3:-}" | |
| printf '%s\0%s\0%s\0' "$status" "$file" "$reason" >&3 | |
| } | |
| function process_fifo_message() { | |
| local status="$1" | |
| local file="$2" | |
| local reason="$3" | |
| case "$status" in | |
| CONVERTED) | |
| # Increment counters | |
| ((files_processed++)) | |
| ((files_converted++)) | |
| # Print feedback | |
| printf "[%0*d/%d] ${ANSI_GREEN}Converted${ANSI_CLEAR} '%s'\n" \ | |
| "${#total_files_in_directory}" \ | |
| "$files_processed" \ | |
| "$total_files_in_directory" \ | |
| "$file" | |
| ;; | |
| SKIPPED) | |
| # Increment counters | |
| ((files_processed++)) | |
| ((files_skipped++)) | |
| # Print feedback | |
| printf "[%0*d/%d] Skipped '%s' (Already Exists)\n" \ | |
| "${#total_files_in_directory}" \ | |
| "$files_processed" \ | |
| "$total_files_in_directory" \ | |
| "$file" | |
| ;; | |
| WARNING) | |
| # Increment counter | |
| ((files_warnings++)) | |
| # Print feedback | |
| local warning_message | |
| case "$reason" in | |
| COVER_EMBED_WARN) | |
| warning_message="Cover Art Embedding Failure!" | |
| ;; | |
| COVER_MOVE_WARN) | |
| warning_message="Cover Art Replacement Failure!" | |
| ;; | |
| COVER_RESIZE_WARN) | |
| warning_message="Cover Art Resize Failure!" | |
| ;; | |
| COVER_NONE_WARN) | |
| warning_message="No Album Art!" | |
| ;; | |
| esac | |
| printf "${ANSI_ORANGE}Warning${ANSI_CLEAR} '%s' (%s)\n" \ | |
| "$file" \ | |
| "$warning_message" | |
| ;; | |
| FAILED) | |
| # Increment counters | |
| ((files_processed++)) | |
| ((files_failed++)) | |
| # Print feedback | |
| local failure_message | |
| case "$reason" in | |
| SIGINT_FAIL) | |
| failure_message="SIGINT" | |
| ;; | |
| TEMP_DIR_CREATE_FAIL) | |
| failure_message="Temporary Directory Creation Failure!" | |
| ;; | |
| OUTPUT_DIR_CREATE_FAIL) | |
| failure_message="Output Directory Creation Failure!" | |
| ;; | |
| MP3_CONVERT_FAIL) | |
| failure_message="MP3 Conversion Failure!" | |
| ;; | |
| WRITE_MOVE_FAIL) | |
| failure_message="Output MP3 File Write/Move Failure!" | |
| ;; | |
| INVALID_MP3_FAIL) | |
| failure_message="Output MP3 Corruption Failure!" | |
| ;; | |
| esac | |
| printf "[%0*d/%d] ${ANSI_RED}Failed${ANSI_CLEAR} '%s' (%s)\n" \ | |
| "${#total_files_in_directory}" \ | |
| "$files_processed" \ | |
| "$total_files_in_directory" \ | |
| "$file" \ | |
| "$failure_message" | |
| ;; | |
| esac | |
| } | |
| # Traps | |
| trap cleanup EXIT | |
| trap on_interrupt SIGINT | |
| # Make temporary directory | |
| TEMP_DIR=$(mktemp -d /tmp/rockbox_converter_mt.XXXXXX) || { | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} Failed to create temporary directory!" | |
| exit 1 | |
| } | |
| readonly TEMP_DIR | |
| # Check for modern bash as script requires 'Wait and Cooperative Exit' (WCE) | |
| if (( BASH_VERSINFO[0] < 4 )) || (( BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4 )); then | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} This script requires Bash 4.4 or newer for proper signal handling (WCE)." >&2 | |
| echo "Current version: ${BASH_VERSION}" >&2 | |
| exit 2 | |
| fi | |
| # Check if 'ffmpeg' and 'ffprobe' are installed | |
| if ! command -v ffmpeg >/dev/null 2>&1 || ! command -v ffprobe >/dev/null 2>&1; then | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} 'ffmpeg' and/or 'ffprobe' are NOT installed!" | |
| echo "sudo apt install ffmpeg # Debian/Ubuntu" | |
| echo "sudo dnf install ffmpeg # Fedora" | |
| echo "brew install ffmpeg # macOS" | |
| exit 2 | |
| fi | |
| # Check if 'magick' (ImageMagick) is installed | |
| if ! command -v magick >/dev/null 2>&1; then | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} ImageMagick is NOT installed!" | |
| echo "sudo apt install imagemagick # Debian/Ubuntu" | |
| echo "sudo dnf install ImageMagick # Fedora" | |
| echo "brew install imagemagick # macOS" | |
| exit 2 | |
| fi | |
| # Arguments | |
| I_DIR="$1" # Directory containing input audio files | |
| O_DIR="$2" # Directory within which to store processed/converted audio files | |
| # Ensure both the input and output directories are specified | |
| if [[ -z "$I_DIR" || -z "$O_DIR" ]]; then | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} Insufficient arguments specified!" | |
| echo "Usage: ${0} <input_directory> <output_directory>" | |
| echo "Example: ./rockbox_converter_mt.sh ~/Desktop/IN ~/Desktop/OUT" | |
| exit 3 | |
| fi | |
| # Check if the provided input directory path is valid | |
| if [[ ! -d "$I_DIR" ]]; then | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} '${I_DIR}' is not a valid directory!" | |
| exit 4 | |
| fi | |
| # Create the output directory if it does not exist | |
| if ! mkdir -p -- "$O_DIR"; then | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} Failed to create output directory '${O_DIR}'!" | |
| exit 5 | |
| fi | |
| # Resolve the supplied paths to their absolute forms | |
| if ! I_DIR=$(cd -- "$I_DIR" && pwd -P); then | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} Failed to resolve input directory path!" | |
| exit 6 | |
| fi | |
| if ! O_DIR=$(cd -- "$O_DIR" && pwd -P); then | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} Failed to resolve output directory path!" | |
| exit 6 | |
| fi | |
| # Check if the input directory is the output directory | |
| if [[ "$I_DIR" == "$O_DIR" ]]; then | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} Input and output directories must be different!" | |
| exit 7 | |
| fi | |
| # Check if the output directory is a child of the input directory | |
| case "$O_DIR" in | |
| "$I_DIR"/*) | |
| echo -e "${ANSI_RED}ERROR:${ANSI_CLEAR} Output directory cannot be inside input directory!" | |
| exit 8 | |
| ;; | |
| esac | |
| # Recursively capture subdirectories | |
| input_dirs=() | |
| max_dir_path_length=0 | |
| function capture_subdirectories() { | |
| # Store parent directory to recurse | |
| local dir="$1" | |
| local item | |
| # Add the current directory to the array | |
| input_dirs+=("$dir") | |
| # Update the maximum directory length | |
| if (( ${#dir} > max_dir_path_length )); then | |
| max_dir_path_length=${#dir} | |
| fi | |
| # Loop through all items in the directory | |
| for item in "$dir"/*; do | |
| # Ignore symlinked directories | |
| if [ -d "$item" ] && [ ! -L "$item" ]; then | |
| # Recursively call the function for subdirectories | |
| item=$(cd -- "$item" && pwd -P) | |
| capture_subdirectories "$item" | |
| fi | |
| done | |
| } | |
| capture_subdirectories "$I_DIR" | |
| # Print the directories that are going to be processed | |
| echo "" | |
| echo -e "${ANSI_ORANGE}DIRECTORIES IDENTIFIED${ANSI_CLEAR}" | |
| printf "%$(( max_dir_path_length + 7 ))s\n" | tr ' ' "=" | |
| counter=1 | |
| for dir in "${input_dirs[@]}"; do | |
| printf "${ANSI_GREY}%05d) %s${ANSI_CLEAR}\n" "$counter" "$dir" | |
| ((counter++)) | |
| done | |
| printf "%$(( max_dir_path_length + 7 ))s\n\n" | tr ' ' "=" | |
| # Obtain thread count | |
| thread_count=$(get_thread_count) | |
| # Print feedback | |
| echo -e "${ANSI_ORANGE}COMMENCING CONVERSION${ANSI_CLEAR}" | |
| # Initialise global counters | |
| dir_counter=1 | |
| total_files_converted=0 | |
| total_files_skipped=0 | |
| total_files_failed=0 | |
| total_files_warnings=0 | |
| # Global halt variable | |
| halt_requested=0 | |
| # Iterate through all supported audio files in all input subdirectories | |
| for dir in "${input_dirs[@]}"; do | |
| # Cease if SIGINT | |
| (( halt_requested )) && break | |
| # Reset counters | |
| files_processed=0 | |
| files_converted=0 | |
| files_skipped=0 | |
| files_failed=0 | |
| files_warnings=0 | |
| total_files_in_directory=0 | |
| for audio_file_extension in "${SUPPORTED_AUDIO_EXTENSIONS[@]}"; do | |
| for input_audio_file_path in "${dir}"/${audio_file_extension}; do | |
| if [[ -f "$input_audio_file_path" ]]; then | |
| ((total_files_in_directory++)) | |
| fi | |
| done | |
| done | |
| if (( total_files_in_directory > 0 )); then | |
| # Print feedback | |
| echo -e "${ANSI_GREEN}(${dir_counter}/${#input_dirs[@]})${ANSI_CLEAR} Converting ${total_files_in_directory} supported audio files in '${dir}'" | |
| # Create and open named pipe | |
| FIFO="${TEMP_DIR}/status.${dir_counter}" | |
| mkfifo "$FIFO" | |
| exec 3<> "$FIFO" | |
| # Commence job submission and throttling in background subshell | |
| ( | |
| # Update halting variable using SIGUSR1 | |
| halt_requested_jobsub=0 | |
| trap 'halt_requested_jobsub=1' USR1 | |
| for audio_file_extension in "${SUPPORTED_AUDIO_EXTENSIONS[@]}"; do | |
| (( halt_requested_jobsub )) && break | |
| for input_audio_file_path in "${dir}"/${audio_file_extension}; do | |
| (( halt_requested_jobsub )) && break 2 | |
| if [[ -s "$input_audio_file_path" ]]; then | |
| convert_file "$input_audio_file_path" & | |
| fi | |
| while (( $(jobs -rp | wc -l) >= thread_count )); do | |
| (( halt_requested_jobsub )) && break 3 | |
| wait -n | |
| done | |
| done | |
| done | |
| # Wait for remaining jobs to finish | |
| while [[ -n "$(jobs -rp)" ]]; do | |
| wait 2>/dev/null | |
| done | |
| # Signal finished to main loop | |
| send_status "DONE" | |
| ) & | |
| # Store job submission PID | |
| jobsub_pid=$! | |
| # Main shell reads from FIFO | |
| while \ | |
| IFS= read -r -d '' status && | |
| IFS= read -r -d '' file && | |
| IFS= read -r -d '' reason | |
| do | |
| if [[ "$status" == "DONE" ]]; then | |
| break | |
| fi | |
| process_fifo_message "$status" "$file" "$reason" | |
| done <&3 | |
| # Close FIFO | |
| exec 3>&- | |
| # Clear job submission PID | |
| wait "$jobsub_pid" 2>/dev/null | |
| jobsub_pid="" | |
| # Print Directory Summary | |
| echo "" | |
| echo "Directory Summary:" | |
| echo -e " ${ANSI_GREEN}[+]${ANSI_CLEAR} Converted: ${files_converted}" | |
| echo -e " ${ANSI_GREY}[-]${ANSI_CLEAR} Skipped: ${files_skipped}" | |
| echo -e " ${ANSI_RED}[x]${ANSI_CLEAR} Failed: ${files_failed}" | |
| echo -e " ${ANSI_ORANGE}[!]${ANSI_CLEAR} Warnings: ${files_warnings}" | |
| echo "" | |
| # Update totals | |
| total_files_converted=$(( total_files_converted + files_converted )) | |
| total_files_skipped=$(( total_files_skipped + files_skipped )) | |
| total_files_failed=$(( total_files_failed + files_failed )) | |
| total_files_warnings=$(( total_files_warnings + files_warnings )) | |
| else | |
| # Print feedback | |
| echo -e "${ANSI_GREEN}(${dir_counter}/${#input_dirs[@]})${ANSI_CLEAR} Skipping '${dir}' (No supported audio files)" | |
| fi | |
| # Increment counter | |
| ((dir_counter++)) | |
| done | |
| # Print Final Global Summary | |
| echo -e "${ANSI_ORANGE}===============================================================================${ANSI_CLEAR}" | |
| echo -e "${ANSI_ORANGE} FINISHED! ${ANSI_CLEAR}" | |
| echo -e "${ANSI_ORANGE}===============================================================================${ANSI_CLEAR}" | |
| echo "Global Summary (All Directories):" | |
| echo -e " ${ANSI_GREEN}[+]${ANSI_CLEAR} Total Converted: ${ANSI_GREEN}${total_files_converted}${ANSI_CLEAR} " | |
| echo -e " ${ANSI_GREY}[-]${ANSI_CLEAR} Total Skipped: ${ANSI_GREY}${total_files_skipped}${ANSI_CLEAR} " | |
| echo -e " ${ANSI_RED}[x]${ANSI_CLEAR} Total Failed: ${ANSI_RED}${total_files_failed}${ANSI_CLEAR} " | |
| echo -e " ${ANSI_ORANGE}[!]${ANSI_CLEAR} Total Warnings: ${ANSI_ORANGE}${total_files_warnings}${ANSI_CLEAR} " | |
| echo -e "${ANSI_ORANGE}===============================================================================${ANSI_CLEAR}" | |
| if (( total_files_failed > 0 || halt_requested )); then | |
| exit 1 | |
| fi | |
| exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment