• Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups

    Options for scripting or command-line control

    Technical Help
    1
    2
    65
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • J
      Jellby last edited by

      As long as the smart playlists don't offer all the options I'd like, I'm willing to write an external script to select songs from the database and add them.

      What is the best option to...

      1. Identify songs in the database (mostly interested in songs from local directories). There's no unique song id, as far as I can see. Should I use the whole fingerprint? The url?
      2. Identify albums in the database. There's an album_id, but that's empty in my case. Should I use album + effictive_albumartist?
      3. Add songs to the playlist. Should I just use the command-line flag -a or -l, with a list of filenames? Or is there some other way to add songs from the database?
      J 1 Reply Last reply Reply Quote 0
      • J
        Jellby @Jellby last edited by

        For it's worth, this is what I came up with. Note that it's customized for my own use and library, you may need to change some things if you want to use it.

        Some notes:

        • This is designed to be run when Strawberry is stopped, with an empty (or finished, or not yet started) playlist.
        • exclude means the matching directories will be ignored. In this case everything with /Demos/ or /Classical/.
        • Since this is for full album random play, it will stop if there is some half-played album (any album with songs with different playcounts).
        • I have the disc tag only for series like "Complete Works", where I typically don't want to play all discs sequentially. For the more common double disc albums, I just tag all tracks sequentially. If you have disc tags, you may want to tweak the album selection scheme (album_is).

        What I'd appreciate from the Strawberry side:

        • A way to add tracks directly from the database, without having to use the URL (e.g. some kind of track ids).
        • Some notification when a playlist ends, or a way to run a script when a playlist ends (and possibly some events).
        • Support for MPRIS TrackList interface.
        • And of course the ability of doing all of this from within the player, without using an external script.
        #!/usr/bin/env python3
        
        import sqlite3
        import dbus
        import urllib.parse
        import re
        import sys
        import os.path
        import subprocess
        import numpy as np
        from datetime import datetime, timedelta
        
        # Set up database location and options
        dbfile = '/home/ignacio/.local/share/strawberry/strawberry/strawberry.db'
        album_is = ['album', 'effective_albumartist', 'disc'] # These identify an album
        exclude_dirs = ['Demos', 'Classical']
        recent = timedelta(days=120)
        
        # Format for album in notifications
        def album_print(row):
          disc = f' disc {row["disc"]}' if row['disc'] > 0 else ''
          return f'<i>{row["album"]}</i> ({row["effective_albumartist"]}){disc}'
        
        # Set up notification system
        item = 'org.freedesktop.Notifications'
        ntf = dbus.Interface(dbus.SessionBus().get_object(item, '/'+item.replace('.', '/')), item)
        def notify(title='Title', body='Body.'):
          ntf.Notify('randomalbum', 0, '', title, body, [], {'urgency': 1}, 5000)
        
        # Check Strawberry status
        try:
          plyr = dbus.Interface(dbus.SessionBus().get_object('org.mpris.MediaPlayer2.strawberry', '/org/mpris/MediaPlayer2'), 'org.freedesktop.DBus.Properties')
          status = plyr.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus')
        except dbus.exceptions.DBusException:
          notify('Not running!', 'Strawberry is not running. Exit.')
          sys.exit(1)
        if status != 'Stopped':
          notify('Not stopped!', 'Strawberry is not stopped. Exit.')
          sys.exit(1)
        
        # Open database
        con = sqlite3.connect(f'file:{dbfile}?mode=ro', uri=True)
        con.row_factory = sqlite3.Row
        cur = con.cursor()
        
        # Check partially played albums (with multiple playcounts)
        album_is_list = ', '.join(album_is)
        res = cur.execute(f'SELECT COUNT ( DISTINCT playcount ) AS "nr_of_playcounts", {album_is_list} FROM songs GROUP BY {album_is_list}')
        first_songs = res.fetchall()
        msg = ''
        for i in first_songs:
          msg = []
          if i['nr_of_playcounts'] > 1:
            msg += '* '+album_print(i)
        if msg:
          notify('Multiple playcounts', '\n'.join(msg))
          sys.exit(1)
        
        # Get data for the first song of each album
        res = cur.execute(f'SELECT MIN(track), url, playcount, lastplayed, {album_is_list} FROM songs GROUP BY {album_is_list}')
        first_songs = res.fetchall()
        
        now = datetime.now()
        
        exclude_re = '/(' + '|'.join([urllib.parse.quote(i, safe="/(),'!&;+=") for i in exclude_dirs]) + ')/'
        
        mask = [True for i in first_songs]
        
        # Manually exclude directories
        for i,j in enumerate(first_songs):
          if re.search(exclude_re,j['url']):
            mask[i] = False
        
        max_playcount = max([j['playcount'] for i,j in enumerate(first_songs) if mask[i]])
        
        # Exclude recently-played songs
        for i,j in enumerate(first_songs):
          dt = datetime.fromtimestamp(j['lastplayed'])
          if now-dt < recent:
            mask[i] = False
        
        # Tentatively exclude max-played songs,
        # unless the result is empty
        mp_mask = [True for i in first_songs]
        for i,j in enumerate(first_songs):
          if j['playcount'] >= max_playcount:
            mp_mask[i] = False
        
        new_mask = [i and j for i,j in zip(mask,mp_mask)]
        if any(new_mask):
          mask = new_mask
        else:
          notify('Library exhausted', f'Increasing max. playcount to {max_playcount+1}.')
        
        # Pick a random song out of the remaining ones
        weights = np.array([1 if i else 0 for i in mask])
        left = np.count_nonzero(weights)
        pick = np.random.choice(len(mask), p=weights/np.sum(weights))
        pick = first_songs[pick]
        
        filename = urllib.parse.unquote(urllib.parse.urlsplit(pick['url']).path)
        if (os.path.isfile(filename)):
          # Select the full album (disc) from the picked song
          notify('Next album', album_print(pick) + f'\n[out of {left}]')
          album = tuple([pick[i] for i in album_is])
          album_exp = ' AND '.join([f'{i} IS ?' for i in album_is])
          res = cur.execute(f'SELECT url FROM songs WHERE {album_exp} ORDER BY track', album)
          # Add the album, replacing the current playlist
          subprocess.call(['strawberry', '-l'] + [i['url'] for i in res.fetchall()])
        else:
          notify('Missing file', f'File not found. Is the library mounted?\n{filename}')
          sys.exit(1)
        
        con.close()
        
        1 Reply Last reply Reply Quote 0
        • First post
          Last post
        Powered by NodeBB | Contributors