--- /dev/null
+
+
+Use this setting to translate broadcaster-specific, country-specific or other customised genre tags into tags recognised by tvheadend.
+
+Example:
+
+```
+192=20
+208=16
+224=35
+```
+
+| Line | Meaning |
+| ------------- | ------------- |
+|192=20|Translate decimal 192 (0xC0 = Australian-specific 'comedy') to decimal 20 (0x14 = ETSI standard 'comedy').|
+|208=16|Translate decimal 208 (0xD0 = Australian-specific 'drama') to decimal 16 (0x10 = ETSI standard 'movie/drama (general)').|
+|224=35|Translate decimal 224 (0xE0 = Australian-specific 'documentary') to decimal 35 (0x23 = ETSI standard 'documentary').|
+
+See: [Search the ETSI web site for the latest version of the 'ETSI EN 300 468' standard.](https://www.etsi.org/standards#page=1&search=%22ETSI%20EN%20300%20468%22&title=0&etsiNumber=1&content=0&version=1&onApproval=1&published=1&withdrawn=1&historical=1&isCurrent=1&superseded=1&startDate=1988-01-15&endDate=2023-07-09&harmonized=0&keyword=&TB=&stdType=&frequency=&mandate=&collection=&sort=1)
+
+Search for 'content_descriptor' in the standards document.
\ No newline at end of file
epggrab_ota_set_cron();
}
+static void
+epggrab_class_ota_genre_translation_notify(void *self, const char *lang)
+{
+ epggrab_ota_set_genre_translation();
+}
+
CLASS_DOC(epgconf)
PROP_DOC(cron)
+PROP_DOC(ota_genre_translation)
const idclass_t epggrab_class = {
.ic_snode = &epggrab_conf.idnode,
.name = N_("OTA (Over-the-air) Grabber Settings"),
.number = 3,
},
- {}
+ {
+ .name = N_("OTA (Over-the-air) Genre Translation"),
+ .number = 4,
+ },
+ {}
},
.ic_properties = (const property_t[]){
{
.opts = PO_EXPERT,
.group = 3,
},
+ {
+ .type = PT_STR,
+ .id = "ota_genre_translation",
+ .name = N_("Over-the-air Genre Translation"),
+ .desc = N_("Translate the genre codes received from the broadcaster to another genre code."
+ "<br>Use the form xxx=yyy, where xxx and yyy are "
+ "'ETSI EN 300 468' content descriptor values expressed in decimal (0-255). "
+ "<br>Genre code xxx will be converted to genre code yyy."
+ "<br>Use a separate line for each genre code to be converted."),
+ .doc = prop_doc_ota_genre_translation,
+ .off = offsetof(epggrab_conf_t, ota_genre_translation),
+ .notify = epggrab_class_ota_genre_translation_notify,
+ .opts = PO_MULTILINE | PO_EXPERT,
+ .group = 4,
+ },
{}
}
};
uint32_t epgdb_periodicsave;
uint32_t epgdb_saveafterimport;
char *ota_cron;
+ char *ota_genre_translation;
uint32_t ota_timeout;
uint32_t ota_initial;
uint32_t int_initial;
/*
* Set configuration
*/
-int epggrab_activate_module ( epggrab_module_t *mod, int activate );
-void epggrab_ota_set_cron ( void );
-void epggrab_ota_trigger ( int secs );
-void epggrab_rerun_internal ( void );
+int epggrab_activate_module ( epggrab_module_t *mod, int activate );
+void epggrab_ota_set_cron ( void );
+void epggrab_ota_set_genre_translation ( void );
+void epggrab_ota_trigger ( int secs );
+void epggrab_rerun_internal ( void );
/*
* Load/Save
htsmsg_t *epggrab_ota_module_id_list( const char *lang );
const char *epggrab_ota_check_module_id( const char *id );
+/*
+ * Global variable for genre translation
+ */
+extern unsigned char epggrab_ota_genre_translation[256];
+
#endif /* __EPGGRAB_H__ */
/* **************************************************************************
static int _eit_desc_content
( epggrab_module_t *mod, const uint8_t *ptr, int len, eit_event_t *ev )
{
+ uint8_t tempPtr = 0; //Temporary variable to hold a (potentially) changed *ptr value.
+ tempPtr = *ptr;
while (len > 1) {
- if (*ptr == 0xb1)
+ //Get the potentially new genre value.
+ tempPtr = epggrab_ota_genre_translation[*ptr];
+ //If we did get a translation, write a trace for debugging.
+ if(tempPtr != *ptr)
+ {
+ tvhtrace(LS_TBL_EIT, "Translating '%d' to '%d'", *ptr, tempPtr);
+ }
+ if (tempPtr == 0xb1) //0xB1 is the genre code for 'Black and White'
ev->bw = 1;
- else if (*ptr < 0xb0) {
+ else if (tempPtr < 0xb0) { //0xB0 is the start of the 'Special Characteristics' block.
if (!ev->genre) ev->genre = calloc(1, sizeof(epg_genre_list_t));
- epg_genre_list_add_by_eit(ev->genre, *ptr);
+ epg_genre_list_add_by_eit(ev->genre, (const uint8_t)tempPtr); //Cast as a 'const'
}
len -= 2;
ptr += 2;
tvh_mutex_t epggrab_ota_mutex;
+unsigned char epggrab_ota_genre_translation[256];
+
SKEL_DECLARE(epggrab_ota_mux_skel, epggrab_ota_mux_t);
SKEL_DECLARE(epggrab_svc_link_skel, epggrab_ota_svc_link_t);
epggrab_ota_arm((time_t)-1);
}
+void
+epggrab_ota_set_genre_translation ( void )
+{
+
+ tvhtrace(LS_EPGGRAB, "Processing genre code translations.");
+
+ //Take the raw setting data and parse it into the genre translation table.
+ //There is some sanity checking done, however, the data is entered as freeform
+ //text by the user and errors may still slip through.
+ //Note: Each line that comes back from the WebUI is separated by a LF (0x0A).
+ //^^^^ This has been tested with a Windows WebUI client and it only sends LF, not CR/LF.
+
+ lock_assert(&global_lock);
+ tvh_mutex_lock(&epggrab_ota_mutex);
+
+ //Reset the translation table to 1:1 here before proceeding.
+ for(int i = 0; i < 256; i++)
+ {
+ epggrab_ota_genre_translation[i] = i;
+ }
+
+ int tempPos = 0;
+ int outPos = 0;
+ int tempLen = 0;
+ char *tempPair = NULL;
+ int tempFrom = -1;
+ int tempTo = -1;
+ tempLen = strlen(epggrab_conf.ota_genre_translation);
+
+ //Make sure that this is large enough to take the whole config string in one go
+ //to allow for it being full of nonsense entered by the user.
+ tempPair = calloc(tempLen + 1, 1);
+
+ //Read through the user input byte by byte and parse out the lines
+ for(tempPos = 0; tempPos < tempLen; tempPos++)
+ {
+ //If the current character is not a new line or a comma. (Comma is an undocumented courtesy!)
+ if(epggrab_conf.ota_genre_translation[tempPos] != 10 && epggrab_conf.ota_genre_translation[tempPos] != ',')
+ {
+ //Only allow numerals, '=' and '#' to pass through.
+ //ASCII 0-9 = 48-57, '=' = 61 (in decimal)
+ if((epggrab_conf.ota_genre_translation[tempPos] >= 48 && epggrab_conf.ota_genre_translation[tempPos] <= 57) || epggrab_conf.ota_genre_translation[tempPos] == 61 || epggrab_conf.ota_genre_translation[tempPos] == '#')
+ {
+ tempPair[outPos] = epggrab_conf.ota_genre_translation[tempPos];
+ tempPair[outPos + 1] = 0;
+ outPos++;
+ }
+ }
+
+ //If we have a new line or a comma or we are at the end of the config string
+ if(epggrab_conf.ota_genre_translation[tempPos] == 10 || epggrab_conf.ota_genre_translation[tempPos] == ',' || tempPos == (tempLen - 1))
+ {
+ //Only process non-null strings.
+ if(strlen(tempPair) != 0)
+ {
+ tempFrom = -1;
+ tempTo = -1;
+ sscanf(tempPair, "%d=%d", &tempFrom, &tempTo);
+ //Test that the to/from values are sane before proceeding.
+ if(tempFrom > -1 && tempFrom < 256 && tempTo > -1 && tempTo < 256)
+ {
+ tvhtrace(LS_EPGGRAB, "Valid translation from '%d' to '%d'.", tempFrom, tempTo);
+ epggrab_ota_genre_translation[tempFrom] = tempTo;
+ }
+ else
+ {
+ tvhtrace(LS_EPGGRAB, "Ignoring '%s'.", tempPair);
+ }
+ outPos = 0;
+ tempPair[0] = 0;
+ }
+ }
+ }
+
+ for(int x = 0; x < 256; x++)
+ {
+ if(epggrab_ota_genre_translation[x] != x)
+ {
+ tvhtrace(LS_EPGGRAB, "Genre '%d' (0x%02x) translates to genre '%d' (0x%02x).", x, x, epggrab_ota_genre_translation[x], epggrab_ota_genre_translation[x]);
+ }
+ }
+ free(tempPair);
+
+ tvh_mutex_unlock(&epggrab_ota_mutex);
+}
+
/******************************************************************************
* Editor Configuration
*