HowTo: convert audio to TAF fast and easy (macOS/Linux)

:small_orange_diamond: Intro :small_orange_diamond:
I was looking for a way to encode any audio files into taf as fast as possible on a powerful desktop machine (e.g. MacBook Pro). The Teddycloud has an audio encoder built-in (web interface & cli) but it’s really slow since it’s running on a Raspberry Pi 4 (in my case). Whereas running the teddycloud container on your desktop machine just for taf conversion is a bit overkill and adds unnecessary complexity (docker, handling with volumes, certificate check at every start etc.).

I wanted a solution which is fast, easy to use and easy to automate/script (e.g. for batch conversion of multiple files). As a final result, here’s a time comparison for an audiobook with ~75 minutes playtime.

Transcoding time:
Teddycloud Pi4 (Web/CLI): 7m10s
MacBook M1 Pro (native): 45s
→ roughly 9,5x faster.

:small_orange_diamond: Solution :small_orange_diamond:
A TAF file is basically an opus sound file but with a special header. That’s why it’s not enough to just encode your audio with tools like ffmpeg. Luckily there’s a python script named Opus2Tonie which automates the process and in this short guide I’ll post installation instructions how to set it up on both macOS and Linux.

As a second step, instead of using this script only on the command-line, we’ll create a macOS context menu integration which let’s you convert any folder or file with a single mouse click (thx to @ingorichter). Furthermore, the file will even be uploaded to your Teddycloud library and a native system notification is shown at the end.


:small_orange_diamond: Installation instructions (macOS/Linux) :small_orange_diamond:

  1. install Homebrew (macOS-only)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  1. install ffmpeg
brew install ffmpeg / sudo apt install ffmpeg -y
  1. install opus-tools
brew install opus-tools / sudo apt install opus-tools
  1. install python
brew install python@3.13 / sudo apt install python3.13
  1. setup python3
cd
python3 -m venv .venv
source .venv/bin/activate
  1. install google protobuf
pip3 install google
pip3 install protobuf
pip3 install google-api-core
  1. checkout opus2tonie & patch header
git clone https://github.com/bailli/opus2tonie.git
cd opus2tonie
curl -LOs "https://github.com/bailli/opus2tonie/files/12489794/tonie_header_pb2.py.txt" && mv tonie_header_pb2.py.txt tonie_header_pb2.py

Now you can use opus2tonie.py on the command-line and it should successfully convert any audio file to taf. Until this point, this should work both on macOS and Linux:

python3 opus2tonie.py [INPUT_FILE.mp3] [OUTPUT_FILE.taf]

The input can also be a directory. In this case, all files inside the directory will be merged into one single taf file:

python3 opus2tonie.py [INPUT_DIR] [OUTPUT_FILE.taf]


:small_orange_diamond: Automator Quick Action (macOS-only) :small_orange_diamond:

In order to trigger the encoding and Teddycloud upload with a simple right click on your mouse in Finder, we have to set up a new Automator quick action which triggers a shell script.

  1. open the Automator app
  2. select “new document” → Quick action
  3. at the top set “Workflow accepts Files and Folders in Finder”
  4. type “shell” in the search field and select the “run shell script” action
  5. paste the following content, change ip address & opus2tonie-path (line 1 & 2)
  6. save the Quck Action → Name will be the name in your context menu
TEDDYCLOUD_IP=192.168.178.11
OPUS_2_TONIE_PATH=/Users/marco/dev/toniebox/opus2tonie

export PATH=/opt/homebrew/bin:$PATH
source ~/.venv/bin/activate
OUTPUT_FILE="${@%.*}.taf"
BASE_NAME=$(basename ${OUTPUT_FILE})

python3 $OPUS_2_TONIE_PATH/opus2tonie.py "$@" $OUTPUT_FILE > $OPUS_2_TONIE_PATH/log.txt 2>&1

if curl -F "file=@$OUTPUT_FILE" "http://$TEDDYCLOUD_IP/api/fileUpload?path=&special=library"; then
	osascript -e "display notification \"Die Datei $BASE_NAME befindet sich jetzt in deiner Teddycloud\" with title \"Upload erfolgreich\"" & 
else
	osascript -e 'display notification "Etwas ist schief gelaufen mit deinem Datei-Upload. Bitte überprüfe die Logs." with title "Teddycloud Fehler"'
fi

In Automator it should look like this:

Happy transcoding!

1 Like

:small_orange_diamond: Bug :small_orange_diamond:

When using a directoriy as input, the opus2tonie.py script is considering every possible filetype as a valid candidate, also non-audio types (like jpg for example). This leads to corrupt/unplayable TAF files for example if there’s a cover.jpg in the same directory.

:small_orange_diamond: Fix :small_orange_diamond:

In opus2tonie.py, change the filter_directories function in line 927 and filter only for audio file extensions like this:

def filter_directories(glob_list):
    result = []
    EXTENTIONS = {".mp3", ".mp2", ".m4a", ".m4b", ".opus", ".ogg", ".wav", ".aac", ".mp4"}
    for name in glob_list:
        ext = os.path.splitext(name)[1]
        if os.path.isfile(name) and ext in EXTENTIONS:
            result.append(name)
    return result

:small_orange_diamond: Batch Encode :small_orange_diamond:

If you have opus2tonie.py running on your local machine, here’s a shell script which

  • batch converts all subfolders to taf
  • uploads the resulting taf to Teddycloud (optional)
  • deletes the taf on your PC/Mac (optional)

:small_orange_diamond: How it works :small_orange_diamond:

Imagine the following file structure: An audiobook series (Sandmann) with many episodes and different tracks for each episode.

The script iterates over all subfolders and creates one taf audio file with chapters for each episode.

Result:

  • Sandmann - Episode 01 - Gute Nacht.taf
  • Sandmann - Episode 02 - Schlaf gut.taf
  • Sandmann - Episode 03 - Träume süß.taf
  • Sandmann - Episode 04 - Bis zum Morgen.taf

:small_orange_diamond: Shell Script (Linux/macOS) :small_orange_diamond:

Please save this script in a file (batch-covert.sh), make it executable (chmod +x batch-convert.sh) and adapt the first two lines according to your setup.

Source Code:

#!/bin/bash
TEDDYCLOUD_IP=192.168.178.11
OPUS_2_TONIE_PATH=/Users/marco/dev/toniebox/opus2tonie

SEPARATOR="-----------------------------------------------"
echo $SEPARATOR
while [[ "$#" -gt 0 ]]; do
    case $1 in
        -s|--source) SOURCE="$2"; shift ;;
        -u|--upload) UPLOAD=1 ;;
        -c|--cleanup) CLEANUP=1 ;;
        *) echo "Unbekannter Parameter: $1"; exit 1 ;;
    esac
    shift
done

echo "Quell-Verzeichnis: $SOURCE"
echo "Upload in die Teddycloud: $UPLOAD"
echo "Lösche TAF nach Upload: $CLEANUP"
echo "Log: $OPUS_2_TONIE_PATH/log.txt"

AUDIOBOOK_SERIES=$(basename "$SOURCE")

echo $SEPARATOR
source ~/.venv/bin/activate
cd $SOURCE
for d in */ ; do
    DIRNAME=$(echo "$d" | awk -F/ '{print$1}')
    OUTPUT_FILE="${AUDIOBOOK_SERIES} - ${DIRNAME}.taf"
    echo "Aktueller Ordner: "
    echo "$DIRNAME"
    echo "Starte Transkodierung: "
    echo "${OUTPUT_FILE}..."
    python3 $OPUS_2_TONIE_PATH/opus2tonie.py "$DIRNAME" "$OUTPUT_FILE" >> $OPUS_2_TONIE_PATH/log.txt 2>&1
    if [[ $UPLOAD ]]; then
        echo "Lade Datei in die Teddycloud..."
        response_code=$(curl -s -o /dev/null -F "file=@$OUTPUT_FILE" -w "%{http_code}" "http://$TEDDYCLOUD_IP/api/fileUpload?path=&special=library")
        if [ "${response_code}" != 200 ]; then 
            echo "Fehler beim Upload, Datei wird nicht gelöscht."
            echo $SEPARATOR
            continue
        fi
    fi
    if [[ $CLEANUP ]]; then
        echo "Lösche taf Datei..."
        rm "$OUTPUT_FILE"
    fi
    echo "Fertig!"
    echo $SEPARATOR
done

The script accepts the following input parameters:

  • -s source-directory~/audiobooks/Sandmann in this case (mandatory)
  • -u → upload to Teddycloud (optional)
  • -c→ cleanup/delete taf file (optional)

Example call:

./batch-convert.sh -s /Users/marco/audiobooks/Sandmann -u -c