Languages and Subtitles in XBMC

In XBMC the support for different languages in movies or subtitlelanguages is minimal. In this guide I will show what I did, to display the language and subtitle language of my movies. So it covers how to get the audiolanguage in the database and how to use this information to display it in the skin.

My aproach isn't very good and mostly a hack, but because there are only so few resources in the internet, I still want to show it.

Generic things explained

XBMC uses for language abbrevations the iso 639-2 norm with 3 letters.

Extracting language from your moviefiles

By default xbmc will look into the metadata of your movies and extract information from it. This metadata is similar to the commonly know id3-tags in mp3 files or the metadata in images. With exiftool you can look at this information. I looked at my library and found that only very few files have the audiolanguage tag in them. With exiftool I think it is possible to add this tag to every file.

Another way to have the audio and subtitle language in your databse is to provide .nfo files for them. I tried this with a minimal .nfo-file:

<movie>
    <fileinfo>
        <streamdetails>
            <audio>
                <language>ger</language>
            </audio>
            <subtitle>
                <language>eng</language>
            </subtitle>
        </streamdetails>
    </fileinfo>
</movie>

The problem is, when xbmc notices a nfo-file it won't scrap the internet for additional data and so this solution didn't work :/.

Since I now already had the effort to put a .nfo file everywhere I didn't wanted to stop and just renamed .nfo to .xml, so that it won't be parsed anymore but is still usable to me.

script to read a .xml and write to the xbmc database

Since the xbmc database is just an sqlite file, it is very easy to read/write from/to it. So I created a python script which looks in my moviefiles, extracts the .xml and puts this information into the database. The script looks like this:

#!/usr/bin/python3
import sqlite3
import sys
import os
try:
    from lxml import etree
except:
    import xml.etree.ElementTree as etree


XBMCDB = '/home/balrok/.xbmc/userdata/Database/MyVideos78.db'
MOVIEPATH = '/home/balrok/filme'


def run():
    conn = sqlite3.connect(XBMCDB)
    cur = conn.cursor()

    cur.execute("SELECT streamdetails.idFile FROM streamdetails WHERE streamdetails.iStreamType=2")
    data = cur.fetchall()
    listOfStreamsWithSub = []
    for row in data:
        listOfStreamsWithSub.append(row)

    cur.execute("SELECT path.strPath, streamdetails.idFile FROM path, files, streamdetails WHERE streamdetails.idFile = files.idFile AND path.idPath=files.idPath AND streamdetails.iStreamType=1 AND strPath LIKE '"+MOVIEPATH+"%'")

    data = cur.fetchall()
    updateDict = {}
    todoCreateSub = []
    for row in data:
        strPath, idFile = row
        lang,sub = extractLanguageSubtitleFromPath(strPath)
        langsub = lang+sub
        print(strPath +" "+ langsub)
        if langsub not in updateDict:
            updateDict[langsub] = []
        updateDict[langsub].append(idFile)
        if lang == "":
            print(strPath, "got no language")
        if sub != "" and idFile not in listOfStreamsWithSub:
            todoCreateSub.append(idFile)

    for idFile in todoCreateSub:
        query = "INSERT INTO streamdetails (idFile, iStreamType) VALUES(%d, 2)" % idFile
        cur.execute(query)

    for langsub in updateDict:
        lang = langsub[:3]
        sub = langsub[3:]
        ids = updateDict[langsub]
        ids = ','.join(str(x) for x in ids)
        query = "UPDATE streamdetails SET strAudioLanguage='%s', strSubtitleLanguage='%s' WHERE iStreamType IN (1,2) AND idFile IN (%s)" % (lang, sub, ids)
        print(query)
        cur.execute(query)
    conn.commit()
    conn.close()


def extractLanguageSubtitleFromXML(file):
    with open(file, 'r') as f:
        content = f.read()
        fxml = etree.fromstring(content)
        lang = fxml.find(".//audio/language")
        sub = fxml.find(".//subtitle/language")
        if lang is not None:
            lang = lang.text
        else:
            lang = ""
        if sub is not None:
            sub = sub.text
        else:
            sub = ""
        return lang, sub
    return "", ""

langSubCache = {}
def extractLanguageSubtitleFromPath(path):
    if path not in langSubCache:
        if not os.path.exists(path):
            print("no such path", path)
            langSubCache[path] = ("", "")
        else:
            for file in os.listdir(path):
                if file[-4:] == '.xml':
                    langSubCache[path] = extractLanguageSubtitleFromXML(os.path.join(path,file))
                    break
            else:
                print("no xml for " + path)
                langSubCache[path] = ("", "")
    return langSubCache[path]

run()

Backing up the database is also easy: just copy the .db file :)

So the advantage of this solution is, that it works for every file and doesn't take so long. The disadvantage is, that you will need a .xml file in every folder and then, when xbmc updates the streamdetails table, all this information is lost. The streamdetails table will be updated when you watch the movie.

So I have to put this script into /etc/init.d/xbmc so that it will also start when xbmc starts.

modyfying your skin

I use aeon.nox 5 and modified the tvshows and movies listings to display the language flag. And also modified the movie-information view to display it in the lowest right corner.

In tvshows I can only display it in the episodes view because season and tvshow don't have the audiolanguage property.

diff --git a/.xbmc/addons/skin.aeon.nox.5/1080i/DialogVideoInfo.xml b/.xbmc/addons/skin.aeon.nox.5/1080i/DialogVideoInfo.xml
index 75bffc4..01f7dd7 100644
--- a/.xbmc/addons/skin.aeon.nox.5/1080i/DialogVideoInfo.xml
+++ b/.xbmc/addons/skin.aeon.nox.5/1080i/DialogVideoInfo.xml
@@ -635,13 +635,19 @@
                            <height>90</height>
                            <texture border="1">separator3.png</texture>
                        </control>
