Skip to content

Commit

Permalink
Merge remote-tracking branch 'el133/slskd-python-api_support' into de…
Browse files Browse the repository at this point in the history
…velop
  • Loading branch information
rembo10 committed May 26, 2024
2 parents 33d1d17 + 489c6cb commit c0c636d
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 11 deletions.
34 changes: 32 additions & 2 deletions data/interfaces/default/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,33 @@ <h1 class="clearfix"><i class="fa fa-gear"></i> Settings</h1>
<label>Prefer</label>
<input type="radio" name="prefer_torrents" id="prefer_torrents_0" value="0" ${config['prefer_torrents_0']}>NZBs
<input type="radio" name="prefer_torrents" id="prefer_torrents_1" value="1" ${config['prefer_torrents_1']}>Torrents
<input type="radio" name="prefer_torrents" id="prefer_torrents_2" value="2" ${config['prefer_torrents_2']}>No Preference
<input type="radio" name="prefer_torrents" id="prefer_torrents_2" value="2" ${config['prefer_torrents_2']}>Soulseek
<input type="radio" name="prefer_torrents" id="prefer_torrents_3" value="3" ${config['prefer_torrents_3']}>No Preference
</div>
</fieldset>
</td>
<td>
<fieldset>
<legend>Soulseek</legend>
<div class="row">
<label>Soulseek API URL</label>
<input type="text" name="soulseek_api_url" value="${config['soulseek_api_url']}" size="50">
</div>
<div class="row">
<label>Soulseek API KEY</label>
<input type="text" name="soulseek_api_key" value="${config['soulseek_api_key']}" size="20">
</div>
<div class="row">
<label title="Path to folder where Headphones can find the downloads.">
Soulseek Download Dir:
</label>
<input type="text" name="soulseek_download_dir" value="${config['soulseek_download_dir']}" size="50">
</div>
<div class="row">
<label title="Path to folder where Headphones can find the downloads.">
Soulseek Incomplete Download Dir:
</label>
<input type="text" name="soulseek_incomplete_download_dir" value="${config['soulseek_incomplete_download_dir']}" size="50">
</div>
</fieldset>
</td>
Expand Down Expand Up @@ -594,14 +620,18 @@ <h1 class="clearfix"><i class="fa fa-gear"></i> Settings</h1>
</div>
</div>
</fieldset>

<fieldset>
<legend>Other</legend>
<fieldset>
<div class="row checkbox left">
<input id="use_bandcamp" type="checkbox" class="bigcheck" name="use_bandcamp" value="1" ${config['use_bandcamp']} /><label for="use_bandcamp"><span class="option">Bandcamp</span></label>
</div>
</fieldset>
<fieldset>
<div class="row checkbox left">
<input id="use_soulseek" type="checkbox" class="bigcheck" name="use_soulseek" value="1" ${config['use_soulseek']} /><label for="use_soulseek"><span class="option">Soulseek</span></label>
</div>
</fieldset>
</fieldset>
</td>
<td>
Expand Down
5 changes: 5 additions & 0 deletions headphones/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ def __repr__(self):
'SONGKICK_ENABLED': (int, 'Songkick', 1),
'SONGKICK_FILTER_ENABLED': (int, 'Songkick', 0),
'SONGKICK_LOCATION': (str, 'Songkick', ''),
'SOULSEEK_API_URL': (str, 'Soulseek', ''),
'SOULSEEK_API_KEY': (str, 'Soulseek', ''),
'SOULSEEK_DOWNLOAD_DIR': (str, 'Soulseek', ''),
'SOULSEEK_INCOMPLETE_DOWNLOAD_DIR': (str, 'Soulseek', ''),
'SOULSEEK': (int, 'Soulseek', 0),
'SUBSONIC_ENABLED': (int, 'Subsonic', 0),
'SUBSONIC_HOST': (str, 'Subsonic', ''),
'SUBSONIC_PASSWORD': (str, 'Subsonic', ''),
Expand Down
36 changes: 31 additions & 5 deletions headphones/postprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from beets import logging as beetslogging
from mediafile import MediaFile, FileTypeError, UnreadableFileError
from beetsplug import lyrics as beetslyrics
from headphones import notifiers, utorrent, transmission, deluge, qbittorrent
from headphones import notifiers, utorrent, transmission, deluge, qbittorrent, soulseek
from headphones import db, albumart, librarysync
from headphones import logger, helpers, mb, music_encoder
from headphones import metadata
Expand All @@ -36,20 +36,44 @@


def checkFolder():
logger.debug("Checking download folder for completed downloads (only snatched ones).")
logger.info("Checking download folder for completed downloads (only snatched ones).")

with postprocessor_lock:
myDB = db.DBConnection()
snatched = myDB.select('SELECT * from snatched WHERE Status="Snatched"')

# If soulseek is used, this part will get the status from the soulseek api and return completed and errored albums
completed_albums, errored_albums = set(), set()
if any(album['Kind'] == 'soulseek' for album in snatched):
completed_albums, errored_albums = soulseek.download_completed()

for album in snatched:
if album['FolderName']:
folder_name = album['FolderName']
single = False
if album['Kind'] == 'nzb':
download_dir = headphones.CONFIG.DOWNLOAD_DIR
if album['Kind'] == 'soulseek':
if folder_name in errored_albums:
# If the album had any tracks with errors in it, the whole download is considered faulty. Status will be reset to wanted.
logger.info(f"Album with folder '{folder_name}' had errors during download. Setting status to 'Wanted'.")
myDB.action('UPDATE albums SET Status="Wanted" WHERE AlbumID=? AND Status="Snatched"', (album['AlbumID'],))

# Folder will be removed from configured complete and Incomplete directory
complete_path = os.path.join(headphones.CONFIG.SOULSEEK_DOWNLOAD_DIR, folder_name)
incomplete_path = os.path.join(headphones.CONFIG.SOULSEEK_INCOMPLETE_DOWNLOAD_DIR, folder_name)
for path in [complete_path, incomplete_path]:
try:
shutil.rmtree(path)
except Exception as e:
pass
continue
elif folder_name in completed_albums:
download_dir = headphones.CONFIG.SOULSEEK_DOWNLOAD_DIR
else:
continue
elif album['Kind'] == 'nzb':
download_dir = headphones.CONFIG.DOWNLOAD_DIR
elif album['Kind'] == 'bandcamp':
download_dir = headphones.CONFIG.BANDCAMP_DIR
download_dir = headphones.CONFIG.BANDCAMP_DIR
else:
if headphones.CONFIG.DELUGE_DONE_DIRECTORY and headphones.CONFIG.TORRENT_DOWNLOADER == 3:
download_dir = headphones.CONFIG.DELUGE_DONE_DIRECTORY
Expand Down Expand Up @@ -1172,6 +1196,8 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
download_dirs.append(dir)
if headphones.CONFIG.DOWNLOAD_DIR and not dir:
download_dirs.append(headphones.CONFIG.DOWNLOAD_DIR)
if headphones.CONFIG.SOULSEEK_DOWNLOAD_DIR and not dir:
download_dirs.append(headphones.CONFIG.SOULSEEK_DOWNLOAD_DIR)
if headphones.CONFIG.DOWNLOAD_TORRENT_DIR and not dir:
download_dirs.append(
headphones.CONFIG.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING, 'replace'))
Expand Down
53 changes: 50 additions & 3 deletions headphones/searcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
notifiers,
qbittorrent,
rutracker,
soulseek,
transmission,
utorrent,
)
Expand Down Expand Up @@ -279,6 +280,8 @@ def strptime_musicbrainz(date_str):


def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):


NZB_PROVIDERS = (headphones.CONFIG.HEADPHONES_INDEXER or
headphones.CONFIG.NEWZNAB or
headphones.CONFIG.NZBSORG or
Expand Down Expand Up @@ -319,7 +322,11 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
results = searchNZB(album, new, losslessOnly, albumlength)

if not results and headphones.CONFIG.BANDCAMP:
results = searchBandcamp(album, new, albumlength)
results = searchBandcamp(album, new, albumlength)

elif headphones.CONFIG.PREFER_TORRENTS == 2 and not choose_specific_download:
results = searchSoulseek(album, new, losslessOnly, albumlength)

else:

nzb_results = None
Expand Down Expand Up @@ -363,6 +370,7 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
(data, result) = preprocess(sorted_search_results)

if data and result:
#print(f'going to send stuff to downloader. data: {data}, album: {album}')
send_to_downloader(data, result, album)


Expand Down Expand Up @@ -868,11 +876,15 @@ def send_to_downloader(data, result, album):
except Exception as e:
logger.error('Couldn\'t write NZB file: %s', e)
return

elif kind == 'bandcamp':
folder_name = bandcamp.download(album, result)
logger.info("Setting folder_name to: {}".format(folder_name))


elif kind == 'soulseek':
soulseek.download(user=result.user, filelist=result.files)
folder_name = result.folder

else:
folder_name = '%s - %s [%s]' % (
unidecode(album['ArtistName']).replace('/', '_'),
Expand Down Expand Up @@ -1929,14 +1941,49 @@ def set_proxy(proxy_url):
return results


def searchSoulseek(album, new=False, losslessOnly=False, albumlength=None):
# Not using some of the input stuff for now or ever
replacements = {
'...': '',
' & ': ' ',
' = ': ' ',
'?': '',
'$': '',
' + ': ' ',
'"': '',
',': '',
'*': '',
'.': '',
':': ''
}

num_tracks = get_album_track_count(album['AlbumID'])
year = get_year_from_release_date(album['ReleaseDate'])
cleanalbum = unidecode(helpers.replace_all(album['AlbumTitle'], replacements)).strip()
cleanartist = unidecode(helpers.replace_all(album['ArtistName'], replacements)).strip()

results = soulseek.search(artist=cleanartist, album=cleanalbum, year=year, losslessOnly=losslessOnly, num_tracks=num_tracks)

return results


def get_album_track_count(album_id):
# Not sure if this should be considered a helper function.
myDB = db.DBConnection()
track_count = myDB.select('SELECT COUNT(*) as count FROM tracks WHERE AlbumID=?', [album_id])[0]['count']
return track_count


# THIS IS KIND OF A MESS AND PROBABLY NEEDS TO BE CLEANED UP


def preprocess(resultlist):
for result in resultlist:

headers = {'User-Agent': USER_AGENT}

if result.kind == 'soulseek':
return True, result

if result.kind == 'torrent':

# rutracker always needs the torrent data
Expand Down
Loading

0 comments on commit c0c636d

Please sign in to comment.