Bash script "Automated TAF to MP3 converter"

Hello Guys,

i want to share my script, which automaticaly converts all TAFs from TeddyCloud library to fully tagged MP3 albums.
grafik


My personal use is to have all my tonies e.g. on my cars hifi system, too:

Features

  • The script converts only new TAFs. → Already converted are skipped.
  • Full ID3v2.3 tag information parsed from latest ‘tonies.json’.

What it does?

  • Download latest ‘tonies.json’ for ID3v2.3 tag information.
  • Process all TAFs from SOURCE directory.
  • Download cover image, crop, square, resize and add boarder.
  • Parse ‘tonies.json’ for full ID3v2.3 tag information
    • Performer
    • Album
    • Track name (from tonies.json if present!)
    • TrackNr./TrackTotal
    • Disc/DiscTotal
    • Genre
    • Comment includes Model
    • Cover (front)
  • Convert to single or SPLIT mp3 file. Default is split mode.
  • Check for already converted TAFs

How to use it?

  1. Create new file and copy code.
  2. Grant file execution permissions
  3. Adjust the follwoing variables to your setup:
    SOURCE → TeddyCloud library directory, e.g. “/teddycloud/library/by/audioID/”
    TARGET → directory of your choise, e.g. “/home/user/export/”
    OPUS2TONIE → path to Opus2Tonie python script which is used to extract the AudioID from TAF
    TMP → local temp folter to process data. Will be deleted afterwards.

Prerequisites

  • jq to parse JSON data on bash
  • ffmpeg to convert TAF to mp3 and add cover
  • imagemagic to process image for perfect fit
  • Opus2Tonie must be downloaded. It requires Python3
  • Opus2Tonie requires python3-protobuf to decoce protobuf.
    please install it via pip and/or apt

Bash script

./_convert.sh

#!/bin/bash

VERSION=1.1
FORCE=0
SPLIT=1
TEST=0
OPUS2TONIE="/srv/Transfer/TAF_CONVERTER/opus2tonie-main/opus2tonie.py"
SOURCE="/teddycloud/library/by/audioID/"
TARGET="/srv/Transfer/TAF_CONVERTER/Target/"
TMP="./.tmp"
TONIES_JSON_URL="https://raw.githubusercontent.com/toniebox-reverse-engineering/tonies-json/release/tonies.json"
TONIES_JSON="tonies.json"

echo
echo -e "\e[1mAutomated TAF to MP3 converter v$VERSION\e[0m"
echo

usage()
{
  echo "Usage: ${0##*/} [-h] [-t] [-f] [-s]"
  echo
  echo "  -t  Test Mode. Do not modify filesystem."
  echo "  -f  FORCE. Remove already converted. Usefull to update ID3 tags."
  echo "  -s  SINLGE. Create sinlge MP3 instead of split variant."
  echo
  exit 0
}


# Init
[ ! -d $TMP ] && mkdir $TMP
[ ! -d $SOURCE ] && exit 101
[ ! -d $TARGET ] && exit 102

while getopts "h?sft" opt; do
  case "$opt" in
    t )
      TEST=1
      ;;
    f )
      FORCE=1
      ;;
    s )
      SPLIT=0
      ;;
    h|\?|* )
        usage
        ;;
  esac
done

# Download latest tonies.json
pushd $TMP > /dev/null 2>&1
curl -s -o $TONIES_JSON $TONIES_JSON_URL
popd > /dev/null 2>&1


for TAF in $SOURCE*.taf; do
  echo Processing file \"$TAF\"...
  # Extraxt AUDIO_ID from filename
  AUDIO_ID="$(basename $TAF .taf)"
  # Get ID3 Tag info from tonies.json using AUDIO_ID
  pushd $TMP > /dev/null 2>&1
  ID3_COMPOSER="tonies®"
  ID3_GENRE="Tonies"
  ID3_PART_POSITION="1"
  ID3_PART_TOTAL="1"
  ID3_PERFORMER=$(cat $TONIES_JSON | jq -r ".[] | select(.audio_id[] | contains(\"$AUDIO_ID\")) | .series" | head -n1)
  ID3_ALBUM=$(cat $TONIES_JSON | jq -r ".[] | select(.audio_id[] | contains(\"$AUDIO_ID\")) | .title" | head -n1)
  ID3_TRACK_NAME=$(cat $TONIES_JSON | jq -r ".[] | select(.audio_id[] | contains(\"$AUDIO_ID\")) | .episodes" | head -n1)
  ID3_COMMENT="Tonies Hörfigur "
  ID3_COMMENT+=$(cat $TONIES_JSON | jq -r ".[] | select(.audio_id[] | contains(\"$AUDIO_ID\")) | .model" | head -n1)
  ID3_COVER=$(cat $TONIES_JSON | jq -r ".[] | select(.audio_id[] | contains(\"$AUDIO_ID\")) | .pic" | head -n1)
  ID3_TRACK_NAME_POSITION="1"
  ID3_TRACK_NAME_TOTAL="1"
  ALBUM_DIR=$(echo "$AUDIO_ID - $ID3_PERFORMER - $ID3_TRACK_NAME" | sed 's|/|, |g' | sed 's|?|?|g' | sed 's|:|꞉|g')

  # Cleanup alreay existing MP3s in case of FORCE=1
  (( $FORCE )) && rm -rf "${TARGET}/${ALBUM_DIR}"

  # Check if TAF was already converted
  if [ -f "${TARGET}${AUDIO_ID}.mp3" ] ; then
    echo -e "\t- MP3 version found: \e[33mSkip\e[0m.\n"
        continue
  elif [ -f "${TARGET}/${ALBUM_DIR}/${AUDIO_ID}.mp3" ] ; then
    echo -e "\t- MP3 version found (single): \e[33mSkip\e[0m.\n"
    continue
  elif [ -f "${TARGET}/${ALBUM_DIR}/${AUDIO_ID} - Track #1.mp3" ] ; then
    echo -e "\t- MP3 version found (split): \e[33mSkip\e[0m.\n"
    continue
  fi

  # Check if TAF exists in tonies.json
  if [ -z "$ID3_PERFORMER" ] ; then
    echo -e "\tUnknown tonie: \e[34mSkip\e[0m.\n"
        continue
  fi

  echo -e "\t- AUDIO_ID: $AUDIO_ID"
  echo -e "\t- PERFOMER: $ID3_PERFORMER"
  echo -e "\t- ALBUM: $ID3_ALBUM"
  echo -e "\t- TRACK: $ID3_TRACK_NAME"

  if [ ! -z "$ID3_COVER" ] ; then
    # Downloading cover artwork....
    echo -en "\t- Loading Cover art: "
    curl -s -o _cover.png $ID3_COVER
    convert -gravity center _cover.png -background white -alpha remove -alpha off -fuzz 1% -trim +repage -bordercolor white -border 75 -resize 750x750 -extent 750x750 -define jpeg:extent=230kb cover.jpeg
    rm _cover.png
    echo -e "\e[32mOk\e[0m."
  fi

  if [ $SPLIT -eq "0" ] ; then
    echo -en "\t- Convert TAF to single MP3: "
        # Create album folder
        TARGET2="${TARGET}${ALBUM_DIR}/"
        if [ $TEST -eq "0" ] ; then
      [ ! -d "$TARGET2" ] && mkdir "$TARGET2"
      # Remove TAF Header from OGG container
      dd if="${SOURCE}${AUDIO_ID}.taf" of="$AUDIO_ID".ogg bs=4096 skip=1 status=none
      # Convert OGG to MP3
      ffmpeg -hide_banner -loglevel panic -y -i "$AUDIO_ID.ogg"  -metadata artist="$ID3_PERFORMER" -metadata album="$ID3_ALBUM" -metadata title="$ID3_TRACK_NAME" -metadata composer="$ID3_COMPOSER" -metadata genre="$ID3_GENRE" -metadata comment="$ID3_COMMENT" -metadata track="$ID3_TRACK_NAME_POSITION/$ID3_TRACK_NAME_TOTAL" -metadata disc="$ID3_PART_POSITION/$ID3_PART_TOTAL" "$AUDIO_ID.mp3"
      # Add Cover image to MP3
      ffmpeg -hide_banner -loglevel panic -y -i "$AUDIO_ID.mp3" -i cover.jpeg -c copy -map 0 -map 1 -id3v2_version 3 -metadata:s:v title="Album cover" -metadata:s:v comment="Cover (front)" "${TARGET2}${AUDIO_ID}.mp3"
    fi
    echo -e "\e[32mOk\e[0m."
  else
    echo -en "\t- Get tracknames from JSON: "
    ID3_TRACK_NAMES=$(cat $TONIES_JSON | jq -r ".[] | select(.audio_id[] | contains(\"$AUDIO_ID\")) | .tracks" | sed 's|\[||g' | sed 's|\]||g' | tr -d '\n' | sed 's|,  |;|g' | xargs)
    IFS=';' read -a ID3_TRACK_NAMES_ARR <<< "$ID3_TRACK_NAMES"
    echo -e "\e[32mOk\e[0m."
    echo -en "\t- Convert TAF to split MP3: "
        # Create album folder
        TARGET2="${TARGET}${ALBUM_DIR}/"
        if [ $TEST -eq "0" ] ; then
      [ ! -d "$TARGET2" ] && mkdir "$TARGET2"
      # Split TAF into single OGG tracks
      rm *.opus > /dev/null 2>&1
      $OPUS2TONIE --split "${SOURCE}${AUDIO_ID}.taf" .  > /dev/null 2>&1
          ID3_TRACK_NAME_TOTAL=$(ls *.opus | wc -l)

      i=1
      for TRACK in *.opus; do
        [ -z "${ID3_TRACK_NAMES_ARR[$i-1]}" ] && ID3_TRACK_NAMES_ARR[$i-1]="Track $i"
        # Convert OPUS to MP3
        ffmpeg -hide_banner -loglevel panic -y -i "$TRACK"  -metadata artist="$ID3_PERFORMER" -metadata album="$ID3_ALBUM" -metadata title="${ID3_TRACK_NAMES_ARR[$i-1]}" -metadata composer="$ID3_COMPOSER" -metadata genre="$ID3_GENRE" -metadata comment="$ID3_COMMENT" -metadata track="$i/$ID3_TRACK_NAME_TOTAL" -metadata disc="$ID3_PART_POSITION/$ID3_PART_TOTAL" "$AUDIO_ID - Track #$i.mp3"
        # Add Cover image to MP3
        ffmpeg -hide_banner -loglevel panic -y -i "$AUDIO_ID - Track #$i.mp3" -i cover.jpeg -c copy -map 0 -map 1 -id3v2_version 3 -metadata:s:v title="Album cover" -metadata:s:v comment="Cover (front)" "${TARGET2}${AUDIO_ID} - Track #$i.mp3"
            let "i=i+1"
      done
        fi
    echo -e "\e[32mOk\e[0m."
  fi
  popd > /dev/null 2>&1
  echo
