source_utils_Subtitles.bs

' Roku translates the info provided in subtitleTracks into availableSubtitleTracks
' Including ignoring tracks, if they are not understood, thus making indexing unpredictable.
' This function translates between our internel selected subtitle index
' and the corresponding index in availableSubtitleTracks.
function availSubtitleTrackIdx(video, sub_idx) as integer
    url = video.Subtitles[sub_idx].Track.TrackName
    idx = 0
    for each availTrack in video.availableSubtitleTracks
        ' The TrackName must contain the URL we supplied originally, though
        ' Roku mangles the name a bit, so we check if the URL is a substring, rather
        ' than strict equality
        if Instr(1, availTrack.TrackName, url)
            return idx
        end if
        idx = idx + 1
    end for
    return -1
end function

' Identify the default subtitle track for a given video id
' returns the server-side track index for the appriate subtitle
function defaultSubtitleTrackFromVid(video_id) as integer
    meta = ItemMetaData(video_id)
    if isValid(meta) and isValid(meta.json) and isValid(meta.json.mediaSources)
        subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
        default_text_subs = defaultSubtitleTrack(subtitles["all"], true) ' Find correct subtitle track (forced text)
        if default_text_subs <> -1
            return default_text_subs
        else
            if m.global.session.user.settings["playback.subs.onlytext"] = false
                return defaultSubtitleTrack(subtitles["all"]) ' if no appropriate text subs exist, allow non-text
            else
                return -1
            end if
        end if
    end if
    ' No valid mediaSources (i.e. LiveTV)
    return -1
end function


' Identify the default subtitle track
' if "requires_text" is true, only return a track if it is textual
'     This allows forcing text subs, since roku requires transcoding of non-text subs
' returns the server-side track index for the appriate subtitle
function defaultSubtitleTrack(sorted_subtitles, require_text = false) as integer
    if m.global.session.user.configuration.SubtitleMode = "None"
        return -1 ' No subtitles desired: select none
    end if

    for each item in sorted_subtitles
        ' Only auto-select subtitle if language matches preference
        languageMatch = (m.global.session.user.configuration.SubtitleLanguagePreference = item.Track.Language)
        ' Ensure textuality of subtitle matches preferenced passed as arg
        matchTextReq = ((require_text and item.IsTextSubtitleStream) or not require_text)
        if languageMatch and matchTextReq
            if m.global.session.user.configuration.SubtitleMode = "Default" and (item.isForced or item.IsDefault or item.IsExternal)
                return item.Index ' Finds first forced, or default, or external subs in sorted list
            else if m.global.session.user.configuration.SubtitleMode = "Always" and not item.IsForced
                return item.Index ' Select the first non-forced subtitle option in the sorted list
            else if m.global.session.user.configuration.SubtitleMode = "OnlyForced" and item.IsForced
                return item.Index ' Select the first forced subtitle option in the sorted list
            else if m.global.session.user.configuration.SubtitlePlaybackMode = "Smart" and (item.isForced or item.IsDefault or item.IsExternal)
                ' Simplified "Smart" logic here mimics Default (as that is fallback behavior normally)
                ' Avoids detecting preferred audio language (as is utilized in main client)
                return item.Index
            end if
        end if
    end for
    return -1 ' Keep current default behavior of "None", if no correct subtitle is identified
end function

' Given a set of subtitles, and a subtitle index (the index on the server, not in the list provided)
' this will set all relevant settings for roku (mainly closed captions) and return the index of the
' subtitle track specified, but indexed based on the provided list of subtitles
function setupSubtitle(video, subtitles, subtitle_idx = -1) as integer
    if subtitle_idx = -1
        ' If we are not using text-based subtitles, turn them off
        return -1
    end if

    ' Translate the raw index to one relative to the provided list
    subtitleSelIdx = getSubtitleSelIdxFromSubIdx(subtitles, subtitle_idx)

    selectedSubtitle = subtitles[subtitleSelIdx]

    if isValid(selectedSubtitle) and isValid(selectedSubtitle.IsEncoded)
        if selectedSubtitle.IsEncoded
            ' With encoded subtitles, turn off captions
            video.globalCaptionMode = "Off"
        else
            ' If this is a text-based subtitle, set relevant settings for roku captions
            video.globalCaptionMode = "On"
            video.subtitleTrack = video.availableSubtitleTracks[availSubtitleTrackIdx(video, subtitleSelIdx)].TrackName
        end if
    end if

    return subtitleSelIdx
end function

