Importing M3U playlists into Volumio

All,

I had the challenge to import my M3U playlists into volumio. I couldn’t find anything that worked for me so I tried to create a bash script which would do the trick.
It does what it should do … but not very fast, but I don’t care as long as it does what it should do. See it as a first version. If you are not happy with it (and you have some programming skills) modify my script and enjoy!

As I said, it’s not very fast but it supports utf-8 but supports only simple M3U files, meaning the header, so it identifies itself as a M3U playlist, followed by full paths to the track.
I run it on my volumio on a Pi. I advise, at least for the first time, you run it with the -d option so you get debug info and feedback that it is doing something LOL.

Finally; what it doesn’t do is albumcover … I haven’t figured out how to do that. You have an idea! Update my code and upload it!

Here is the code, pretty well documented, copy it into a file (m3u2volumio) on your volumio instance, make it executable and have fun!

Feedback always appreciated!

Cheers,
Arjan

#!/bin/bash
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

MY_NAME=$(basename "${0}")
MYVERSION=1

#
# For the real devlopers .. Yes this script is not performant, can be written better BUT it does what it should do :)
#
# It has by default a debug() en debug-n() function to help debugging and be verbose. However it does
# hit a performance penaulty. If that's to much ..... then change every line holding 
#
#   "debug " into "#debug "
#
#  and
#
#   "debug-n " into "#debug-n "
#
# First how does a playlist within volumio (found in /data/playlists) look like.
# Every playlist is build out of a number of entries. An example entry is (in real life it is one line
# split it up for readability):
# 
#  {"service":"mpd",
#   "uri":"mnt/NAS/Muziek/0-9/10CC/1973 - 10cc/03 - Donna.flac",
#   "title":"Donna",
#   "artist":"10CC",
#   "album":"10cc",
#   "albumart":"/albumart?cacheid=795&web=10CC/10cc/extralarge&path=%2Fmnt%2FNAS%2FMuziek%2F0-9%2F10CC%2F1973%20-%2010cc&icon=fa-tags&metadata=false"}
#
# A volumio Playlist will become
#
#   [entry1, entry2, ...., entryN]
#
# Explanation how the script does it's trick
# ------------------------------------------
# The input playlist has a basedir path which can be different from what volumio expects. How ....
#
# In my setup I have a NAS (called nas542) where all (audio) files reside. Those files are controlled/edited by my PC.
# On my PC I have mounted the audio files on /Media/audio. Therefore all the playlist I create will have tracks starting with /Media/audio.
# The playlist I have created are actually stored somewhere under /Media/audio (to be precise /Media/audio/Playlist/arjan).
#
# Example of a track in one of my (M3U) playlists: 
#
#  /Media/audio/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# In volumio I have configured that music can be found on a networkdisk \\nas542\audio and gave it the alias Muziek
# Volumio has mounted this  \\nas542\audio on /mnt/NAS/Muziek (this is controlled by volumio based on how you configured it in the UI). 
# Therefore the example track I showed above, is in the context of volumio: 
#
#  /mnt/NAS/Muziek/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# After I created a playlist via volumio in volumo, I examed the created playlist (located in /data/playlist). It turns out that the path to the track
# in the volumo playlist is: 
#
#  mnt/NAS/Muziek/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# It has removed the first / 
#
# Therefore the track in the m3u playlist:
#
#  /Media/audio/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# becomes in the playlist on volumio:
#
#  mnt/NAS/Muziek/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# The script needs to make this translation
#
# Let's go!
#


#
# Directories and settings, all from the perspective of the system
# where you run this tool
# 
# The location where the input M3U playlists kan be found
M3UTOPDIR=/mnt/NAS/Muziek/Playlists
# All tracks in the input M3U file start with the following base path
INPUTBASE=/Media/audio
# The paht of all tracks in the in the volumio playlist  should start with this
PLAYLISTBASE=mnt/NAS/Muziek
# The location where all the M3U input playlist can be found.
# This can be a directory structure but it is assumed that all
# M3U playlist have NOT the same filename
VOLUMIOPLAYLISTDIR=/data/playlist
# Location of the albumart folder within volumio
ALBUMARTPATH=/data