done

# Clean up
rm -rf $TMP
exit 0

Hope this wil help.

Cheers.

1 Like

Thank you so much for sharing this. I wonder if there is a straightforward way to convert single .taf files to .wav.

I installed ffmpeg and tried but I get this:

Error opening input file 1234567.taf.
Error opening input files: Invalid data found when processing input

I read somewhere that .taf files are only re-labelled opus files but changing the file to .opus does not change anything. I guess the files have somehow been tampered with to make it harder to convert them.

This is not 100% true. TAFs are specially crafter OPUS audio files with a custom 4k custom header.

see:

Yes, and you can remove the header using dd and convert it afterwards using ffmpeg as done in the script.

Hi,
i would need some help to make it run.
i created the bash file as described and now when i run:

sudo sh _convert.sh

it gives me:

-e Automated TAF to MP3 converter v1.1

_convert.sh: 130: Syntax error: redirection unexpected

and it only creates the tonies.json in the folder but nothing more.

what i did so far:

apt install jq -y 
apt install ffmpeg 
apt install imagemagick
apt install python3-protobuf

the paths in the _convert.sh are adjusted as follows:

OPUS2TONIE="opus2tonie-main/opus2tonie.py"
SOURCE="audioID/"
TARGET="Tonies2MP3/"
TMP=".tmp"

for a test it is now all in the same folder as the _convert.sh. i copied also the audioID folder with the TAFs from teddycloud.

if you run it with sudo, it may use a different shell.

can you try this:

sudo bash ./script.sh

Cheerse

thank you! now it is running.
but it stops when it comes to mkdir for each TAF. it says unable to create folder: file or folder not found
do i need to give it permissions? how?

i used i using full path. maybe is related to your relative path usage.
Can you try to set full path?

yes, it worked. now it creates the folders, but they remain empty.
the script stops again at access to '*.opus' not possible: file or folder not found

i already tried with absolute and relative paths…

the TAF is split by using the Opus2Tonie script. the resulting opus files are saved into the TMP dir.
Seems that the command $OPUS2TONIE --split "${SOURCE}${AUDIO_ID}.taf does not split the TAF

do you have a final “/” at your source and target path?

SOURCE="/teddycloud/library/by/audioID/"
TARGET="/srv/Transfer/TAF_CONVERTER/Target/"

and please try this for TMP:

TMP="./.tmp"
do you have a final “/” at your source and target path?

yes, and i also tried with TMP="./.tmp"
same result.

i removed the last line of the script rm -rf $TMP and checked the temp files. it is generated and there is a .jpg and tonies.json

then the splitting is not done. can you try this inside tmp dir? replace all var with the correct values.
check if opus2tonie returns any errror.

now it works, thank you!

you were right, opus did not get any files to work with, i changed opus again to the absolute path and it works.

You are welcome.

Hi @OliKo
Thanks for the script and possibility to get the files to another medium.

