RSSAmplifier

aryan's blog · Sep 22, 2025

Flac to AAC Conversion

0
Sign in to vote or save

This site does not allow itself to be embedded. You can still read it on the original site — the toolbar below keeps your place in the directory.

Wrote a simple fish script which converts flac files to AAC.

I recently bought a Shokz OpenSwim Pro headphone to listen to music while swimming.

The unfortunate thing about water is that electromagnetic waves, especially around the 2.4 Ghz frequency which Bluetooth uses, don't travel at all through it. So you can't really use bluetooth while swimming, which means I've had to go back to the old fashioned way of copying music files manually to my music player.

Most of my music in in FLAC, and while the Shokz headphones do support FLAC, I don't really need lossless high quality audio while swimming. So to make use of the limited storage space I wanted to convert my FLAC files to a lossy format. Unfortunately the Shokz headphones don't support Opus (boo!) so I decided to use AAC instead (patents be damned).

So here is a simple fish script which looks at the current directory and converts all the FLAC files in it or in any of its subdirectories to AAC. Its main features are:

  • Support for various encoders (uses Apple AudioToolbox which is Apple's high quality encoder on Mac, with support for libfdk and the native FFMPEG encoder as well).
  • Supports VBR and setting output quality
  • Uses all CPU cores by default (for parallel execution) but can be manually set to specific number of cores as well.
function flac2aac
    # Parse arguments with defaults
    argparse 'q/quality=' 'd/delete' 'p/parallel' 'e/encoder=' 'h/help' -- $argv
    or return
    # Show help
    if set -q _flag_help
        echo "Usage: flac2aac [-q quality] [-d] [-p] [-e encoder] [-h]"
        echo "  -q/--quality:  VBR quality (default: 2 for Apple, 4 for FDK, 0 for native)"
        echo "  -d/--delete:   Delete original FLAC files after conversion"
        echo "  -p/--parallel: Use parallel processing (all CPU cores)"
        echo "  -e/--encoder:  Force specific encoder (aac_at/libfdk_aac/aac)"
        echo "  -h/--help:     Show this help"
        return 0
    end
    # Detect OS and choose best encoder
    set os (uname)
    set encoder ""
    set quality_default 2
    set quality_param "-q:a"
    if set -q _flag_encoder
        # User specified encoder
        set encoder $_flag_encoder
    else
        # Auto-detect best encoder
        switch $os
            case Darwin
                # macOS - use Apple AudioToolbox
                if ffmpeg -encoders 2>/dev/null | grep -q aac_at
                    set encoder "aac_at"
                    set quality_default 2
                    echo (set_color green)"✓ Using Apple AudioToolbox encoder (best quality)"(set_color normal)
                else
                    echo (set_color yellow)"⚠ Apple encoder not found, falling back..."(set_color normal)
                end
            case Linux FreeBSD
                # Linux/BSD - prefer FDK-AAC, fallback to native
                if ffmpeg -encoders 2>/dev/null | grep -q libfdk_aac
                    set encoder "libfdk_aac"
                    set quality_default 4
                    set quality_param "-vbr"
                    echo (set_color green)"✓ Using FDK-AAC encoder (best open-source)"(set_color normal)
                else if ffmpeg -encoders 2>/dev/null | grep -q aac
                    set encoder "aac"
                    set quality_default 0
                    set quality_param "-q:a"
                    echo (set_color yellow)"⚠ Using native FFmpeg AAC encoder"(set_color normal)
                end
        end
    end
    # Fallback if no encoder found
    if test -z "$encoder"
        set encoder "aac"
        set quality_default 0
        echo (set_color red)"⚠ Using fallback AAC encoder"(set_color normal)
    end
    # Set quality
    set quality (test -n "$_flag_quality" && echo $_flag_quality || echo $quality_default)
    # Build ffmpeg codec parameters based on encoder
    set codec_params "-c:a $encoder"
    switch $encoder
        case aac_at
            # Apple AudioToolbox: -q:a 0-14 (2 recommended)
            set codec_params "$codec_params -q:a $quality"
            echo "Quality range: 0-14 (using: $quality)"
        case libfdk_aac
            # FDK-AAC: -vbr 1-5 (4 recommended)
            # VBR modes: 1=~32kbps, 2=~48kbps, 3=~64kbps, 4=~128kbps, 5=~192kbps
            set codec_params "$codec_params -vbr $quality"
            echo "Quality range: 1-5 (using: $quality)"
        case aac
            # Native FFmpeg: -q:a 0.1-2 (0.3 recommended) or -b:a for CBR
            if test -n "$_flag_quality"
                set codec_params "$codec_params -q:a $quality"
                echo "Quality range: 0.1-2 (using: $quality)"
            else
                # Use CBR for native encoder as more reliable
                set codec_params "$codec_params -b:a 192k"
                echo "Using CBR 192k (specify -q for VBR)"
            end
    end
    # Find all FLAC files
    set flac_files **.flac
    if test (count $flac_files) -eq 0
        echo "No FLAC files found"
        return 1
    end
    echo "Found "(set_color yellow)(count $flac_files)(set_color normal)" FLAC files"
    echo ""
    # file conversion
    function _convert_single_file
        set f $argv[1]
        set codec_params $argv[2]
        set delete_flag $argv[3]
        set output (string replace ".flac" ".m4a" "$f")
        if test -f "$output"
            echo (set_color yellow)"⚠ Skipping: $f (exists)"(set_color normal)
            return 2
        end
        set output_dir (dirname "$output")
        test -d "$output_dir" || mkdir -p "$output_dir"
        echo (set_color blue)"Converting: $f"(set_color normal)
        if ffmpeg -i "$f" $codec_params -map_metadata 0 -loglevel warning "$output"
            echo (set_color green)"✓ Completed: $output"(set_color normal)
            test "$delete_flag" = "yes" && rm "$f"
            return 0
        else
            echo (set_color red)"✗ Failed: $f"(set_color normal)
            return 1
        end
    end
    # parallel processing
    if set -q _flag_parallel
        echo (set_color cyan)"Using parallel processing..."(set_color normal)
        # Determine number of CPU cores
        switch $os
            case Darwin
                set cores (sysctl -n hw.ncpu)
            case Linux
                set cores (nproc)
            case '*'
                set cores 4
        end
        echo "Processing with $cores cores"
        echo ""
        # temporary script for parallel execution
        set delete_flag (set -q _flag_delete && echo "yes" || echo "no")
        # Use GNU parallel if available, otherwise fallback to xargs
        if command -v parallel >/dev/null
            printf '%s\n' $flac_files | parallel -j $cores --bar "
                set output (string replace '.flac' '.m4a' '{}')
                test -f \"\$output\" && exit 0
                set output_dir (dirname \"\$output\")
                test -d \"\$output_dir\" || mkdir -p \"\$output_dir\"
                ffmpeg -i '{}' $codec_params -map_metadata 0 -loglevel warning \"\$output\" && echo '✓ {}' || echo '✗ {}'
                test '$delete_flag' = 'yes' && test -f \"\$output\" && rm '{}'
            "
        else
            # xargs version
            printf '%s\0' $flac_files | xargs -0 -n 1 -P $cores -I {} fish -c "
                set output (string replace '.flac' '.m4a' '{}')
                test -f \"\$output\" && exit 0
                set output_dir (dirname \"\$output\")
                test -d \"\$output_dir\" || mkdir -p \"\$output_dir\"
                ffmpeg -i '{}' $codec_params -map_metadata 0 -loglevel warning \"\$output\"
                test '$delete_flag' = 'yes' && test -f \"\$output\" && rm '{}'
            "
        end
    else
        # Serial processing
        set successful 0
        set failed 0
        set skipped 0
        set delete_flag (set -q _flag_delete && echo "yes" || echo "no")
        for f in $flac_files
            _convert_single_file "$f" "$codec_params" "$delete_flag"
            switch $status
                case 0
                    set successful (math $successful + 1)
                case 1
                    set failed (math $failed + 1)
                case 2
                    set skipped (math $skipped + 1)
            end
        end
        # Summary
        echo ""
        echo (set_color green)"Summary:"(set_color normal)
        echo "  Successful: "(set_color green)$successful(set_color normal)
        echo "  Failed:     "(set_color red)$failed(set_color normal)
        echo "  Skipped:    "(set_color yellow)$skipped(set_color normal)
    end
end

The most recent version of the script will be available on my GitHub: https://github.com/aryonoco/random_scripts/blob/main/flac2aac.fish

Read on blog.ameri.coffee

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.