-                       <control type="image">
-                           <width>198</width>
-                           <colordiffuse>grey</colordiffuse>
-                           <include>MediaFlagVars</include>
-                           <texture>flags/cc.png</texture>
-                           <visible>!IsEmpty(ListItem.SubtitleLanguage)</visible>
-                       </control>
+                        <control type="image">
+                            <width>78</width>
+                            <height>78</height>
+                            <aspectratio>keep</aspectratio>
+                            <texture background="true" fallback="flags/studios/default-studio.png">$INFO[ListItem.AudioLanguage,flags/language/,.png]</texture>
+                        </control>
+                        <control type="image">
+                            <width>78</width>
+                            <height>78</height>
+                            <aspectratio>keep</aspectratio>
+                            <texture background="true" fallback="flags/studios/default-studio.png">$INFO[ListItem.SubtitleLanguage,flags/language/,.png]</texture>
+                            <visible>!IsEmpty(ListItem.SubtitleLanguage)</visible>
+                        </control>
                    </control>
                    <control type="image">
                        <posy>90</posy>
diff --git a/.xbmc/addons/skin.aeon.nox.5/1080i/View_50_List.xml b/.xbmc/addons/skin.aeon.nox.5/1080i/View_50_List.xml
index ea1c4db..293e510 100644
--- a/.xbmc/addons/skin.aeon.nox.5/1080i/View_50_List.xml
+++ b/.xbmc/addons/skin.aeon.nox.5/1080i/View_50_List.xml
@@ -44,6 +44,7 @@
                            <height>60</height>
                            <texture border="1">separator2.png</texture>
                        </control>
+                        <!--
                        <control type="image">
                            <posx>15</posx>
                            <posy>8</posy>
@@ -55,6 +56,34 @@
                            <fadetime>IconCrossfadeTime</fadetime>
                            <visible>!SubString(ListItem.Icon,Default,left)</visible>
                        </control>
+                        -->
+                        <control type="image">
+                           <posx>15</posx>
+                           <posy>8</posy>
+                            <width>40</width>
+                            <height>40</height>
+                            <aspectratio>keep</aspectratio>
+                            <texture background="true" fallback="flags/studios/default-studio.png">$INFO[ListItem.AudioLanguage,flags/language/,.png]</texture>
+                            <visible>!IsEmpty(ListItem.SubtitleLanguage)</visible>
+                        </control>
+                        <control type="image">
+                           <posx>65</posx>
+                           <posy>8</posy>
+                            <width>40</width>
+                            <height>40</height>
+                            <aspectratio>keep</aspectratio>
+                            <texture background="true" fallback="flags/studios/default-studio.png">$INFO[ListItem.SubtitleLanguage,flags/language/,.png]</texture>
+                            <visible>!IsEmpty(ListItem.SubtitleLanguage)</visible>
+                        </control>
+                        <control type="image">
+                           <posx>15</posx>
+                           <posy>8</posy>
+                            <width>100</width>
+                            <height>100</height>
+                            <aspectratio>keep</aspectratio>
+                            <texture background="true" fallback="flags/studios/default-studio.png">$INFO[ListItem.AudioLanguage,flags/language/,.png]</texture>
+                            <visible>IsEmpty(ListItem.SubtitleLanguage)</visible>
+                        </control>
                        <control type="image">
                            <posx>15</posx>
                            <posy>8</posy>
@@ -252,6 +281,7 @@
                            <height>60</height>
                            <texture border="1">separator2.png</texture>
                        </control>
+                        <!--
                        <control type="image">
                            <posx>15</posx>
                            <posy>8</posy>
@@ -273,6 +303,34 @@
                            <fadetime>IconCrossfadeTime</fadetime>
                            <visible>!SubString(ListItem.Icon,Default,left)</visible>
                        </control>
+                        -->
+                        <control type="image">
+                           <posx>15</posx>
+                           <posy>8</posy>
+                            <width>40</width>
+                            <height>40</height>
+                            <aspectratio>keep</aspectratio>
+                            <texture background="true" fallback="flags/studios/default-studio.png">$INFO[ListItem.AudioLanguage,flags/language/,.png]</texture>
+                            <visible>!IsEmpty(ListItem.SubtitleLanguage)</visible>
+                        </control>
+                        <control type="image">
+                           <posx>65</posx>
+                           <posy>8</posy>
+                            <width>40</width>
+                            <height>40</height>
+                            <aspectratio>keep</aspectratio>
+                            <texture background="true" fallback="flags/studios/default-studio.png">$INFO[ListItem.SubtitleLanguage,flags/language/,.png]</texture>
+                            <visible>!IsEmpty(ListItem.SubtitleLanguage)</visible>
+                        </control>
+                        <control type="image">
+                           <posx>15</posx>
+                           <posy>8</posy>
+                            <width>100</width>
+                            <height>100</height>
+                            <aspectratio>keep</aspectratio>
+                            <texture background="true" fallback="flags/studios/default-studio.png">$INFO[ListItem.AudioLanguage,flags/language/,.png]</texture>
+                            <visible>IsEmpty(ListItem.SubtitleLanguage)</visible>
+                        </control>
                        <control type="label">
                            <posx>135</posx>
                            <posy>0</posy>

other resources

german forum thread 2 methods are explained: 1) use the existing data from streamdetails, 2) rename folders and use that information in the skin

addon for movielanguage I didn't need it.

Commentaires: