Skip to content

Writing an OpenMeta player

drinfernoo edited this page Mar 14, 2019 · 1 revision

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/Players folder
  • 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

Table of Contents

Parameters

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 %20 if the target add-on calls urllib2.unquote on the parameter)

Movie parameters:

  • 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)"

TV show parameters:

  • 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..."

More parameters:

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]}

OpenMeta player format

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.

Full examples

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"
   }
  ]
 ]
}

Tips and Tricks

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.

Browsing an external add-on

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}",
   ".*"
  ]
 }
]

Using library integration

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"
 }
]

Using search

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}", ".*"
  ]
 }
]

Player template

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:

  • #018E0E FF018E0E = Works as is
  • #FB122F FFFB122F = Needs edit(s) in addon's code
  • #0147FA 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>"
   }
  ]
 ]
}