#
#
#
#  Changes below this ..... know what your are doing
#
#
#
#

# A temporary file
ALLM3U=/tmp/allm3u$$.txt
# File for error messages
ERROR=/tmp/error$$.txt
# It is now ...
NOW=$(date +%F:%T)
# Is the first line with a trick in the M3U file coming
FIRSTLINECOMING=false
# Do we want debug messages
DEBUG=false
# All trakcs in utf-8
TRACKSUTF8="/tmp/tracks-utf8-$$.txt"

touch "${ERROR}"

#
# Help functions
#
show_help() {
   echo "This tool imports .m3u files into Volumio playlists"
   echo " "
   echo "Options:"
   echo "  -h  Show help"
   echo "  -v  Show version"
   echo "  -d  Enable debug mode"
   echo "  -M  Directory with M3U playlists (default: ${M3UTOPDIR})"
   echo "  -V  Directory for Volumio playlists (default: ${VOLUMIOPLAYLISTDIR})"
   echo "  -I  Base dir of input files (default: ${INPUTBASE})"
   echo "  -P  Base dir for Volumio playlist entries (default: ${PLAYLISTBASE})"
}

#
#  error message 
#
show_error() {
    echo "Unknown option or incorrect command line."
    echo "Use -h to get help."
}

#
#   debug funtions
#
debug() {
    if ${DEBUG}; then
        echo "${1}"
    fi
}
debug-n() {
    if ${DEBUG}; then
        echo -n "${1}"
    fi
}

#
# Escape JSON‑unsafe characters
#
json_escape() {
    echo -n "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}

#
# Convert metadata safely to UTF‑8
#
safe_utf8() {
    iconv -f UTF-8 -t UTF-8//TRANSLIT 2>/dev/null <<< "$1"
}

#
# Write a playlist entry
#
write_entry() {
    echo -n "{\"service\":\"mpd\",\"uri\":\"${URI}\"," >> "${OUTPUT}"

    # Title
    if [[ ${URI} == *.flac ]]; then
        TITLE=$(metaflac --show-tag=title "/${URI}" 2>>"${ERROR}" | cut -d= -f2)
    elif [[ ${URI} == *.mp3 ]]; then
        TITLE=$(id3v2 --list "/${URI}" 2>>"${ERROR}" | grep ^TIT2 | sed 's/^TIT2 (.*): //')
    fi
    TITLE=$(safe_utf8 "${TITLE}")
    TITLE=$(json_escape "${TITLE:-TBD}")
    echo -n "\"title\":\"${TITLE}\"," >> "${OUTPUT}"

    # Artist
    if [[ ${URI} == *.flac ]]; then
        ARTIST=$(metaflac --show-tag=artist "/${URI}" 2>>"${ERROR}" | cut -d= -f2)
    elif [[ ${URI} == *.mp3 ]]; then
        ARTIST=$(id3v2 --list "/${URI}" 2>>"${ERROR}" | grep ^TPE1 | sed 's/^TPE1 (.*): //')
    fi
    ARTIST=$(safe_utf8 "${ARTIST}")
    ARTIST=$(json_escape "${ARTIST:-TBD}")
    echo -n "\"artist\":\"${ARTIST}\"," >> "${OUTPUT}"

    # Album
    if [[ ${URI} == *.flac ]]; then
        ALBUM=$(metaflac --show-tag=album "/${URI}" 2>>"${ERROR}" | cut -d= -f2)
    elif [[ ${URI} == *.mp3 ]]; then
        ALBUM=$(id3v2 --list "/${URI}" 2>>"${ERROR}" | grep ^TALB | sed 's/^TALB (.*): //')
    fi
    ALBUM=$(safe_utf8 "${ALBUM}")
    ALBUM=$(json_escape "${ALBUM:-TBD}")
    echo -n "\"album\":\"${ALBUM}\"," >> "${OUTPUT}"

    # Albumcover placeholder
    #
    # I haven't figured out how to do this ....
    #
    echo -n "\"albumcover\":\"TBD\"}" >> "${OUTPUT}"
}

#
# Command‑line parsing
#

PARSED_ARGUMENTS=$(getopt -n m3u2volumio -o dvhM:V:I:P: -- "$@")
VALID_ARGUMENTS=$?
if [ "$VALID_ARGUMENTS" != "0" ]; then
{
  show_error
  exit 1
}
fi

eval set -- "$PARSED_ARGUMENTS"
while true
do
    case "$1" in
        -M) M3UTOPDIR="${2}"; shift 2 ;;
        -V) VOLUMIOPLAYLISTDIR="${2}"; shift 2 ;;
        -I) INPUTBASE="${2}"; shift 2 ;;
        -P) PLAYLISTBASE="${2}"; shift 2 ;;
        -d) DEBUG=true; shift ;;
        -v) echo "Version: ${MYVERSION}"; exit 0 ;;
        -h) show_help; exit 0 ;;
        --) shift; break ;;
        *) show_error; exit 3 ;;
    esac
done

#
# Directory checks
#
for d in "${M3UTOPDIR}" "${VOLUMIOPLAYLISTDIR}"
do
    if [ ! -d "${d}" ]; then
        echo "${d} does not exist"
        exit 4
    fi
done

#
# find all playlist, they could be in a directory structure
# It is assumed that every m3u playlist has a unique filename
#
find "${M3UTOPDIR}" -type f -name "*.m3u" > "${ALLM3U}"

#
# Process each M3U file
#
while read -r M3U
do
    debug  "Processing file ${M3U} with appr. $(wc -l < "${M3U}") tracks"

    # Convert to guaranteed UTF‑8
    if ! iconv -f UTF-8 -t UTF-8 "${M3U}" > "${TRACKSUTF8}" 2>>"${ERROR}"; then
    {
        debug "Invalid UTF‑8 in ${M3U}, skipping"
        echo "Invalid UTF‑8 in ${M3U}, skipping" >> "${ERROR}"
        continue
    }
    fi

    #
    # Just started with a new M3U file, we expect the first line with
    # a track is still coming.
    # Once whe have that it becomes 'false'
    FIRSTLINECOMING=true

    while read -r LINE
    do
        debug-n "."

        if [ "${LINE}" = "#EXTM3U" ]; then
        {
            # First line of a M3U, create a ne output file 
            OUTPUT="${VOLUMIOPLAYLISTDIR}/$(basename "${M3U}") | sed -e 's/.m3u//' "

            #
            # Does this output file already exist
            #
            if [ -f "${OUTPUT}" ]; then
            {
                debug " "
                debug "Backup original ${OUTPUT}"
                mv "${OUTPUT}" "${OUTPUT}-${NOW}"
            }
            fi

            #
            # Create the OUTPUT file and put the first char into it
            #
            echo -n '[' > "${OUTPUT}"
            # Read next line
            continue
        }
        fi

        TRACK=$(echo "${LINE}" | sed -e "s#${INPUTBASE}#${PLAYLISTBASE}#")

        if [ -f "/${TRACK}" ]; then

            URI="${TRACK}"

            if ${FIRSTLINECOMING}; then
                FIRSTLINECOMING=false
            else
                echo -n ',' >> "${OUTPUT}"
            fi

            write_entry

        else
        {
            debug " "
            debug "Track does not exist therefore skipped: /${TRACK}"
            echo "Track does not exist therefore skipped: /${TRACK} in ${M3U}" >> "${ERROR}"
        }
        fi

    done < "${TRACKSUTF8}"

    debug "Done"

    echo -n ']' >> "${OUTPUT}"
    rm -f "${TRACKSUTF8}"

done < "${ALLM3U}"

# Show errors if any
if [ "$(wc -l < "${ERROR}")" -ne 0 ]; then
    echo "Found these errors:"
    cat "${ERROR}"
fi

rm -f "${ALLM3U}" "${ERROR}"

2 Likes

All,

I could do with some help :wink: I am working on a script which imports a .m3u playlist into volumio https://community.volumio.com/t/importing-m3u-playlists-into-volumio/75355 but I am struggling with the albumart.

I try to have volumio extract the albumart out of the file. I used Google to get the syntax, I desperately asked CoPilot … but nothing gives me the desired output: a nice image of the album/song.

Any of the goeroes who can help me with this struggle? I tried the two examples below and many variations :slight_smile:

“/albumart?path=%2fmnt%2fNAS%2fMuziek%2fA%2fAbba%2f1981%20The%20Visi
tors%2f01%20-%20The%20Visitors.flac”

“/albumart?path=%2fmnt%2fNAS%2fMuziek%2fA%2fAbba%2f1981%20The%20Visi
tors%2f01%20-%20The%20Visitors.flac%26metadata%3dtrue”

All tips and trics appreciated!
Arjan

you might take a look at this code:|

It might give you some ideas, however it hasn’t been maintained in the last 5 years

@Wheaten Thanks. I already looked at this one. But it doesn’t write the albumart. In index.js at line 433-440 the track is written into a volumio playlist. It only writes title and artist.

But thanks for pointing out!
Arjan

Try this:
(cacheid I just used a random number)

"/albumart?cacheid=1005&path=%2fmnt%2fNAS%2fMuziek%2fA%2fAbba%2f1981%20The%20Visi
tors%2f01%20-%20The%20Visitors&icon=fa-tags&metadata=false",

I also found “cacheid=”, never tried it because I don’t know the cacheid.
I just edited in the file manually … no succes. Do you have any idea how to get that cacheid for a track?

I also looked at what the field albumart looks like when I create a playlist within volumio:

“albumart”:“/albumart?cacheid=796&web=Electric%20Light%20Orchestra/Time/extralarge&path=%2Fmnt%2FNAS%2FMuziek%2FE%2FElectric%20Light%20Orchestra%2F1981%20Time&icon=fa-tags&metadata=false”

I can’t figure out (yet) how I get the cacheid. The web= is set within volumio. But I don’t want ot use that because I have set it to “extralarge” but other users will have something different. The scirpt should not only work for me :slight_smile:
It also points to a directory. In that directory I have an album cover (cover.jpg or cover.png), but does everybody have that?
icon=fa-tags, no clue what that means and what I have found using google is that metadata=false means it needs to pick the albumart form the directory. If metadata=true it should get it from the metadata inside the file.

Unfortunately I don’t.
I did this with one of the playlists and saved it with a new name.
Rebooted the device and it worked.

Don’t use metadata=true, Volumio won’t read into files
icon=fa-tags is needed to fall back to an icon

All,
Here is the script including albumart. Please read the comment inside the script, that’s where the documentation is :smiley:
Enjoy!!
Arjan

#!/bin/bash
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

MY_NAME=$(basename "${0}")
MYVERSION=2

#
# For the real devlopers .. Yes this script is not performant, can be written better BUT it does what it should do :)
#
# It has by default a debug() en debug-n() function to help debugging and be verbose. However it does
# hit a performance penaulty. If that's to much ..... then change every line holding 
#
#   "debug " into "#debug "
#
#  and
#
#   "debug-n " into "#debug-n "
#
# First how does a playlist within volumio (found in /data/playlists) look like.
# Every playlist is build out of a number of entries. An example entry is (in real life it is one line
# split it up for readability):
# 
#  {"service":"mpd",
#   "uri":"mnt/NAS/Muziek/0-9/10CC/1973 - 10cc/03 - Donna.flac",
#   "title":"Donna",
#   "artist":"10CC",
#   "album":"10cc",
#   "albumart":"/albumart?cacheid=795&web=10CC/10cc/extralarge&path=%2Fmnt%2FNAS%2FMuziek%2F0-9%2F10CC%2F1973%20-%2010cc&icon=fa-tags&metadata=false"}
#
# A volumio Playlist will become
#
#   [entry1, entry2, ...., entryN]
#
# Explanation how the script does it's trick
# ------------------------------------------
# The input playlist has a basedir path which can be different from what volumio expects. How .... ???
#
# In my setup I have a NAS (called nas542) where all (audio) files reside. Those files are controlled/edited by my PC.
# On my PC I have mounted the audio files on /Media/audio. Therefore all the playlist I create will have tracks starting with /Media/audio.
# The playlist I have created are actually stored somewhere under /Media/audio (to be precise /Media/audio/Playlist/arjan) and therefore they
# are accessable by every device who has access to the my NAS.
#
# Example of a track in one of my (M3U) playlists: 
#
#  /Media/audio/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# In volumio I have configured that music can be found on a networkdisk \\nas542\audio and gave it the alias Muziek
# 
# Volumio has mounted this  \\nas542\audio on /mnt/NAS/Muziek (this is controlled by volumio based on how you configured it in the UI). 
# Therefore the example track I showed above, is in the context of volumio: 
#
#  /mnt/NAS/Muziek/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# After I created a playlist via volumio in volumio, I examed the created playlist (located in /data/playlist). It turns out that the path to the track
# in the volumo playlist is: 
#
#  mnt/NAS/Muziek/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# It has removed the first / 
#
# Therefore the track in the m3u playlist:
#
#  /Media/audio/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# becomes in the playlist on volumio:
#
#  mnt/NAS/Muziek/W/Who/1973 Quadrophenia/02.-The Real Me.flac
#
# The script needs to make this translation
#
# Let's go!
#


#
# Directories and settings, all from the perspective of the volumio system
# where you run this tool (in my case a Pi)
# 
# The top location where the input M3U playlists can be found (could be in a dir structure)
# It is assumed that all files have unique names
M3UTOPDIR=/mnt/NAS/Muziek/Playlists
# All tracks in the input M3U file start with the following base path
INPUTBASE=/Media/audio
# The paht of all tracks in the in the volumio playlist should start with this
PLAYLISTBASE=mnt/NAS/Muziek
# Location where volumio stores its playlists
VOLUMIOPLAYLISTDIR=/data/playlist
# Location of the albumart folder within volumio
ALBUMARTPATH=/data

#
#
#
#  Changes below this ..... know what your are doing
#
#
#
#

# A temporary file
ALLM3U=/tmp/allm3u$$.txt
# File for error messages
ERROR=/tmp/error$$.txt
# It is now ...
NOW=$(date +%F:%T)
# Is the first line with a trick in the M3U file coming?
FIRSTLINECOMING=false
# Do we want debug messages
DEBUG=false
# Do we want to backup volumio playlists
BACKUP=false
# All trakcs in utf-8
TRACKSUTF8="/tmp/tracks-utf8-$$.txt"

touch "${ERROR}"

#
# Help functions
#
show_help() {
   echo "This tool imports .m3u files into Volumio playlists"
   echo " "
   echo "Options:"
   echo "  -h  Show help"
   echo "  -v  Show version"
   echo "  -d  Enable debug mode"
   echo "  -b  If volumio playlist exists make a backup"
   echo "  -M  Directory with M3U playlists (default: ${M3UTOPDIR})"
   echo "  -V  Directory for Volumio playlists (default: ${VOLUMIOPLAYLISTDIR})"
   echo "  -I  Base dir of input files (default: ${INPUTBASE})"
   echo "  -P  Base dir for Volumio playlist entries (default: ${PLAYLISTBASE})"
}

#
#  error message 
#
show_error() {
    echo "Unknown option or incorrect command line."
    echo "Use -h to get help."
}

#
#   debug funtions
#
debug() {
    if ${DEBUG}; then
        echo "${1}"
    fi
}
debug-n() {
    if ${DEBUG}; then
        echo -n "${1}"
    fi
}

#
# Escape JSON‑unsafe characters
#
json_escape() {
    echo -n "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}

#
# Convert metadata safely to UTF‑8
#
safe_utf8() {
    iconv -f UTF-8 -t UTF-8//TRANSLIT 2>/dev/null <<< "$1"
}

#
# text 2 url encoded text
#
urlencode() {
    local string="${1}"
    local strlen=${#string}
    local encoded=""
    local pos c o

    for (( pos=0 ; pos<strlen ; pos++ )); do
        c=${string:$pos:1}
        case "$c" in
            [-_.~a-zA-Z0-9] ) o="${c}" ;;
            * )               printf -v o '%%%02x' "'$c" 
        esac
        encoded+="${o}"
    done
    echo "${encoded}"
}

#
# Write a playlist entry
#
write_entry() {
    echo -n "{\"service\":\"mpd\",\"uri\":\"${URI}\"," >> "${OUTPUT}"

    #
    # Title
    #
    if [[ ${URI} == *.flac ]]; then
        TITLE=$(metaflac --show-tag=title "/${URI}" 2>>"${ERROR}" | cut -d= -f2)
    elif [[ ${URI} == *.mp3 ]]; then
        TITLE=$(id3v2 --list "/${URI}" 2>>"${ERROR}" | grep ^TIT2 | sed 's/^TIT2 (.*): //')
    fi
    TITLE=$(safe_utf8 "${TITLE}")
    TITLE=$(json_escape "${TITLE:-TBD}")
    echo -n "\"title\":\"${TITLE}\"," >> "${OUTPUT}"

    #
    # Artist
    #
    if [[ ${URI} == *.flac ]]; then
        ARTIST=$(metaflac --show-tag=artist "/${URI}" 2>>"${ERROR}" | cut -d= -f2)
    elif [[ ${URI} == *.mp3 ]]; then
        ARTIST=$(id3v2 --list "/${URI}" 2>>"${ERROR}" | grep ^TPE1 | sed 's/^TPE1 (.*): //')
    fi
    ARTIST=$(safe_utf8 "${ARTIST}")
    ARTIST=$(json_escape "${ARTIST:-TBD}")
    echo -n "\"artist\":\"${ARTIST}\"," >> "${OUTPUT}"

    #
    # Album
    #
    if [[ ${URI} == *.flac ]]; then
        ALBUM=$(metaflac --show-tag=album "/${URI}" 2>>"${ERROR}" | cut -d= -f2)
    elif [[ ${URI} == *.mp3 ]]; then
        ALBUM=$(id3v2 --list "/${URI}" 2>>"${ERROR}" | grep ^TALB | sed 's/^TALB (.*): //')
    fi
    ALBUM=$(safe_utf8 "${ALBUM}")
    ALBUM=$(json_escape "${ALBUM:-TBD}")
    echo -n "\"album\":\"${ALBUM}\"," >> "${OUTPUT}"

    #
    # Albumart
    #
    # In my environment every track has albumart inside it, and every folder
    # has a cover.png or a cover.jpg file in it (next to the tracks)
    #
    
    #
    # If you don't want albumart then use next line
    #
    # echo -n "\"albumcover\":\"TBD\"}" >> "${OUTPUT}"
    
    #
    # Get the image from the metadata inside the file (as I have been told)
    #
    # NOTE: This one gives you the default volumio image. You get albumart but not
    # the one you were expecting .....
    #
    #URL=$(urlencode "/${URI}&metadata=true")
    #echo -n "\"albumart\":\"/albumart?path=${URL}\"}" >> "${OUTPUT}"

    #
    # Get the image from the directory
    #
    #URL=$(urlencode "/${URI}")
    #echo -n "\"albumart\":\"/albumart?path=/${URI}&metadata=false\"}" >> "${OUTPUT}"

    #
    # Get the image from the folder where the track is located
    #
    URL=$(urlencode "/${URI}")
    echo -n "\"albumart\":\"/albumart?path=${URL}\"}" >> "${OUTPUT}"

}

#
# Command‑line parsing
#

PARSED_ARGUMENTS=$(getopt -n m3u2volumio -o bdvhM:V:I:P: -- "$@")
VALID_ARGUMENTS=$?
if [ "$VALID_ARGUMENTS" != "0" ]; then
{
  show_error
  exit 1
}
fi

eval set -- "$PARSED_ARGUMENTS"
while true
do
    case "$1" in
        -M) M3UTOPDIR="${2}"; shift 2 ;;
        -V) VOLUMIOPLAYLISTDIR="${2}"; shift 2 ;;
        -I) INPUTBASE="${2}"; shift 2 ;;
        -P) PLAYLISTBASE="${2}"; shift 2 ;;
        -b) BACKUP=true; shift ;;
        -d) DEBUG=true; shift ;;
        -v) echo "Version: ${MYVERSION}"; exit 0 ;;
        -h) show_help; exit 0 ;;
        --) shift; break ;;
        *) show_error; exit 3 ;;
    esac
done

#
# Directory checks
#
for d in "${M3UTOPDIR}" "${VOLUMIOPLAYLISTDIR}"
do
    if [ ! -d "${d}" ]; then
        echo "${d} does not exist"
        exit 4
    fi
done

#
# find all playlist, they could be in a directory structure
# It is assumed that every m3u playlist has a unique filename
#
find "${M3UTOPDIR}" -type f -name "*.m3u" > "${ALLM3U}"

#
# Process each M3U file
#
while read -r M3U
do
    debug  "Processing file ${M3U} with appr. $(wc -l < "${M3U}") tracks"

    # Convert to guaranteed UTF‑8
    if ! iconv -f UTF-8 -t UTF-8 "${M3U}" > "${TRACKSUTF8}" 2>>"${ERROR}"; then
    {
        debug "Invalid UTF‑8 in ${M3U}, skipping"
        echo "Invalid UTF‑8 in ${M3U}, skipping" >> "${ERROR}"
        continue
    }
    fi

    #
    # Just started with a new M3U file, we expect the first line with
    # a track is still coming.
    # Once whe have that it becomes 'false'
    FIRSTLINECOMING=true

    while read -r LINE
    do
        debug-n "."

        if [ "${LINE}" = "#EXTM3U" ]; then
        {
            # First line of a M3U, create a ne output file 
            OUTPUT="${VOLUMIOPLAYLISTDIR}/$(basename "${M3U}"| sed -e 's/.m3u//')"

            #
            # Does this output file already exist and do we need to backup
            #
            if [ -f "${OUTPUT}" ] &&  ${BACKUP}  ; then
            {
                debug " "
                debug "Backup original ${OUTPUT}"
                mv "${OUTPUT}" "${OUTPUT}-${NOW}"
            }
            else
            {
                debug " "
                debug "${OUTPUT} might exist but BACKUP is ${BACKUP}"
            }
            fi

            #
            # Create the OUTPUT file and put the first char into it
            #
            echo -n '[' > "${OUTPUT}"
            # Read next line
            continue
        }
        fi

        TRACK=$(echo "${LINE}" | sed -e "s#${INPUTBASE}#${PLAYLISTBASE}#")

        if [ -f "/${TRACK}" ]; then

            URI="${TRACK}"

            if ${FIRSTLINECOMING}; then
                FIRSTLINECOMING=false
            else
                echo -n ',' >> "${OUTPUT}"
            fi

            write_entry

        else
        {
            debug " "
            debug "Track does not exist therefore skipped: /${TRACK}"
            echo "Track does not exist therefore skipped: /${TRACK} in ${M3U}" >> "${ERROR}"
        }
        fi

    done < "${TRACKSUTF8}"

    debug "Done"

    echo -n ']' >> "${OUTPUT}"
    rm -f "${TRACKSUTF8}"

done < "${ALLM3U}"

# Show errors if any
if [ "$(wc -l < "${ERROR}")" -ne 0 ]; then
    echo "Found these errors:"
    cat "${ERROR}"
fi

rm -f "${ALLM3U}" "${ERROR}" "${TRACKSUTF8}"

1 Like

Great.
I was working on something like this but in a much simpler version: Another Windows playlists importer

I’m reading your one and was wondering: my converter is for importing .m3u8 from a windows hard disk path. Thus the initial path of every entry is example "D:\Music". Can your one be amended to do the same?

Thank you