Can you help me with my error?
- AUDIO_ID: 1611138006
- PERFOMER: Winnie Puuh
- ALBUM: Winnie Puuh - Winnie Puuh auf großer Reise
- TRACK: Winnie Puuh auf großer Reise
- Loading Cover art: Ok.
- Get tracknames from JSON: Ok.
- Convert TAF to split MP3: ls: cannot access ‘*.opus’: No such file or directory
Ok.

The folder ‘1611138006 - Winnie Puuh - Winnie Puuh auf großer Reise’ under target was created but is empty.

pls see here: Bash script "Automated TAF to MP3 converter" - #10 by OliKo
and the following posts. i think you have the same issue with opus2tonie.
Check tmp dir if taf is splitted to opus files. use absolute path.

1 Like

@OliKo thanks again.

My _convert.sh looks like this:

OPUS2TONIE="/srv/Transfer/TAF_CONVERTER/opus2tonie-main/opus2tonie.py"
SOURCE="/var/lib/docker/volumes/teddy_cloud_library/_data/by/audioID/"
TARGET="/srv/Transfer/TAF_CONVERTER/Target/"
TMP="./.tmp"
...
# Clean up
# rm -rf $TMP
exit 0

paths are working:

root@docker:/# cd /srv/Transfer/TAF_CONVERTER/opus2tonie-main/
root@docker:/srv/Transfer/TAF_CONVERTER/opus2tonie-main# cd /
root@docker:/# cd /var/lib/docker/volumes/teddy_cloud_library/_data/by/audioID/
root@docker:/var/lib/docker/volumes/teddy_cloud_library/_data/by/audioID# cd /
root@docker:/# cd /srv/Transfer/TAF_CONVERTER/Target/
root@docker:/srv/Transfer/TAF_CONVERTER/Target# 

in the .tmp folder there is only the cover.jpeg & tonies.json created.
So your guess was right.
The execution of the script only leads into: >

root@docker:/srv/Transfer/TAF_CONVERTER/.tmp# $OPUS2TONIE --split "/var/lib/docker/volumes/teddy_cloud_library/_data
/by/audioID/1611138006.taf
> 
---------------
^c
root@docker:/srv/Transfer/TAF_CONVERTER/.tmp# python3 /srv/Transfer/TAF_CONVERTER/opus2tonie-main/opus2tonie.py --split "/var/lib/docker/volumes/teddy_cloud_library/_data/by/audioID/1611138006.taf
> 

I have tried to run it direct, with python3 and with python3-protobuf.
But no files are created under .tmp

What does the second command return?

1 Like

EDIT: Got it
*The line below is the return so in both cases only the „>“ is returned and the script isn’t doing anything more. *
I have to abort it

My fault … thanks is working great.
For the next dumb one. Try to execute opus2tonie.py.
My error was:
File “/srv/Transfer/TAF_CONVERTER/opus2tonie-main/opus2tonie.py”, line 14, in
import tonie_header_pb2
ModuleNotFoundError: No module named ‘tonie_header_pb2’

So git clone https://github.com/bailli/opus2tonie.git the whole thing again and it works. @OliKo Thank you

there should be any output like:

root@rasp01 /srv/Transfer/OPUS2Tonie # python opus2tonie.py --info 000.taf
[OK] SHA1 hash: 0x855AA41F8417BFD1351E331AC8FC3F42BE758991
[OK] Timestamp: [0x67373661] 2024-11-15 11:54:09
[OK] Opus data length: 14844727 bytes (~98 kbps)
[OK] Opus header OK || 2 channels || 44.1 kHz || 3627 Ogg pages
[OK] Page alignment OK and size OK

[OK] File is valid

[ii] Total runtime: 00:19:48.11
[ii] 7 Tracks:
  Track 01: 00:03:08.10
  Track 02: 00:03:01.96
  Track 03: 00:01:58.05
  Track 04: 00:03:12.39
  Track 05: 00:02:26.61
  Track 06: 00:03:35.83
  Track 07: 00:02:25.15
root@rasp01 /srv/Transfer/OPUS2Tonie # python opus2tonie.py --split 000.taf
[1/7] 01_000.opus
[2/7] 02_000.opus
[3/7] 03_000.opus
[4/7] 04_000.opus
[5/7] 05_000.opus
[6/7] 06_000.opus
[7/7] 07_000.opus
root@rasp01 /srv/Transfer/OPUS2Tonie #

pls check your python, protobuf and opus2tonie setup.
i think you need “protobuf = 4.21.12” to work with opus2tonie. But ther should be an error pointing to this direction when a newer version is used.

root@rasp01 /srv/Transfer/OPUS2Tonie # pip list | grep protobuf
protobuf                           4.21.12
types-protobuf                     3.20