Skip to main content
Tuliprox supports two layers of editorial logic:
  • Reusable templates — named regex fragments referenced with !name! syntax
  • Mapping rules — a small DSL for renaming, regrouping, and enriching content

Template loading

Templates can live in three places:
LocationHow to configure
Inline in source.ymltemplates: block at the top level
Inline in mapping.ymltemplates: block inside mappings:
Central file or directorytemplate_path in config.yml
If a directory is used, all *.yml files are loaded in alphanumeric order and merged. Template names must be globally unique across all sources.

Template syntax

Define templates as named string values:
templates:
  - name: delimiter
    value: '[\s_-]*'
  - name: quality
    value: '(?i)(?P<quality>HD|LQ|4K|UHD)?'
Reference a template by wrapping its name in ! characters:
^.*TF1!delimiter!Series?!delimiter!Films?(!delimiter!!quality!)\s*$
Templates can reference other templates in their value.

mapping.yml structure

The top-level mappings key holds:
KeyDescription
templatesReusable template definitions (same format as source.yml)
mappingList of mapping entries
Each mapping entry defines:
FieldDescription
idUnique identifier — referenced from targets in source.yml
match_as_asciiNormalize Unicode to ASCII before matching
mapperList of mapper rules (filter + script pairs)
counterList of counter rules for injecting sequence numbers

match_as_ascii

When enabled, Tuliprox deunicodes field values before applying filters. This lets simple ASCII patterns match content with accented or special characters:
  • ée
  • öo
  • ßss
mapping:
  - id: my_mapping
    match_as_ascii: true
    mapper: ...

Mapper rules

Each mapper rule contains:
FieldDescription
filterFilter expression — only matching entries run this script
scriptDSL script to execute
mapper:
  - filter: 'Group ~ "(?i)news"'
    script: |
      add_favourite("Favourites")

Mapper DSL reference

Accessing playlist fields

Playlist fields are accessed with the @Field syntax. Fields can also be assigned to update the output:
VariableDescription
@GroupChannel group name
@TitleChannel title
@NameChannel name
@CaptionChannel caption
@UrlStream URL
@GenreContent genre
@InputSource input name

Variable assignment

quality = @Caption ~ "\\b([F]?HD[i]?)\\b"

Regex capture

Regex matches expose numbered captures via .1, .2, etc., and named captures via .name:
title_match = @Caption ~ "(.*?)\\:\\s*(.*)"
title_prefix = title_match.1
title_name = title_match.2

match block

Select a value based on which variables are truthy:
result = match {
  (var1, var2) => result1,
  var2 => result2,
  _ => default
}

map block

Map a variable’s value to a new value:
quality = map quality {
  "720p" => "HD",
  "1080p" => "FHD",
  "4K" => "UHD",
  _ => quality,
}
Range syntax is supported for numeric mapping:
year_group = map year {
  ..2019 => "< 2020",
  _ => year_text,
}

for_each

Iterate over split values or regex capture groups:
genres = split(@Genre, "[,/&]")
genres.for_each((_, genre) => {
  add_favourite(concat("Genre - ", genre))
})

Nested map blocks

map blocks can be nested:
prefix = map quality {
  "HD" => "01.",
  "FHD" => "02.",
  _ => map group {
    "NEWS" => "04.",
    "DOCU" => "05.",
    _ => group
  },
}

Built-in functions

FunctionDescription
concat(a, b, ...)Concatenate strings
uppercase(s)Convert to uppercase
lowercase(s)Convert to lowercase
capitalize(s)Capitalize first letter
trim(s)Remove leading and trailing whitespace
print(s)Print a value to debug output
number(s)Parse a string as a number
first(a, b, ...)Return the first non-null value
template(name)Expand a named template
replace(s, pattern, replacement)Regex replace
pad(n, width)Zero-pad a number
format(fmt, ...)Format string with positional args
add_favourite(group)Add the current channel to a favourite group
split(s, pattern)Split a string on a regex pattern

Counters

Counters inject auto-incrementing sequence numbers into channel titles or group names.
FieldDescription
filterFilter expression — only matching entries get a counter
valueStarting counter value
fieldField to apply the counter to: title, group, etc.
modifierprefix or suffix
concatSeparator string between counter and field value
paddingMinimum digit width (zero-padded)
mapping:
  - id: simple
    counter:
      - filter: 'Group ~ ".*FR.*"'
        value: 9000
        field: title
        padding: 2
        modifier: suffix
        concat: " - "

Complete mapping.yml example

mappings:
  templates:
    - name: QUALITY
      value: '(?i)\b([FUSL]?HD|SD|4K|1080p|720p|3840p)\b'
  mapping:
    - id: all_channels
      match_as_ascii: true
      mapper:
        - filter: 'Caption ~ "(?i)^(US|USA|United States).*?TNT"'
          script: |
            quality = uppercase(@Caption ~ "!QUALITY!")
            quality = map quality {
              "720p" => "HD",
              "1080p" => "FHD",
              "4K" => "UHD",
              "3840p" => "UHD",
              _ => quality,
            }
            @Group = "United States - Entertainment"

Advanced grouping example

This script groups channels into quality-oriented or category-oriented buckets:
group = @Group ~ "(EU|SATELLITE|NATIONAL|NEWS|MUSIC|SPORT|RELIGION|FILM|KIDS|DOCU)"
quality = @Caption ~ "\\b([F]?HD[i]?)\\b"
title_match = @Caption ~ "(.*?)\\:\\s*(.*)"
title_name = title_match.2

quality = map group {
  "NEWS" | "NATIONAL" | "SATELLITE" => quality,
  _ => null,
}

prefix = map quality {
  "HD" => "01.",
  "FHD" => "02.",
  "HDi" => "03.",
  _ => map group {
    "NEWS" => "04.",
    "DOCU" => "05.",
    "SPORT" => "06.",
    _ => group
  },
}

name = match {
  quality => concat(prefix, " FR [", quality, "]"),
  group => concat(prefix, " FR [", group, "]"),
  _ => prefix
}

@Group = name
@Caption = title_name

Grouping by release year

year_text = @Caption ~ "(\\d{4})\\)?$"
year = number(year_text)
year_group = map year {
  ..2019 => "< 2020",
  _ => year_text,
}
@Group = concat("FR | MOVIES ", year_group)

Filter hints

Filters in mapper rules use the same expression language as target filters:
OperatorDescription
~Regex match
NOTLogical negation
ANDLogical conjunction
ORLogical disjunction
Type = live|vod|seriesMatch by content type
Use regex101.com with the Rust flavor selected to test your regex patterns before adding them to mapping scripts.

Build docs developers (and LLMs) love