Playlist Maker
#1
I really hate databasing a music collection because of the lack of control. I started liking digital music so much more once I stopped using iTunes and the iPod internal software. It's also pretty critical to not be limited to the formats that one for-profit company deems acceptable.

Most of the time, I want to browse by artist, but there are lots of things that I want to be organized in idiosyncratic ways. For example, I have trouble remembering the names of compilations, and there are many comps I think of as being more connected to the label that released them rather than their name. So even if the database does a reasonably good job of linking compilations, they're still hard for me to find (and I find it annoying to scroll through an artist list that has an entry for every artist that appears one time on one comp I have). Using a directory structure, I can keep things grouped by how I mentally map things, so when I feel like listening to the rebetika comp on Canary, I can go to the Canary Records directory and find it even if I can't remember what it's called. There are also lots of artists that have releases under different names, such as all the different variations of Sun Ra (and the Astro-Infinity Arkestra, and His Myth-Science Arkestra, and His Solar Arkestra, etc,etc) or artists who appear in lots of different bands/small ensembles (jazz people in general, but especially someone like Anthony Braxton) where I want them all grouped. Interpreting this just based on the artist tag on some duet or trio albums is not going to work because the main person I'm interested in might not be the first one credited. There are also things like the Harry Smith compilations, where his name does not appear on it at all, but that's how I think of them.

All of this is to say that I can only really use my collection when I can maintain how it's structured. Due to the size of my library, indexing for the database was taking forever, and FLAC files were unplayable until I forced it to stop indexing. Since the file browser isn't sorted, that wasn't a viable option either. I saw some good suggestions on how to create playlists, but doing it manually would take forever, and it really needed to be done recursively. To that end, I wrote something to do the work of generating and sorting playlists to keep the navigation structure and play the files in their proper order. Just to be completely transparent, I have never posted code that I've written online before. I have an art background and to say I have a dabbling experience with programming is quite generous (I think this is the first time I've done something recursive, for instance). There are probably things in the code that are wholly ignorant of best practices and would never be done by someone who actually knows what they're doing and not just furiously paging through an O'Reilly book and typing things into a search engine. I am sharing it in the hope that it will be helpful to other people. I know that by posting it here, it will be seen by people who are legitimate geniuses with software, so please be kind.

This is being shared with no guarantee it will work on your system or with your files/file structure. For reference, I am running Python 3.8.5 on MacOS 15.1. If you see anything that could be improved, please try it out and let me know.

A few notes:
1) By default, it assumes the source file is in the root directory, and that you have a /Music directory that you're reading from and a /Playlists that you're writing to.
   
2) It maintains the same recursive structure of the directories, in other words, you navigate Playlists/Artist/Album/Album.playlist, select that one file, and the album plays.

3) It sorts, but it is not perfect in its sorting:
• It will do alphabetical by capital letters, and then do alphabetical by lowercase letters, so it's best if you're consistent in how your directories are labeled (changing The dB's was a definite pain point in my directory prep...). 
• It doesn't acknowledge articles, so either drop the "The"s, edit to be ", The" at the end, or scroll to T when you want to listen to The Kinks. 
• It's not super graceful with right-to-left languages, but I found by adding extra track numbers to the beginning (or really end) of the filenames solved the issue. I'm guessing most people won't even notice this, and it could also be related to the way I've edited my Bandcamp downloads to not have artist/album info in each filename.

4) It ignores certain files and does not put them in the playlist. The one I was seeing a lot in my testing was hidden files, so it excludes anything that starts with a period. I also have a lot of directories that store cover art or informational items, so it excludes anything that ends with .jpg, .jpeg, .bmp, .png or .pdf. Anything I've ripped with XLD keeps a log, so it ignores anything that ends with .log

5) If you add items and re-run, it skips over any items that already exist. The upside of this is minimizing writes to an SD card. The downside is the Tangara software is going to list the new items at the bottom rather than where they should appear alphabetically. It does create an error log of every playlist it skips, so if you had edited something it should be possible to find if it updated or not. I did one test on moving a playlist from one directory to another, and it didn't bump the edited directory down to the bottom, so maybe that's okay. If in doubt, you can always delete all the playlists and run anew.


Code:
#playlister.py version 1.0

import os

