Skip to content

Instantly share code, notes, and snippets.

@Its4Nik
Created March 3, 2025 18:56
Show Gist options
  • Save Its4Nik/05aed97695461ec7a67839a017648193 to your computer and use it in GitHub Desktop.
Save Its4Nik/05aed97695461ec7a67839a017648193 to your computer and use it in GitHub Desktop.
Convert audio Files using `ffmpeg`, `fzf` and `charmbracelet/gum`
#!/usr/bin/env bash
trap 'exit 1' SIGINT
# Configuration
INDENT=" "
PRESETS_FILE="${HOME}/.config/audio_presets.json"
SYSTEM_PRESETS_DIR="/etc/audio_presets.d"
DEFAULT_PRESETS='{
"presets": [
{
"name": "Standard MP3",
"extension": "mp3",
"options": ["-b:a", "192k"]
},
{
"name": "High Quality Opus",
"extension": "opus",
"options": ["-b:a", "96k", "-vbr", "on"]
},
{
"name": "CD Quality WAV",
"extension": "wav",
"options": ["-ar", "44100", "-ac", "2"]
},
{
"name": "High Quality FLAC",
"extension": "flac",
"options": ["-compression_level", "5"]
},
{
"name": "Low Quality AAC",
"extension": "aac",
"options": ["-b:a", "128k"]
},
{
"name": "Podcast Quality OGG",
"extension": "ogg",
"options": ["-b:a", "64k"]
},
{
"name": "MP3 to OGG",
"extension": "ogg",
"options": ["-qscale:a", "5"]
},
{
"name": "OGG to MP3",
"extension": "mp3",
"options": ["-b:a", "192k"]
},
{
"name": "MP3 to FLAC",
"extension": "flac",
"options": ["-compression_level", "5"]
},
{
"name": "FLAC to MP3",
"extension": "mp3",
"options": ["-b:a", "192k"]
},
{
"name": "AAC to MP3",
"extension": "mp3",
"options": ["-b:a", "192k"]
}
]
}'
# Check dependencies
check_deps() {
local missing=()
command -v ffmpeg &>/dev/null || missing+=("ffmpeg")
command -v gum &>/dev/null || missing+=("gum")
command -v jq &>/dev/null || missing+=("jq")
command -v fzf &>/dev/null || missing+=("fzf")
if [ ${#missing[@]} -gt 0 ]; then
gum style --foreground 196 "${INDENT}Error: Missing dependencies: ${missing[*]}"
exit 1
fi
}
# Select an inpuinit_presetst file using fzf
select_input_file() {
local file
file=$(find . -type f | fzf --height 40)
echo "$file"
}
# Initialize or load presets
init_presets() {
# Ensure user presets file exists
if [ ! -f "$PRESETS_FILE" ]; then
mkdir -p "$(dirname "$PRESETS_FILE")"
echo "$DEFAULT_PRESETS" > "$PRESETS_FILE"
fi
# Load user presets (expects structure: { "presets": [ ... ] })
local user_presets
user_presets=$(jq -c '.presets' "$PRESETS_FILE")
# Load system presets if available
local sys_presets="[]"
if [ -d "$SYSTEM_PRESETS_DIR" ]; then
shopt -s nullglob
json_files=("$SYSTEM_PRESETS_DIR"/*.json)
shopt -u nullglob
if [ ${#json_files[@]} -gt 0 ]; then
sys_presets=$(jq -s 'map(.presets) | add' "${json_files[@]}")
fi
fi
# Combine user and system presets into a stream of preset objects
presets_data=$(jq -c -n --argjson user "$user_presets" --argjson sys "$sys_presets" '$user + $sys | .[]')
}
# Main conversion function
convert_audio() {
gum style --bold --foreground 212 "${INDENT}🎵 Audio Conversion Wizard" --border=rounded
echo
# Select input file using fzf
gum style --bold --foreground 212 "${INDENT}Select input audio file:"
local input_file
input_file=$(select_input_file)
[ -f "$input_file" ] || { gum style --foreground 196 "${INDENT}Input file not found!"; exit 1; }
# Use input file's directory as output destination
local input_dir
input_dir=$(dirname "$input_file")
gum style --bold --foreground 212 "${INDENT}Output directory:"
gum style --foreground 32 "${INDENT}$input_dir"
echo
# Ask if batch conversion is desired
local batch_mode=false
if gum confirm "${INDENT}Batch convert all files with the same extension in $input_dir?"; then
batch_mode=true
fi
# Determine input file extension and select files
local input_ext="${input_file##*.}"
local files_to_convert=()
if [ "$batch_mode" = true ]; then
for f in "$input_dir"/*."$input_ext"; do
[ -f "$f" ] && files_to_convert+=("$f")
done
else
files_to_convert=("$input_file")
fi
# Format selection using presets
gum style --bold --foreground 212 "${INDENT}Select output format:"
local format_choice
format_choice=$(echo -e "$presets_data\n{\"name\":\"Custom Format\",\"extension\":\"\"}" |
jq -r '.name + " (" + .extension + ")"' |
gum choose --height 10)
local preset_name
preset_name=$(echo "$format_choice" | cut -d'(' -f1 | xargs)
local new_extension
new_extension=$(echo "$format_choice" | grep -o '(.*)' | tr -d '() ')
local ffmpeg_opts=()
if [ "$preset_name" != "Custom Format" ]; then
local preset_opts
preset_opts=$(echo "$presets_data" |
jq -r "select(.name == \"$preset_name\") | .options[]" )
for opt in $preset_opts; do
ffmpeg_opts+=("$opt")
done
else
gum style --bold --foreground 212 "${INDENT}Enter custom file extension:"
new_extension=$(gum input --placeholder "ext")
fi
# Additional ffmpeg options
if gum confirm "${INDENT}Add extra ffmpeg options?"; then
gum style --bold --foreground 212 "${INDENT}Enter extra options (space-separated):"
local extra_opts
extra_opts=$(gum input)
for opt in $extra_opts; do
ffmpeg_opts+=("$opt")
done
fi
# Conversion summary
gum style --foreground 212 "${INDENT}Conversion Summary:"
gum format -- "${INDENT}| Files to convert: ${#files_to_convert[@]} file(s)
${INDENT}| Output Format: $(gum style --foreground 32 "$new_extension")
${INDENT}| Options: ${ffmpeg_opts[*]}"
echo
if gum confirm "${INDENT}Start conversion?"; then
for file in "${files_to_convert[@]}"; do
local base_name
base_name=$(basename "$file" | sed 's/\(.*\)\..*/\1/')
local output_path="${input_dir}/${base_name}.${new_extension}"
local cmd=("ffmpeg" "-i" "$file" "${ffmpeg_opts[@]}" "-y" "$output_path")
echo "${INDENT}Converting: $file -> $output_path"
if "${cmd[@]}" 2>&1 | grep -i -v "deprecated"; then
gum style --bold --foreground 46 "${INDENT}✅ Converted: $file"
else
gum style --bold --foreground 196 "${INDENT}❌ Failed: $file"
fi
done
else
gum style --italic --foreground 240 "${INDENT}Conversion canceled"
exit 0
fi
}
# Main execution
check_deps
init_presets
convert_audio
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment