pep8, pyflakes, and pylint suggested fixes

This commit is contained in:
Jamie Magee
2015-11-27 13:55:11 +01:00
parent ef4a844233
commit 327585327f
34 changed files with 1878 additions and 1255 deletions
+298 -167
View File
@@ -13,20 +13,19 @@
# You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
import os
import re
import shutil
import uuid
import beets
import threading
import itertools
import headphones
import os
import re
import beets
import headphones
from beets import autotag
from beets import config as beetsconfig
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
from beetsplug import lyrics as beetslyrics
from headphones import notifiers, utorrent, transmission
from headphones import db, albumart, librarysync
from headphones import logger, helpers, request, mb, music_encoder
@@ -48,19 +47,21 @@ def checkFolder():
else:
download_dir = headphones.CONFIG.DOWNLOAD_TORRENT_DIR
album_path = os.path.join(download_dir, album['FolderName']).encode(headphones.SYS_ENCODING, 'replace')
album_path = os.path.join(download_dir, album['FolderName']).encode(
headphones.SYS_ENCODING, 'replace')
logger.debug("Checking if %s exists" % album_path)
if os.path.exists(album_path):
logger.info('Found "' + album['FolderName'] + '" in ' + album['Kind'] + ' download folder. Verifying....')
logger.info('Found "' + album['FolderName'] + '" in ' + album[
'Kind'] + ' download folder. Verifying....')
verify(album['AlbumID'], album_path, album['Kind'])
else:
logger.info("No folder name found for " + album['Title'])
logger.debug("Checking download folder finished.")
def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=False):
def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=False):
myDB = db.DBConnection()
release = myDB.action('SELECT * from albums WHERE AlbumID=?', [albumid]).fetchone()
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
@@ -72,11 +73,14 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
try:
release_list = mb.getReleaseGroup(albumid)
except Exception as e:
logger.error('Unable to get release information for manual album with rgid: %s. Error: %s', albumid, e)
logger.error(
'Unable to get release information for manual album with rgid: %s. Error: %s',
albumid, e)
return
if not release_list:
logger.error('Unable to get release information for manual album with rgid: %s', albumid)
logger.error('Unable to get release information for manual album with rgid: %s',
albumid)
return
# Since we're just using this to create the bare minimum information to
@@ -85,7 +89,9 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
release_dict = mb.getRelease(releaseid)
if not release_dict:
logger.error('Unable to get release information for manual album with rgid: %s. Cannot continue', albumid)
logger.error(
'Unable to get release information for manual album with rgid: %s. Cannot continue',
albumid)
return
# Check if the artist is added to the database. In case the database is
@@ -93,21 +99,26 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
# prevents new artists from appearing suddenly. In case forced is True,
# this check is skipped, since it is assumed the user wants this.
if headphones.CONFIG.FREEZE_DB and not forced:
artist = myDB.select("SELECT ArtistName, ArtistID FROM artists WHERE ArtistId=? OR ArtistName=?", [release_dict['artist_id'], release_dict['artist_name']])
artist = myDB.select(
"SELECT ArtistName, ArtistID FROM artists WHERE ArtistId=? OR ArtistName=?",
[release_dict['artist_id'], release_dict['artist_name']])
if not artist:
logger.warn("Continuing would add new artist '%s' (ID %s), " \
"but database is frozen. Will skip postprocessing for " \
"album with rgid: %s", release_dict['artist_name'],
release_dict['artist_id'], albumid)
"but database is frozen. Will skip postprocessing for " \
"album with rgid: %s", release_dict['artist_name'],
release_dict['artist_id'], albumid)
myDB.action('UPDATE snatched SET status = "Frozen" WHERE status NOT LIKE "Seed%" and AlbumID=?', [albumid])
myDB.action(
'UPDATE snatched SET status = "Frozen" WHERE status NOT LIKE "Seed%" and AlbumID=?',
[albumid])
frozen = re.search(r' \(Frozen\)(?:\[\d+\])?', albumpath)
if not frozen:
if headphones.CONFIG.RENAME_FROZEN:
renameUnprocessedFolder(albumpath, tag="Frozen")
else:
logger.warn(u"Won't rename %s to mark as 'Frozen', because it is disabled.", albumpath.decode(headphones.SYS_ENCODING, 'replace'))
logger.warn(u"Won't rename %s to mark as 'Frozen', because it is disabled.",
albumpath.decode(headphones.SYS_ENCODING, 'replace'))
return
logger.info(u"Now adding/updating artist: " + release_dict['artist_name'])
@@ -123,7 +134,8 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
"DateAdded": helpers.today(),
"Status": "Paused"}
logger.info("ArtistID: " + release_dict['artist_id'] + " , ArtistName: " + release_dict['artist_name'])
logger.info("ArtistID: " + release_dict['artist_id'] + " , ArtistName: " + release_dict[
'artist_name'])
if headphones.CONFIG.INCLUDE_EXTRAS:
newValueDict['IncludeExtras'] = 1
@@ -150,18 +162,17 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
# Delete existing tracks associated with this AlbumID since we're going to replace them and don't want any extras
myDB.action('DELETE from tracks WHERE AlbumID=?', [albumid])
for track in release_dict['tracks']:
controlValueDict = {"TrackID": track['id'],
"AlbumID": albumid}
newValueDict = {"ArtistID": release_dict['artist_id'],
"ArtistName": release_dict['artist_name'],
"AlbumTitle": release_dict['title'],
"AlbumASIN": release_dict['asin'],
"TrackTitle": track['title'],
"TrackDuration": track['duration'],
"TrackNumber": track['number']
}
"ArtistName": release_dict['artist_name'],
"AlbumTitle": release_dict['title'],
"AlbumASIN": release_dict['asin'],
"TrackTitle": track['title'],
"TrackDuration": track['duration'],
"TrackNumber": track['number']
}
myDB.upsert("tracks", newValueDict, controlValueDict)
@@ -169,7 +180,8 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
newValueDict = {"Status": "Paused"}
myDB.upsert("artists", newValueDict, controlValueDict)
logger.info(u"Addition complete for: " + release_dict['title'] + " - " + release_dict['artist_name'])
logger.info(u"Addition complete for: " + release_dict['title'] + " - " + release_dict[
'artist_name'])
release = myDB.action('SELECT * from albums WHERE AlbumID=?', [albumid]).fetchone()
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
@@ -185,11 +197,14 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
downloaded_cuecount += 1
# if any of the files end in *.part, we know the torrent isn't done yet. Process if forced, though
elif files.lower().endswith(('.part', '.utpart')) and not forced:
logger.info("Looks like " + os.path.basename(albumpath).decode(headphones.SYS_ENCODING, 'replace') + " isn't complete yet. Will try again on the next run")
logger.info(
"Looks like " + os.path.basename(albumpath).decode(headphones.SYS_ENCODING,
'replace') + " isn't complete yet. Will try again on the next run")
return
# Split cue
if headphones.CONFIG.CUE_SPLIT and downloaded_cuecount and downloaded_cuecount >= len(downloaded_track_list):
if headphones.CONFIG.CUE_SPLIT and downloaded_cuecount and downloaded_cuecount >= len(
downloaded_track_list):
if headphones.CONFIG.KEEP_TORRENT_FILES and Kind == "torrent":
albumpath = helpers.preserve_torrent_directory(albumpath)
if albumpath and helpers.cue_split(albumpath):
@@ -202,7 +217,10 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
try:
f = MediaFile(downloaded_track)
except Exception as e:
logger.info(u"Exception from MediaFile for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + u" : " + unicode(e))
logger.info(
u"Exception from MediaFile for: " + downloaded_track.decode(headphones.SYS_ENCODING,
'replace') + u" : " + unicode(
e))
continue
if not f.artist:
@@ -219,7 +237,8 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
logger.debug('Matching metadata album: %s with album name: %s' % (metaalbum, dbalbum))
if metaartist == dbartist and metaalbum == dbalbum:
doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind, keep_original_folder)
doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind,
keep_original_folder)
return
# test #2: filenames
@@ -237,7 +256,8 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
logger.debug('Checking if track title: %s is in file name: %s' % (dbtrack, filetrack))
if dbtrack in filetrack:
doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind, keep_original_folder)
doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind,
keep_original_folder)
return
# test #3: number of songs and duration
@@ -269,32 +289,42 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal
logger.debug('Database track duration: %i' % db_track_duration)
delta = abs(downloaded_track_duration - db_track_duration)
if delta < 240:
doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind, keep_original_folder)
doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind,
keep_original_folder)
return
logger.warn(u'Could not identify album: %s. It may not be the intended album.', albumpath.decode(headphones.SYS_ENCODING, 'replace'))
myDB.action('UPDATE snatched SET status = "Unprocessed" WHERE status NOT LIKE "Seed%" and AlbumID=?', [albumid])
logger.warn(u'Could not identify album: %s. It may not be the intended album.',
albumpath.decode(headphones.SYS_ENCODING, 'replace'))
myDB.action(
'UPDATE snatched SET status = "Unprocessed" WHERE status NOT LIKE "Seed%" and AlbumID=?',
[albumid])
processed = re.search(r' \(Unprocessed\)(?:\[\d+\])?', albumpath)
if not processed:
if headphones.CONFIG.RENAME_UNPROCESSED:
renameUnprocessedFolder(albumpath, tag="Unprocessed")
else:
logger.warn(u"Won't rename %s to mark as 'Unprocessed', because it is disabled.", albumpath.decode(headphones.SYS_ENCODING, 'replace'))
logger.warn(u"Won't rename %s to mark as 'Unprocessed', because it is disabled.",
albumpath.decode(headphones.SYS_ENCODING, 'replace'))
def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind=None, keep_original_folder=False):
logger.info('Starting post-processing for: %s - %s' % (release['ArtistName'], release['AlbumTitle']))
def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind=None,
keep_original_folder=False):
logger.info(
'Starting post-processing for: %s - %s' % (release['ArtistName'], release['AlbumTitle']))
# Check to see if we're preserving the torrent dir
if (headphones.CONFIG.KEEP_TORRENT_FILES and Kind == "torrent" and 'headphones-modified' not in albumpath) or headphones.CONFIG.KEEP_ORIGINAL_FOLDER or keep_original_folder:
new_folder = os.path.join(albumpath, 'headphones-modified'.encode(headphones.SYS_ENCODING, 'replace'))
logger.info("Copying files to 'headphones-modified' subfolder to preserve downloaded files for seeding")
if (
headphones.CONFIG.KEEP_TORRENT_FILES and Kind == "torrent" and 'headphones-modified' not in albumpath) or headphones.CONFIG.KEEP_ORIGINAL_FOLDER or keep_original_folder:
new_folder = os.path.join(albumpath,
'headphones-modified'.encode(headphones.SYS_ENCODING, 'replace'))
logger.info(
"Copying files to 'headphones-modified' subfolder to preserve downloaded files for seeding")
try:
shutil.copytree(albumpath, new_folder)
# Update the album path with the new location
albumpath = new_folder
except Exception as e:
logger.warn("Cannot copy/move files to temp folder: " + new_folder.decode(headphones.SYS_ENCODING, 'replace') + ". Not continuing. Error: " + str(e))
logger.warn("Cannot copy/move files to temp folder: " + new_folder.decode(
headphones.SYS_ENCODING, 'replace') + ". Not continuing. Error: " + str(e))
return
# Need to update the downloaded track list with the new location.
@@ -317,8 +347,8 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
return
except (FileTypeError, UnreadableFileError):
logger.error("Track file is not a valid media file: %s. Not " \
"continuing.", downloaded_track.decode(
headphones.SYS_ENCODING, "replace"))
"continuing.", downloaded_track.decode(
headphones.SYS_ENCODING, "replace"))
return
except IOError:
logger.error("Unable to find media file: %s. Not continuing.")
@@ -328,9 +358,9 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
# files, which requires write permissions. This step just check this, so
# it will not try and fail lateron, with strange exceptions.
if headphones.CONFIG.EMBED_ALBUM_ART or headphones.CONFIG.CLEANUP_FILES or \
headphones.CONFIG.ADD_ALBUM_ART or headphones.CONFIG.CORRECT_METADATA or \
headphones.CONFIG.EMBED_LYRICS or headphones.CONFIG.RENAME_FILES or \
headphones.CONFIG.MOVE_FILES:
headphones.CONFIG.ADD_ALBUM_ART or headphones.CONFIG.CORRECT_METADATA or \
headphones.CONFIG.EMBED_LYRICS or headphones.CONFIG.RENAME_FILES or \
headphones.CONFIG.MOVE_FILES:
try:
with open(downloaded_track, "a+b") as fp:
@@ -338,11 +368,11 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
except IOError as e:
logger.debug("Write check exact error: %s", e)
logger.error("Track file is not writable. This is required " \
"for some post processing steps: %s. Not continuing.",
downloaded_track.decode(headphones.SYS_ENCODING, "replace"))
"for some post processing steps: %s. Not continuing.",
downloaded_track.decode(headphones.SYS_ENCODING, "replace"))
return
#start encoding
# start encoding
if headphones.CONFIG.MUSIC_ENCODER:
downloaded_track_list = music_encoder.encode(albumpath)
@@ -389,7 +419,8 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
renameFiles(albumpath, downloaded_track_list, release)
if headphones.CONFIG.MOVE_FILES and not headphones.CONFIG.DESTINATION_DIR:
logger.error('No DESTINATION_DIR has been set. Set "Destination Directory" to the parent directory you want to move the files to')
logger.error(
'No DESTINATION_DIR has been set. Set "Destination Directory" to the parent directory you want to move the files to')
albumpaths = [albumpath]
elif headphones.CONFIG.MOVE_FILES and headphones.CONFIG.DESTINATION_DIR:
albumpaths = moveFiles(albumpath, release, tracks)
@@ -400,15 +431,20 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
myDB = db.DBConnection()
myDB.action('UPDATE albums SET status = "Downloaded" WHERE AlbumID=?', [albumid])
myDB.action('UPDATE snatched SET status = "Processed" WHERE Status NOT LIKE "Seed%" and AlbumID=?', [albumid])
myDB.action(
'UPDATE snatched SET status = "Processed" WHERE Status NOT LIKE "Seed%" and AlbumID=?',
[albumid])
# Check if torrent has finished seeding
if headphones.CONFIG.TORRENT_DOWNLOADER == 1 or headphones.CONFIG.TORRENT_DOWNLOADER == 2:
seed_snatched = myDB.action('SELECT * from snatched WHERE Status="Seed_Snatched" and AlbumID=?', [albumid]).fetchone()
seed_snatched = myDB.action(
'SELECT * from snatched WHERE Status="Seed_Snatched" and AlbumID=?',
[albumid]).fetchone()
if seed_snatched:
hash = seed_snatched['FolderName']
torrent_removed = False
logger.info(u'%s - %s. Checking if torrent has finished seeding and can be removed' % (release['ArtistName'], release['AlbumTitle']))
logger.info(u'%s - %s. Checking if torrent has finished seeding and can be removed' % (
release['ArtistName'], release['AlbumTitle']))
if headphones.CONFIG.TORRENT_DOWNLOADER == 1:
torrent_removed = transmission.removeTorrent(hash, True)
else:
@@ -416,15 +452,20 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
# Torrent removed, delete the snatched record, else update Status for scheduled job to check
if torrent_removed:
myDB.action('DELETE from snatched WHERE status = "Seed_Snatched" and AlbumID=?', [albumid])
myDB.action('DELETE from snatched WHERE status = "Seed_Snatched" and AlbumID=?',
[albumid])
else:
myDB.action('UPDATE snatched SET status = "Seed_Processed" WHERE status = "Seed_Snatched" and AlbumID=?', [albumid])
myDB.action(
'UPDATE snatched SET status = "Seed_Processed" WHERE status = "Seed_Snatched" and AlbumID=?',
[albumid])
# Update the have tracks for all created dirs:
for albumpath in albumpaths:
librarysync.libraryScan(dir=albumpath, append=True, ArtistID=release['ArtistID'], ArtistName=release['ArtistName'])
librarysync.libraryScan(dir=albumpath, append=True, ArtistID=release['ArtistID'],
ArtistName=release['ArtistName'])
logger.info(u'Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
logger.info(
u'Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
statusmessage = "Download and Postprocessing completed"
@@ -530,7 +571,8 @@ def embedAlbumArt(artwork, downloaded_track_list):
try:
f = MediaFile(downloaded_track)
except:
logger.error(u'Could not read %s. Not adding album art' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
logger.error(u'Could not read %s. Not adding album art' % downloaded_track.decode(
headphones.SYS_ENCODING, 'replace'))
continue
logger.debug('Adding album art to: %s' % downloaded_track)
@@ -539,7 +581,8 @@ def embedAlbumArt(artwork, downloaded_track_list):
f.art = artwork
f.save()
except Exception as e:
logger.error(u'Error embedding album art to: %s. Error: %s' % (downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e)))
logger.error(u'Error embedding album art to: %s. Error: %s' % (
downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e)))
continue
@@ -552,16 +595,18 @@ def addAlbumArt(artwork, albumpath, release):
year = ''
values = {'$Artist': release['ArtistName'],
'$Album': release['AlbumTitle'],
'$Year': year,
'$artist': release['ArtistName'].lower(),
'$album': release['AlbumTitle'].lower(),
'$year': year
}
'$Album': release['AlbumTitle'],
'$Year': year,
'$artist': release['ArtistName'].lower(),
'$album': release['AlbumTitle'].lower(),
'$year': year
}
album_art_name = helpers.replace_all(headphones.CONFIG.ALBUM_ART_FORMAT.strip(), values) + ".jpg"
album_art_name = helpers.replace_all(headphones.CONFIG.ALBUM_ART_FORMAT.strip(),
values) + ".jpg"
album_art_name = helpers.replace_illegal_chars(album_art_name).encode(headphones.SYS_ENCODING, 'replace')
album_art_name = helpers.replace_illegal_chars(album_art_name).encode(headphones.SYS_ENCODING,
'replace')
if headphones.CONFIG.FILE_UNDERSCORES:
album_art_name = album_art_name.replace(' ', '_')
@@ -587,7 +632,8 @@ def cleanupFiles(albumpath):
try:
os.remove(os.path.join(r, files))
except Exception as e:
logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), e))
logger.error(u'Could not remove file: %s. Error: %s' % (
files.decode(headphones.SYS_ENCODING, 'replace'), e))
def renameNFO(albumpath):
@@ -596,12 +642,16 @@ def renameNFO(albumpath):
for r, d, f in os.walk(albumpath):
for file in f:
if file.lower().endswith('.nfo'):
logger.debug('Renaming: "%s" to "%s"' % (file.decode(headphones.SYS_ENCODING, 'replace'), file.decode(headphones.SYS_ENCODING, 'replace') + '-orig'))
logger.debug('Renaming: "%s" to "%s"' % (
file.decode(headphones.SYS_ENCODING, 'replace'),
file.decode(headphones.SYS_ENCODING, 'replace') + '-orig'))
try:
new_file_name = os.path.join(r, file)[:-3] + 'orig.nfo'
os.rename(os.path.join(r, file), new_file_name)
except Exception as e:
logger.error(u'Could not rename file: %s. Error: %s' % (os.path.join(r, file).decode(headphones.SYS_ENCODING, 'replace'), e))
logger.error(u'Could not rename file: %s. Error: %s' % (
os.path.join(r, file).decode(headphones.SYS_ENCODING, 'replace'), e))
def moveFiles(albumpath, release, tracks):
logger.info("Moving files: %s" % albumpath)
@@ -630,25 +680,26 @@ def moveFiles(albumpath, release, tracks):
for r, d, f in os.walk(albumpath):
try:
origfolder = os.path.basename(os.path.normpath(r).decode(headphones.SYS_ENCODING, 'replace'))
origfolder = os.path.basename(
os.path.normpath(r).decode(headphones.SYS_ENCODING, 'replace'))
except:
origfolder = u''
values = {'$Artist': artist,
'$SortArtist': sortname,
'$Album': album,
'$Year': year,
'$Type': releasetype,
'$OriginalFolder': origfolder,
'$First': firstchar.upper(),
'$artist': artist.lower(),
'$sortartist': sortname.lower(),
'$album': album.lower(),
'$year': year,
'$type': releasetype.lower(),
'$first': firstchar.lower(),
'$originalfolder': origfolder.lower()
}
'$SortArtist': sortname,
'$Album': album,
'$Year': year,
'$Type': releasetype,
'$OriginalFolder': origfolder,
'$First': firstchar.upper(),
'$artist': artist.lower(),
'$sortartist': sortname.lower(),
'$album': album.lower(),
'$year': year,
'$type': releasetype.lower(),
'$first': firstchar.lower(),
'$originalfolder': origfolder.lower()
}
folder = helpers.replace_all(headphones.CONFIG.FOLDER_FORMAT.strip(), values, normalize=True)
@@ -672,15 +723,20 @@ def moveFiles(albumpath, release, tracks):
files_to_move.append(os.path.join(r, files))
if any(files.lower().endswith('.' + x.lower()) for x in headphones.LOSSY_MEDIA_FORMATS):
lossy_media = True
if any(files.lower().endswith('.' + x.lower()) for x in headphones.LOSSLESS_MEDIA_FORMATS):
if any(files.lower().endswith('.' + x.lower()) for x in
headphones.LOSSLESS_MEDIA_FORMATS):
lossless_media = True
# Do some sanity checking to see what directories we need to create:
make_lossy_folder = False
make_lossless_folder = False
lossy_destination_path = os.path.normpath(os.path.join(headphones.CONFIG.DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING, 'replace')
lossless_destination_path = os.path.normpath(os.path.join(headphones.CONFIG.LOSSLESS_DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING, 'replace')
lossy_destination_path = os.path.normpath(
os.path.join(headphones.CONFIG.DESTINATION_DIR, folder)).encode(headphones.SYS_ENCODING,
'replace')
lossless_destination_path = os.path.normpath(
os.path.join(headphones.CONFIG.LOSSLESS_DESTINATION_DIR, folder)).encode(
headphones.SYS_ENCODING, 'replace')
# If they set a destination dir for lossless media, only create the lossy folder if there is lossy media
if headphones.CONFIG.LOSSLESS_DESTINATION_DIR:
@@ -704,7 +760,9 @@ def moveFiles(albumpath, release, tracks):
try:
shutil.rmtree(lossless_destination_path)
except Exception as e:
logger.error("Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (lossless_destination_path.decode(headphones.SYS_ENCODING, 'replace'), e))
logger.error(
"Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (
lossless_destination_path.decode(headphones.SYS_ENCODING, 'replace'), e))
create_duplicate_folder = True
if not headphones.CONFIG.REPLACE_EXISTING_FOLDERS or create_duplicate_folder:
@@ -713,7 +771,9 @@ def moveFiles(albumpath, release, tracks):
i = 1
while True:
newfolder = temp_folder + '[%i]' % i
lossless_destination_path = os.path.normpath(os.path.join(headphones.CONFIG.LOSSLESS_DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace')
lossless_destination_path = os.path.normpath(
os.path.join(headphones.CONFIG.LOSSLESS_DESTINATION_DIR, newfolder)).encode(
headphones.SYS_ENCODING, 'replace')
if os.path.exists(lossless_destination_path):
i += 1
else:
@@ -724,7 +784,8 @@ def moveFiles(albumpath, release, tracks):
try:
os.makedirs(lossless_destination_path)
except Exception as e:
logger.error('Could not create lossless folder for %s. (Error: %s)' % (release['AlbumTitle'], e))
logger.error('Could not create lossless folder for %s. (Error: %s)' % (
release['AlbumTitle'], e))
if not make_lossy_folder:
return [albumpath]
@@ -737,7 +798,9 @@ def moveFiles(albumpath, release, tracks):
try:
shutil.rmtree(lossy_destination_path)
except Exception as e:
logger.error("Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (lossy_destination_path.decode(headphones.SYS_ENCODING, 'replace'), e))
logger.error(
"Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (
lossy_destination_path.decode(headphones.SYS_ENCODING, 'replace'), e))
create_duplicate_folder = True
if not headphones.CONFIG.REPLACE_EXISTING_FOLDERS or create_duplicate_folder:
@@ -746,7 +809,9 @@ def moveFiles(albumpath, release, tracks):
i = 1
while True:
newfolder = temp_folder + '[%i]' % i
lossy_destination_path = os.path.normpath(os.path.join(headphones.CONFIG.DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace')
lossy_destination_path = os.path.normpath(
os.path.join(headphones.CONFIG.DESTINATION_DIR, newfolder)).encode(
headphones.SYS_ENCODING, 'replace')
if os.path.exists(lossy_destination_path):
i += 1
else:
@@ -757,7 +822,8 @@ def moveFiles(albumpath, release, tracks):
try:
os.makedirs(lossy_destination_path)
except Exception as e:
logger.error('Could not create folder for %s. Not moving: %s' % (release['AlbumTitle'], e))
logger.error(
'Could not create folder for %s. Not moving: %s' % (release['AlbumTitle'], e))
return [albumpath]
logger.info('Checking which files we need to move.....')
@@ -768,26 +834,34 @@ def moveFiles(albumpath, release, tracks):
for file_to_move in files_to_move:
if any(file_to_move.lower().endswith('.' + x.lower()) for x in headphones.LOSSY_MEDIA_FORMATS):
if any(file_to_move.lower().endswith('.' + x.lower()) for x in
headphones.LOSSY_MEDIA_FORMATS):
helpers.smartMove(file_to_move, lossy_destination_path)
elif any(file_to_move.lower().endswith('.' + x.lower()) for x in headphones.LOSSLESS_MEDIA_FORMATS):
elif any(file_to_move.lower().endswith('.' + x.lower()) for x in
headphones.LOSSLESS_MEDIA_FORMATS):
helpers.smartMove(file_to_move, lossless_destination_path)
# If it's a non-music file, move it to both dirs
# TODO: Move specific-to-lossless files to the lossless dir only
else:
moved_to_lossy_folder = helpers.smartMove(file_to_move, lossy_destination_path, delete=False)
moved_to_lossless_folder = helpers.smartMove(file_to_move, lossless_destination_path, delete=False)
moved_to_lossy_folder = helpers.smartMove(file_to_move, lossy_destination_path,
delete=False)
moved_to_lossless_folder = helpers.smartMove(file_to_move,
lossless_destination_path,
delete=False)
if moved_to_lossy_folder or moved_to_lossless_folder:
try:
os.remove(file_to_move)
except Exception as e:
logger.error("Error deleting file '" + file_to_move.decode(headphones.SYS_ENCODING, 'replace') + "' from source directory")
logger.error(
"Error deleting file '" + file_to_move.decode(headphones.SYS_ENCODING,
'replace') + "' from source directory")
else:
logger.error("Error copying '" + file_to_move.decode(headphones.SYS_ENCODING, 'replace') + "'. Not deleting from download directory")
logger.error("Error copying '" + file_to_move.decode(headphones.SYS_ENCODING,
'replace') + "'. Not deleting from download directory")
elif make_lossless_folder and not make_lossy_folder:
@@ -817,11 +891,14 @@ def moveFiles(albumpath, release, tracks):
if headphones.CONFIG.FOLDER_PERMISSIONS_ENABLED:
try:
os.chmod(os.path.normpath(temp_f).encode(headphones.SYS_ENCODING, 'replace'), int(headphones.CONFIG.FOLDER_PERMISSIONS, 8))
os.chmod(os.path.normpath(temp_f).encode(headphones.SYS_ENCODING, 'replace'),
int(headphones.CONFIG.FOLDER_PERMISSIONS, 8))
except Exception as e:
logger.error("Error trying to change permissions on folder: %s. %s", temp_f.decode(headphones.SYS_ENCODING, 'replace'), e)
logger.error("Error trying to change permissions on folder: %s. %s",
temp_f.decode(headphones.SYS_ENCODING, 'replace'), e)
else:
logger.debug("Not changing folder permissions, since it is disabled: %s", temp_f.decode(headphones.SYS_ENCODING, 'replace'))
logger.debug("Not changing folder permissions, since it is disabled: %s",
temp_f.decode(headphones.SYS_ENCODING, 'replace'))
# If we failed to move all the files out of the directory, this will fail too
try:
@@ -840,7 +917,6 @@ def moveFiles(albumpath, release, tracks):
def correctMetadata(albumid, release, downloaded_track_list):
logger.info('Preparing to write metadata to tracks....')
lossy_items = []
lossless_items = []
@@ -850,14 +926,18 @@ def correctMetadata(albumid, release, downloaded_track_list):
try:
if any(downloaded_track.lower().endswith('.' + x.lower()) for x in headphones.LOSSLESS_MEDIA_FORMATS):
if any(downloaded_track.lower().endswith('.' + x.lower()) for x in
headphones.LOSSLESS_MEDIA_FORMATS):
lossless_items.append(beets.library.Item.from_path(downloaded_track))
elif any(downloaded_track.lower().endswith('.' + x.lower()) for x in headphones.LOSSY_MEDIA_FORMATS):
elif any(downloaded_track.lower().endswith('.' + x.lower()) for x in
headphones.LOSSY_MEDIA_FORMATS):
lossy_items.append(beets.library.Item.from_path(downloaded_track))
else:
logger.warn("Skipping: %s because it is not a mutagen friendly file format", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
logger.warn("Skipping: %s because it is not a mutagen friendly file format",
downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
except Exception as e:
logger.error("Beets couldn't create an Item from: %s - not a media file? %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e))
logger.error("Beets couldn't create an Item from: %s - not a media file? %s",
downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e))
for items in [lossy_items, lossless_items]:
@@ -865,18 +945,24 @@ def correctMetadata(albumid, release, downloaded_track_list):
continue
try:
cur_artist, cur_album, candidates, rec = autotag.tag_album(items, search_artist=helpers.latinToAscii(release['ArtistName']), search_album=helpers.latinToAscii(release['AlbumTitle']))
cur_artist, cur_album, candidates, rec = autotag.tag_album(items,
search_artist=helpers.latinToAscii(
release['ArtistName']),
search_album=helpers.latinToAscii(
release['AlbumTitle']))
except Exception as e:
logger.error('Error getting recommendation: %s. Not writing metadata', e)
return False
if str(rec) == 'Recommendation.none':
logger.warn('No accurate album match found for %s, %s - not writing metadata', release['ArtistName'], release['AlbumTitle'])
logger.warn('No accurate album match found for %s, %s - not writing metadata',
release['ArtistName'], release['AlbumTitle'])
return False
if candidates:
dist, info, mapping, extra_items, extra_tracks = candidates[0]
else:
logger.warn('No accurate album match found for %s, %s - not writing metadata', release['ArtistName'], release['AlbumTitle'])
logger.warn('No accurate album match found for %s, %s - not writing metadata',
release['ArtistName'], release['AlbumTitle'])
return False
logger.info('Beets recommendation for tagging items: %s' % rec)
@@ -896,13 +982,16 @@ def correctMetadata(albumid, release, downloaded_track_list):
for item in items:
try:
item.write()
logger.info("Successfully applied metadata to: %s", item.path.decode(headphones.SYS_ENCODING, 'replace'))
logger.info("Successfully applied metadata to: %s",
item.path.decode(headphones.SYS_ENCODING, 'replace'))
except Exception as e:
logger.warn("Error writing metadata to '%s': %s", item.path.decode(headphones.SYS_ENCODING, 'replace'), str(e))
logger.warn("Error writing metadata to '%s': %s",
item.path.decode(headphones.SYS_ENCODING, 'replace'), str(e))
return False
return True
def embedLyrics(downloaded_track_list):
logger.info('Adding lyrics')
@@ -916,14 +1005,18 @@ def embedLyrics(downloaded_track_list):
for downloaded_track in downloaded_track_list:
try:
if any(downloaded_track.lower().endswith('.' + x.lower()) for x in headphones.LOSSLESS_MEDIA_FORMATS):
if any(downloaded_track.lower().endswith('.' + x.lower()) for x in
headphones.LOSSLESS_MEDIA_FORMATS):
lossless_items.append(beets.library.Item.from_path(downloaded_track))
elif any(downloaded_track.lower().endswith('.' + x.lower()) for x in headphones.LOSSY_MEDIA_FORMATS):
elif any(downloaded_track.lower().endswith('.' + x.lower()) for x in
headphones.LOSSY_MEDIA_FORMATS):
lossy_items.append(beets.library.Item.from_path(downloaded_track))
else:
logger.warn("Skipping: %s because it is not a mutagen friendly file format", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
logger.warn("Skipping: %s because it is not a mutagen friendly file format",
downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
except Exception as e:
logger.error("Beets couldn't create an Item from: %s - not a media file? %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e))
logger.error("Beets couldn't create an Item from: %s - not a media file? %s",
downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e))
for items in [lossy_items, lossless_items]:
@@ -963,7 +1056,8 @@ def renameFiles(albumpath, downloaded_track_list, release):
try:
f = MediaFile(downloaded_track)
except:
logger.info("MediaFile couldn't parse: %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
logger.info("MediaFile couldn't parse: %s",
downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
continue
if not f.disc:
@@ -998,26 +1092,28 @@ def renameFiles(albumpath, downloaded_track_list, release):
sortname = artistname
values = {'$Disc': discnumber,
'$Track': tracknumber,
'$Title': title,
'$Artist': artistname,
'$SortArtist': sortname,
'$Album': release['AlbumTitle'],
'$Year': year,
'$disc': discnumber,
'$track': tracknumber,
'$title': title.lower(),
'$artist': artistname.lower(),
'$sortartist': sortname.lower(),
'$album': release['AlbumTitle'].lower(),
'$year': year
}
'$Track': tracknumber,
'$Title': title,
'$Artist': artistname,
'$SortArtist': sortname,
'$Album': release['AlbumTitle'],
'$Year': year,
'$disc': discnumber,
'$track': tracknumber,
'$title': title.lower(),
'$artist': artistname.lower(),
'$sortartist': sortname.lower(),
'$album': release['AlbumTitle'].lower(),
'$year': year
}
ext = os.path.splitext(downloaded_track)[1]
new_file_name = helpers.replace_all(headphones.CONFIG.FILE_FORMAT.strip(), values).replace('/', '_') + ext
new_file_name = helpers.replace_all(headphones.CONFIG.FILE_FORMAT.strip(),
values).replace('/', '_') + ext
new_file_name = helpers.replace_illegal_chars(new_file_name).encode(headphones.SYS_ENCODING, 'replace')
new_file_name = helpers.replace_illegal_chars(new_file_name).encode(headphones.SYS_ENCODING,
'replace')
if headphones.CONFIG.FILE_UNDERSCORES:
new_file_name = new_file_name.replace(' ', '_')
@@ -1028,19 +1124,22 @@ def renameFiles(albumpath, downloaded_track_list, release):
new_file = os.path.join(albumpath, new_file_name)
if downloaded_track == new_file_name:
logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " is not neccessary")
logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING,
'replace') + " is not neccessary")
continue
logger.debug('Renaming %s ---> %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), new_file_name.decode(headphones.SYS_ENCODING, 'replace'))
logger.debug('Renaming %s ---> %s',
downloaded_track.decode(headphones.SYS_ENCODING, 'replace'),
new_file_name.decode(headphones.SYS_ENCODING, 'replace'))
try:
os.rename(downloaded_track, new_file)
except Exception as e:
logger.error('Error renaming file: %s. Error: %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e)
logger.error('Error renaming file: %s. Error: %s',
downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e)
continue
def updateFilePermissions(albumpaths):
for folder in albumpaths:
logger.info("Updating file permissions in %s", folder)
for r, d, f in os.walk(folder):
@@ -1053,7 +1152,8 @@ def updateFilePermissions(albumpaths):
logger.error("Could not change permissions for file: %s", full_path)
continue
else:
logger.debug("Not changing file permissions, since it is disabled: %s", full_path.decode(headphones.SYS_ENCODING, 'replace'))
logger.debug("Not changing file permissions, since it is disabled: %s",
full_path.decode(headphones.SYS_ENCODING, 'replace'))
def renameUnprocessedFolder(path, tag):
@@ -1076,7 +1176,6 @@ def renameUnprocessedFolder(path, tag):
def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_original_folder=False):
logger.info('Force checking download folder for completed downloads')
ignored = 0
@@ -1089,9 +1188,11 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
if dir:
download_dirs.append(dir.encode(headphones.SYS_ENCODING, 'replace'))
if headphones.CONFIG.DOWNLOAD_DIR and not dir:
download_dirs.append(headphones.CONFIG.DOWNLOAD_DIR.encode(headphones.SYS_ENCODING, 'replace'))
download_dirs.append(
headphones.CONFIG.DOWNLOAD_DIR.encode(headphones.SYS_ENCODING, 'replace'))
if headphones.CONFIG.DOWNLOAD_TORRENT_DIR and not dir:
download_dirs.append(headphones.CONFIG.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING, 'replace'))
download_dirs.append(
headphones.CONFIG.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING, 'replace'))
# If DOWNLOAD_DIR and DOWNLOAD_TORRENT_DIR are the same, remove the duplicate to prevent us from trying to process the same folder twice.
download_dirs = list(set(download_dirs))
@@ -1108,7 +1209,8 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
# Scan for subfolders
subfolders = os.listdir(download_dir)
ignored += helpers.path_filter_patterns(subfolders,
headphones.CONFIG.IGNORED_FOLDERS, root=download_dir)
headphones.CONFIG.IGNORED_FOLDERS,
root=download_dir)
for folder in subfolders:
path_to_folder = os.path.join(download_dir, folder)
@@ -1125,7 +1227,7 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
if folders:
logger.debug('Expanded post processing folders: %s', folders)
logger.info('Found %d folders to process (%d ignored).',
len(folders), ignored)
len(folders), ignored)
else:
logger.info('Found no folders to process. Aborting.')
return
@@ -1143,15 +1245,23 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
# underscores -> dots (this might be hit or miss since it assumes all
# spaces/underscores came from sab replacing values
logger.debug('Attempting to find album in the snatched table')
snatched = myDB.action('SELECT AlbumID, Title, Kind, Status from snatched WHERE FolderName LIKE ?', [folder_basename]).fetchone()
snatched = myDB.action(
'SELECT AlbumID, Title, Kind, Status from snatched WHERE FolderName LIKE ?',
[folder_basename]).fetchone()
if snatched:
if headphones.CONFIG.KEEP_TORRENT_FILES and snatched['Kind'] == 'torrent' and snatched['Status'] == 'Processed':
logger.info('%s is a torrent folder being preserved for seeding and has already been processed. Skipping.', folder_basename)
if headphones.CONFIG.KEEP_TORRENT_FILES and snatched['Kind'] == 'torrent' and snatched[
'Status'] == 'Processed':
logger.info(
'%s is a torrent folder being preserved for seeding and has already been processed. Skipping.',
folder_basename)
continue
else:
logger.info('Found a match in the database: %s. Verifying to make sure it is the correct album', snatched['Title'])
verify(snatched['AlbumID'], folder, snatched['Kind'], keep_original_folder=keep_original_folder)
logger.info(
'Found a match in the database: %s. Verifying to make sure it is the correct album',
snatched['Title'])
verify(snatched['AlbumID'], folder, snatched['Kind'],
keep_original_folder=keep_original_folder)
continue
# Attempt 2: strip release group id from filename
@@ -1165,13 +1275,19 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
if rgid:
rgid = possible_rgid
release = myDB.action('SELECT ArtistName, AlbumTitle, AlbumID from albums WHERE AlbumID=?', [rgid]).fetchone()
release = myDB.action(
'SELECT ArtistName, AlbumTitle, AlbumID from albums WHERE AlbumID=?',
[rgid]).fetchone()
if release:
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album', release['ArtistName'], release['AlbumTitle'])
verify(release['AlbumID'], folder, forced=True, keep_original_folder=keep_original_folder)
logger.info(
'Found a match in the database: %s - %s. Verifying to make sure it is the correct album',
release['ArtistName'], release['AlbumTitle'])
verify(release['AlbumID'], folder, forced=True,
keep_original_folder=keep_original_folder)
continue
else:
logger.info('Found a (possibly) valid Musicbrainz release group id in album folder name.')
logger.info(
'Found a (possibly) valid Musicbrainz release group id in album folder name.')
verify(rgid, folder, forced=True)
continue
@@ -1184,13 +1300,18 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
name = album = year = None
if name and album:
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?', [name, album]).fetchone()
release = myDB.action(
'SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?',
[name, album]).fetchone()
if release:
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album', release['ArtistName'], release['AlbumTitle'])
logger.info(
'Found a match in the database: %s - %s. Verifying to make sure it is the correct album',
release['ArtistName'], release['AlbumTitle'])
verify(release['AlbumID'], folder, keep_original_folder=keep_original_folder)
continue
else:
logger.info('Querying MusicBrainz for the release group id for: %s - %s', name, album)
logger.info('Querying MusicBrainz for the release group id for: %s - %s', name,
album)
try:
rgid = mb.findAlbumID(helpers.latinToAscii(name), helpers.latinToAscii(album))
except:
@@ -1219,13 +1340,18 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
name = album = None
if name and album:
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?', [name, album]).fetchone()
release = myDB.action(
'SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?',
[name, album]).fetchone()
if release:
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album', release['ArtistName'], release['AlbumTitle'])
logger.info(
'Found a match in the database: %s - %s. Verifying to make sure it is the correct album',
release['ArtistName'], release['AlbumTitle'])
verify(release['AlbumID'], folder, keep_original_folder=keep_original_folder)
continue
else:
logger.info('Querying MusicBrainz for the release group id for: %s - %s', name, album)
logger.info('Querying MusicBrainz for the release group id for: %s - %s', name,
album)
try:
rgid = mb.findAlbumID(helpers.latinToAscii(name), helpers.latinToAscii(album))
except:
@@ -1243,13 +1369,18 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
logger.debug('Attempt to extract album name by assuming it is the folder name')
if '-' not in folder_basename:
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE AlbumTitle LIKE ?', [folder_basename]).fetchone()
release = myDB.action(
'SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE AlbumTitle LIKE ?',
[folder_basename]).fetchone()
if release:
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album', release['ArtistName'], release['AlbumTitle'])
logger.info(
'Found a match in the database: %s - %s. Verifying to make sure it is the correct album',
release['ArtistName'], release['AlbumTitle'])
verify(release['AlbumID'], folder, keep_original_folder=keep_original_folder)
continue
else:
logger.info('Querying MusicBrainz for the release group id for: %s', folder_basename)
logger.info('Querying MusicBrainz for the release group id for: %s',
folder_basename)
try:
rgid = mb.findAlbumID(album=helpers.latinToAscii(folder_basename))
except:
@@ -1264,6 +1395,6 @@ def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None, keep_orig
# Fail here
logger.info("Couldn't parse '%s' into any valid format. If adding " \
"albums from another source, they must be in an 'Artist - Album " \
"[Year]' format, or end with the musicbrainz release group id.",
folder_basename)
"albums from another source, they must be in an 'Artist - Album " \
"[Year]' format, or end with the musicbrainz release group id.",
folder_basename)