fix: redundant userdata update for music rating
add settings for music ratings import/export
This commit is contained in:
parent
2d7f8a5a06
commit
ae06548c9d
5 changed files with 142 additions and 106 deletions
|
@ -112,9 +112,11 @@ if __name__ == '__main__':
|
||||||
if newvalue:
|
if newvalue:
|
||||||
newvalue = int(newvalue)
|
newvalue = int(newvalue)
|
||||||
if newvalue > 5: newvalue = "5"
|
if newvalue > 5: newvalue = "5"
|
||||||
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
if utils.settings('enableUpdateSongRating') == "true":
|
||||||
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
||||||
API.updateUserRating(embyid, like, favourite, deletelike)
|
if utils.settings('enableExportSongRating') == "true":
|
||||||
|
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
||||||
|
API.updateUserRating(embyid, like, favourite, deletelike)
|
||||||
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
|
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
|
||||||
kodicursor.execute(query, (newvalue,itemid,))
|
kodicursor.execute(query, (newvalue,itemid,))
|
||||||
kodiconn.commit()
|
kodiconn.commit()
|
||||||
|
|
|
@ -274,6 +274,7 @@ class DownloadUtils():
|
||||||
verify=verifyssl)
|
verify=verifyssl)
|
||||||
|
|
||||||
elif type == "POST":
|
elif type == "POST":
|
||||||
|
print url
|
||||||
r = requests.post(url,
|
r = requests.post(url,
|
||||||
json=postBody,
|
json=postBody,
|
||||||
headers=header,
|
headers=header,
|
||||||
|
|
|
@ -1613,6 +1613,9 @@ class Music(Items):
|
||||||
Items.__init__(self, embycursor, musiccursor)
|
Items.__init__(self, embycursor, musiccursor)
|
||||||
|
|
||||||
self.directstream = utils.settings('streamMusic') == "true"
|
self.directstream = utils.settings('streamMusic') == "true"
|
||||||
|
self.enableimportsongrating = utils.settings('enableImportSongRating') == "true"
|
||||||
|
self.enableexportsongrating = utils.settings('enableExportSongRating') == "true"
|
||||||
|
self.enableupdatesongrating = utils.settings('enableUpdateSongRating') == "true"
|
||||||
self.userid = utils.window('emby_currUser')
|
self.userid = utils.window('emby_currUser')
|
||||||
self.server = utils.window('emby_server%s' % self.userid)
|
self.server = utils.window('emby_server%s' % self.userid)
|
||||||
|
|
||||||
|
@ -1952,9 +1955,9 @@ class Music(Items):
|
||||||
#the server doesn't support comment on songs so this will always be empty
|
#the server doesn't support comment on songs so this will always be empty
|
||||||
comment = API.getOverview()
|
comment = API.getOverview()
|
||||||
|
|
||||||
#if enabled, try to get the rating and comment value from the file itself
|
#if enabled, try to get the rating from file and/or emby
|
||||||
if not self.directstream:
|
if not self.directstream:
|
||||||
rating, comment, hasEmbeddedCover = self.getSongTagsFromFile(itemid, rating, API)
|
rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating)
|
||||||
|
|
||||||
##### GET THE FILE AND PATH #####
|
##### GET THE FILE AND PATH #####
|
||||||
if self.directstream:
|
if self.directstream:
|
||||||
|
@ -2191,7 +2194,7 @@ class Music(Items):
|
||||||
# Process playstates
|
# Process playstates
|
||||||
playcount = userdata['PlayCount']
|
playcount = userdata['PlayCount']
|
||||||
dateplayed = userdata['LastPlayedDate']
|
dateplayed = userdata['LastPlayedDate']
|
||||||
rating, comment, hasEmbeddedCover = self.getSongTagsFromFile(itemid, rating, API)
|
rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating)
|
||||||
|
|
||||||
query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?"
|
query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?"
|
||||||
kodicursor.execute(query, (playcount, dateplayed, rating, kodiid))
|
kodicursor.execute(query, (playcount, dateplayed, rating, kodiid))
|
||||||
|
@ -2203,89 +2206,6 @@ class Music(Items):
|
||||||
|
|
||||||
emby_db.updateReference(itemid, checksum)
|
emby_db.updateReference(itemid, checksum)
|
||||||
|
|
||||||
def getSongTagsFromFile(self, embyid, emby_rating, API):
|
|
||||||
|
|
||||||
kodicursor = self.kodicursor
|
|
||||||
|
|
||||||
previous_values = None
|
|
||||||
filename = API.getFilePath()
|
|
||||||
rating = 0
|
|
||||||
emby_rating = int(round(emby_rating, 0))
|
|
||||||
|
|
||||||
#get file rating and comment tag from file itself.
|
|
||||||
#TODO: should we make this an optional setting if it impacts sync speed too much ?
|
|
||||||
file_rating, comment, hasEmbeddedCover = musicutils.getSongTags(filename)
|
|
||||||
|
|
||||||
emby_dbitem = self.emby_db.getItem_byId(embyid)
|
|
||||||
try:
|
|
||||||
kodiid = emby_dbitem[0]
|
|
||||||
except TypeError:
|
|
||||||
# Item is not in database.
|
|
||||||
currentvalue = None
|
|
||||||
else:
|
|
||||||
query = "SELECT rating FROM song WHERE idSong = ?"
|
|
||||||
kodicursor.execute(query, (kodiid,))
|
|
||||||
try:
|
|
||||||
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
|
|
||||||
except: currentvalue = None
|
|
||||||
|
|
||||||
# Only proceed if we actually have a rating from the file
|
|
||||||
if file_rating is None and currentvalue:
|
|
||||||
return (currentvalue, comment, False)
|
|
||||||
elif file_rating is None and not currentvalue:
|
|
||||||
return (emby_rating, comment, False)
|
|
||||||
|
|
||||||
self.logMsg("getSongTagsFromFile --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
|
|
||||||
|
|
||||||
updateFileRating = False
|
|
||||||
updateEmbyRating = False
|
|
||||||
|
|
||||||
if currentvalue != None:
|
|
||||||
# we need to translate the emby values...
|
|
||||||
if emby_rating == 1 and currentvalue == 2:
|
|
||||||
emby_rating = 2
|
|
||||||
if emby_rating == 3 and currentvalue == 4:
|
|
||||||
emby_rating = 4
|
|
||||||
|
|
||||||
if (emby_rating == file_rating) and (file_rating != currentvalue):
|
|
||||||
#the rating has been updated from kodi itself, update change to both emby ands file
|
|
||||||
rating = currentvalue
|
|
||||||
updateFileRating = True
|
|
||||||
updateEmbyRating = True
|
|
||||||
elif (emby_rating != currentvalue) and (file_rating == currentvalue):
|
|
||||||
#emby rating changed - update the file
|
|
||||||
rating = emby_rating
|
|
||||||
updateFileRating = True
|
|
||||||
elif (file_rating != currentvalue) and (emby_rating == currentvalue):
|
|
||||||
#file rating was updated, sync change to emby
|
|
||||||
rating = file_rating
|
|
||||||
updateEmbyRating = True
|
|
||||||
elif (emby_rating != currentvalue) and (file_rating != currentvalue):
|
|
||||||
#both ratings have changed (corner case) - the highest rating wins...
|
|
||||||
if emby_rating > file_rating:
|
|
||||||
rating = emby_rating
|
|
||||||
updateFileRating = True
|
|
||||||
else:
|
|
||||||
rating = file_rating
|
|
||||||
updateEmbyRating = True
|
|
||||||
else:
|
|
||||||
#nothing has changed, just return the current value
|
|
||||||
rating = currentvalue
|
|
||||||
else:
|
|
||||||
# no rating yet in DB, always prefer file details...
|
|
||||||
rating = file_rating
|
|
||||||
updateEmbyRating = True
|
|
||||||
|
|
||||||
if updateFileRating:
|
|
||||||
musicutils.updateRatingToFile(rating, filename)
|
|
||||||
|
|
||||||
if updateEmbyRating:
|
|
||||||
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
|
|
||||||
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(rating)
|
|
||||||
API.updateUserRating(embyid, like, favourite, deletelike)
|
|
||||||
|
|
||||||
return (rating, comment, hasEmbeddedCover)
|
|
||||||
|
|
||||||
def remove(self, itemid):
|
def remove(self, itemid):
|
||||||
# Remove kodiid, fileid, pathid, emby reference
|
# Remove kodiid, fileid, pathid, emby reference
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
|
|
|
@ -17,9 +17,9 @@ import base64
|
||||||
def logMsg(msg, lvl=1):
|
def logMsg(msg, lvl=1):
|
||||||
utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
|
utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
|
||||||
|
|
||||||
def getRealFileName(filename):
|
def getRealFileName(filename,useTemp = False):
|
||||||
#get the filename path accessible by python if possible...
|
#get the filename path accessible by python if possible...
|
||||||
isTemp = False
|
useTemp = False
|
||||||
|
|
||||||
if not xbmcvfs.exists(filename):
|
if not xbmcvfs.exists(filename):
|
||||||
logMsg( "File does not exist! %s" %(filename), 0)
|
logMsg( "File does not exist! %s" %(filename), 0)
|
||||||
|
@ -38,14 +38,16 @@ def getRealFileName(filename):
|
||||||
filename = filename.replace("smb://","\\\\").replace("/","\\")
|
filename = filename.replace("smb://","\\\\").replace("/","\\")
|
||||||
else:
|
else:
|
||||||
#file can not be accessed by python directly, we copy it for processing...
|
#file can not be accessed by python directly, we copy it for processing...
|
||||||
isTemp = True
|
useTemp = True
|
||||||
|
|
||||||
|
if useTemp:
|
||||||
if "/" in filename: filepart = filename.split("/")[-1]
|
if "/" in filename: filepart = filename.split("/")[-1]
|
||||||
else: filepart = filename.split("\\")[-1]
|
else: filepart = filename.split("\\")[-1]
|
||||||
tempfile = "special://temp/"+filepart
|
tempfile = "special://temp/"+filepart
|
||||||
xbmcvfs.copy(filename, tempfile)
|
xbmcvfs.copy(filename, tempfile)
|
||||||
filename = xbmc.translatePath(tempfile).decode("utf-8")
|
filename = xbmc.translatePath(tempfile).decode("utf-8")
|
||||||
|
|
||||||
return (isTemp,filename)
|
return (useTemp,filename)
|
||||||
|
|
||||||
def getEmbyRatingFromKodiRating(rating):
|
def getEmbyRatingFromKodiRating(rating):
|
||||||
# Translation needed between Kodi/ID3 rating and emby likes/favourites:
|
# Translation needed between Kodi/ID3 rating and emby likes/favourites:
|
||||||
|
@ -62,6 +64,111 @@ def getEmbyRatingFromKodiRating(rating):
|
||||||
if (rating >= 5): favourite = True
|
if (rating >= 5): favourite = True
|
||||||
return(like, favourite, deletelike)
|
return(like, favourite, deletelike)
|
||||||
|
|
||||||
|
def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating):
|
||||||
|
previous_values = None
|
||||||
|
filename = API.getFilePath()
|
||||||
|
rating = 0
|
||||||
|
emby_rating = int(round(emby_rating, 0))
|
||||||
|
|
||||||
|
#get file rating and comment tag from file itself.
|
||||||
|
if enableimportsongrating:
|
||||||
|
file_rating, comment, hasEmbeddedCover = getSongTags(filename)
|
||||||
|
else:
|
||||||
|
file_rating = 0
|
||||||
|
comment = ""
|
||||||
|
hasEmbeddedCover = False
|
||||||
|
|
||||||
|
|
||||||
|
emby_dbitem = emby_db.getItem_byId(embyid)
|
||||||
|
try:
|
||||||
|
kodiid = emby_dbitem[0]
|
||||||
|
except TypeError:
|
||||||
|
# Item is not in database.
|
||||||
|
currentvalue = None
|
||||||
|
else:
|
||||||
|
query = "SELECT rating FROM song WHERE idSong = ?"
|
||||||
|
kodicursor.execute(query, (kodiid,))
|
||||||
|
try:
|
||||||
|
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
|
||||||
|
except: currentvalue = None
|
||||||
|
|
||||||
|
# Only proceed if we actually have a rating from the file
|
||||||
|
if file_rating is None and currentvalue:
|
||||||
|
return (currentvalue, comment, False)
|
||||||
|
elif file_rating is None and not currentvalue:
|
||||||
|
return (emby_rating, comment, False)
|
||||||
|
|
||||||
|
logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
|
||||||
|
|
||||||
|
updateFileRating = False
|
||||||
|
updateEmbyRating = False
|
||||||
|
|
||||||
|
if currentvalue != None:
|
||||||
|
# we need to translate the emby values...
|
||||||
|
if emby_rating == 1 and currentvalue == 2:
|
||||||
|
emby_rating = 2
|
||||||
|
if emby_rating == 3 and currentvalue == 4:
|
||||||
|
emby_rating = 4
|
||||||
|
|
||||||
|
#if updating rating into file is disabled, we ignore the rating in the file...
|
||||||
|
if not enableupdatesongrating:
|
||||||
|
file_rating = currentvalue
|
||||||
|
#if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
|
||||||
|
if not enableexportsongrating:
|
||||||
|
emby_rating = currentvalue
|
||||||
|
|
||||||
|
if (emby_rating == file_rating) and (file_rating != currentvalue):
|
||||||
|
#the rating has been updated from kodi itself, update change to both emby ands file
|
||||||
|
rating = currentvalue
|
||||||
|
updateFileRating = True
|
||||||
|
updateEmbyRating = True
|
||||||
|
elif (emby_rating != currentvalue) and (file_rating == currentvalue):
|
||||||
|
#emby rating changed - update the file
|
||||||
|
rating = emby_rating
|
||||||
|
updateFileRating = True
|
||||||
|
elif (file_rating != currentvalue) and (emby_rating == currentvalue):
|
||||||
|
#file rating was updated, sync change to emby
|
||||||
|
rating = file_rating
|
||||||
|
updateEmbyRating = True
|
||||||
|
elif (emby_rating != currentvalue) and (file_rating != currentvalue):
|
||||||
|
#both ratings have changed (corner case) - the highest rating wins...
|
||||||
|
if emby_rating > file_rating:
|
||||||
|
rating = emby_rating
|
||||||
|
updateFileRating = True
|
||||||
|
else:
|
||||||
|
rating = file_rating
|
||||||
|
updateEmbyRating = True
|
||||||
|
else:
|
||||||
|
#nothing has changed, just return the current value
|
||||||
|
rating = currentvalue
|
||||||
|
else:
|
||||||
|
# no rating yet in DB
|
||||||
|
if enableimportsongrating:
|
||||||
|
#prefer the file rating
|
||||||
|
rating = file_rating
|
||||||
|
#determine if we should also send the rating to emby server
|
||||||
|
if enableexportsongrating:
|
||||||
|
if emby_rating == 1 and file_rating == 2:
|
||||||
|
emby_rating = 2
|
||||||
|
if emby_rating == 3 and file_rating == 4:
|
||||||
|
emby_rating = 4
|
||||||
|
if emby_rating != file_rating:
|
||||||
|
updateEmbyRating = True
|
||||||
|
|
||||||
|
elif enableexportsongrating:
|
||||||
|
#set the initial rating to emby value
|
||||||
|
rating = emby_rating
|
||||||
|
|
||||||
|
if updateFileRating and enableupdatesongrating:
|
||||||
|
updateRatingToFile(rating, filename)
|
||||||
|
|
||||||
|
if updateEmbyRating and enableexportsongrating:
|
||||||
|
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
|
||||||
|
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
|
||||||
|
API.updateUserRating(embyid, like, favourite, deletelike)
|
||||||
|
|
||||||
|
return (rating, comment, hasEmbeddedCover)
|
||||||
|
|
||||||
def getSongTags(file):
|
def getSongTags(file):
|
||||||
# Get the actual ID3 tags for music songs as the server is lacking that info
|
# Get the actual ID3 tags for music songs as the server is lacking that info
|
||||||
rating = 0
|
rating = 0
|
||||||
|
@ -120,31 +227,33 @@ def getSongTags(file):
|
||||||
def updateRatingToFile(rating, file):
|
def updateRatingToFile(rating, file):
|
||||||
#update the rating from Emby to the file
|
#update the rating from Emby to the file
|
||||||
|
|
||||||
isTemp,filename = getRealFileName(file)
|
isTemp,tempfile = getRealFileName(file,True)
|
||||||
logMsg( "setting song rating: %s for filename: %s" %(rating,filename))
|
logMsg( "setting song rating: %s for filename: %s" %(rating,tempfile))
|
||||||
|
|
||||||
if not filename:
|
if not tempfile:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if filename.lower().endswith(".flac"):
|
if tempfile.lower().endswith(".flac"):
|
||||||
audio = FLAC(filename)
|
audio = FLAC(tempfile)
|
||||||
calcrating = int(round((float(rating) / 5) * 100, 0))
|
calcrating = int(round((float(rating) / 5) * 100, 0))
|
||||||
audio["rating"] = str(calcrating)
|
audio["rating"] = str(calcrating)
|
||||||
audio.save()
|
audio.save()
|
||||||
elif filename.lower().endswith(".mp3"):
|
elif tempfile.lower().endswith(".mp3"):
|
||||||
audio = ID3(filename)
|
audio = ID3(tempfile)
|
||||||
calcrating = int(round((float(rating) / 5) * 255, 0))
|
calcrating = int(round((float(rating) / 5) * 255, 0))
|
||||||
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
|
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
|
||||||
audio.save()
|
audio.save()
|
||||||
else:
|
else:
|
||||||
logMsg( "Not supported fileformat: %s" %(filename))
|
logMsg( "Not supported fileformat: %s" %(tempfile))
|
||||||
|
|
||||||
#remove tempfile if needed....
|
#once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp
|
||||||
if isTemp:
|
#safety check: we check if we can read the new tags successfully from the tempfile before deleting anything
|
||||||
|
checksum_rating, checksum_comment, checksum_hasEmbeddedCover = getSongTags(tempfile)
|
||||||
|
if checksum_rating == rating:
|
||||||
xbmcvfs.delete(file)
|
xbmcvfs.delete(file)
|
||||||
xbmcvfs.copy(filename,file)
|
xbmcvfs.copy(tempfile,file)
|
||||||
xbmcvfs.delete(filename)
|
xbmcvfs.delete(tempfile)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
#file in use ?
|
#file in use ?
|
||||||
|
|
|
@ -60,6 +60,10 @@
|
||||||
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" default="false" />
|
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="Ignore specials in next episodes" default="false" />
|
||||||
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" />
|
<setting id="additionalUsers" type="text" label="Permanent users to add to the session" default="" />
|
||||||
<setting id="startupDelay" type="number" label="Startup Delay (seconds)" default="0" option="int" />
|
<setting id="startupDelay" type="number" label="Startup Delay (seconds)" default="0" option="int" />
|
||||||
|
<setting type="sep" label="Music metadata options" />
|
||||||
|
<setting id="enableImportSongRating" type="bool" label="Import Music song rating directly from files" default="true" />
|
||||||
|
<setting id="enableExportSongRating" type="bool" label="Convert Music song rating to Emby (likes/favourites)" default="false" />
|
||||||
|
<setting id="enableUpdateSongRating" type="bool" label="Allow rating in song files to be updated by Kodi/Emby" default="false" />
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="30022">
|
<category label="30022">
|
||||||
|
|
Loading…
Reference in a new issue