Kodi Community Forum

Full Version: Weird Metadata issue on 2nd play (1st play works as expected)
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Ive built my own addon which can play items and gets the next item to play and adds it to a playlist. This all works fine and I can get all the correct metadata setup on a listitem and then linked to my playlist entries.

However I seem to be getting an issue where the VideoPlayer.DBTYPE is always empty, despite the mediatype being correctly set and on the 1st play of a file all the meta data displays correctly in Yatse.  But if you stop and replay the same file it just displays basic meta data in yatse (eg date =1969 but date is actually set).

Similarly if you play a file, 1st time yatse shows all the correct metadata, 2nd time it doesnt, but when it plays the next file on the playlist everything is ok again.
The metadata for the 1st play list item and the playlist listitems are being setup in basically the same way.  I've made a couple of changes here and there trying to get it to work consistently but nothing seems to work once its played once.

I think it has something to do with the Plugin content or plugin handle perhaps? Like the list item is in some way persistent on the same addon handle so it remembers and doesnt work properly the 2nd time? Or something similar but more directly tied to the DB?

python:
xbmcplugin.setContent(-1, 'episodes')

li = xbmcgui.ListItem(label, iconImage=thumb)
li.setProperty('fanart_image', fanart)
li.setProperty('startoffset', str(resumeTimeInSeconds))
li.setProperty('DBID', dbid)
li.setProperty('TVShowTitle', show_title)
li.setProperty('Episode', str(show_episode))
li.setProperty('Season', str(show_season))
#li.setProperty('Cast', cast)
#li.setProperty('CastAndRole', cast_role)
li.setProperty('Duration', duration)
li.setArt({ 'poster': poster, 'fanart': fanart, 'banner': banner, 'clearlogo': clearlogo, 'landscape': landscape, 'thumb': thumb})

try:
    json_result = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "id":1, "method": "VideoLibrary.GetEpisodeDetails", "params": {"episodeid": '+str(dbid)+', "properties": ["art"]}}')
    json_result = json.loads(json_result)
    #xbmc.log(str(json_result['result']['episodedetails']['art'])+'===>PHIL', level=xbmc.LOGNOTICE)
    li.setArt(json_result['result']['episodedetails']['art'])
except:
    pass

li.setProperty('IsPlayable', 'true')
li.setProperty('IsFolder', 'false')
#li.setInfo('video', {'title': title,'genre': genre, 'plotoutline': plotoutline, 'plot': plot, 'path': PTN_download,'premiered': premiered, 'dbid': dbid, 'mediatype': dbtype, 'writer': writer, 'director': director, 'duration': duration, 'IMDBNumber': imdb, 'MPAA': MPAA, 'Rating': rating, 'Studio': studio, 'Year': year, 'Tagline': tagline, 'Set': set, 'SetID': setid})
li.setInfo('video', {'title': title, 'TVShowTitle': show_title, 'Episode': str(show_episode), 'Season': show_season,'genre': genre, 'plotoutline': plotoutline, 'plot': plot, 'path': PTN_download,'premiered': premiered, 'dbid': dbid, 'mediatype': dbtype, 'duration': duration, 'IMDBNumber': imdb, 'Rating': rating, 'Year': year})

try:
    json_result = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "id":1, "method": "VideoLibrary.GetEpisodeDetails", "params": {"episodeid": '+str(dbid)+', "properties": ["title","plot","votes","rating","writer","firstaired","playcount","runtime","director","productioncode","season","episode","originaltitle","showtitle","cast","streamdetails","lastplayed","fanart","thumbnail","file","resume","tvshowid","dateadded","uniqueid","specialsortseason","specialsortepisode","userrating","seasonid","ratings"]}}')
    json_result = json.loads(json_result)
    #xbmc.log(str(json_result['result']['episodedetails']['art'])+'===>PHIL', level=xbmc.LOGNOTICE)
    json_result['result']['episodedetails']['mediatype'] = 'episode'
    li.setInfo(type='Video', infoLabels=str(json_result['result']['episodedetails']))
except:
    pass

li.setPath(PTN_download)

xbmcgui.Window(10000).setProperty('Next_EP.ResolvedUrl', 'true')
xbmcgui.Window(10000).setProperty('Next_EP.Url', PTN_download)
xbmcgui.Window(10000).clearProperty('Next_EP.TMDB_action')
playlist.add(PTN_download, li)
#xbmcplugin.addDirectoryItem(handle=-1, url=PTN_download , listitem=li, isFolder=False)
xbmcplugin.setResolvedUrl(-1, True, li)
xbmcplugin.endOfDirectory(-1)
On line 26 I see 'mediatype': dbtype which should set the listitem properly if dbtype is defined.  What I can't see is where the value of dbtype is being set.  To be certain of this you might want to manually set the value to episode or movie higher iup n your code to see if you get the desired results.  Then you can work on how to set it based upon the media type which is playing.


Jeff
(2021-02-22, 20:22)jbinkley60 Wrote: [ -> ]On line 26 I see 'mediatype': dbtype which should set the listitem properly if dbtype is defined.  What I can't see is where the value of dbtype is being set.  To be certain of this you might want to manually set the value to episode or movie higher iup n your code to see if you get the desired results.  Then you can work on how to set it based upon the media type which is playing.


Jeff
The "dbtype" field is populated further up the script, and in the "1st play" script i've manually set it to 'episode' in a couple of different places and a couple of different ways to see what effect if any that had.

Like follows:

python:
li = xbmcgui.ListItem(label, iconImage=thumb)
li.setProperty('fanart_image', fanart)
li.setProperty('startoffset', str(resumeTimeInSeconds))
li.setProperty('DBID', dbid)
li.setProperty('mediatype', 'episode')
li.setProperty('DBTYPE', 'episode')
li.setProperty('TVShowTitle', show_title)
li.setProperty('Episode', str(show_episode))
li.setProperty('Season', str(show_season))
li.setProperty('Duration', duration)
li.setArt({ 'poster': poster, 'fanart': fanart, 'banner': banner, 'clearlogo': clearlogo, 'landscape': landscape, 'thumb': thumb})
try:
    json_result = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "id":1, "method": "VideoLibrary.GetEpisodeDetails", "params": {"episodeid": '+str(dbid)+', "properties": ["art"]}}')
    json_result = json.loads(json_result)
    art = json_result['result']['episodedetails']['art']
    li.setArt(json_result['result']['episodedetails']['art'])
except:
    pass
li.setProperty('IsPlayable', 'true')
li.setProperty('IsFolder', 'false')
li.setProperty('DBTYPE', 'episode')
li.setInfo('video', {'title': title, 'TVShowTitle': show_title, 'Episode': str(show_episode), 'Season': show_season,'genre': genre, 'plotoutline': plotoutline, 'plot': plot, 'path': PTN_download,'premiered': premiered, 'dbid': dbid, 'mediatype': dbtype, 'duration': duration, 'IMDBNumber': imdb, 'Rating': rating, 'Year': year})
li.setInfo('video', {'sortseason': int(show_season), 'rating': str(rating), 'plotoutline': str(plot), 'year': int(year), 'duration': int(duration), 'FileNameAndPath': str(PTN_download), 'plot': str(plot), 'votes': 0, 'sortepisode': int(show_episode), 'title': str(episode_name), 'aired': str(premiered)+'T00:00:00.000Z', 'season': int(show_season), 'tvshowtitle': str(show_title), 'mediatype': 'episode', 'genre': [], 'dateadded': str(premiered)+'T00:00:00.000Z', 'episode': int(show_episode), 'premiered': str(premiered)+'T00:00:00.000Z', 'originaltitle': str(episode_name), 'sorttitle': str(episode_name)})

try:
    json_result = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "id":1, "method": "VideoLibrary.GetEpisodeDetails", "params": {"episodeid": '+str(dbid)+', "properties": ["title","plot","votes","rating","writer","firstaired","playcount","runtime","director","productioncode","season","episode","originaltitle","showtitle","cast","streamdetails","lastplayed","fanart","thumbnail","file","resume","tvshowid","dateadded","uniqueid","specialsortseason","specialsortepisode","userrating","seasonid","ratings"]}}')
    json_result = unicode(json_result, 'utf-8', errors='ignore')
    json_result = json.loads(json_result)
    json_result['result']['episodedetails']['mediatype'] = 'episode'
    json_result['result']['episodedetails']['sortseason'] = int(show_season)
    json_result['result']['episodedetails']['rating'] = str(rating)
    json_result['result']['episodedetails']['plotoutline'] = str(plot)
    json_result['result']['episodedetails']['year'] = int(year)
    json_result['result']['episodedetails']['duration'] = int(duration)
    json_result['result']['episodedetails']['FileNameAndPath'] = str(PTN_download)
    json_result['result']['episodedetails']['plot'] = str(plot)
    json_result['result']['episodedetails']['votes'] = 0
    json_result['result']['episodedetails']['sortepisode'] = int(show_episode)
    json_result['result']['episodedetails']['title'] = str(episode_name)
    json_result['result']['episodedetails']['aired'] = str(premiered)+'T00:00:00.000Z'
    json_result['result']['episodedetails']['season'] = int(show_season)
    json_result['result']['episodedetails']['tvshowtitle'] = str(show_title)
    json_result['result']['episodedetails']['mediatype'] = 'episode'
    json_result['result']['episodedetails']['genre'] = [(str(genre))]
    json_result['result']['episodedetails']['dateadded'] = str(premiered)+'T00:00:00.000Z'
    json_result['result']['episodedetails']['episode'] = int(show_episode)
    json_result['result']['episodedetails']['premiered'] = str(premiered)+'T00:00:00.000Z'
    json_result['result']['episodedetails']['originaltitle'] = str(episode_name)
    json_result['result']['episodedetails']['sorttitle'] = str(episode_name)
    json_result['result']['episodedetails']['DBTYPE'] = 'episode'
    json_result['result']['episodedetails']['DBID'] = int(dbid)
    json_result['result']['episodedetails']['art'] = art
    xbmc.log(str(json_result['result']['episodedetails'])+'===>PHIL', level=xbmc.LOGNOTICE)
    li.setProperty('Cast', str(json_result['result']['episodedetails']['cast']))
    li.setProperty('CastAndRole', str(json_result['result']['episodedetails']['cast']))
    li.setInfo(type='Video', infoLabels=str(json_result['result']['episodedetails']))
    
except:
    pass
li.setProperty('DBTYPE', 'episode')
li.setPath(PTN_download)

playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
current_action = xbmcgui.Window(10000).getProperty('Next_EP.TMDB_action')

xbmc.executebuiltin('Dialog.Close(busydialog)')
xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
playlist.add(PTN_download, li)
xbmcplugin.setResolvedUrl(-1, True, li)
xbmcplugin.endOfDirectory(-1)

xbmc.Player().play(playlist)


And having DBTYPE/mediatype manually set didnt make any difference.

However after having posted the thread I was looking at the FILES db table and I think i've fixed my own problem.
So it seems as if the path to the resolved files is preserved in the DB and this preempts the specifically populated metadata.  So if I delete the entry when the file is resolved this solves the problem.

python:
import sqlite3
con = sqlite3.connect('~/.kodi/userdata/Database/MyVideos116.db')
cur = con.cursor()
delete_result = cur.execute("DELETE FROM files WHERE strFilename = '"+str(file_name)+"' ;")
con.commit()
cur.close()
Nice find.  FYI, I prefer to use the parameter approach vs. string concatenation for my DB SQL commands. 

db.execute('DELETE FROM files WHERE strFilename=?',(ifile_name,))

I find it much easier when dealing with larger more complex queries with lots of fields and variables.


Jeff
(2021-02-22, 21:12)jbinkley60 Wrote: [ -> ]Nice find.  FYI, I prefer to use the parameter approach vs. string concatenation for my DB SQL commands. 

db.execute('DELETE FROM files WHERE strFilername=?',(ifile_name,))

I find it much easier when dealing with larger more complex queries with lots of fields and variables.


Jeff

Are the parameters a python thing or an SQL thing?

Yeah I only just noticed that the meta data in yatse wasn't always guff and started looking into it.
So I'm glad I was able to puzzle it out as it was only going to annoy me.
SQL Lite by its nature is parameterized but the Python SQL Lite module allows for the variable to parameter mapping, which makes for safer and cleaner code. 

Here's an example of one of my more complex queries.  There's no way I'd try this with string concatenation.

        curf = db.execute('SELECT idFile, playcount, idPath, lastPlayed FROM files INNER JOIN episode \
        USING (idFile) INNER JOIN path USING (idPath) INNER JOIN tvshow USING (idshow)                \
        WHERE tvshow.c00=? and idParentPath=? and episode.c12=? and episode.c13=? COLLATE NOCASE',    \
        (mseries, ppathnumb, mseason, mepisode))     # Check if episode exists in Kodi DB under parent path

Just be aware that the recommended approach to accessing the Kodi database is via JSONRPC and not direct SQl Lite calls.

Jeff
(2021-02-23, 11:51)jbinkley60 Wrote: [ -> ]SQL Lite by its nature is parameterized but the Python SQL Lite module allows for the variable to parameter mapping, which makes for safer and cleaner code. 

Here's an example of one of my more complex queries.  There's no way I'd try this with string concatenation.

        curf = db.execute('SELECT idFile, playcount, idPath, lastPlayed FROM files INNER JOIN episode \
        USING (idFile) INNER JOIN path USING (idPath) INNER JOIN tvshow USING (idshow)                \
        WHERE tvshow.c00=? and idParentPath=? and episode.c12=? and episode.c13=? COLLATE NOCASE',    \
        (mseries, ppathnumb, mseason, mepisode))     # Check if episode exists in Kodi DB under parent path

Just be aware that the recommended approach to accessing the Kodi database is via JSONRPC and not direct SQl Lite calls.

Jeff

Ok cool, ill have to play around with that.
I generally prefer SQL because i know how to do it. Anytime i need to try and figure out how to do something new via jsonrpc I always struggle with the syntax.

Also is it actually possible to delete entries from the DB via jsonrpc?
I know there is stuff you can do in sql which is pretty easy but would require multiple jsonrpc commands. For example getting the next episode of shows after the last episode played can be done in a single sql query. Also multiple sort options in sql which arent possible for jsonrpc calls.
Yes, there is a way to do what you are doing in SQL (file table record delete) with JSON RPC but I am not the person to ask how to do it.  At some point I want to spend more time learning it.


Jeff
(2021-02-23, 17:09)jbinkley60 Wrote: [ -> ]Yes, there is a way to do what you are doing in SQL (file table record delete) with JSON RPC but I am not the person to ask how to do it.  At some point I want to spend more time learning it.


Jeff
Well i went looking on the Wiki and it gave no indications of any delete method which wasn't to do with PVR timers.
Personally I just use whichever method is most convenient.  Can't figure out the jsonrpc call to get the information I need but can knock some sql together which will do it, ill do it in sql.

I dont get why kodi devs are so precious about the database.  If all you are doing is querying the database who cares what method returns the information you need?
Why run two queries to get the same information which can be returned in SQL in a one go by using multiple subqueries, in ways completely impossible to jsonprc. I keep hoping the next update they will make the JSONRPC basically a frontend for SQL queries, which are clearly much more powerful and useful to someone more tv centric like myself.
I share many of your sentiments on this topic.  There is also a performance hit for JSON RPC.  With SQL calls I can refresh 13K records on an Intel NUC in a minute and 5 minutes on a Vero 4K+ / RPi 4 platform.  I did want to ask, if you want some code to automatically detect the Kodi version number and open the right database file I can provide it to you.


Jeff
(2021-02-23, 18:59)jbinkley60 Wrote: [ -> ]I share many of your sentiments on this topic.  There is also a performance hit for JSON RPC.  With SQL calls I can refresh 13K records on an Intel NUC in a minute and 5 minutes on a Vero 4K+ / RPi 4 platform.  I did want to ask, if you want some code to automatically detect the Kodi version number and open the right database file I can provide it to you.


Jeff
Yeah actually that would be useful, cheers.  Ive got some code which I use to detect the "cpuinfo" to detect my Vero and make decisions about X265 support:

python:

    f = open("/proc/cpuinfo", "r")
    x265_enabled = 'False'
    for line in f:
        if 'Vero4K' in line.strip() or 'Raspberry Pi 4' in line.strip():
            x265_enabled = 'True'
    f.close()

For the DB ive just been doing try/except and using one DB version or another depending on if the first one failed. But something a bit more robust would be good. Ive been thinking about just using the highest numbered "MyVideo" db file in userdata/database but i suspect you probably have a better way of doing it.
You can look in this file of my addon.  Here's the relevant code:

def get_installedversion():
    # retrieve current installed version
    json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["version", "name"]}, "id": 1 }')
    json_query = json.loads(json_query)
    version_installed = []
    if 'result' in json_query and 'version' in json_query['result']:
        version_installed  = json_query['result']['version']['major']
    return str(version_installed)
    
installed_version = get_installedversion()


def getDatabaseName():
    if installed_version == '10':
        return "MyVideos37.db"
    elif installed_version == '11':
        return "MyVideos60.db"
    elif installed_version == '12':
        return "MyVideos75.db"
    elif installed_version == '13':
        return "MyVideos78.db"
    elif installed_version == '14':
        return "MyVideos90.db"
    elif installed_version == '15':
        return "MyVideos93.db"
    elif installed_version == '16':
        return "MyVideos99.db"
    elif installed_version == '17':
        return "MyVideos107.db"
    elif installed_version == '18':
        return "MyVideos116.db"
    elif installed_version == '19':
        return "MyVideos117.db"
       
    return ""  


def openKodiDB():                                  #  Open Kodi database
    try:
        from sqlite3 import dbapi2 as sqlite
    except:
        from pysqlite2 import dbapi2 as sqlite
                      
    DB = os.path.join(xbmcvfs.translatePath("special://database"), getDatabaseName())
    db = sqlite.connect(DB)

    return(db)   


Jeff
(2021-02-23, 21:02)jbinkley60 Wrote: [ -> ]You can look in this file of my addon.  Here's the relevant code:

def get_installedversion():
    # retrieve current installed version
    json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["version", "name"]}, "id": 1 }')
    json_query = json.loads(json_query)
    version_installed = []
    if 'result' in json_query and 'version' in json_query['result']:
        version_installed  = json_query['result']['version']['major']
    return str(version_installed)
    
installed_version = get_installedversion()


def getDatabaseName():
    if installed_version == '10':
        return "MyVideos37.db"
    elif installed_version == '11':
        return "MyVideos60.db"
    elif installed_version == '12':
        return "MyVideos75.db"
    elif installed_version == '13':
        return "MyVideos78.db"
    elif installed_version == '14':
        return "MyVideos90.db"
    elif installed_version == '15':
        return "MyVideos93.db"
    elif installed_version == '16':
        return "MyVideos99.db"
    elif installed_version == '17':
        return "MyVideos107.db"
    elif installed_version == '18':
        return "MyVideos116.db"
    elif installed_version == '19':
        return "MyVideos117.db"
       
    return ""  


def openKodiDB():                                  #  Open Kodi database
    try:
        from sqlite3 import dbapi2 as sqlite
    except:
        from pysqlite2 import dbapi2 as sqlite
                      
    DB = os.path.join(xbmcvfs.translatePath("special://database"), getDatabaseName())
    db = sqlite.connect(DB)

    return(db)   


Jeff

Cheers.

There is also an info label, System.BuildVersion which might do the same job as your RPC call?

But I'm definitely going to include that. I've got multiple versions of the same add-on for different versions so this will allow me to just have one and have it do the couple of different things on demand.
I think this code precedes the info label item being available.  Probably would be sufficient for newer versions of Kodi. 

Note that the Kodi 19 version of this particular line is:

    DB = os.path.join(xbmcvfs.translatePath("special://database"), getDatabaseName())

whereas older versions it is:

    DB = os.path.join(xbmc.translatePath("special://database"), getDatabaseName()) 

This could be merged into an if statement to handle multiple versions but I maintain separate addon files so it's a minor thing for me to handle.


Jeff