' The subtitle index on the server differs from the index we track locally
' This function converts the former into the latter
function getSubtitleSelIdxFromSubIdx(subtitles, sub_idx) as integer
    selIdx = 0
    if sub_idx = -1 then return -1
    for each item in subtitles
        if item.Index = sub_idx
            return selIdx
        end if
        selIdx = selIdx + 1
    end for
    return -1
end function

function selectSubtitleTrack(tracks, current = -1) as integer
    video = m.scene.focusedChild.focusedChild
    trackSelected = selectSubtitleTrackDialog(video.Subtitles, video.SelectedSubtitle)
    if trackSelected = invalid or trackSelected = -1 ' back pressed in Dialog - no selection made
        return -2
    else
        return trackSelected - 1
    end if
end function

' Present Dialog to user to select subtitle track
function selectSubtitleTrackDialog(tracks, currentTrack = -1)
    iso6392 = getSubtitleLanguages()
    options = ["None"]
    for each item in tracks
        forced = ""
        default = ""
        if item.IsForced then forced = " [Forced]"
        if item.IsDefault then default = " - Default"
        if isValid(item.Track.Language)
            language = iso6392.lookup(item.Track.Language)
            if language = invalid then language = item.Track.Language
        else
            language = "Undefined"
        end if
        options.push(language + forced + default)
    end for
    return option_dialog(options, "Select a subtitle track", currentTrack + 1)
end function

sub changeSubtitleDuringPlayback(newid)

    ' If no subtitles set
    if newid = invalid or newid = -1
        turnoffSubtitles()
        return
    end if

    video = m.scene.focusedChild.focusedChild

    ' If no change of subtitle track, return
    if newid = video.SelectedSubtitle then return

    currentSubtitles = video.Subtitles[video.SelectedSubtitle]
    newSubtitles = video.Subtitles[newid]

    if newSubtitles.IsEncoded or (isValid(currentSubtitles) and currentSubtitles.IsEncoded)
        ' With encoded subtitles we need to stop/start playback
        video.control = "stop"
        AddVideoContent(video, video.mediaSourceId, video.audioIndex, newSubtitles.Index, video.position * 10000000)
        video.control = "play"
    else
        ' Switching from text to text (or none to text) does not require stopping playback
        video.globalCaptionMode = "On"
        video.subtitleTrack = video.availableSubtitleTracks[availSubtitleTrackIdx(video, newid)].TrackName
    end if

    video.SelectedSubtitle = newid

end sub

sub turnoffSubtitles()
    video = m.scene.focusedChild.focusedChild
    current = video.SelectedSubtitle
    video.SelectedSubtitle = -1
    video.globalCaptionMode = "Off"
    device = CreateObject("roDeviceInfo")
    device.EnableAppFocusEvent(false)
    ' Check if Enoded subtitles are being displayed, and turn off
    if current > -1 and video.Subtitles[current].IsEncoded
        video.control = "stop"
        AddVideoContent(video, video.mediaSourceId, video.audioIndex, -1, video.position * 10000000)
        video.control = "play"
    end if
end sub

'Checks available subtitle tracks and puts subtitles in forced, default, and non-default/forced but preferred language at the top
function sortSubtitles(id as string, MediaStreams)
    tracks = { "forced": [], "default": [], "normal": [] }
    'Too many args for using substitute
    prefered_lang = m.global.session.user.configuration.SubtitleLanguagePreference
    for each stream in MediaStreams
        if stream.type = "Subtitle"

            url = ""
            if isValid(stream.DeliveryUrl)
                url = buildURL(stream.DeliveryUrl)
            end if

            stream = {
                "Track": { "Language": stream.language, "Description": stream.displaytitle, "TrackName": url },
                "IsTextSubtitleStream": stream.IsTextSubtitleStream,
                "Index": stream.index,
                "IsDefault": stream.IsDefault,
                "IsForced": stream.IsForced,
                "IsExternal": stream.IsExternal,
                "IsEncoded": stream.DeliveryMethod = "Encode"
            }
            if stream.isForced
                trackType = "forced"
            else if stream.IsDefault
                trackType = "default"
            else
                trackType = "normal"
            end if
            if prefered_lang <> "" and prefered_lang = stream.Track.Language
                tracks[trackType].unshift(stream)
            else
                tracks[trackType].push(stream)
            end if
        end if
    end for

    tracks["default"].append(tracks["normal"])
    tracks["forced"].append(tracks["default"])

    textTracks = []
    for i = 0 to tracks["forced"].count() - 1
        if tracks["forced"][i].IsTextSubtitleStream
            textTracks.push(tracks["forced"][i].Track)
        end if
    end for
    return { "all": tracks["forced"], "text": textTracks }
end function

function getSubtitleLanguages()
    return {
        "aar": "Afar",
        "abk": "Abkhazian",
        "ace": "Achinese",
        "ach": "Acoli",
        "ada": "Adangme",
        "ady": "Adyghe; Adygei",
        "afa": "Afro-Asiatic languages",
        "afh": "Afrihili",
        "afr": "Afrikaans",
        "ain": "Ainu",
        "aka": "Akan",
        "akk": "Akkadian",
        "alb": "Albanian",
        "ale": "Aleut",
        "alg": "Algonquian languages",
        "alt": "Southern Altai",
        "amh": "Amharic",
        "ang": "English, Old (ca.450-1100)",
        "anp": "Angika",
        "apa": "Apache languages",
        "ara": "Arabic",
        "arc": "Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)",
        "arg": "Aragonese",
        "arm": "Armenian",
        "arn": "Mapudungun; Mapuche",
        "arp": "Arapaho",
        "art": "Artificial languages",
        "arw": "Arawak",
        "asm": "Assamese",
        "ast": "Asturian; Bable; Leonese; Asturleonese",
        "ath": "Athapascan languages",
        "aus": "Australian languages",
        "ava": "Avaric",
        "ave": "Avestan",
        "awa": "Awadhi",
        "aym": "Aymara",
        "aze": "Azerbaijani",
        "bad": "Banda languages",
        "bai": "Bamileke languages",
        "bak": "Bashkir",
        "bal": "Baluchi",
        "bam": "Bambara",
        "ban": "Balinese",
        "baq": "Basque",
        "bas": "Basa",
        "bat": "Baltic languages",
        "bej": "Beja; Bedawiyet",
        "bel": "Belarusian",
        "bem": "Bemba",
        "ben": "Bengali",
        "ber": "Berber languages",
        "bho": "Bhojpuri",
        "bih": "Bihari languages",
        "bik": "Bikol",
        "bin": "Bini; Edo",
        "bis": "Bislama",
        "bla": "Siksika",
        "bnt": "Bantu (Other)",
        "bos": "Bosnian",
        "bra": "Braj",
        "bre": "Breton",
        "btk": "Batak languages",
        "bua": "Buriat",
        "bug": "Buginese",
        "bul": "Bulgarian",
        "bur": "Burmese",
        "byn": "Blin; Bilin",
        "cad": "Caddo",
        "cai": "Central American Indian languages",
        "car": "Galibi Carib",
        "cat": "Catalan; Valencian",
        "cau": "Caucasian languages",
        "ceb": "Cebuano",
        "cel": "Celtic languages",
        "cha": "Chamorro",
        "chb": "Chibcha",
        "che": "Chechen",
        "chg": "Chagatai",
        "chi": "Chinese",
        "chk": "Chuukese",
        "chm": "Mari",
        "chn": "Chinook jargon",
        "cho": "Choctaw",
        "chp": "Chipewyan; Dene Suline",
        "chr": "Cherokee",
        "chu": "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic",
        "chv": "Chuvash",
        "chy": "Cheyenne",
        "cmc": "Chamic languages",
        "cop": "Coptic",
        "cor": "Cornish",
        "cos": "Corsican",
        "cpe": "Creoles and pidgins, English based",
        "cpf": "Creoles and pidgins, French-based ",
        "cpp": "Creoles and pidgins, Portuguese-based ",
        "cre": "Cree",
        "crh": "Crimean Tatar; Crimean Turkish",
        "crp": "Creoles and pidgins ",
        "csb": "Kashubian",
        "cus": "Cushitic languages",
        "cze": "Czech",
        "dak": "Dakota",
        "dan": "Danish",
        "dar": "Dargwa",
        "day": "Land Dayak languages",
        "del": "Delaware",
        "den": "Slave (Athapascan)",
        "dgr": "Dogrib",
        "din": "Dinka",
        "div": "Divehi; Dhivehi; Maldivian",
        "doi": "Dogri",
        "dra": "Dravidian languages",
        "dsb": "Lower Sorbian",
        "dua": "Duala",
        "dum": "Dutch, Middle (ca.1050-1350)",
        "dut": "Dutch; Flemish",
        "dyu": "Dyula",
        "dzo": "Dzongkha",
        "efi": "Efik",
        "egy": "Egyptian (Ancient)",
        "eka": "Ekajuk",
        "elx": "Elamite",
        "eng": "English",
        "enm": "English, Middle (1100-1500)",
        "epo": "Esperanto",
        "est": "Estonian",
        "ewe": "Ewe",
        "ewo": "Ewondo",
        "fan": "Fang",
        "fao": "Faroese",
        "fat": "Fanti",
        "fij": "Fijian",
        "fil": "Filipino; Pilipino",
        "fin": "Finnish",
        "fiu": "Finno-Ugrian languages",
        "fon": "Fon",
        "fre": "French",
        "frm": "French, Middle (ca.1400-1600)",
        "fro": "French, Old (842-ca.1400)",
        "frc": "French (Canada)",
        "frr": "Northern Frisian",
        "frs": "Eastern Frisian",
        "fry": "Western Frisian",
        "ful": "Fulah",
        "fur": "Friulian",
        "gaa": "Ga",
        "gay": "Gayo",
        "gba": "Gbaya",
        "gem": "Germanic languages",
        "geo": "Georgian",
        "ger": "German",
        "gez": "Geez",
        "gil": "Gilbertese",
        "gla": "Gaelic; Scottish Gaelic",
        "gle": "Irish",
        "glg": "Galician",
        "glv": "Manx",
        "gmh": "German, Middle High (ca.1050-1500)",
        "goh": "German, Old High (ca.750-1050)",
        "gon": "Gondi",
        "gor": "Gorontalo",
        "got": "Gothic",
        "grb": "Grebo",
        "grc": "Greek, Ancient (to 1453)",
        "gre": "Greek, Modern (1453-)",
        "grn": "Guarani",
        "gsw": "Swiss German; Alemannic; Alsatian",
        "guj": "Gujarati",
        "gwi": "Gwich'in",
        "hai": "Haida",
        "hat": "Haitian; Haitian Creole",
        "hau": "Hausa",
        "haw": "Hawaiian",
        "heb": "Hebrew",
        "her": "Herero",
        "hil": "Hiligaynon",
        "him": "Himachali languages; Western Pahari languages",
        "hin": "Hindi",
        "hit": "Hittite",
        "hmn": "Hmong; Mong",
        "hmo": "Hiri Motu",
        "hrv": "Croatian",
        "hsb": "Upper Sorbian",
        "hun": "Hungarian",
        "hup": "Hupa",
        "iba": "Iban",
        "ibo": "Igbo",
        "ice": "Icelandic",
        "ido": "Ido",
        "iii": "Sichuan Yi; Nuosu",
        "ijo": "Ijo languages",
        "iku": "Inuktitut",
        "ile": "Interlingue; Occidental",
        "ilo": "Iloko",
        "ina": "Interlingua (International Auxiliary Language Association)",
        "inc": "Indic languages",
        "ind": "Indonesian",
        "ine": "Indo-European languages",
        "inh": "Ingush",
        "ipk": "Inupiaq",
        "ira": "Iranian languages",
        "iro": "Iroquoian languages",
        "ita": "Italian",
        "jav": "Javanese",
        "jbo": "Lojban",
        "jpn": "Japanese",
        "jpr": "Judeo-Persian",
        "jrb": "Judeo-Arabic",
        "kaa": "Kara-Kalpak",
        "kab": "Kabyle",
        "kac": "Kachin; Jingpho",
        "kal": "Kalaallisut; Greenlandic",
        "kam": "Kamba",
        "kan": "Kannada",
        "kar": "Karen languages",
        "kas": "Kashmiri",
        "kau": "Kanuri",
        "kaw": "Kawi",
        "kaz": "Kazakh",
        "kbd": "Kabardian",
        "kha": "Khasi",
        "khi": "Khoisan languages",
        "khm": "Central Khmer",
        "kho": "Khotanese; Sakan",
        "kik": "Kikuyu; Gikuyu",
        "kin": "Kinyarwanda",
        "kir": "Kirghiz; Kyrgyz",
        "kmb": "Kimbundu",
        "kok": "Konkani",
        "kom": "Komi",
        "kon": "Kongo",
        "kor": "Korean",
        "kos": "Kosraean",
        "kpe": "Kpelle",
        "krc": "Karachay-Balkar",
        "krl": "Karelian",
        "kro": "Kru languages",
        "kru": "Kurukh",
        "kua": "Kuanyama; Kwanyama",
        "kum": "Kumyk",
        "kur": "Kurdish",
        "kut": "Kutenai",
        "lad": "Ladino",
        "lah": "Lahnda",
        "lam": "Lamba",
        "lao": "Lao",
        "lat": "Latin",
        "lav": "Latvian",
        "lez": "Lezghian",
        "lim": "Limburgan; Limburger; Limburgish",
        "lin": "Lingala",
        "lit": "Lithuanian",
        "lol": "Mongo",
        "loz": "Lozi",
        "ltz": "Luxembourgish; Letzeburgesch",
        "lua": "Luba-Lulua",
        "lub": "Luba-Katanga",
        "lug": "Ganda",
        "lui": "Luiseno",
        "lun": "Lunda",
        "luo": "Luo (Kenya and Tanzania)",
        "lus": "Lushai",
        "mac": "Macedonian",
        "mad": "Madurese",
        "mag": "Magahi",
        "mah": "Marshallese",
        "mai": "Maithili",
        "mak": "Makasar",
        "mal": "Malayalam",
        "man": "Mandingo",
        "mao": "Maori",
        "map": "Austronesian languages",
        "mar": "Marathi",
        "mas": "Masai",
        "may": "Malay",
        "mdf": "Moksha",
        "mdr": "Mandar",
        "men": "Mende",
        "mga": "Irish, Middle (900-1200)",
        "mic": "Mi'kmaq; Micmac",
        "min": "Minangkabau",
        "mis": "Uncoded languages",
        "mkh": "Mon-Khmer languages",
        "mlg": "Malagasy",
        "mlt": "Maltese",
        "mnc": "Manchu",
        "mni": "Manipuri",
        "mno": "Manobo languages",
        "moh": "Mohawk",
        "mon": "Mongolian",
        "mos": "Mossi",
        "mul": "Multiple languages",
        "mun": "Munda languages",
        "mus": "Creek",
        "mwl": "Mirandese",
        "mwr": "Marwari",
        "myn": "Mayan languages",
        "myv": "Erzya",
        "nah": "Nahuatl languages",
        "nai": "North American Indian languages",
        "nap": "Neapolitan",
        "nau": "Nauru",
        "nav": "Navajo; Navaho",
        "nbl": "Ndebele, South; South Ndebele",
        "nde": "Ndebele, North; North Ndebele",
        "ndo": "Ndonga",
        "nds": "Low German; Low Saxon; German, Low; Saxon, Low",
        "nep": "Nepali",
        "new": "Nepal Bhasa; Newari",
        "nia": "Nias",
        "nic": "Niger-Kordofanian languages",
        "niu": "Niuean",
        "nno": "Norwegian Nynorsk; Nynorsk, Norwegian",
        "nob": "Bokmål, Norwegian; Norwegian Bokmål",
        "nog": "Nogai",
        "non": "Norse, Old",
        "nor": "Norwegian",
        "nqo": "N'Ko",
        "nso": "Pedi; Sepedi; Northern Sotho",
        "nub": "Nubian languages",
        "nwc": "Classical Newari; Old Newari; Classical Nepal Bhasa",
        "nya": "Chichewa; Chewa; Nyanja",
        "nym": "Nyamwezi",
        "nyn": "Nyankole",
        "nyo": "Nyoro",
        "nzi": "Nzima",
        "oci": "Occitan (post 1500); Provençal",
        "oji": "Ojibwa",
        "ori": "Oriya",
        "orm": "Oromo",
        "osa": "Osage",
        "oss": "Ossetian; Ossetic",
        "ota": "Turkish, Ottoman (1500-1928)",
        "oto": "Otomian languages",
        "paa": "Papuan languages",
        "pag": "Pangasinan",
        "pal": "Pahlavi",
        "pam": "Pampanga; Kapampangan",
        "pan": "Panjabi; Punjabi",
        "pap": "Papiamento",
        "pau": "Palauan",
        "peo": "Persian, Old (ca.600-400 B.C.)",
        "per": "Persian",
        "phi": "Philippine languages",
        "phn": "Phoenician",
        "pli": "Pali",
        "pol": "Polish",
        "pon": "Pohnpeian",
        "por": "Portuguese",
        "pob": "Portuguese (Brazil)",
        "pra": "Prakrit languages",
        "pro": "Provençal, Old (to 1500)",
        "pus": "Pushto; Pashto",
        "qaa-qtz": "Reserved for local use",
        "que": "Quechua",
        "raj": "Rajasthani",
        "rap": "Rapanui",
        "rar": "Rarotongan; Cook Islands Maori",
        "roa": "Romance languages",
        "roh": "Romansh",
        "rom": "Romany",
        "rum": "Romanian; Moldavian; Moldovan",
        "run": "Rundi",
        "rup": "Aromanian; Arumanian; Macedo-Romanian",
        "rus": "Russian",
        "sad": "Sandawe",
        "sag": "Sango",
        "sah": "Yakut",
        "sai": "South American Indian (Other)",
        "sal": "Salishan languages",
        "sam": "Samaritan Aramaic",
        "san": "Sanskrit",
        "sas": "Sasak",
        "sat": "Santali",
        "scn": "Sicilian",
        "sco": "Scots",
        "sel": "Selkup",
        "sem": "Semitic languages",
        "sga": "Irish, Old (to 900)",
        "sgn": "Sign Languages",
        "shn": "Shan",
        "sid": "Sidamo",
        "sin": "Sinhala; Sinhalese",
        "sio": "Siouan languages",
        "sit": "Sino-Tibetan languages",
        "sla": "Slavic languages",
        "slo": "Slovak",
        "slv": "Slovenian",
        "sma": "Southern Sami",
        "sme": "Northern Sami",
        "smi": "Sami languages",
        "smj": "Lule Sami",
        "smn": "Inari Sami",
        "smo": "Samoan",
        "sms": "Skolt Sami",
        "sna": "Shona",
        "snd": "Sindhi",
        "snk": "Soninke",
        "sog": "Sogdian",
        "som": "Somali",
        "son": "Songhai languages",
        "sot": "Sotho, Southern",
        "spa": "Spanish; Latin",
        "spa": "Spanish; Castilian",
        "srd": "Sardinian",
        "srn": "Sranan Tongo",
        "srp": "Serbian",
        "srr": "Serer",
        "ssa": "Nilo-Saharan languages",
        "ssw": "Swati",
        "suk": "Sukuma",
        "sun": "Sundanese",
        "sus": "Susu",
        "sux": "Sumerian",
        "swa": "Swahili",
        "swe": "Swedish",
        "syc": "Classical Syriac",
        "syr": "Syriac",
        "tah": "Tahitian",
        "tai": "Tai languages",
        "tam": "Tamil",
        "tat": "Tatar",
        "tel": "Telugu",
        "tem": "Timne",
        "ter": "Tereno",
        "tet": "Tetum",
        "tgk": "Tajik",
        "tgl": "Tagalog",
        "tha": "Thai",
        "tib": "Tibetan",
        "tig": "Tigre",
        "tir": "Tigrinya",
        "tiv": "Tiv",
        "tkl": "Tokelau",
        "tlh": "Klingon; tlhIngan-Hol",
        "tli": "Tlingit",
        "tmh": "Tamashek",
        "tog": "Tonga (Nyasa)",
        "ton": "Tonga (Tonga Islands)",
        "tpi": "Tok Pisin",
        "tsi": "Tsimshian",
        "tsn": "Tswana",
        "tso": "Tsonga",
        "tuk": "Turkmen",
        "tum": "Tumbuka",
        "tup": "Tupi languages",
        "tur": "Turkish",
        "tut": "Altaic languages",
        "tvl": "Tuvalu",
        "twi": "Twi",
        "tyv": "Tuvinian",
        "udm": "Udmurt",
        "uga": "Ugaritic",
        "uig": "Uighur; Uyghur",
        "ukr": "Ukrainian",
        "umb": "Umbundu",
        "und": "Undetermined",
        "urd": "Urdu",
        "uzb": "Uzbek",
        "vai": "Vai",
        "ven": "Venda",
        "vie": "Vietnamese",
        "vol": "Volapük",
        "vot": "Votic",
        "wak": "Wakashan languages",
        "wal": "Walamo",
        "war": "Waray",
        "was": "Washo",
        "wel": "Welsh",
        "wen": "Sorbian languages",
        "wln": "Walloon",
        "wol": "Wolof",
        "xal": "Kalmyk; Oirat",
        "xho": "Xhosa",
        "yao": "Yao",
        "yap": "Yapese",
        "yid": "Yiddish",
        "yor": "Yoruba",
        "ypk": "Yupik languages",
        "zap": "Zapotec",
        "zbl": "Blissymbols; Blissymbolics; Bliss",
        "zen": "Zenaga",
        "zgh": "Standard Moroccan Tamazight",
        "zha": "Zhuang; Chuang",
        "znd": "Zande languages",
        "zul": "Zulu",
        "zun": "Zuni",
        "zxx": "No linguistic content; Not applicable",
        "zza": "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki"
    }
end function