def folder_maker (source_path, destination_path, items, i):
  sub_items=[]
 
  while i < len(items):
    sub_source_path = f'{source_path}/{items[i]}'
    sub_destination_path = f'{destination_path}/{items[i]}'
 
   
    if os.path.isdir(sub_source_path): 
      if not os.path.isdir(sub_destination_path):
        print(f'Creating directory {sub_destination_path}')
        os.mkdir (sub_destination_path)
      else:
        print('Playlist directory exists. Moving on to next item')

      next_level_items= [f for f in os.listdir(sub_source_path) if not f.startswith('.') if not f.endswith('.jpg') if not f.endswith('.jpeg') if not f.endswith('.bmp') if not f.endswith('.png') if not f.endswith('.pdf') if not f.endswith('.log')]
      next_level_items.sort()
      folder_maker (sub_source_path,sub_destination_path,next_level_items, 0)

      i=i+1
 
    if os.path.isfile(sub_source_path):           
      playlist_path ='/' + sub_source_path.lstrip(local_dir)
      sub_items.append(playlist_path)
      sub_items.sort()
     
      i=i+1

    if i is len(items) and sub_items:
      divvied_path=sub_destination_path.rsplit('/',2)
      playlist_name = f'{divvied_path[0]}/{divvied_path[1]}/{divvied_path[1]}.playlist'
     
      if not os.path.isfile(playlist_name):
        print(f'Writing Playlist for {divvied_path[1]} at\n{playlist_name}')
       
        j=0
        while j <len(sub_items):
          with open(playlist_name, 'a', encoding='utf8') as wf:
            wf.write(f'{sub_items[j]}\n')
            j=j+1
      else:
        print('Playlist File exists. See error log')
        with open(error_log, 'a', encoding='utf8') as wf:
          wf.write(f'The playlist file:\n{divvied_path[1]}/{divvied_path[1]}.playlist\nwas already in the playlist directory. It has not been updated, even if the items in the Music directory have changed\n\n')

      i=i+1
 
local_dir=os.path.dirname(os.path.realpath(__file__))

source_folder = f'{local_dir}/Music'
print(f'The source directory for music is located at:\n{source_folder}')

dest_folder = f'{local_dir}/Playlists'
print(f'The destination directory for playlists will be:\n{dest_folder}')

error_log =f'{local_dir}/error_log.txt'
print(f'Playlists that already exist will be printed in the error log\n{error_log}')

source_folder_items = [f for f in os.listdir(source_folder) if not f.startswith('.')]
source_folder_items.sort()
if source_folder_items:
  folder_maker(source_folder,dest_folder,source_folder_items,0)

print (f'\n\n\n\n\n\n\n\nThere were {len(source_folder_items)} items in {source_folder} that were indexed.')

if os.path.isfile(error_log):
  print(f'\nSome playlists already existed. They are listed in \n{error_log}')

.txt   playlister.py.txt (Size: 2.7 KB / Downloads: 3)
  Reply
#2
My hero!!!! I was planning on writing a bash script to do the same thing for the exact same reasons, so thanks for saving me the effort :^)
  Reply
#3
BTW, the official documentation page states that you can make playlists manually via a text file.

This is probably fine if you want to make a playlist with about 30 items, but this would be tedious to make a playlist with over +100 songs.

An alternative tip for the time being: if you use Auxio and you've basically dragged and dropped your music collection into your Tangara's SD card; then you can export any playlist from Auxio in the format of ".m3u" and save it onto your Tangara.
  Reply
#4
I think I had imported the ".m3u" file with the relative path option in Auxio. This wasn't the exact prefix for the file path names, but I remember I had to delete a prefix like "./..*" and replace it with "Music/*" with a text editor, as described from the "Playlists" page. (Luckily, "find and replace" will help you here.)

A side effect of having incorrect path names for a song ais that certain tracks will make Tangara suddenly stop - and if you're have a queue open with more than 2-3 songs lined up, then the music will suddenly stop. Wait long enough, and Tangara will soft crash and reboot. (I was troubleshooting a playlist, and only had this symptom. After looking at the playlist file again, I realize the path name to the offending song still had a wrong prefix. Correcting this, every song on the playlist worked correctly.)

A small note: the literal order of the imported playlist is the exact order shown in the playlist file. If you want to change up the order, then you'll have to manually rearrange the order with a text editor. Also, currently shuffling a playlist doesn't mean that every song will be played exactly once before songs repeat - this is different than how shuffling is set up in Auxio.
  Reply
#5
(4 hours ago)taivlam Wrote: Also, currently shuffling a playlist doesn't mean that every song will be played exactly once before songs repeat - this is different than how shuffling is set up in Auxio.

IIRC, shuffling will play each song once before it repeats any *but* there are some times where the queue needs to be reshuffled. If any items are added, or as the playlist is still opening, the shuffle will only pick ones that are currently loaded. When all the tracks finish loading into the queue from the playlist, the queue is shuffled properly and from then it should act the way you've described.
  Reply


Forum Jump: