src/intlconv.c \
src/profile.c \
src/bouquet.c \
+ src/ratinglabels.c \
src/lock.c \
src/string_list.c \
src/wizard.c \
src/api/api_caclient.c \
src/api/api_profile.c \
src/api/api_bouquet.c \
+ src/api/api_ratinglabel.c \
src/api/api_language.c \
src/api/api_satip.c \
src/api/api_timeshift.c \
JAVASCRIPT += $(ROOTPATH)/app/wizard.js
JAVASCRIPT += $(ROOTPATH)/tv.js
JAVASCRIPT += $(ROOTPATH)/app/servicemapper.js
+JAVASCRIPT += $(ROOTPATH)/app/ratinglabels.js
JAVASCRIPT += $(ROOTPATH)/app/tvheadend.js
--- /dev/null
+<tvh_include>inc/ratinglabel_contents</tvh_include>
+
+---
+
+## Overview
+
+This tab lists all defined parental rating labels.
+
+
+
+A 'rating label' is a text code like 'PG', 'PG-13' or 'FSK 12' used to identify the parental rating classification of a TV programme.
+
+Rating labels can be sourced from the OTA EPG grabber or from the XMLTV grabber.
+
+**NOTE:** Rating labels are not enabled by default and must be enabled in the [EPG Grabber](class/epggrab) module under 'General Settings'.
+
+
+# DVB OTA
+
+Ratings from the OTA EPG do not contain rating text like 'PG', instead, a combination of country code and age is transmitted, eg: AUS + 8. It is the responsibility of the receiver unit to decode this combination and determine the rating text to display.
+
+When the rating labels module encounters a new country and age combination, it will create a placeholder entry in the rating labels table as follows:
+
+
+
+When a placeholder label is in use, the programme details in the EPG will show this placeholder entry rather than the expected value.
+
+
+
+You are required to manually edit this placeholder entry in order to provide the appropriate rating text to display. The correct text can be found by searching for the specific programme in another EPG source or by obtaining the classification guidelines in the location (country) in question. This only needs to be done once for each label, all other programmes with that label will be automatically adjusted.
+
+
+
+**NOTE:** In the example, the age provided by DVB is '10', whereas the age displayed is '13'. This is because the DVB standard subtracts 3 from some recommended ages before transmission meaning that the receiver must add 3 to the number received. When creating a placeholder label, this module will automatically add 3 where appropriate.
+
+
+# XMLTV
+
+Ratings from XMLTV contain the rating label text, but not the recommended age.
+```
+<rating system="ACMA">
+ <value>MA</value>
+</rating>
+```
+
+When a new rating is encountered from an XMLTV EPG source, a placeholder label similar to the DVB ones is created and you will need to add the country code and the ages.
+
+
+
+# Combined DVB OTA and XMLTV
+
+If you have multiple EPG sources for different groups of channels, it is possible to map the ratings from those multiple sources to produce a single unified rating system. This can be done by adjusting the 'display age' and 'display label' of the various sources until they are matched to your requirements.
+
+The same rating label can be used for both DVB OTA and XMLTV EPG sources. Because DVB OTA is matched on Country+Age and XMLTV is matched on Authority+Label, a rating label that contains all 4 of these values will be selected and the 'display age' and 'display label' will be used for both EPG sources.
+
+Sources can also be kept seperated by ensuring that a DVB OTA rating does not have an 'authority' that matches any XMLTV sources and that an XMLTV rating does not have an 'age' or 'country' that matches a DVB OTA source, only a 'display age'.
+
+
+---
[EPG Grabber Channels](class/epggrab_channel) | EPG data sources used by channels
[EPG Grabber](class/epggrab) | EPG grabber configuration
[EPG Grabber Modules](class/epggrab_mod) | EPG grabber module management
-
+[Rating Labels Module](class/ratinglabel) | Rating Labels management
--- /dev/null
+Contents | Description
+---------------------------------------|------------------------
+[Overview](#overview) | Tab overview
+[Items/Properties](#items) | Items and Properties
+[EPG Grabber](class/epggrab) | EPG grabber configuration
+
+
+
api_service_init();
api_channel_init();
api_bouquet_init();
+ api_ratinglabel_init();
api_epg_init();
api_epggrab_init();
api_status_init();
void api_service_init ( void );
void api_channel_init ( void );
void api_bouquet_init ( void );
+void api_ratinglabel_init ( void );
void api_mpegts_init ( void );
void api_epg_init ( void );
void api_epggrab_init ( void );
#include "dvr/dvr.h"
#include "lang_codes.h"
#include "string_list.h"
+#include "epggrab.h" //Needed to be able to test for epggrab_conf.epgdb_processparentallabels
static htsmsg_t *
api_epg_get_list ( const char *s )
api_epg_entry ( epg_broadcast_t *eb, const char *lang, const access_t *perm, const char **blank )
{
const char *s, *blank2 = NULL;
- char buf[32];
+ char buf[128];
channel_t *ch = eb->channel;
htsmsg_t *m, *m2;
epg_episode_num_t epnum;
htsmsg_add_str(m, "episodeUri", eb->episodelink->uri);
if (eb->serieslink)
htsmsg_add_str(m, "serieslinkUri", eb->serieslink->uri);
-
+
/* Channel Info */
api_epg_add_channel(m, ch, *blank);
-
+
/* Time */
htsmsg_add_s64(m, "start", eb->start);
htsmsg_add_s64(m, "stop", eb->stop);
if (eb->age_rating)
htsmsg_add_u32(m, "ageRating", eb->age_rating);
+ if(epggrab_conf.epgdb_processparentallabels)
+ {
+ if (eb->rating_label)
+ {
+ if(eb->rating_label->rl_display_label){
+ htsmsg_add_str(m, "ratingLabel", eb->rating_label->rl_display_label);
+ }
+ if(eb->rating_label->rl_icon){
+ s = eb->rating_label->rl_icon;
+ if (!strempty(s)) {
+ s = imagecache_get_propstr(s, buf, sizeof(buf));
+ if (s)
+ htsmsg_add_str(m, "ratingLabelIcon", s);
+ }//END we got an imagecache
+ }//END rating label icon is not null
+ }//END rating label is not null
+ }//END parental labels enabled.
+
if (eb->first_aired)
htsmsg_add_s64(m, "first_aired", eb->first_aired);
if (eb->copyright_year)
/* Next event */
if ((eb = epg_broadcast_get_next(eb)))
htsmsg_add_u32(m, "nextEventId", eb->id);
-
+
return m;
}
char *lang, *title_esc, *title_anchor;
epg_set_t *serieslink = NULL;
const char *title = NULL;
-
+
if (htsmsg_get_u32(args, "eventId", &id))
return EINVAL;
--- /dev/null
+/*
+ * API - ratinglabel calls
+ *
+ * Copyright (C) 2014 Jaroslav Kysela (Original Bouquets)
+ * Copyright (C) 2023 DeltaMikeCharlie (Updated for Rating Labels)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TVH_API_RATINGLABEL_H__
+#define __TVH_API_RATINGLABEL_H__
+
+#include "tvheadend.h"
+#include "ratinglabels.h"
+#include "access.h"
+#include "api.h"
+
+static int
+api_ratinglabel_list
+ ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ ratinglabel_t *rl;
+ htsmsg_t *l;
+ char ubuf[UUID_HEX_SIZE];
+
+ l = htsmsg_create_list();
+ tvh_mutex_lock(&global_lock);
+ RB_FOREACH(rl, &ratinglabels, rl_link)
+ {
+ htsmsg_add_msg(l, NULL, htsmsg_create_key_val(idnode_uuid_as_str(&rl->rl_id, ubuf), rl->rl_country ?: ""));
+ }
+ tvh_mutex_unlock(&global_lock);
+ *resp = htsmsg_create_map();
+ htsmsg_add_msg(*resp, "entries", l);
+
+ return 0;
+}
+
+static void
+api_ratinglabel_grid
+ ( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf )
+{
+ ratinglabel_t *rl;
+
+ RB_FOREACH(rl, &ratinglabels, rl_link)
+ idnode_set_add(ins, (idnode_t*)rl, &conf->filter, perm->aa_lang_ui);
+}
+
+static int
+api_ratinglabel_create
+ ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ htsmsg_t *conf;
+ ratinglabel_t *rl;
+
+ if (!(conf = htsmsg_get_map(args, "conf")))
+ return EINVAL;
+
+ tvh_mutex_lock(&global_lock);
+ rl = ratinglabel_create(NULL, conf, NULL, NULL);
+ if (rl)
+ api_idnode_create(resp, &rl->rl_id);
+ tvh_mutex_unlock(&global_lock);
+
+ return 0;
+}
+
+void api_ratinglabel_init ( void )
+{
+ static api_hook_t ah[] = {
+ { "ratinglabel/list", ACCESS_ADMIN, api_ratinglabel_list, NULL },
+ { "ratinglabel/class", ACCESS_ADMIN, api_idnode_class, (void*)&ratinglabel_class },
+ { "ratinglabel/grid", ACCESS_ADMIN, api_idnode_grid, api_ratinglabel_grid },
+ { "ratinglabel/create", ACCESS_ADMIN, api_ratinglabel_create, NULL },
+ { NULL },
+ };
+
+ api_register_all(ah);
+}
+
+#endif /* __TVH_API_RATINGLABEL_H__ */
uint32_t de_content_type; /* Content type (from EPG) (only code) */
uint16_t de_copyright_year; /* Copyright year (from EPG) */
uint16_t de_dvb_eid;
- uint16_t de_age_rating; /* Age rating (from EPG) */
+ uint16_t de_age_rating; /* Age rating (from EPG) */
+ //Depending how old the recording is, the current rating label system
+ //may have changed, so keep an absolute copy of the values at
+ //the time of recording rather than pointing to a rating label
+ //object that may no longer exist many years later.
+ char *de_rating_label_saved; /* Saved rating label for once the recording has been completed*/
+ char *de_rating_icon_saved; /* Saved rating icon full path (not image cache) for once the recording has been completed*/
+ ratinglabel_t *de_rating_label; /* 'Live' rating label object */
int de_pri;
int de_dont_reschedule;
time_t start, time_t stop,
time_t start_extra, time_t stop_extra,
dvr_prio_t pri, int retention, int removal,
- int playcount, int playposition, int age_rating);
+ int playcount, int playposition, int age_rating,
+ ratinglabel_t *rating_label);
void dvr_destroy_by_channel(channel_t *ch, int delconf);
#include "notify.h"
#include "compat.h"
#include "string_list.h"
+#include "epggrab.h" //Needed to get the epggrab_conf.epgdb_processparentallabels flag.
struct dvr_entry_list dvrentries;
static int dvr_in_init;
static dvr_entry_t *_dvr_duplicate_event(dvr_entry_t *de);
+static const void *dvr_entry_class_rating_icon_url_get(void *o);
+
/*
*
*/
htsmsg_add_u32(conf, "content_type", genre->code / 16);
if(e->age_rating)
htsmsg_add_u32(conf, "age_rating", e->age_rating);
+
+ //Only process these fields if rating labels are enabled.
+ if(epggrab_conf.epgdb_processparentallabels){
+ if(e->rating_label){
+ htsmsg_set_uuid(conf, "rating_label_uuid", &e->rating_label->rl_id.in_uuid);
+
+ if(e->rating_label->rl_icon){
+ htsmsg_add_str(conf, "rating_icon_saved", imagecache_get_propstr(e->rating_label->rl_icon, tbuf, sizeof(tbuf)));
+ }
+ }
+ }//END rating labels enabled.
+
}
de = dvr_entry_create(NULL, conf, 0);
const char *lang, time_t start, time_t stop,
time_t start_extra, time_t stop_extra,
dvr_prio_t pri, int retention, int removal,
- int playcount, int playposition, int age_rating)
+ int playcount, int playposition, int age_rating,
+ ratinglabel_t *rating_label)
{
char buf[40];
int save = 0, updated = 0;
time_t start, time_t stop,
time_t start_extra, time_t stop_extra,
dvr_prio_t pri, int retention, int removal, int playcount, int playposition,
- int age_rating )
+ int age_rating, ratinglabel_t *rating_label )
{
return _dvr_entry_update(de, enabled, dvr_config_uuid,
NULL, ch, title, subtitle, summary, desc, lang,
start, stop, start_extra, stop_extra,
pri, retention, removal, playcount, playposition,
- age_rating);
+ age_rating, rating_label);
}
/**
gmtime2local(e2->start, t1buf, sizeof(t1buf)),
gmtime2local(e2->stop, t2buf, sizeof(t2buf)));
_dvr_entry_update(de, -1, NULL, e2, NULL, NULL, NULL, NULL, NULL,
- NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1, 0);
+ NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1, 0, NULL);
return;
}
}
assert(de->de_bcast == e);
if (de->de_sched_state != DVR_SCHEDULED) continue;
_dvr_entry_update(de, -1, NULL, e, NULL, NULL, NULL, NULL, NULL,
- NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1, 0);
+ NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1, 0, NULL);
}
LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link) {
if (de->de_sched_state != DVR_SCHEDULED) continue;
epg_broadcast_get_title(e, NULL),
channel_get_name(e->channel, channel_blank_name));
_dvr_entry_update(de, -1, NULL, e, NULL, NULL, NULL, NULL, NULL,
- NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1, 0);
+ NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1, 0, NULL);
}
}
}
return &prop_ptr;
}
+static int
+dvr_entry_class_rating_set(void *o, const void *v)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ ratinglabel_t *rl = NULL;
+
+ //If RL processing is not enabled, return a null and exit.
+ if(!epggrab_conf.epgdb_processparentallabels){
+ de->de_rating_label = NULL;
+ return 0;
+ }
+
+ if (!dvr_entry_is_editable(de))
+ return 0;
+
+ //If the entry is in the past, don't link to the RL object.
+ if (de->de_stop < gclk()){
+ de->de_rating_label = NULL;
+ return 0;
+ }
+
+ rl = v ? ratinglabel_find_from_uuid(v) : NULL;
+
+ //If the rating label is found.
+ if(rl){
+ //Set the rating label pointer in the DVR entry object
+ de->de_rating_label = rl;
+
+ //Save the label and icon values
+ if(de->de_rating_label_saved){
+ free(de->de_rating_label_saved);
+ }
+
+ if(rl->rl_display_label){
+ de->de_rating_label_saved = strdup(rl->rl_display_label);
+ }
+
+ if(de->de_rating_icon_saved){
+ free(de->de_rating_icon_saved);
+ }
+
+ if(rl->rl_icon){
+ de->de_rating_icon_saved = strdup(rl->rl_icon);
+ }
+
+ return 1;
+ }//END we got an RL object.
+
+ return 0;
+}
+
+//Return the UUID string for this rating label of this entry.
+//If RL is not enabled, this function must return an empty string,
+//returning NULL will cause a crash.
+static const void *
+dvr_entry_class_rating_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ if (de->de_rating_label)
+ idnode_uuid_as_str(&de->de_rating_label->rl_id, prop_sbuf);
+ else
+ prop_sbuf[0] = '\0';
+ return &prop_sbuf_ptr;
+}
+
static int
dvr_entry_class_pri_set(void *o, const void *v)
{
(void)imagecache_get_id(dvr_entry_get_fanart_image(o));
}
+static const void *
+dvr_entry_class_rating_icon_url_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ ratinglabel_t *rl = de->de_rating_label;
+ if ((rl == NULL) || (de->de_sched_state > DVR_SCHEDULED)) {
+ //See if there is a saved icon and if so return the imagecache path for that icon.
+ prop_ptr = "";
+ if(de->de_rating_icon_saved){
+ prop_ptr = de->de_rating_icon_saved;
+ prop_ptr = imagecache_get_propstr(prop_ptr, prop_sbuf, PROP_SBUF_LEN);
+ }
+ } else {
+ //Get the icon from the live RL object.
+ return ratinglabel_class_get_icon (rl);
+ }
+ return &prop_ptr;
+}
+
+static const void *
+dvr_entry_class_rating_label_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ ratinglabel_t *rl = de->de_rating_label;
+ if (rl == NULL) {
+ prop_ptr = "";
+ if(de->de_rating_label_saved){
+ prop_ptr = de->de_rating_label_saved;
+ }
+ } else {
+ if(de->de_sched_state == DVR_SCHEDULED){
+ prop_ptr = rl->rl_display_label;
+ }
+ else
+ {
+ prop_ptr = de->de_rating_label_saved;
+ }
+
+ }
+ return &prop_ptr;
+}
+
static const void *
dvr_entry_class_duplicate_get(void *o)
{
.off = offsetof(dvr_entry_t, de_age_rating),
.opts = PO_RDONLY | PO_EXPERT,
},
+ {
+ .type = PT_STR,
+ .id = "rating_label_saved",
+ .name = N_("Saved Rating Label"),
+ .desc = N_("Saved parental rating for once recording is complete."),
+ .off = offsetof(dvr_entry_t, de_rating_label_saved),
+ .opts = PO_RDONLY | PO_NOUI,
+ },
+ {
+ .type = PT_STR,
+ .id = "rating_icon_saved",
+ .name = N_("Saved Rating Icon Path"),
+ .desc = N_("Saved parental rating icon for once recording is complete."),
+ .off = offsetof(dvr_entry_t, de_rating_icon_saved),
+ .opts = PO_RDONLY | PO_NOUI,
+ },
+ //This needs to go after the 'saved' properties because loading the RL object
+ //can refresh the 'saved' objects for scheduled entries.
+ {
+ .type = PT_STR,
+ .id = "rating_label_uuid",
+ .name = N_("Rating Label UUID"),
+ .desc = N_("Parental rating label UUID."),
+ .set = dvr_entry_class_rating_set,
+ .get = dvr_entry_class_rating_get,
+ .opts = PO_RDONLY | PO_NOUI,
+ },
+ //This needs to go after the RL object is loaded because the
+ //getter needs the object in order to get the imagecache icon path.
+ {
+ .type = PT_STR,
+ .id = "rating_icon",
+ .name = N_("Rating Icon"),
+ .desc = N_("Rating Icon URL."),
+ .get = dvr_entry_class_rating_icon_url_get,
+ .opts = PO_HIDDEN | PO_RDONLY | PO_NOSAVE | PO_NOUI,
+ },
+ {
+ .type = PT_STR,
+ .id = "rating_label",
+ .name = N_("Rating Label"),
+ .desc = N_("Rating Label."),
+ .get = dvr_entry_class_rating_label_get,
+ .opts = PO_HIDDEN | PO_RDONLY | PO_NOSAVE,
+ },
{}
}
};
/* Persist entry so we save the filename details to avoid orphan
* files if we crash before the programme completes recording.
*/
+ de->de_rating_label = NULL; //Forget the rating label pointer and only rely on the saved values from here on.
dvr_entry_changed(de);
htsp_dvr_entry_update(de);
if(code == 0) {
* Object (Generic routines)
* *************************************************************************/
-static void _epg_object_destroy
+static void _epg_object_destroy
( epg_object_t *eo, epg_object_tree_t *tree )
{
assert(eo->refcount == 0);
return 0;
}
-static void _epg_channel_rem_broadcast
+static void _epg_channel_rem_broadcast
( channel_t *ch, epg_broadcast_t *ebc, epg_broadcast_t *ebc_new )
{
RB_REMOVE(&ch->ch_epg_schedule, ebc, sched_link);
}
break;
}
-
+
/* Change (update HTSP) */
if (cur != ch->ch_epg_now || nxt != ch->ch_epg_next) {
tvhdebug(LS_EPG, "now/next %u/%u set on %s",
if (nxt) nxt->ops->putref(nxt);
}
-static epg_broadcast_t *_epg_channel_add_broadcast
+static epg_broadcast_t *_epg_channel_add_broadcast
( channel_t *ch, epg_broadcast_t **bcast, epggrab_module_t *src,
int create, int *save, epg_changes_t *changed )
{
}
}
}
-
+
/* Changed */
*save |= 1;
save |= epg_broadcast_set_star_rating(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_AGE_RATING))
save |= epg_broadcast_set_age_rating(broadcast, 0, NULL);
+ if (!(changes & EPG_CHANGED_RATING_LABEL))
+ save |= epg_broadcast_set_rating_label(broadcast, 0, NULL);
if (!(changes & EPG_CHANGED_IMAGE))
save |= epg_broadcast_set_image(broadcast, NULL, NULL);
if (!(changes & EPG_CHANGED_GENRE))
*save |= epg_broadcast_set_is_repeat(ebc, src->is_repeat, &changes);
*save |= epg_broadcast_set_star_rating(ebc, src->star_rating, &changes);
*save |= epg_broadcast_set_age_rating(ebc, src->age_rating, &changes);
+ *save |= epg_broadcast_set_rating_label(ebc, src->rating_label, &changes);
*save |= epg_broadcast_set_image(ebc, src->image, &changes);
*save |= epg_broadcast_set_genre(ebc, &src->genre, &changes);
*save |= epg_broadcast_set_title(ebc, src->title, &changes);
}
g1 = g2;
}
-
+
/* Insert all entries */
if (genre) {
LIST_FOREACH(g1, genre, link)
changed, EPG_CHANGED_AGE_RATING);
}
+int epg_broadcast_set_rating_label
+ ( epg_broadcast_t *b, ratinglabel_t *rating_label, epg_changes_t *changed )
+{
+ if (!b || !rating_label) return 0;
+
+ if(rating_label != b->rating_label){
+ b->rating_label = rating_label;
+ if (changed) *changed |= EPG_CHANGED_RATING_LABEL;
+ return 1;
+ }
+
+ return 0;
+}
+
int epg_broadcast_set_first_aired
( epg_broadcast_t *b, time_t aired, epg_changes_t *changed )
{
if (!b || !b->subtitle) return NULL;
return lang_str_get(b->subtitle, lang);
}
+const ratinglabel_t *epg_broadcast_get_rating_label ( epg_broadcast_t *b )
+{
+ if (!b || !b->rating_label) return NULL;
+ return b->rating_label;
+}
const char *epg_broadcast_get_summary ( epg_broadcast_t *b, const char *lang )
{
htsmsg_add_u32(m, "star", broadcast->star_rating);
if (broadcast->age_rating)
htsmsg_add_u32(m, "age", broadcast->age_rating);
+ if (broadcast->rating_label)
+ {
+ htsmsg_add_str(m, "ratlab", idnode_uuid_as_str((idnode_t *)(broadcast->rating_label), ubuf));
+ }
if (broadcast->image)
htsmsg_add_str(m, "img", broadcast->image);
if (broadcast->title)
*save |= epg_broadcast_set_star_rating(ebc, u32, &changes);
if (!htsmsg_get_u32(m, "age", &u32))
*save |= epg_broadcast_set_age_rating(ebc, u32, &changes);
+ if ((str = htsmsg_get_str(m, "ratlab")))
+ {
+ //Convert the UUID string saved on disk into an idnode ID
+ //and then fetch the ratinglabel object.
+ *save |= epg_broadcast_set_rating_label(ebc, ratinglabel_find_from_uuid(str), &changes);
+ }
if ((str = htsmsg_get_str(m, "img")))
*save |= epg_broadcast_set_image(ebc, str, &changes);
LIST_INSERT_HEAD(list, g2, link);
} else {
while (g1) {
-
+
/* Already exists */
if (g1->code == genre->code) return 0;
// Note: if partial=1 and genre is a major only category then all minor
// entries will also match
-int epg_genre_list_contains
+int epg_genre_list_contains
( epg_genre_list_t *list, epg_genre_t *genre, int partial )
{
uint8_t mask = 0xFF;
if (channel && tag == NULL) {
if (channel_access(channel, perm, 0))
_eq_add_channel(eq, channel);
-
+
/* Tag based */
} else if (tag) {
idnode_list_mapping_t *ilm;
#include "lang_str.h"
#include "string_list.h"
#include "access.h"
+#include "ratinglabels.h" //Needed for the ratinglabel_t struct.
/*
* External forward decls
#define EPG_CHANGED_AGE_RATING (1ULL<<31)
#define EPG_CHANGED_FIRST_AIRED (1ULL<<32)
#define EPG_CHANGED_COPYRIGHT_YEAR (1ULL<<33)
+#define EPG_CHANGED_RATING_LABEL (1ULL<<34)
typedef struct epg_object_ops {
void (*getref) ( void *o ); ///< Get a reference
RB_ENTRY(epg_object) id_link; ///< Global (ID) link
LIST_ENTRY(epg_object) un_link; ///< Global unref'd link
LIST_ENTRY(epg_object) up_link; ///< Global updated link
-
+
epg_object_type_t type; ///< Specific object type
uint32_t id; ///< Internal ID
time_t updated; ///< Last time object was changed
/* EpNum format helper */
// output string will be:
-// if (episode_num)
+// if (episode_num)
// ret = pre
// if (season_num) ret += sprintf(sfmt, season_num)
// if (season_cnt && cnt) ret += sprintf(cnt, season_cnt)
struct channel *channel; ///< Channel being broadcast on
RB_ENTRY(epg_broadcast) sched_link; ///< Schedule link
LIST_HEAD(, dvr_entry) dvr_entries; ///< Associated DVR entries
-
+
/* */
uint16_t dvb_eid; ///< DVB Event ID
time_t start; ///< Start time
/* Misc flags */
uint8_t star_rating; ///< Star rating
uint8_t age_rating; ///< Age certificate
+ ratinglabel_t *rating_label; ///< Age certificate label (eg: 'PG')
uint8_t is_new; ///< New series / file premiere
uint8_t is_repeat; ///< Repeat screening
uint8_t running; ///< EPG running flag
};
/* Lookup */
-epg_broadcast_t *epg_broadcast_find_by_time
+epg_broadcast_t *epg_broadcast_find_by_time
( struct channel *ch, struct epggrab_module *src,
time_t start, time_t stop, int create, int *save, epg_changes_t *changes );
epg_broadcast_t *epg_broadcast_find_by_eid ( struct channel *ch, uint16_t eid );
int epg_broadcast_set_is_hd
( epg_broadcast_t *b, uint8_t hd, epg_changes_t *changed )
__attribute__((warn_unused_result));
-int epg_broadcast_set_lines
+int epg_broadcast_set_lines
( epg_broadcast_t *b, uint16_t lines, epg_changes_t *changed )
__attribute__((warn_unused_result));
int epg_broadcast_set_aspect
int epg_broadcast_set_age_rating
( epg_broadcast_t *b, uint8_t age, epg_changes_t *changed )
__attribute__((warn_unused_result));
+int epg_broadcast_set_rating_label
+ ( epg_broadcast_t *b, ratinglabel_t *rating_label, epg_changes_t *changed )
+ __attribute__((warn_unused_result));
/* Accessors */
epg_broadcast_t *epg_broadcast_get_prev( epg_broadcast_t *b );
epg_broadcast_t *epg_broadcast_get_next( epg_broadcast_t *b );
-const char *epg_broadcast_get_title
+const char *epg_broadcast_get_title
( const epg_broadcast_t *b, const char *lang );
const char *epg_broadcast_get_subtitle
( epg_broadcast_t *b, const char *lang );
( epg_broadcast_t *b, const char *lang );
const char *epg_broadcast_get_keyword_cached
( epg_broadcast_t *b, const char *lang );
+const ratinglabel_t *epg_broadcast_get_rating_label
+ ( epg_broadcast_t *b );
/* Episode number heplers */
// Note: this does NOT strdup the text field
void epg_broadcast_get_epnum
( const epg_broadcast_t *b, epg_episode_num_t *epnum );
-size_t epg_broadcast_epnumber_format
+size_t epg_broadcast_epnumber_format
( epg_broadcast_t *b, char *buf, size_t len,
const char *pre, const char *sfmt,
const char *sep, const char *efmt,
/* Serialization */
htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *b );
-epg_broadcast_t *epg_broadcast_deserialize
+epg_broadcast_t *epg_broadcast_deserialize
( htsmsg_t *m, int create, int *save );
/* ************************************************************************
epggrab_conf.epgdb_periodicsave * 3600);
idnode_notify_changed(&epggrab_conf.idnode);
-
+
/* Load module config (channels) */
eit_load();
opentv_load();
.off = offsetof(epggrab_conf_t, epgdb_saveafterimport),
.group = 1,
},
+ {
+ .type = PT_BOOL,
+ .id = "epgdb_processparentallabels",
+ .name = N_("Process Parental Rating Labels"),
+ .desc = N_("Convert broadcast ratings codes into "
+ "human-readable labels like 'PG' or 'FSK 16'."),
+ .off = offsetof(epggrab_conf_t, epgdb_processparentallabels),
+ .group = 1,
+ },
{
.type = PT_STR,
.id = "cron",
epggrab_conf.channel_reicon = 0;
epggrab_conf.epgdb_periodicsave = 0;
epggrab_conf.epgdb_saveafterimport = 0;
+ epggrab_conf.epgdb_processparentallabels = 0;
epggrab_cron_multi = NULL;
/* Initialise the OTA subsystem */
epggrab_ota_init();
-
+
/* Load config */
_epggrab_load();
uint32_t channel_reicon;
uint32_t epgdb_periodicsave;
uint32_t epgdb_saveafterimport;
+ uint32_t epgdb_processparentallabels;
char *ota_cron;
char *ota_genre_translation;
uint32_t ota_timeout;
#include "input.h"
#include "input/mpegts/dvb_charset.h"
#include "dvr/dvr.h"
+#include "ratinglabels.h"
/* ************************************************************************
* Opaque
uint8_t bw;
uint8_t parental;
+ ratinglabel_t *rating_label;
uint8_t is_new;
time_t first_aired;
( epggrab_module_t *mod, const uint8_t *ptr, int len, eit_event_t *ev )
{
int cnt = 0, sum = 0, i = 3;
+
+ char tmpCountry[4];
+ int tmpAge = 0;
+ ratinglabel_t *rl = NULL;
+
while (len > 3) {
+
+ //If we are processing parental rating labels.
+ if(epggrab_conf.epgdb_processparentallabels)
+ {
+ //Get the recommended age for this rating.
+ //0x00 undefined
+ //0x01 to 0x0F minimum age = rating + 3 years
+ //0x10 to 0xFF defined by the broadcaster
+ if(ptr[i] == 0)
+ {
+ tmpAge = 0;
+ }
+ else
+ {
+ tmpAge = ptr[i]; //Do not add 3 here, do that with the 'display age'.
+ }
+
+ //Get the country code for this rating.
+ tmpCountry[0] = ptr[0];
+ tmpCountry[1] = ptr[1];
+ tmpCountry[2] = ptr[2];
+ tmpCountry[3] = 0;
+
+ tvhtrace(LS_TBL_EIT, "Country '%s', age '%d'", tmpCountry, tmpAge);
+
+ //Look for a matching rating label
+ rl = ratinglabel_find_from_eit(tmpCountry, tmpAge);
+
+ //If we have found a rating label, save the details and exit.
+ //ie, ony use the first parental rating found.
+ //TODO: In future, if (eg in Europe) the rating codes from multiple
+ //countries are present, select the one that user prefers.
+ //A new config option will be needed for this.
+ //HOWEVER: A sampling of EIT data from European users
+ //suggests that this will not be necessary.
+ if(rl){
+ ev->parental = rl->rl_display_age;
+ ev->rating_label = rl;
+ return 0;
+ }
+ }//END rating labels are being processed.
+ //If rating labels are not processed, do the original TVH process.
+
if ( ptr[i] && ptr[i] < 0x10 ) {
cnt++;
sum += (ptr[i] + 3);
}
len -= 4;
i += 4;
- }
+ }//END loop through descriptors
// Note: we ignore the country code and average the lot!
if (cnt)
ev->parental = (uint8_t)(sum / cnt);
*save |= epg_broadcast_set_genre(ebc, ev->genre, &changes);
if (ev->parental)
*save |= epg_broadcast_set_age_rating(ebc, ev->parental, &changes);
+
+ if (ev->rating_label)
+ {
+ tvhtrace(mod->subsys, "About to save rating label '%p'", ev->rating_label);
+ *save |= epg_broadcast_set_rating_label(ebc, ev->rating_label, &changes);
+ }
+
if (ev->subtitle)
*save |= epg_broadcast_set_subtitle(ebc, ev->subtitle, &changes);
else if ((short_target == 0 || short_target == 2) && ev->summary)
break;
case DVB_DESC_PARENTAL_RAT:
r = _eit_desc_parental(mod, ptr, dlen, &ev);
+ if(epggrab_conf.epgdb_processparentallabels){
+ if(ev.rating_label){
+ tvhtrace(mod->subsys, "RATINGLABEL '%d' '%s'", ev.parental, ev.rating_label->rl_display_label);
+ } else {
+ tvhtrace(mod->subsys, "RATINGLABEL '%d' '<NONE>'", ev.parental);
+ }
+ }
+
break;
case DVB_DESC_CRID:
r = _eit_desc_crid(mod, ptr, dlen, &ev, ed);
static int _xmltv_parse_age_rating
( epg_broadcast_t *ebc, htsmsg_t *body, epg_changes_t *changes )
{
- uint8_t age;
- htsmsg_t *rating, *tags;
+ uint8_t age = 0;
+ htsmsg_t *rating, *tags, *attrib;
const char *s1;
if (!ebc || !body) return 0;
htsmsg_field_t *f;
- HTSMSG_FOREACH(f, body) {
- if (!strcmp(htsmsg_field_name(f), "rating") && (rating = htsmsg_get_map_by_field(f))) {
- if ((tags = htsmsg_get_map(rating, "tags"))) {
- if ((s1 = htsmsg_xml_get_cdata_str(tags, "value"))) {
- /* We map some common ratings since some movies only
- * have one of these flags rather than an age rating.
- */
- if (!strcmp(s1, "TV-G") || !strcmp(s1, "U"))
- age = 3;
- else if (!strcmp(s1, "TV-Y7") || !strcmp(s1, "PG"))
- age = 7;
- else if (!strcmp(s1, "TV-14"))
- age = 14;
- else if (!strcmp(s1, "TV-MA"))
- age = 17;
- else
- age = atoi(s1);
- /* Since age is uint8_t it means some rating systems can
- * underflow and become very large, for example CSA has age
- * rating of -10.
- */
- if (age > 0 && age < 22)
- return epg_broadcast_set_age_rating(ebc, age, changes);
+
+ const char *rating_system; //System attribute from the 'rating' tag : <rating system="ACMA">
+ ratinglabel_t *rl = NULL;
+
+ //Clear the rating label.
+ //If the event is already in the EPG DB with another
+ //rating label, this will clear the existing rating lable
+ //prior to setting the new one -if- the new one
+ //just happens to be null.
+ ebc->rating_label = rl;
+
+ //Only look for rating labels if enabled.
+ if(epggrab_conf.epgdb_processparentallabels){
+ HTSMSG_FOREACH(f, body) {
+ if (!strcmp(htsmsg_field_name(f), "rating") && (rating = htsmsg_get_map_by_field(f))) {
+ //Look for a 'system' attribute of the 'rating' tag
+ rating_system = NULL;
+ if ((attrib = htsmsg_get_map(rating, "attrib"))){
+ rating_system = htsmsg_get_str(attrib, "system");
+ }//END get the atrtibutes for the rating tag.
+ //Look for sub-tags of the 'rating' tag
+ if ((tags = htsmsg_get_map(rating, "tags"))) {
+ //Look the the 'value' tag containing the actual rating text
+ if ((s1 = htsmsg_xml_get_cdata_str(tags, "value"))) {
+ //tvherror(LS_RATINGLABELS, "XMLTV got system: '%s' / '%s'", rating_system, s1);
+
+ rl = ratinglabel_find_from_xmltv(rating_system, s1);
+
+ if(rl){
+ tvhtrace(LS_RATINGLABELS, "Found label: '%s' / '%s' / '%s' / '%d'", rl->rl_authority, rl->rl_label, rl->rl_country, rl->rl_age);
+ ebc->rating_label = rl;
+ if (rl->rl_display_age >= 0 && rl->rl_display_age < 22){
+ return epg_broadcast_set_age_rating(ebc, rl->rl_display_age, changes);
+ }//END age sanity test
+ }//END we matched a rating label
+ }//END we got a value to inspect
+ }//END get sub-tags of the rating tag.
+ }//END got the 'rating' tag.
+ }//END loop through each XML tag
+ }//END rating labels enabled
+ else
+ //Perform the existing XMLTV lookup
+ {
+ HTSMSG_FOREACH(f, body) {
+ if (!strcmp(htsmsg_field_name(f), "rating") && (rating = htsmsg_get_map_by_field(f))) {
+ if ((tags = htsmsg_get_map(rating, "tags"))) {
+ if ((s1 = htsmsg_xml_get_cdata_str(tags, "value"))) {
+ /* We map some common ratings since some movies only
+ * have one of these flags rather than an age rating.
+ */
+ if (!strcmp(s1, "TV-G") || !strcmp(s1, "U"))
+ age = 3;
+ else if (!strcmp(s1, "TV-Y7") || !strcmp(s1, "PG"))
+ age = 7;
+ else if (!strcmp(s1, "TV-14"))
+ age = 14;
+ else if (!strcmp(s1, "TV-MA"))
+ age = 17;
+ else
+ age = atoi(s1);
+ /* Since age is uint8_t it means some rating systems can
+ * underflow and become very large, for example CSA has age
+ * rating of -10.
+ */
+ if (age > 0 && age < 22)
+ return epg_broadcast_set_age_rating(ebc, age, changes);
+ }
}
}
}
}
+
+
return 0;
}
char *s, *str2 = NULL, *saveptr = NULL;
if (str == NULL) continue;
if (strstr(str, "|") == 0) {
-
+
if (strlen(str) > 255) {
str2 = strdup(str);
str2[255] = '\0';
str = str2;
}
-
+
if (!credits_names) credits_names = string_list_create();
string_list_insert(credits_names, str);
#endif
#include "settings.h"
+#include "epggrab.h" //Needed to be able to test for epggrab_conf.epgdb_processparentallabels
/* **************************************************************************
* Datatypes and variables
static void *htsp_server, *htsp_server_2;
-#define HTSP_PROTO_VERSION 36
+#define HTSP_PROTO_VERSION 37
#define HTSP_ASYNC_OFF 0x00
#define HTSP_ASYNC_ON 0x01
uint32_t u32;
char buf[512];
char ubuf[UUID_HEX_SIZE];
+ const char *str;
htsmsg_add_u32(out, "id", idnode_get_short_uuid(&de->de_id));
//To not risk breaking older clients, only
//provide the 'age rating' via HTSP if the requested
- //API version if greater than 35.
- if (htsp->htsp_version > 35)
+ //API version if greater than 36.
+ if (htsp->htsp_version > 36)
{
+ //Having the age in the DVR entry is new, that is why
+ //it is processed inside the version test.
htsmsg_add_u32(out, "ageRating", de->de_age_rating);
+
+ //Only go on to add the rating label stuff if
+ //rating labels are enabled.
+ if(epggrab_conf.epgdb_processparentallabels){
+ //If this is still scheduled (in the future) then send the current values,
+ //if not, send the 'saved' values.
+
+ if(de->de_sched_state == DVR_SCHEDULED){
+ if(de->de_rating_label){
+ if(de->de_rating_label->rl_display_label){
+ htsmsg_add_str(out, "ratingLabel", de->de_rating_label->rl_display_label);
+ }
+ //If the rating icon is not null.
+ if(de->de_rating_label->rl_icon){
+ str = de->de_rating_label->rl_icon;
+ if (!strempty(str)) {
+ str = imagecache_get_propstr(str, buf, sizeof(buf));
+ if (str)
+ htsmsg_add_str(out, "ratingIcon", str);
+ }//END got an imagecache location
+ }//END icon not null
+ }
+ }
+ else
+ {
+ if(de->de_rating_label_saved){
+ if(de->de_rating_label_saved){
+ htsmsg_add_str(out, "ratingLabel", de->de_rating_label_saved);
+ }
+ if(de->de_rating_icon_saved){
+ str = de->de_rating_icon_saved;
+ if (!strempty(str)) {
+ str = imagecache_get_propstr(str, buf, sizeof(buf));
+ if (str)
+ htsmsg_add_str(out, "ratingIcon", str);
+ }//END got an imagecache location
+ }//END icon not null
+ }
+ }
+ }//END processing rating labels is enabled
}
if (htsp->htsp_version < 6) code = (code >> 4) & 0xF;
htsmsg_add_u32(out, "contentType", code);
}
- if (e->age_rating)
+ if (e->age_rating){
htsmsg_add_u32(out, "ageRating", e->age_rating);
+ }
+ //To not risk breaking older clients, only
+ //provide the 'rating label' & 'rating icon' via HTSP if the requested
+ //version if greater than 36.
+ //Because this is the EPG, do not restrict the ageRating based on version,
+ //that field was added in a very early version.
+ if (htsp->htsp_version > 36)
+ {
+ //If we are processing parental labels
+ if(epggrab_conf.epgdb_processparentallabels)
+ {
+ //If this event had a label pointer that is not null
+ if (e->rating_label)
+ {
+ //If there is a 'display label'
+ //Do not fall-back to the 'label' because the 'display label'
+ //may be intentionally null.
+ if(e->rating_label->rl_display_label){
+ htsmsg_add_str(out, "ratingLabel", e->rating_label->rl_display_label);
+ }
+ //If the rating icon is not null.
+ if(e->rating_label->rl_icon){
+ str = e->rating_label->rl_icon;
+ if (!strempty(str)) {
+ str = imagecache_get_propstr(str, buf, sizeof(buf));
+ if (str)
+ htsmsg_add_str(out, "ratingIcon", str);
+ }//END got an imagecache location
+ }//END icon not null
+ }//END rating label not null
+ }//END parental labels enabled.
+ }//END HTSP version check
+
if (e->star_rating)
htsmsg_add_u32(out, "starRating", e->star_rating);
if (e->copyright_year)
channel_t *channel = NULL;
int enabled, retention, removal, playcount = -1, playposition = -1;
int age_rating;
+ ratinglabel_t *rating_label;
de = htsp_findDvrEntry(htsp, in, &out, 0);
if (de == NULL)
removal = htsmsg_get_u32_or_default(in, "removal", DVR_RET_REM_DVRCONFIG);
priority = htsmsg_get_u32_or_default(in, "priority", DVR_PRIO_NOTSET);
age_rating = htsmsg_get_u32_or_default(in, "ageRating", 0);
+ rating_label = NULL; //Rating labels not supported for manually created DVR entries
title = htsmsg_get_str(in, "title");
subtitle = htsmsg_get_str(in, "subtitle");
summary = htsmsg_get_str(in, "summary");
de = dvr_entry_update(de, enabled, dvr_config_name, channel, title, subtitle,
summary, desc, lang, start, stop, start_extra, stop_extra,
priority, retention, removal, playcount, playposition,
- age_rating);
+ age_rating, rating_label);
return htsp_success();
}
return 0;
tvh_mutex_lock(&imagecache_lock);
-
+
/* Skeleton */
SKEL_ALLOC(imagecache_skel);
imagecache_skel->url = url;
#include "transcoding/codec.h"
#include "profile.h"
#include "bouquet.h"
+#include "ratinglabels.h"
#include "tvhtime.h"
#include "packet.h"
#include "streaming.h"
cb = mti->mti_callback;
LIST_REMOVE(mti, mti_link);
mti->mti_callback = NULL;
-
+
mtimer_running = mti;
tvh_mutex_unlock(&mtimer_lock);
tvhftrace(LS_MAIN, http_client_init);
tvhftrace(LS_MAIN, esfilter_init);
tvhftrace(LS_MAIN, bouquet_init);
+ tvhftrace(LS_MAIN, ratinglabel_init);
tvhftrace(LS_MAIN, service_init);
tvhftrace(LS_MAIN, descrambler_init);
tvhftrace(LS_MAIN, dvb_init);
tvhftrace(LS_MAIN, service_done);
tvhftrace(LS_MAIN, channel_done);
tvhftrace(LS_MAIN, bouquet_done);
+ tvhftrace(LS_MAIN, ratinglabel_done);
tvhftrace(LS_MAIN, subscription_done);
tvhftrace(LS_MAIN, access_done);
tvhftrace(LS_MAIN, epg_done);
break;
}
}
-
+
/* Setter */
if (p->set && snew)
save = p->set(obj, snew);
assert(p->get); /* requirement */
if (val)
htsmsg_add_msg(m, name, (htsmsg_t*)val);
-
+
/* Single */
} else {
switch(p->type) {
const property_t *p;
htsmsg_field_t *f;
int b, total = 0, count = 0;
-
+
HTSMSG_FOREACH(f, list) {
total++;
if (!htsmsg_field_get_bool(f, &b)) {
--- /dev/null
+/*
+ * tvheadend, Rating Labels
+ * Copyright (C) 2014 Jaroslav Kysela (Original Bouquets)
+ * Copyright (C) 2023 DeltaMikeCharlie (Updated for Rating Labels)
+ *
+ * 'Rating labels' are text codes like 'PG', 'PG-13', 'FSK 12', etc,
+ * and are related to the parental classification code values
+ * that are broadcast via DVB as numbers.
+ * Each country/region has their own ratings.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tvheadend.h"
+#include "settings.h"
+#include "access.h"
+#include "imagecache.h"
+#include "ratinglabels.h"
+#include "input.h"
+
+#include "channels.h" //Needed to loop through channels when deleting RL pointers from EPG
+#include "dvr/dvr.h" //Needed to check recordings for RL pointers.
+
+ratinglabel_tree_t ratinglabels;
+
+void ratinglabel_init(void);
+void ratinglabel_done(void);
+ratinglabel_t *ratinglabel_find_from_eit(char *country, int age);
+ratinglabel_t *ratinglabel_find_from_xmltv(const char *authority, const char *label);
+ratinglabel_t *ratinglabel_find_from_uuid(const char *string_uuid);
+const char *ratinglabel_get_icon(ratinglabel_t *rl);
+
+static htsmsg_t *ratinglabel_class_save(idnode_t *self, char *filename, size_t fsize);
+const void *ratinglabel_class_get_icon (void *obj);
+ratinglabel_t *ratinglabel_create_placeholder(int enabled, const char *country, int age,
+ int display_age, const char *display_label,
+ const char *label, const char *authority);
+
+/**
+ *
+ */
+static int
+_rl_cmp(const void *a, const void *b)
+{
+ return 1;
+}
+
+/*
+ Used at EPG load time to get the parentalrating object from the
+ UUID string that is stored in the EPG database.
+*/
+ratinglabel_t *
+ratinglabel_find_from_uuid(const char *string_uuid)
+{
+
+ if(string_uuid[0] == 0){
+ tvhtrace(LS_RATINGLABELS, "Empty UUID when matching rating label, exiting.");
+ return NULL;
+ }
+
+ tvh_uuid_t binary_uuid;
+ ratinglabel_t *rl = NULL;
+ ratinglabel_t *temp_rl = NULL;
+
+ if (!uuid_set(&binary_uuid, string_uuid)) {
+
+ RB_FOREACH(temp_rl, &ratinglabels, rl_link)
+ {
+ if(!memcmp((const void *)&binary_uuid, (const void *)(temp_rl->rl_id).in_uuid.bin, UUID_BIN_SIZE)){
+ rl = temp_rl;
+ if(rl->rl_enabled){
+ tvhdebug(LS_RATINGLABELS, "Matched UUID '%s' with rating label '%s' / '%d'.", string_uuid, rl->rl_display_label, rl->rl_display_age);
+ return rl;
+ }
+ }
+ }//END FOR loop
+ }//END the binary conversion worked
+
+ //To get here, either the UUID was invalid or there was no matching ENABLED ratinglabel.
+ return NULL;
+
+}
+
+/*
+ Used by the XMLTV EPG load process when a <rating> tag
+ is encountered. Match the Authority+Label to a rating label and
+ return a pointer to that object if the label in enabled, null if not.
+*/
+ratinglabel_t *
+ratinglabel_find_from_xmltv(const char *authority, const char *label){
+
+ ratinglabel_t *rl = NULL;
+ ratinglabel_t *temp_rl = NULL;
+
+ RB_FOREACH(temp_rl, &ratinglabels, rl_link)
+ {
+ //If both authorities are null OR both authorities match
+ if((!authority && !temp_rl->rl_authority) || !strcasecmp(temp_rl->rl_authority, authority)){
+
+ //Match 2 null labels
+ if(!temp_rl->rl_label && !label){
+ tvherror(LS_RATINGLABELS, " matched 2 nulls.");
+ rl = temp_rl;
+ break;
+ }
+
+ //if only 1 of the labels is null, then no match.
+ //This test protects strcasecmp from a null pointer
+ if(!temp_rl->rl_label || !label){
+ //No match found
+ }
+ else
+ {
+ if(!strcasecmp(temp_rl->rl_label, label)){
+ rl = temp_rl;
+ break;
+ }
+ }
+ }
+ }
+
+ //Did we get a match?
+ if(rl)
+ {
+ if(!rl->rl_enabled)
+ {
+ tvhtrace(LS_RATINGLABELS, "Not enabled, returning NULL.");
+ return NULL;
+ }
+ }
+ else
+ {
+ char tmpLabel[129]; //Authority+label
+
+ snprintf(tmpLabel, 128, "XMLTV:%s:%s", authority, label);
+
+ //The XMLTV module is holding a lock. Unlock and then relock.
+ tvh_mutex_unlock(&global_lock);
+ rl = ratinglabel_create_placeholder(1, "???", 0, 0, tmpLabel, label, authority);
+ tvh_mutex_lock(&global_lock);
+
+ }
+
+ return rl;
+
+}
+
+/*
+ Used by the EIT EPG load process when a parental rating tag
+ is encountered. Match the Country+Age to a rating label and
+ return a pointer to that object if the label in enabled, null if not.
+*/
+ratinglabel_t *
+ratinglabel_find_from_eit(char *country, int age)
+{
+ tvhdebug(LS_RATINGLABELS, "Looking for '%s', '%d'. Count: '%d'.", country, age, ratinglabels.entries);
+
+ ratinglabel_t *rl = NULL;
+ ratinglabel_t *temp_rl = NULL;
+ int tmpAge = 0;
+
+ //RB_FIND was tried but it gave some false negatives
+ //so I decided to do things the hard way.
+ RB_FOREACH(temp_rl, &ratinglabels, rl_link)
+ {
+ if(temp_rl->rl_age == age){
+ if(!strcasecmp(temp_rl->rl_country, country)){
+ rl = temp_rl;
+ break;
+ }
+ }
+ }
+
+ //Did we get a match?
+ if(rl)
+ {
+ if(!rl->rl_enabled)
+ {
+ tvhtrace(LS_RATINGLABELS, "Not enabled, returning NULL.");
+ return NULL;
+ }
+ }
+ else
+ {
+ tvhtrace(LS_RATINGLABELS, "Not found, creating placeholder '%s' / '%d'. Count: '%d' ", country, age, ratinglabels.entries);
+
+ char tmpLabel[17]; //DBV+Country+Age+null
+
+ snprintf(tmpLabel, 16, "DVB:%s:%d", country, age);
+
+ //Do the DVB age adjustment
+ //0x00 undefined
+ //0x01 to 0x0F minimum age = rating + 3 years
+ //0x10 to 0xFF defined by the broadcaster
+ tmpAge = age;
+ if((age < 0x10) && (age != 0x00)){
+ tmpAge = age + 3;
+ }
+
+ rl = ratinglabel_create_placeholder(1, country, age, tmpAge, tmpLabel, tmpLabel, "NONE");
+
+ }
+
+ return rl;
+}
+
+/*
+ Create a placeholder label for newly encountered ratings.
+ The user needs to manually provide the appropriate fields.
+*/
+ratinglabel_t *
+ratinglabel_create_placeholder(int enabled, const char *country, int age,
+ int display_age, const char *display_label,
+ const char *label, const char *authority){
+
+ ratinglabel_t *rl_new = NULL;
+ htsmsg_t *msg_new = htsmsg_create_map();
+
+ htsmsg_add_bool(msg_new, "enabled", enabled);
+ htsmsg_add_s64(msg_new, "age", age);
+ htsmsg_add_s64(msg_new, "display_age", display_age);
+ htsmsg_add_str(msg_new, "country", country);
+ htsmsg_add_str(msg_new, "label", label);
+ htsmsg_add_str(msg_new, "display_label", display_label);
+ htsmsg_add_str(msg_new, "authority", authority);
+
+ tvh_mutex_lock(&global_lock);
+ rl_new = ratinglabel_create(NULL, msg_new, NULL, NULL);
+ if (rl_new)
+ {
+ tvhtrace(LS_RATINGLABELS, "Success: Created placeholder '%s' / '%d' : '%s / '%s'. Count: '%d'", country, age, label, authority, ratinglabels.entries);
+ idnode_changed((idnode_t *)rl_new);
+ }
+
+ tvh_mutex_unlock(&global_lock);
+
+ return rl_new;
+
+}
+
+
+/**
+ * Free up the memory and safely dispose of the ratinglabel object
+ */
+static void
+ratinglabel_free(ratinglabel_t *rl)
+{
+ idnode_save_check(&rl->rl_id, 1);
+ idnode_unlink(&rl->rl_id);
+
+ free(rl->rl_country);
+ free(rl->rl_display_label);
+ free(rl->rl_label);
+ free(rl->rl_authority);
+ free(rl->rl_icon);
+ free(rl);
+}
+
+/**
+ * Create a reating label object
+ */
+ratinglabel_t *
+ratinglabel_create(const char *uuid, htsmsg_t *conf,
+ const char *name, const char *src)
+{
+ //Minimum fields: (country+age) or (authority+label)
+ //If the XMLTV has no 'system' then that function needs
+ //send something like 'xmltvraw' as the authority.
+ //'remapping' could see multiple country+age+label conbinations
+
+ tvhtrace(LS_RATINGLABELS, "Creating rating label '%s'.", uuid);
+ ratinglabel_t *rl, *rl2 = NULL;
+ int i;
+
+ lock_assert(&global_lock);
+
+ rl = calloc(1, sizeof(ratinglabel_t));
+
+ if (idnode_insert(&rl->rl_id, uuid, &ratinglabel_class, 0)) {
+ if (uuid)
+ tvherror(LS_RATINGLABELS, "Invalid rating label UUID '%s'", uuid);
+ ratinglabel_free(rl);
+ return NULL;
+ }
+
+ if (conf) {
+ rl->rl_in_load = 1;
+ idnode_load(&rl->rl_id, conf);
+ rl->rl_in_load = 0;
+ if (!htsmsg_get_bool(conf, "shield", &i) && i)
+ rl->rl_shield = 1;
+ }
+
+ rl2 = RB_INSERT_SORTED(&ratinglabels, rl, rl_link, _rl_cmp);
+ if (rl2) {
+ ratinglabel_free(rl);
+ return NULL;
+ }
+
+ //Load the rating icon into the image cache if it is not already there.
+ if(rl){
+ if(rl->rl_icon){
+ (void)imagecache_get_id(rl->rl_icon);
+ }
+ }
+
+ rl->rl_saveflag = 1;
+
+ return rl;
+}
+
+/**
+ * This deletes an individual ratinglabel object
+ */
+static void
+ratinglabel_destroy(ratinglabel_t *rl)
+{
+
+ if (!rl){
+ return;
+ }
+
+ tvhtrace(LS_RATINGLABELS, "Deleting rating label '%s' '%d'.", rl->rl_country, rl->rl_age);
+ RB_REMOVE(&ratinglabels, rl, rl_link);
+
+ ratinglabel_free(rl);
+}
+
+/*
+ *
+ */
+void
+ratinglabel_completed(ratinglabel_t *rl, uint32_t seen)
+{
+ idnode_set_t *remove;
+ //size_t z;
+
+ //z=0; //DUMMY
+ //z++; //DUMMY
+
+ if (!rl)
+ return;
+
+ if (!rl->rl_enabled)
+ goto save;
+
+ remove = idnode_set_create(0);
+ idnode_set_free(remove);
+
+save:
+ if (rl->rl_saveflag) {
+ rl->rl_saveflag = 0;
+ idnode_changed(&rl->rl_id);
+ }
+}
+
+/**
+ * Delete a rating label object from memory and disk.
+ * Cleanup EPG entries that use that RL object.
+ */
+void
+ratinglabel_delete(ratinglabel_t *rl)
+{
+ char ubuf[UUID_HEX_SIZE];
+ if (rl == NULL) return;
+ rl->rl_enabled = 0;
+
+ channel_t *ch;
+ epg_broadcast_t *ebc;
+ dvr_entry_t *de;
+ int foundCount = 0;
+ epg_changes_t *changes = NULL;
+ int retVal = 0;
+
+ if (!rl->rl_shield) {
+ hts_settings_remove("epggrab/ratinglabel/%s", idnode_uuid_as_str(&rl->rl_id, ubuf));
+
+ tvhtrace(LS_RATINGLABELS, "Deleting rating label '%s' / '%d'.", rl->rl_display_label, rl->rl_display_age);
+
+ //Read through all of the EPG entries and set the rating label pointer to NULL for matching labels.
+ //If this is not done, if an EPG entry is called that has the deleted RL, then the pointer
+ //will point to rubbish and TVH will most likely crash.
+ //Note: In order to delete the RL object, the mutex is already locked.
+
+ CHANNEL_FOREACH(ch) {
+ if (ch->ch_epg_parent) continue;
+ RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) {
+ if(ebc->rating_label == rl)
+ {
+ //Cause the entry to be flagged as changed
+ ebc->rating_label = NULL; //Clear the pointer to the RL object
+ retVal = epg_broadcast_set_age_rating(ebc, 99, changes);
+ retVal = epg_broadcast_set_age_rating(ebc, 0, changes);
+ ebc->age_rating = 0; //Clear the age rating field.
+ foundCount++;
+ }//END matching RL
+ }//END loop through EPG entries
+ }//END loop through channels
+
+ retVal = 0;
+
+ if (foundCount != 0 && (retVal == 0)){
+ epg_updated();
+ }
+
+ tvhtrace(LS_RATINGLABELS, "Found '%d' EPG entries when deleting rating label.", foundCount);
+
+ //Now check for any upcomming recordings that use this RL and remove their RL UUID.
+ foundCount = 0;
+
+ LIST_FOREACH(de, &dvrentries, de_global_link){
+ if (dvr_entry_is_upcoming(de)){
+ if(de->de_rating_label == rl){
+ tvhtrace(LS_RATINGLABELS, "Removing rating label for scheduled recording '%s'.", de->de_title->first->str);
+ de->de_rating_label = NULL; //Set the rating label UUID for this recording to null
+ dvr_entry_changed(de); //Save this recording.
+ foundCount++;
+ }
+ }
+ }
+
+ tvhtrace(LS_RATINGLABELS, "Found '%d' upcomming recordings when deleting rating label.", foundCount);
+
+ ratinglabel_destroy(rl);
+ } else {
+ idnode_changed(&rl->rl_id);
+ }
+}
+
+/* **************************************************************************
+ * Class definition
+ * **************************************************************************/
+
+static htsmsg_t *
+ratinglabel_class_save(idnode_t *self, char *filename, size_t fsize)
+{
+ ratinglabel_t *rl = (ratinglabel_t *)self;
+ dvr_entry_t *de;
+ channel_t *ch;
+ epg_broadcast_t *ebc;
+ int foundCount = 0;
+ epg_changes_t *changes = NULL;
+ int retVal = 0;
+
+ tvhtrace(LS_RATINGLABELS, "Saving rating label '%s' / '%d'.", rl->rl_display_label, rl->rl_display_age);
+
+ htsmsg_t *c = htsmsg_create_map();
+ char ubuf[UUID_HEX_SIZE];
+ idnode_save(&rl->rl_id, c);
+ if (filename)
+ snprintf(filename, fsize, "epggrab/ratinglabel/%s", idnode_uuid_as_str(&rl->rl_id, ubuf));
+ if (rl->rl_shield)
+ htsmsg_add_bool(c, "shield", 1);
+ rl->rl_saveflag = 0;
+
+ //Check for EPG entries that use this RL and update the age field.
+ CHANNEL_FOREACH(ch) {
+ if (ch->ch_epg_parent) continue;
+ RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) {
+ if(ebc->rating_label == rl)
+ {
+ //This could either be a change to the display_label or the display_age
+ //or perhaps event the icon, regardless, whatever the change,
+ //for the EPG update to be pushed out to any attached client
+ //if required we need to have made a change.
+ retVal = epg_broadcast_set_age_rating(ebc, 99, changes);
+ retVal = epg_broadcast_set_age_rating(ebc, rl->rl_display_age, changes);
+ foundCount++;
+ }//END match the RL
+ }//END loop through EPG entries for that channel
+ }//END loop through channels
+
+ //This is a workaround so that I can evaluate retVal and not have the compiler warning kill my buzz.
+ //If the RL has changed, the EPG entry will be updated, I just need a variable to hold the return
+ //code from epg_broadcast_set_age_rating even though I'm not going to use it for anything.
+ retVal = 0;
+
+ if (foundCount != 0 && (retVal == 0)){
+ epg_updated();
+ }
+
+ tvhtrace(LS_RATINGLABELS, "Found '%d' EPG entries when updating rating label.", foundCount);
+
+ foundCount = 0;
+
+ //Check for upcomming recordings that need their saved RL details to be updated.
+ LIST_FOREACH(de, &dvrentries, de_global_link){
+ if (dvr_entry_is_upcoming(de)){
+ if(de->de_rating_label == rl){
+ tvhtrace(LS_RATINGLABELS, "Updating rating label for scheduled recording '%s', from '%s' / '%d' to '%s' / '%d'.", de->de_title->first->str, de->de_rating_label_saved, de->de_age_rating, rl->rl_display_label, rl->rl_display_age);
+
+ //Update the recording's RL details.
+ de->de_rating_label_saved = strdup(rl->rl_display_label);
+ de->de_age_rating = rl->rl_display_age;
+
+ //If this RL has an icon, save that, else, ensure that the recording's RL icon is empty.
+ if(rl->rl_icon){
+ de->de_rating_icon_saved = strdup(rl->rl_icon);
+ }
+ else {
+ de->de_rating_icon_saved = NULL;
+ }
+
+ dvr_entry_changed(de); //Save this recording and push it out to clients.
+ foundCount++;
+ }//END we matched the RL
+ }//END we got an upcomminf
+ }//END loop through recordings
+
+ tvhtrace(LS_RATINGLABELS, "Found '%d' upcomming recordings when updating rating label.", foundCount);
+
+ return c;
+}
+
+static void
+ratinglabel_class_delete(idnode_t *self)
+{
+ ratinglabel_delete((ratinglabel_t *)self);
+}
+
+//For compatability, return the 'display label' if the 'title' is requested
+//because RLs don't have a title.
+static void
+ratinglabel_class_get_title
+ (idnode_t *self, const char *lang, char *dst, size_t dstsize)
+{
+ ratinglabel_t *rl = (ratinglabel_t *)self;
+ snprintf(dst, dstsize, "%s", rl->rl_display_label);
+}
+
+/* exported for others */
+htsmsg_t *
+ratinglabel_class_get_list(void *o, const char *lang)
+{
+ htsmsg_t *m = htsmsg_create_map();
+ htsmsg_add_str(m, "type", "api");
+ htsmsg_add_str(m, "uri", "ratinglabel/list");
+ htsmsg_add_str(m, "event", "ratinglabel");
+ return m;
+}
+
+//Get the icon from the rating label object
+const char *
+ratinglabel_get_icon ( ratinglabel_t *rl )
+{
+ if(rl){
+ return rl->rl_icon;
+ }
+ return NULL;
+}
+
+//This is defined as a property getter and is delivered
+//via the JSON API.
+const void *
+ratinglabel_class_get_icon ( void *obj )
+{
+ prop_ptr = ratinglabel_get_icon(obj);
+ if (!strempty(prop_ptr)){
+ prop_ptr = imagecache_get_propstr(prop_ptr, prop_sbuf, PROP_SBUF_LEN);
+ }
+ return &prop_ptr;
+}
+
+static void
+ratinglabel_class_enabled_notify ( void *obj, const char *lang )
+{
+ int a=1;
+ ratinglabel_t *rl = obj;
+
+ if (rl->rl_enabled)
+ a++;
+}
+
+CLASS_DOC(ratinglabel)
+
+const idclass_t ratinglabel_class = {
+ .ic_class = "ratinglabel",
+ .ic_caption = N_("EPG Parental Rating Labels"),
+ .ic_doc = tvh_doc_ratinglabel_class,
+ .ic_event = "ratinglabel",
+ .ic_perm_def = ACCESS_ADMIN,
+ .ic_save = ratinglabel_class_save,
+ .ic_get_title = ratinglabel_class_get_title,
+ .ic_delete = ratinglabel_class_delete,
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_BOOL,
+ .id = "enabled",
+ .name = N_("Enabled"),
+ .desc = N_("Enable/disable the rating label."),
+ .def.i = 1,
+ .off = offsetof(ratinglabel_t, rl_enabled),
+ .notify = ratinglabel_class_enabled_notify,
+ },
+ {
+ .type = PT_STR,
+ .id = "country",
+ .name = N_("Country"),
+ .desc = N_("Country recieved via OTA EPG."),
+ .off = offsetof(ratinglabel_t, rl_country),
+ },
+ {
+ .type = PT_INT,
+ .id = "age",
+ .name = N_("Age"),
+ .desc = N_("Unprocessed rating 'age' received via DVB OTA EPG."),
+ .off = offsetof(ratinglabel_t, rl_age),
+ //.doc = prop_doc_ratinglabel_mapping_options,
+ },
+ {
+ .type = PT_INT,
+ .id = "display_age",
+ .name = N_("Display Age"),
+ .desc = N_("Age to use in the EPG parental rating field."),
+ .off = offsetof(ratinglabel_t, rl_display_age),
+ //.doc = prop_doc_ratinglabel_mapping_options,
+ },
+ {
+ .type = PT_STR,
+ .id = "display_label",
+ .name = N_("Display Label"),
+ .desc = N_("Rating label to be displayed."),
+ .off = offsetof(ratinglabel_t, rl_display_label),
+ },
+ {
+ .type = PT_STR,
+ .id = "label",
+ .name = N_("Label"),
+ .desc = N_("XML 'rating' tag value to match events received via XMLTV."),
+ .off = offsetof(ratinglabel_t, rl_label),
+ },
+ {
+ .type = PT_STR,
+ .id = "authority",
+ .name = N_("Authority"),
+ .desc = N_("XMLTV 'system' attribute to match events received via XMLTV."),
+ .off = offsetof(ratinglabel_t, rl_authority),
+ },
+ {
+ .type = PT_STR,
+ .id = "icon",
+ .name = N_("Icon"),
+ .desc = N_("File name for this rating's icon."),
+ .off = offsetof(ratinglabel_t, rl_icon),
+ },
+ {
+ .type = PT_STR,
+ .id = "icon_public_url",
+ .name = N_("Icon URL"),
+ .desc = N_("The imagecache path to the icon to use/used "
+ "for the rating label."),
+ .get = ratinglabel_class_get_icon,
+ .opts = PO_RDONLY | PO_NOSAVE | PO_HIDDEN,
+ },
+ {}
+ }
+};
+
+/**
+ *
+ */
+void
+ratinglabel_init(void)
+{
+ tvhtrace(LS_RATINGLABELS, "Initialising Rating Labels");
+ htsmsg_t *c, *m;
+ htsmsg_field_t *f;
+ ratinglabel_t *rl;
+
+ RB_INIT(&ratinglabels);
+ idclass_register(&ratinglabel_class);
+
+ /* Load */
+ if ((c = hts_settings_load("epggrab/ratinglabel")) != NULL) {
+ HTSMSG_FOREACH(f, c) {
+ if (!(m = htsmsg_field_get_map(f))) continue;
+ rl = ratinglabel_create(htsmsg_field_name(f), m, NULL, NULL);
+ if (rl)
+ {
+ tvhtrace(LS_RATINGLABELS, "Loaded label: '%s' / '%d', enabled: '%d', label count: '%d'", rl->rl_display_label, rl->rl_display_age, rl->rl_enabled, ratinglabels.entries);
+ rl->rl_saveflag = 0;
+ }
+ }
+ htsmsg_destroy(c);
+ }
+
+}
+
+//Delete all of the ratinglabel objects.
+//This appears to be called from main.c.
+void
+ratinglabel_done(void)
+{
+ ratinglabel_t *rl;
+
+ tvh_mutex_lock(&global_lock);
+ while ((rl = RB_FIRST(&ratinglabels)) != NULL)
+ ratinglabel_destroy(rl);
+ tvh_mutex_unlock(&global_lock);
+}
--- /dev/null
+/*
+ * TV headend - Rating Labels
+ * Copyright (C) 2014 Jaroslav Kysela (Original Bouquets)
+ * Copyright (C) 2023 DeltaMikeCharlie (Updated for Rating Labels)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RATINGLABEL_H_
+#define RATINGLABEL_H_
+
+#include "idnode.h"
+#include "htsmsg.h"
+
+typedef struct ratinglabel {
+ idnode_t rl_id;
+ RB_ENTRY(ratinglabel) rl_link;
+
+ int rl_saveflag; //} These were kept because
+ int rl_in_load; //} they were in the module that I copied
+ int rl_shield; //} and I was not sure if I could delete them
+
+ int rl_enabled; //Can this label be matched to?
+ char *rl_country; //The 3 byte country code from the EIT
+ int rl_age; //The age value from the EIT
+ int rl_display_age; //The age value to actually use
+ char *rl_display_label; //The label to actually use
+ char *rl_label; //The rating label from XMLTV
+ char *rl_authority; //The 'system' from XMLTV
+ char *rl_icon; //The pretty picture
+
+} ratinglabel_t;
+/*
+*** EIT Documentation
+
+parental_rating_descriptor(){
+ descriptor_tag 8 uimsbf
+ descriptor_length 8 uimsbf
+ for (i=0;i<N;i++){
+ country_code 24 bslbf
+ rating 8 uimsbf
+ }
+}
+
+country_code: This 24-bit field identifies a country using the 3
+character code as specified in ISO 3166 [41]. Each character is
+coded into 8-bits according to ISO/IEC 8859-1 [23] and inserted
+in order into the 24-bit field. In the case that the 3 characters
+represent a number in the range 900 to 999, then country_code
+specifies an ETSI defined group of countries. These allocations
+are found in TS 101 162 [i.1].
+
+rating: This 8-bit field is coded according to following table,
+giving the recommended minimum age in years of the end user.
+
+0x00 undefined
+0x01 to 0x0F minimum age = rating + 3 years
+0x10 to 0xFF defined by the broadcaster
+
+
+*** XMLTV Sample
+<rating system="VCHIP">
+ <value>TV-G</value>
+</rating>
+*/
+
+typedef RB_HEAD(,ratinglabel) ratinglabel_tree_t;
+
+extern ratinglabel_tree_t ratinglabels;
+
+extern const idclass_t ratinglabel_class;
+
+htsmsg_t * ratinglabel_class_get_list(void *o, const char *lang);
+const void *ratinglabel_class_get_icon (void *obj);
+
+ratinglabel_t * ratinglabel_create(const char *uuid, htsmsg_t *conf,
+ const char *name, const char *src);
+
+void ratinglabel_delete(ratinglabel_t *rl);
+
+void ratinglabel_completed(ratinglabel_t *rl, uint32_t seen);
+void ratinglabel_change_comment(ratinglabel_t *rl, const char *comment, int replace);
+
+extern void ratinglabel_init(void);
+extern void ratinglabel_done(void);
+
+extern ratinglabel_t *ratinglabel_find_from_eit(char *country, int age);
+extern ratinglabel_t *ratinglabel_find_from_xmltv(const char *authority, const char *label);
+extern ratinglabel_t *ratinglabel_find_from_uuid(const char *string_uuid);
+
+#endif /* RATINGLABEL_H_ */
#if ENABLE_DDCI
[LS_DDCI] = { "ddci", N_("DD-CI") },
#endif
- [LS_UDP] = { "udp", N_("UDP Streamer") },
+ [LS_UDP] = { "udp", N_("UDP Streamer") },
+ [LS_RATINGLABELS] = { "ratinglabels", N_("Rating Labels") },
};
#if ENABLE_DDCI
LS_DDCI,
#endif
- LS_UDP,
+ LS_UDP,
+ LS_RATINGLABELS,
LS_LAST /* keep this last */
};
var genre = params[21].value;
/* channelname is unused param 22 */
var fanart_image = params[23].value;
+ /* broadcast is unused param 24 */
var age_rating = params[25].value;
+ var rating_label = params[26].value;
+ var rating_icon = params[27].value;
var content = '<div class="dvr-details-dialog">' +
'<div class="dvr-details-dialog-background-image"></div>' +
'<div class="dvr-details-dialog-content">';
content += tvheadend.sortAndAddArray(keyword, _('Keywords'));
if (category)
content += tvheadend.sortAndAddArray(category, _('Categories'));
+
+ if (rating_icon)
+ content += '<img class="x-epg-rlicon" src="' + rating_icon + '">';
+
if (age_rating)
content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Age Rating') + ':</span><span class="x-epg-desc">' + age_rating + '</span></div>';
+ if (rating_label)
+ content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Parental Rating') + ':</span><span class="x-epg-desc">' + rating_label + '</span></div>';
if (status)
- content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Status') + ':</span><span class="x-epg-body">' + status + '</span></div>';
+ content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Status') + ':</span><span class="x-epg-desc">' + status + '</span></div>';
if (filesize)
- content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('File size') + ':</span><span class="x-epg-body">' + parseInt(filesize / 1000000) + ' MB</span></div>';
+ content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('File size') + ':</span><span class="x-epg-desc">' + parseInt(filesize / 1000000) + ' MB</span></div>';
if (comment)
- content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Comment') + ':</span><span class="x-epg-body">' + comment + '</span></div>';
+ content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Comment') + ':</span><span class="x-epg-desc">' + comment + '</span></div>';
if (autorec_caption)
- content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Autorec') + ':</span><span class="x-epg-body">' + autorec_caption + '</span></div>';
+ content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Autorec') + ':</span><span class="x-epg-desc">' + autorec_caption + '</span></div>';
if (timerec_caption)
- content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Time Scheduler') + ':</span><span class="x-epg-body">' + timerec_caption + '</span></div>';
+ content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Time Scheduler') + ':</span><span class="x-epg-desc">' + timerec_caption + '</span></div>';
if (chicon)
content += '</div>'; /* x-epg-bottom */
content += '</div>'; //dialog content
list: 'channel_icon,disp_title,disp_subtitle,disp_summary,episode_disp,start_real,stop_real,' +
'duration,disp_description,status,filesize,comment,duplicate,' +
'autorec_caption,timerec_caption,image,copyright_year,credits,keyword,category,' +
- 'first_aired,genre,channelname,fanart_image,broadcast,age_rating',
+ 'first_aired,genre,channelname,fanart_image,broadcast,age_rating,rating_label,rating_icon',
},
success: function(d) {
d = json_decode(d);
del: true,
list: 'category,enabled,duplicate,disp_title,disp_extratext,episode_disp,' +
'channel,image,copyright_year,start_real,stop_real,duration,pri,filesize,' +
- 'sched_status,errors,data_errors,config_name,owner,creator,comment,genre,broadcast,age_rating',
+ 'sched_status,errors,data_errors,config_name,owner,creator,comment,genre,broadcast,age_rating,rating_label',
columns: {
disp_title: {
renderer: tvheadend.displayWithYearAndDuplicateRenderer(),
del: false,
list: 'disp_title,disp_extratext,episode_disp,channel,channelname,' +
'start_real,stop_real,duration,filesize,copyright_year,' +
- 'sched_status,errors,data_errors,playcount,url,config_name,owner,creator,comment,age_rating',
+ 'sched_status,errors,data_errors,playcount,url,config_name,owner,creator,comment,age_rating,rating_label',
columns: {
disp_title: {
renderer: tvheadend.displayWithYearRenderer(),
_('The associated file will be removed from storage.'),
list: 'disp_title,disp_extratext,episode_disp,channel,channelname,' +
'image,copyright_year,start_real,stop_real,duration,filesize,status,' +
- 'sched_status,errors,data_errors,playcount,url,config_name,owner,creator,comment,age_rating',
+ 'sched_status,errors,data_errors,playcount,url,config_name,owner,creator,comment,age_rating,rating_label',
columns: {
disp_title: {
renderer: tvheadend.displayWithYearRenderer(),
del: true,
list: 'disp_title,disp_extratext,episode_disp,channel,channelname,image,' +
'copyright_year,start_real,stop_real,duration,status,' +
- 'sched_status,errors,data_errors,url,config_name,owner,creator,comment,age_rating',
+ 'sched_status,errors,data_errors,url,config_name,owner,creator,comment,age_rating,rating_label',
columns: {
disp_title: {
renderer: tvheadend.displayWithYearRenderer(),
content += tvheadend.sortAndAddArray(event.category, _('Categories'));
if (event.starRating)
content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Star Rating') + ':</span><span class="x-epg-desc">' + event.starRating + '</span></div>';
+
+ if (event.ratingLabelIcon)
+ content += '<img class="x-epg-rlicon" src="' + event.ratingLabelIcon + '">';
+
if (event.ageRating)
content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Age Rating') + ':</span><span class="x-epg-desc">' + event.ageRating + '</span></div>';
+ if (event.ratingLabel)
+ content += '<div class="x-epg-meta"><span class="x-epg-prefix">' + _('Parental Rating') + ':</span><span class="x-epg-desc">' + event.ratingLabel + '</span></div>';
+
if (event.genre) {
var genre = [];
Ext.each(event.genre, function(g) {
{ name: 'category' },
{ name: 'keyword' },
{ name: 'ageRating' },
+ { name: 'ratingLabel' },
+ { name: 'ratingLabelIcon' },
{ name: 'copyright_year' },
{ name: 'new' },
{ name: 'genre' },
dataIndex: 'starRating',
renderer: renderInt
},
+ {
+ width: 50,
+ id: 'ratingLabel',
+ header: _("Rating"),
+ tooltip: _("Parental Rating"),
+ dataIndex: 'ratingLabel',
+ renderer: renderInt
+ },
{
width: 50,
id: 'ageRating',
{ type: 'string', dataIndex: 'episodeOnscreen' },
{ type: 'intsplit', dataIndex: 'channelNumber', intsplit: 1000000 },
{ type: 'string', dataIndex: 'channelName' },
+ { type: 'string', dataIndex: 'ratingLabel' },
{ type: 'numeric', dataIndex: 'starRating' },
{ type: 'numeric', dataIndex: 'ageRating' }
]
margin: 5px;
width: 20%;
max-height: 99px;
+ max-width: 99px;
+}
+
+.x-epg-rlicon {
+ float: right;
+ margin: 5px;
+ max-height: 50px;
}
.x-epg-time {
}
.x-epg-genre {
- margin-left: 5px;
+ margin-left: 10px;
}
.x-epg-duplicate {
--- /dev/null
+tvheadend.ratinglabel = function(panel, index) {
+
+ tvheadend.idnode_grid(panel, {
+ url: 'api/ratinglabel',
+ all: 1,
+ titleS: _('Rating Label'),
+ titleP: _('Rating Labels'),
+ iconCls: 'baseconf',
+ tabIndex: index,
+ columns: {
+ enabled: { width: 70 },
+ country: { width: 80 },
+ age: { width: 50 },
+ display_age: { width: 100 },
+ display_label: { width: 100 },
+ label: { width: 100 },
+ authority: { width: 100 },
+ icon: { width: 200 }
+ },
+ add: {
+ url: 'api/ratinglabel',
+ create: { }
+ },
+ del: true,
+ uilevel: 'expert',
+ del: true,
+ sort: {
+ field: 'country',
+ direction: 'ASC'
+ }
+ });
+
+ return panel;
+
+}
tvheadend.epggrab_map(chepg);
tvheadend.epggrab_base(chepg);
tvheadend.epggrab_mod(chepg);
+ tvheadend.ratinglabel(chepg);
cp.add(chepg);