Repository navigation
Writing an OpenMeta player
An OpenMeta player is a .json file that specifies how to link an external add-on, a local directory or your local library to OpenMeta. A player can either specify a parameterized link to use for playing the media directly or specify the steps to use in order to browse through an external add-on until list items that match the requested media are found. This guide will explain the format of a player file, provide full examples and then provide a few guidelines that may make it easier for you to write a working player file. Minimal familiarity with Python, JSON and/or regex is recommended.
Tips:
- OpenMeta players are located in the
special://profile/addon_data/plugin.video.openmeta/Playersfolder - If you want to maintain a set of OpenMeta players online, create a GitHub repository with all players and use the following endpoint as the link to use in the add-on's settings:
https://api.github.com/repos/:owner/:repo/zipball
The hints in a player can use parameters provided by OpenMeta according to the
selected media itself. For example, {title} is the movie's name, {imdb} is the
movie's IMDb identifier, etc.
Parameters are written inside curly brackets ({ and }). Formatting is also supported, e.g.: {season:02d} is replaced with a 2-letters season number with a leading zero if needed. Note that escaping curly brackets, if you need it for any other purpose other than
specifying parameters, is achieved by writing {{ and }}.
Text parameters also have siblings that determine how whitespaces are interpreted.
Currently you can use _escaped for %2520 (equivalent to the encoded whitespace symbol %20 after unquoting), _escaped+ for %252B (equiva;ent to the encoded plus sign %2B after unquoting), _+ for plus and _- for dash.
For example:
-
{title}: Awesome movie -
{title_+}: Awesome+movie -
{title_-}: Awesome-movie -
{title_escaped}: Awesome%2520movie -
{title_escaped+}: Awesome%252Bmovie
For other replacements you may apply functions to text parameters. Currently two functions are supported: ws to replace whitespaces and replace to replace any substring. The characters [, ., :, and ], must be escaped to &sbo;, ˙, :, and &sbc;, respectively.
Examples:
-
{title|ws(%2B)}will replace whitespaces with%2B(URL encoding for +) -
{title|replace(˙,%20)}will replace dots with%20(URL encoding for whitespsace) -
{title|replace(˙, )|ws(%2520)}will replace dots with whitespaces and then whitespaces with%2520(turns into%20if the target add-on callsurllib2.unquoteon the parameter)
- id: TMDb id
- imdb: IMDb id
- trakt: Trakt id
- slug: Trakt slug
- title: Movie title
- year: Movie release year
- name: Formatted as "title (year)"
- released: Release date, formatted as "(yyyy-mm-dd)"
- id: TVDB id
- imdb: IMDb id
- tmdb: TMDb id
- trakt: Trakt id
- slug: Trakt slug
- epid: Episode TVDB id
- epimdb: Episode IMDb id
- eptmdb: Episode TMDb id (currently not populated, returns: 0 (int))
- eptrakt: Episode Trakt id (currently not populated, returns: 0 (int))
- showname: Show name as listed in TVDB
- clearname: Show name excluding year if present
-
name:
Formatted as
{showname} S{season:02d}E{episode:02d}or{showname} {absolute_number}for anime - title: Episode title
- season: Season number
- episode: Episode number
- absolute_number: Absolute episode number
- firstaired: Episode's first aired date, formatted as "(yyyy-mm-dd)"
- series_firstaired: Series' first aired date, formatted as "(yyyy-mm-dd)"
- year: Series' first aired year
- epyear: Episode's first aired year (defaults to 1980 (int))
- epmonth: Episode's first aired month (defaults to 1 (int))
- epday: Episode's first aired day (defaults to 1 (int))
- network: Broadcast network
- network_clear: Broadcast network without country code (e.g., "ABC (US)" -> "ABC")
- genres: Genres as listed in TMDb, formatted as "Action / Adventure / Comedy / etc..."
Additional parameters can be accessed through the `info' parameter. Documentation is missing because these parameters can usually be omitted. For now I'll give a few examples:
{info[plot]}{info[genre]}{info[rating]}{info[votes]}{info[poster]}{info[fanart]}
Root: dictionary
"name": string
Display name (HTML tags allowed)
"repository": string [optional]
id of the repository that hosts the addon belonging to this player
"plugin": string [optional]
Ignore player if a plugin with the id of the specified value is not
installed.
"id": string [optional, default=from file name]
Unique string identifier. As convention please use:
"{your_name}.{addon_name}.{type_of_player}.{action_of_player}"
"filters": dictionary [optional]
Ignore player if filters don't match.
Currently only "network" filter for tvshows and live are supported.
Example: {"network": "BBC"}.
"postprocess": string [optional]
Restricted python code (imports, modules and builtins are disallowed).
The code should only be used to modify found links. Use the variable
"link" to refer to the link to modify.
Example: "link.replace('demo=1','demo=0')"
"movies": list<CommandSequence> [optional]
List of CommandSequence entries for playing a movie video.
OpenMeta executes all CommandSequence entries (one at the time).
"tvshows": list<CommandSequence> [optional]
List of CommandSequence entries for playing an episode.
OpenMeta executes all CommandSequence entries (one at the time).
CommandSequence: list<CommandItem>
List of CommandItem entries.
OpenMeta stops executing a CommandSequence at the first successful CommandItem.
CommandItem: dict
"action": string [optional, default="PLAY"]
PLAY: Use it if final URL is video.
ACTIVATE: Use it if final URL is a folder or a custom view.
"language": string [optional, default="en"]
Language to use for parameters.
"link": string
A parameterized URL.
If URL is final then the "steps" item should be omitted.
Example:
[*] plugin://plugin.video.example/{imdb}/play
If browsing list items is needed then a "plugin://" URL should be used
as a starting point for external browsing.
Examples:
[*] plugin://plugin.video.example/?action=search&q={title}
[*] plugin://plugin.video.example/all/
[*] plugin://plugin.video.example
"steps": list<string> [optional]
List of parameterized hints to specify how to browse the external addon or folder.
For example if plugin.video.example provides a list of public
domain movies in its root, and after selecting a movie the user needs
to select one of two list items: "play movie" or "play trailer" then
the steps to use are: ["{title}", "play movie"].
There are 3 types of steps are available:
[*] **Regex** - Regular expression steps are used by default.
Please see common regex symbols below if you are unfamiliar with regex.
The matching is done by comparing the text in the addon to the step's regex.
Before comparing, the following symbols are replaced with white-space in
both the text from the addon and parameters: ., %20. Start a step with >< to
to remove label formatting from the addon's text before compairing.
[*] **Info** - Some addons specify season and episode number as an info label
instead of the list item label. The {season}, {episode} and {season}x{episode}
steps are specifically designed to match not only against the list item label
but also against info labels. if you don't know if an addon sets the
info-labels, you can see it in `Media info' view of confluence (it would show
"Episode: SxE" on the right panel if the info-labels are set).
[*] **Keyboard or Any** - "Keyboard" steps are used to send text to the virtual keyboard.
Continuing the previous example, if plugin.video.example has a Search item
that opens the keyboard to search for a movie then the steps to use are:
["Search", "@keyboard:{title}", "{title}", "play movie"].
"Any" steps are used to specify a group of directories in which to
continue the next steps. "Any" comes in a couple of flavours:
@any - Next steps continue in all dirs.
@anyexcept:<string> - Next steps continue in all dirs except <string>.
@anynotcontaining:<string> - Next steps continue in dirs not containing <string>.
@anycontaining:<string> - Next steps continue in dirs containing <string>.
<string> for "Any" can contain multiple terms seperated by |.
Note that a "keyboard" or "any" step can never be the last in a steps list.
Common regex symbols:
. - Any character.
* - Previous character/group can appear zero or many times.
+ - Previous character/group can appear one or more times.
? - Previous character/group is optional.
.* - Match anything (greedy).
.*? - Match anything (not greedy).
$$ - Match start of string or end of string or white-space or [ or ].
Note: not a standard regex but it has been added for convenience.
Filename: provider.library.json
Description: Search media in Kodi's library.
{
"name": "Library",
"tvshows": [
[
{
"link": "tvshows"
}
]
],
"movies": [
[
{
"link": "movies"
}
]
]
}Filename: provider.local.json
Description: Search episode in a local folder. The folder must be configured in Kodi's file explorer.
{
"id": "provider.local",
"name": "Local",
"tvshows": [
[
{
"link": "c:/Videos/",
"steps": [
"{clearname}.*S{season:02d}E{episode:02d}.*(avi|mp4)"
]
}
]
]
}Filename: search.netflix.json
Description: Search using Netflix add-on. This player shows a CommandSequence with 2 steps:
- the first step shows how to integrate a query inside the link (note that the add-on must support this)
- the second step shows how to use a keyboard step (if you can avoid using keyboard steps please do)
{
"name" : "Netflix",
"plugin" : "plugin.video.netflix",
"priority" : 300,
"id" : "search.netflix",
"movies" : [
[
{
"link" : "plugin://plugin.video.netflix/?action=search_result&term={title_+}",
"steps" : [
"{title}"
],
"action" : "PLAY"
},
{
"link" : "plugin://plugin.video.netflix/directory/search/{title_+}/",
"steps" : [
"{title}"
],
"action" : "PLAY"
}
]
],
"tvshows" : [
[
{
"link" : "plugin://plugin.video.netflix/?action=search_result&term={clearname_+}",
"steps" : [
"{clearname}",
".* {season}",
"{episode}"
],
"action" : "PLAY"
},
{
"link" : "plugin://plugin.video.netflix/?action=search_result&term={clearname_+}",
"steps" : [
"{clearname}",
"{episode}"
],
"action" : "PLAY"
},
{
"link" : "plugin://plugin.video.netflix/?action=search_result&term={clearname_+}",
"steps" : [
"{clearname}",
"{clearname}",
"{episode}"
],
"action" : "PLAY"
},
{
"link" : "plugin://plugin.video.netflix/directory/search/{clearname_+}/",
"steps" : [
"{clearname}",
".* {season}",
"{episode}"
],
"action" : "PLAY"
},
{
"link" : "plugin://plugin.video.netflix/directory/search/{clearname_+}/",
"steps" : [
"{clearname}",
"{episode}"
],
"action" : "PLAY"
},
{
"link" : "plugin://plugin.video.netflix/directory/search/{clearname_+}/",
"steps" : [
"{clearname}",
"{clearname}",
"{episode}"
],
"action" : "PLAY"
}
]
]
}These are example of three methods that may be used to write a CommandSequence.
These examples will add support for a (dummy) plugin that has the add-on id plugin.video.example.
Say the add-on lists all movies in English inside Movies -> All and after
choosing a movie it shows (not in a dialog) a few links in the a
"[Quality] name" format.
Here is the CommandSequence to use for adding support for this add-on, preferring 720p first, and falling back to any quality otherwise:
[
{
"link": "plugin://plugin.video.example",
"steps": [
"Movies",
"All",
"{title}",
"\[720p\].*"
]
},
{
"link": "plugin://plugin.video.example",
"steps": [
"Movies",
"All",
"{title}",
".*"
]
}
]Say the add-on has a library integration feature. Use it to add some movie to
your library and locate the generated .strm file. Open it in text editor and
you will see the final URL to use for playing the movie you have added. If
all parameters inside the URL are supported by OpenMeta, then you can use it by
adding a simple CommandSequence. For example if the .strm contains:
`plugin://plugin.video.example/tt1234567/play`
Then use the following CommandSequence:
[
{
"link": "plugin://plugin.video.example/{imdb}/play"
}
]The example above is very simple as there is only one parameter to match. Other add-ons might encode more (or all available) metadata inside the link which would make for a lot more possible parameter-matches. For example:
`plugin://plugin.video.example/?action=smartPlay&actionArgs=%7B%22episodeInfo%22%3A%20%7B%22art%22%3A%20%7B%22fanart%22%3A%20%22http%3A%2F%2Fthetvdb.com%2Fbanners%2Ffanart%2Foriginal%2F78901-95.jpg%22%2C%20%22landscape%22%3A%20%22None%22%2C%20%22poster%22%3A%20%22http%3A%2F%2Fthetvdb.com%2Fbanners%2Fposters%2F5be70d8b6f25e.jpg%22%2C%20%22thumb%22%3A%20%22None%22%7D%2C%20%22ids%22%3A%20%7B%22imdb%22%3A%20%220%22%2C%20%22tmdb%22%3A%200%2C%20%22trakt%22%3A%200%2C%20%22tvdb%22%3A%206771995%2C%20%22info%22%3A%20%7B%22castandrole%22%3A%20%5B%5D%2C%20%22dateadded%22%3A%20%222018-11-29%22%2C%20%22duration%22%3A%202700%2C%20%22episode%22%3A%207%2C%20%22genre%22%3A%20%5B%5D%2C%20%22imdbnumber%22%3A%20%22%22%2C%20%22mediatype%22%3A%20%22episode%22%2C%20%22mpaa%22%3A%20%22None%22%2C%20%22originaltitle%22%3A%20%22Unhuman%20Nature%22%2C%20%22playcount%22%3A%200%2C%20%22plot%22%3A%20%22Sam%20and%20Castiel%20track%20down%20a%20Shaman%2C%20who%20may%20be%20able%20to%20help%20a%20friend.%20Nick%20continues%20to%20spiral%20down%20a%20dark%20path%20as%20he%20looks%20for%20answers%20surrounding%20the%20deaths%20of%20his%20wife%20and%20son.%20Jack%20turns%20to%20Dean%20for%20help%20enjoying%20the%20human%20experience.%22%2C%20%22premiered%22%3A%20%222018-11-29%22%2C%20%22rating%22%3A%200%2C%20%22season%22%3A%2014%2C%20%22sortepisode%22%3A%207%2C%20%22sortseason%22%3A%2014%2C%20%22title%22%3A%20%22Unhuman%20Nature%22%2C%20%22trailer%22%3A%20%22%22%2C%20%22tvshowtitle%22%3A%20%22Supernatural%22%2C%20%22year%22%3A%202005%7D%7D%2C%20%22showInfo%22%3A%20%7B%22art%22%3A%20%7B%22banner%22%3A%20%22http%3A%2F%2Fthetvdb.com%2Fbanners%2Fgraphical%2F5be742bf1a396.jpg%22%2C%20%22fanart%22%3A%20%22http%3A%2F%2Fthetvdb.com%2Fbanners%2Ffanart%2Foriginal%2F78901-95.jpg%22%2C%20%22landscape%22%3A%20%22None%22%2C%20%22poster%22%3A%20%22http%3A%2F%2Fthetvdb.com%2Fbanners%2Fposters%2F5be70d8b6f25e.jpg%22%2C%20%22thumb%22%3A%20%22None%22%7D%2C%20%22ids%22%3A%20%7B%22imdb%22%3A%20%22tt0460681%22%2C%20%22slug%22%3A%20%22supernatural%22%2C%20%22tmdb%22%3A%201622%2C%20%22trakt%22%3A%201611%2C%20%22tvdb%22%3A%2078901%2C%20%22info%22%3A%20%7B%22castandrole%22%3A%20%5B%5D%2C%20%22country%22%3A%20%22%22%2C%20%22duration%22%3A%202700%2C%20%22episodeCount%22%3A%20%22302%22%2C%20%22genre%22%3A%20%5B%5D%2C%20%22imdbnumber%22%3A%20%22tt0460681%22%2C%20%22mediatype%22%3A%20%22tvshow%22%2C%20%22mpaa%22%3A%20%22None%22%2C%20%22no_seasons%22%3A%2015%2C%20%22originaltitle%22%3A%20%22Supernatural%22%2C%20%22playcount%22%3A%200%2C%20%22plot%22%3A%20%22The%20story%20revolves%20around%20two%20brothers%2C%20Sam%20and%20Dean%20Winchester%20as%20they%20follow%20their%20father%27s%20footsteps%2C%20hunting%20down%20evil%20supernatural%20creatures%20such%20as%20monsters%2C%20demons%2C%20and%20even%20fallen%20gods%20while%20trying%20to%20save%20innocent%20people%20along%20the%20way.%20Continuing%20the%20%22family%20business%22%20after%20their%20father%27s%20death%2C%20the%20brothers%20soon%20discovered%20that%20the%20%22hunt%22%20doesn%27t%20just%20involve%20slashing%20and%20hacking%20monsters%20and%20demons%20but%20also%20dealing%20with%20more%20powerful%20creatures%20such%20as%20angels%2C%20reapers%2C%20and%20even%20Death.%22%2C%20%22premiered%22%3A%20%222005-09-13%22%2C%20%22rating%22%3A%200%2C%20%22seasonCount%22%3A%2015%2C%20%22showaliases%22%3A%20%5B%5D%2C%20%22status%22%3A%20%22Continuing%22%2C%20%22trailer%22%3A%20%22%22%2C%20%22tvshowtitle%22%3A%20%22Supernatural%22%2C%20%22year%22%3A%20%222005%22%7D%7D%7D`
After replacing all the info with its appropriate parameter, the player ends up with the following command sequence:
[
{
"link" : "plugin://plugin.video.example/?action=smartPlay&actionArgs=%7B%22episodeInfo%22%3A%20%7B%22art%22%3A%20%7B%22fanart%22%3A%20%22{fanart}%22%2C%20%22landscape%22%3A%20%22{thumbnail}%22%2C%20%22poster%22%3A%20%22{poster}%22%2C%20%22thumb%22%3A%20%22{thumbnail}%22%7D%2C%20%22ids%22%3A%20%7B%22imdb%22%3A%20%22{epimdb}%22%2C%20%22tmdb%22%3A%20{eptmdb}%2C%20%22trakt%22%3A%20{eptrakt}%2C%20%22tvdb%22%3A%20{id}%2C%20%22info%22%3A%20%7B%22castandrole%22%3A%20%5B%5D%2C%20%22dateadded%22%3A%20%22{firstaired}%22%2C%20%22duration%22%3A%20{duration}%2C%20%22episode%22%3A%20{episode}%2C%20%22genre%22%3A%20%5B%5D%2C%20%22imdbnumber%22%3A%20%22{epimdb}%22%2C%20%22mediatype%22%3A%20%22episode%22%2C%20%22mpaa%22%3A%20%22{mpaa}%22%2C%20%22originaltitle%22%3A%20%22{title_+}%22%2C%20%22playcount%22%3A%200%2C%20%22plot%22%3A%20%22{plot_+}%22%2C%20%22premiered%22%3A%20%22{firstaired}%22%2C%20%22rating%22%3A%20{rating}%2C%20%22season%22%3A%20{season}%2C%20%22sortepisode%22%3A%20{episode}%2C%20%22sortseason%22%3A%20{season}%2C%20%22title%22%3A%20%22{title_+}%22%2C%20%22trailer%22%3A%20%22%22%2C%20%22tvshowtitle%22%3A%20%22{clearname_+}%22%2C%20%22year%22%3A%20{year}%7D%7D%2C%20%22showInfo%22%3A%20%7B%22art%22%3A%20%7B%22banner%22%3A%20%22{banner}%22%2C%20%22fanart%22%3A%20%22{fanart}%22%2C%20%22landscape%22%3A%20%22{thumbnail}%22%2C%20%22poster%22%3A%20%22{poster}%22%2C%20%22thumb%22%3A%20%22{thumbnail}%22%7D%2C%20%22ids%22%3A%20%7B%22imdb%22%3A%20%22{imdb}%22%2C%20%22slug%22%3A%20%22{slug}%22%2C%20%22tmdb%22%3A%20{tmdb}%2C%20%22trakt%22%3A%20{trakt}%2C%20%22tvdb%22%3A%20{id}%2C%20%22info%22%3A%20%7B%22castandrole%22%3A%20%5B%5D%2C%20%22country%22%3A%20%22%22%2C%20%22duration%22%3A%20{duration}%2C%20%22episodeCount%22%3A%20%22{episodes}%22%2C%20%22genre%22%3A%20%5B%5D%2C%20%22imdbnumber%22%3A%20%22{imdb}%22%2C%20%22mediatype%22%3A%20%22tvshow%22%2C%20%22mpaa%22%3A%20%22{mpaa}%22%2C%20%22no_seasons%22%3A%20{seasons}%2C%20%22originaltitle%22%3A%20%22{clearname_+}%22%2C%20%22playcount%22%3A%200%2C%20%22plot%22%3A%20%22{plot_+}%22%2C%20%22premiered%22%3A%20%22{series_firstaired}%22%2C%20%22rating%22%3A%20{series_rating}%2C%20%22seasonCount%22%3A%20{seasons_no_specials}%2C%20%22showaliases%22%3A%20%5B%5D%2C%20%22status%22%3A%20%22{status}%22%2C%20%22trailer%22%3A%20%22%22%2C%20%22tvshowtitle%22%3A%20%22{clearname_+}%22%2C%20%22year%22%3A%20%22{year}%22%7D%7D%7D",
"steps" : [],
"action" : "PLAY"
}
]Say the add-on you want to add also has a search feature that allows the user to enter free text and returns a folder listing all movie results.
A. locate the search function inside the add-on's code. Normally searching for the word 'keyboard' will get you close.
B. identify what the code does with the entered search phrase.
In some add-ons a plugin URL would be generated and Container.Update would be
executed with that URL. If that's the case then just print the URL and see
the printed URL in kodi.log. If all parameters in the URL are supported then
replace the parameters in the URL as described earlier (in 2) and use it as
the link in the CommandSequence (followed by any required steps).
In other cases a function would be called after the user enters a search
phrase passing the entered text as an argument. For example, there might be a call to a function named do_search. Now you
need a way to call do_search from OpenMeta using a URL. To do this find the part
of code that parses args (usually this is written at the bottom of the main
.py file) and locate which args are needed in order to call do_search
with the search phrase. For example, you may need to pass mode=2
to call do_search and the search phrase is passed using q=<search_phrase>.
Then the CommandSequence to use (prefering 720p) is:
[
{
"link": "plugin://plugin.video.example/?mode=2&q={title}",
"steps": [
"{title}",
"\[720p\].*"
]
},
{
"link": "plugin://plugin.video.example/?mode=2&q={title}",
"steps": [
"{title}",
".*"
]
}
]Only if it is absolutely not possible to send a search term to the add-on via a plugin:// URL, you may try to use a keyboard step. Here is an example:
[
{
"link": "plugin://plugin.video.example",
"steps": [
"Search",
"keyboard: {title}",
"{title}",
"\[720p\].*"
]
},
{
"link": "plugin://plugin.video.example",
"steps": [
"Search",
"keyboard: {title}",
"{title}", ".*"
]
}
]Full template for getting started with player making. The capital letters in square brackets after the player's name are indicators on the player's functionality.
- M = main player
- L = library player
- C = context player
Those indicators should only be included if additional actions are required for any of the player-types to function. When including the indicators, please also use this color-coding:
-
FF018E0E = Works as is -
FFFB122F = Needs edit(s) in addon's code -
FF0147FA = Needs an account
{
"name" : "<TYPE> [COLOR FF0084FF]-[/COLOR] <ADDONNAME> [[COLOR FF018E0E]M[/COLOR][COLOR FFFB122F]L[/COLOR][COLOR FF0147FA]C[/COLOR]]([COLOR FF0084FF][/COLOR])",
"repository" : "<REPOSITORY-ID>",
"plugin" : "<PLUGIN-ID>",
"priority" : <PRIORITY>,
"id" : "<PLAYER-ID>",
"filters" : {},
"postprocess" : "",
"movies" : [
[
{
"language" : "<LANGUAGE>",
"link" : "<LINK>",
"steps" : [
"<STEP-N>",
"<STEP-N+1>",
"<STEP-N+2>"
],
"action" : "<ACTION>"
}
]
],
"tvshows" : [
[
{
"language" : "<LANGUAGE>",
"link" : "<LINK>",
"steps" : [
"<STEP-N>",
"<STEP-N+1>",
"<STEP-N+2>"
],
"action" : "<ACTION>"
}
]
],
"music" : [
[
{
"language" : "<LANGUAGE>",
"link" : "<LINK>",
"steps" : [
"<STEP-N>",
"<STEP-N+1>",
"<STEP-N+2>"
],
"action" : "<ACTION>"
}
]
],
"musicvideos" : [
[
{
"language" : "<LANGUAGE>",
"link" : "<LINK>",
"steps" : [
"<STEP-N>",
"<STEP-N+1>",
"<STEP-N+2>"
],
"action" : "<ACTION>"
}
]
],
"live" : [
[
{
"language" : "<LANGUAGE>",
"link" : "<LINK>",
"steps" : [
"<STEP-N>",
"<STEP-N+1>",
"<STEP-N+2>"
],
"action" : "<ACTION>"
}
]
]
}