Creating an m3u playlist with relative paths using Bash, Powershell or Python

Most of the media player software lack the option to create playlists with relative paths. Absolute paths make it difficult to move and organize media folders without breaking playlists. Let us see how to create playlists with relative paths using Bash, Powershell, and Python.

I am not sure how many media players support relative paths in a playlist. The three which I tested: VLC, Media Player Classic, and mpv seem to support it.

M3u does not have a format specification, it is more of a de-facto standard. A playlist that works in one player might not work in another player. One major limitation of m3u is that many players expect m3u files to have ASCII encoding.

A note for mpv users

If you are an mpv user like me, you would know that mpv’s support for m3u playlists is limited to very basic playback support. If you want to use playlists with mpv, do take a look at the mpv plugin Mpv-Playlistmanager. It allows you to create and manage playlists from inside mpv. It is a must-have plugin for mpv users in my opinion.

Using PowerShell

In the PowerShell console, CD into the directory containing the media files and run the following command.

gci -recurse | where {$_.extension -eq '.mp4' -Or $_.extension -eq '.mp3'} | Resolve-Path -Relative | Out-File -Encoding UTF8 -FilePath '.\playlist.m3u'

It creates a playlist named playlist.m3u in the current directory. The playlist recursively includes all mp3 files and mp4 files in the current directory. Adjust the file type filter and the name of the playlist as required. If you are not interested in relative paths and just want to create playlists from the command line, try this instead.

gci -recurse | where {$_.extension -eq '.mp4' -Or $_.extension -eq '.mp3'} | select -ExpandProperty FUllName | Out-File -Encoding UTF8 -FilePath '.\playlist.m3u'

The select -ExpandProperty FullName option is for getting rid of the property name header.

Using Bash

CD into the media directory and type the following command:

find . -type f \( -name "*.mp4" -o -name "*.aac" -o -name "*.mp3" \) -fprint playlist.m3u

Add more extensions as required in the format -o -name “*.<extension>” . Add this is before the backslash before the closing bracket.

Since adding a lot of extensions can make the command very long, here is a regex variant:

 find . -type f -iregex ".*\.\(mp4\|mp3\|avi\|m4v\|aac\|mkv\|mov\|webm\|wmv\|flv\)" -fprint playlist.m3u

If you like to sort the playlist by name, use the following command:

find . -type f -iregex ".*\.\(mp4\|mp3\|avi\|m4v\|aac\|mkv\|mov\|webm\|wmv\|flv\)" | sort > playlist.m3u

Using Python

One option which I would have liked my PowerShell command to have is a natural sort option. Natural Sort is a term which is only beginning to gain acceptance. In short, it means sorting roughly the same way Windows Explorer or Mac Finder sorts. Please read my article about  Natural Sort, if you like to know more. 

I was hoping that PowerShell will have built-in support for natural sort, given that Windows Explorer uses natural sort. But there is no natural-sort support in PowerShell. So I wrote a Python script for creating naturally-sorted playlists. Python does not have built-in support for natural sorting either. So I used the Natsort library.

""" Creates an m3u playlist with relative paths inside.
    Playlist is created inside the dir supplied.
    All media files inside the root dir are sorted using 'natural' sort.
"""

import argparse
from os import walk
from os import path
from natsort import natsorted


def get_file_list(root_dir_path):
    media_files = []
    for (dirpath, _dirnames, filenames) in walk(root_dir_path):
        for file in filenames:
            extension = path.splitext(file)[1]
            if extension in ['.mp4', '.mp3', '.mkv', '.aac']:
                media_files.append(path.join(dirpath, file))
    return media_files

def create_playlist(playlist_path, files):
    with open(playlist_path, 'w', encoding='utf-8') as p:
        for f in files:
            print(f, file=p)

def parse_arguments():
    """ Parses the arguments
        directory is required argument
        playlist name defaults to playlist.m3u
        sort_by_files defaults to False"""
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-p", "--play-list", default='playlist.m3u', help="playlist name")
    parser.add_argument(
        "-d", "--directory", required=True, help="root directory")
    parser.add_argument(
        '--sort-by-files',
        dest='sort_by_files',
        action='store_true',
        help='sorts list by file name')
    parser.add_argument(
        '--no-sort-by-files',
        dest='sort_by_files',
        action='store_false',
        help='disables file name based sort')
    parser.set_defaults(sort_by_files=False)
    return parser.parse_args()

def main():
    """ Entry point function """
    args = parse_arguments()
    file_list = get_file_list(args.directory)
    if args.sort_by_files:
        file_list = natsorted(file_list, path.basename)
    else:
        file_list = natsorted(file_list)
    relative_paths = [path.relpath(p, args.directory) for p in file_list]
    print(*relative_paths, sep='\n')
    create_playlist(path.join(args.directory, args.play_list), relative_paths)

if __name__ == '__main__':
    main()

You can run the script like so:

python create_m3u_playlist.py -d "C:\Users\admin\Music" -p my_playlist.m3u
Parameters Description
-d The directory containing media files.
-p Playlist name; do not specify the full path.
–sort-by-files Boolean switch; no arguments needed. Sorts playlist by filename; the default is sort by relative path.
-h Show help

The playlist is created in the directory specified by the -d parameter. By default, the playlist is sorted by full path. To sort by file name only, use the –sort-by-files switch without arguments.

4 thoughts on “Creating an m3u playlist with relative paths using Bash, Powershell or Python”

    1. Sorry about the very delayed reply, I had not logged into the site in the last two months. I am afraid I don’t have an actual solution. M3u playlists need to be in ASCII format, hence the -Encoding ASCII switch in the command. The error was probably caused by non-ASCII characters.
      M3u8 supports Unicode but it is a more complex format and I am not sure if it supports relative paths. I will add a mention of the M3u limitation to the article for the benefit of future visitors.

  1. Great Python script!
    I’m using it to create a whole playlist of my nextcloud collection.
    Given it is ~100GB, it takes a while (specially trough davfs).
    It would be great if the script contains an option to only add to the playlist the recent files (ie new ones since last creation).
    Thanks!

    1. I am not familiar with Nextcloud, but if it supports bash commands, something like this might work. For updating the playlist type the following command from inside the media folder:
      find . -newer playlist.m3u -type f -regex ".*\.\(mp4\|mp3\|avi\|m4v\|aac\|mkv\|mov\|webm\|wmv\|flv\)" >> playlist.m3u
      Do note that this would miss older files that you newly move into your media folder.

Leave a Comment

Your email address will not be published. Required fields are marked *