]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add Parental Rating Labels
authorDeltaMikeCharlie <127641886+DeltaMikeCharlie@users.noreply.github.com>
Tue, 28 Nov 2023 02:38:39 +0000 (13:38 +1100)
committerFlole998 <Flole998@users.noreply.github.com>
Tue, 5 Dec 2023 23:35:06 +0000 (00:35 +0100)
37 files changed:
Makefile
Makefile.webui
docs/class/ratinglabel.md [new file with mode: 0644]
docs/markdown/inc/channels_contents.md
docs/markdown/inc/ratinglabel_contents.md [new file with mode: 0644]
src/api.c
src/api.h
src/api/api_epg.c
src/api/api_ratinglabel.c [new file with mode: 0644]
src/dvr/dvr.h
src/dvr/dvr_db.c
src/dvr/dvr_rec.c
src/epg.c
src/epg.h
src/epggrab.c
src/epggrab.h
src/epggrab/module/eit.c
src/epggrab/module/xmltv.c
src/htsp_server.c
src/imagecache.c
src/main.c
src/prop.c
src/ratinglabels.c [new file with mode: 0644]
src/ratinglabels.h [new file with mode: 0644]
src/tvhlog.c
src/tvhlog.h
src/webui/static/app/dvr.js
src/webui/static/app/epg.js
src/webui/static/app/ext.css
src/webui/static/app/ratinglabels.js [new file with mode: 0644]
src/webui/static/app/tvheadend.js
src/webui/static/img/doc/channel/epgconf_tab.png [changed mode: 0755->0644]
src/webui/static/img/doc/ratinglabel/epg_placeholder.png [new file with mode: 0644]
src/webui/static/img/doc/ratinglabel/rating_labels_complete.png [new file with mode: 0644]
src/webui/static/img/doc/ratinglabel/rating_labels_learned.png [new file with mode: 0644]
src/webui/static/img/doc/ratinglabel/updated_label.png [new file with mode: 0644]
src/webui/static/img/doc/ratinglabel/xmltv_learned.png [new file with mode: 0644]

index 3a7e16557560f154e38c1f89483b5cf5b07093aa..5e9688fbb44ae98217ce0afce4a3343e55216c88 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -275,6 +275,7 @@ SRCS-1 = \
        src/intlconv.c \
        src/profile.c \
        src/bouquet.c \
+       src/ratinglabels.c \
        src/lock.c \
        src/string_list.c \
        src/wizard.c \
@@ -320,6 +321,7 @@ SRCS-2 = \
        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 \
index c0465fe0ee30f3096e7c32ddf229106114c7b35c..e6bc0dace69c20a202bf331d83703096bf6b12b7 100644 (file)
@@ -159,6 +159,7 @@ JAVASCRIPT += $(ROOTPATH)/app/status.js
 JAVASCRIPT += $(ROOTPATH)/app/wizard.js
 JAVASCRIPT += $(ROOTPATH)/tv.js
 JAVASCRIPT += $(ROOTPATH)/app/servicemapper.js
+JAVASCRIPT += $(ROOTPATH)/app/ratinglabels.js
 
 JAVASCRIPT += $(ROOTPATH)/app/tvheadend.js
 
diff --git a/docs/class/ratinglabel.md b/docs/class/ratinglabel.md
new file mode 100644 (file)
index 0000000..69d79ea
--- /dev/null
@@ -0,0 +1,59 @@
+<tvh_include>inc/ratinglabel_contents</tvh_include>
+
+---
+
+## Overview
+
+This tab lists all defined parental rating labels.
+
+!['Complete rating labels list'](static/img/doc/ratinglabel/rating_labels_complete.png)
+
+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:
+
+!['Newly learned rating labels list'](static/img/doc/ratinglabel/rating_labels_learned.png)
+
+When a placeholder label is in use, the programme details in the EPG will show this placeholder entry rather than the expected value.
+
+!['EPG with placeholder rating'](static/img/doc/ratinglabel/epg_placeholder.png)
+
+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.
+
+!['Updated rating label details'](static/img/doc/ratinglabel/updated_label.png)
+
+**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.
+
+!['Rating label learned from xmltv'](static/img/doc/ratinglabel/xmltv_learned.png)
+
+# 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'.
+
+
+---
index baf1fd3830eeb8c611ca36fc79d67d6aab829cc4..3c496cf6fe957833e48296cfc846a82b1ef99bfb 100644 (file)
@@ -8,7 +8,7 @@ Contents                               | Description
 [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
 
 
 
diff --git a/docs/markdown/inc/ratinglabel_contents.md b/docs/markdown/inc/ratinglabel_contents.md
new file mode 100644 (file)
index 0000000..3de35c2
--- /dev/null
@@ -0,0 +1,8 @@
+Contents                               | Description
+---------------------------------------|------------------------
+[Overview](#overview)                  | Tab overview
+[Items/Properties](#items)             | Items and Properties
+[EPG Grabber](class/epggrab)            | EPG grabber configuration
+
+
+
index b224a30c5786f46a8e4473b9d77610066447b199..7e853aa952a095adb029701f017eff9caaa06f12 100644 (file)
--- a/src/api.c
+++ b/src/api.c
@@ -146,6 +146,7 @@ void api_init ( void )
   api_service_init();
   api_channel_init();
   api_bouquet_init();
+  api_ratinglabel_init();
   api_epg_init();
   api_epggrab_init();
   api_status_init();
index a412776b957bcc469027dba064e8d6b6808f8326..7a815b426f8752a7b21ddb005acc374f9e510049 100644 (file)
--- a/src/api.h
+++ b/src/api.h
@@ -67,6 +67,7 @@ void api_input_init         ( void );
 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 );
index 468475cc34f763d84b2177aada696237d99d1e96..35718cc274182e27b1b1d2c862230945d9d0da41 100644 (file)
@@ -26,6 +26,7 @@
 #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 )
@@ -79,7 +80,7 @@ static htsmsg_t *
 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;
@@ -102,10 +103,10 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang, const access_t *perm, con
     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);
@@ -187,6 +188,24 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang, const access_t *perm, con
   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)
@@ -220,7 +239,7 @@ api_epg_entry ( epg_broadcast_t *eb, const char *lang, const access_t *perm, con
   /* Next event */
   if ((eb = epg_broadcast_get_next(eb)))
     htsmsg_add_u32(m, "nextEventId", eb->id);
-  
+
   return m;
 }
 
@@ -635,7 +654,7 @@ api_epg_related
   char *lang, *title_esc, *title_anchor;
   epg_set_t *serieslink = NULL;
   const char *title = NULL;
-  
+
   if (htsmsg_get_u32(args, "eventId", &id))
     return EINVAL;
 
diff --git a/src/api/api_ratinglabel.c b/src/api/api_ratinglabel.c
new file mode 100644 (file)
index 0000000..58e673b
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ *  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__ */
index 3244b93b98914bf8ac024398326b25254d49604d..00c9d70cd700a74712d829e91ddb222a875fd3bf 100644 (file)
@@ -244,7 +244,14 @@ typedef struct dvr_entry {
   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;
@@ -592,7 +599,8 @@ dvr_entry_update( dvr_entry_t *de, int enabled,
                   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);
 
index a7554a3750f94cf95bf0779eba3a20dc692f86f0..318b7f30e23e8ed6d8ae00f188a3c785e0f23290 100644 (file)
@@ -31,6 +31,7 @@
 #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;
@@ -58,6 +59,8 @@ static void dvr_entry_watched_timer_disarm(dvr_entry_t* de);
 
 static dvr_entry_t *_dvr_duplicate_event(dvr_entry_t *de);
 
+static const void *dvr_entry_class_rating_icon_url_get(void *o);
+
 /*
  *
  */
@@ -1209,6 +1212,18 @@ dvr_entry_create_from_htsmsg(htsmsg_t *conf, epg_broadcast_t *e)
       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);
@@ -2416,7 +2431,8 @@ static dvr_entry_t *_dvr_entry_update
     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;
@@ -2654,13 +2670,13 @@ dvr_entry_update
     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);
 }
 
 /**
@@ -2714,7 +2730,7 @@ dvr_event_replaced(epg_broadcast_t *e, epg_broadcast_t *new_e)
                           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;
         }
       }
@@ -2755,7 +2771,7 @@ void dvr_event_updated(epg_broadcast_t *e)
     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;
@@ -2767,7 +2783,7 @@ void dvr_event_updated(epg_broadcast_t *e)
                             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);
     }
   }
 }
@@ -3400,6 +3416,71 @@ dvr_entry_class_channel_name_get(void *o)
   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)
 {
@@ -3947,6 +4028,48 @@ dvr_entry_class_fanart_image_notify(void *o, const char *lang)
   (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)
 {
@@ -4648,6 +4771,51 @@ const idclass_t dvr_entry_class = {
       .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,
+    },
     {}
   }
 };
index eec60585d3d7c923f2a4ad37416ae1a7191bd1ae..97218580301cf1e5e1acf4914cbdb16579119c3c 100644 (file)
@@ -1567,6 +1567,7 @@ dvr_thread_rec_start(dvr_entry_t **_de, streaming_start_t *ss,
     /* 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) {
index 865c908410c63c453c674e0123f7f85e853d0d30..c8f160233fa3909ba979ec2d13a09135314efea2 100644 (file)
--- a/src/epg.c
+++ b/src/epg.c
@@ -109,7 +109,7 @@ void epg_updated ( void )
  * 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);
@@ -475,7 +475,7 @@ int epg_channel_ignore_broadcast(channel_t *ch, time_t start)
   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);
@@ -533,7 +533,7 @@ static void _epg_channel_timer_callback ( void *p )
     }
     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",
@@ -557,7 +557,7 @@ static void _epg_channel_timer_callback ( void *p )
   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 )
 {
@@ -624,7 +624,7 @@ static epg_broadcast_t *_epg_channel_add_broadcast
       }
     }
   }
-  
+
   /* Changed */
   *save |= 1;
 
@@ -988,6 +988,8 @@ int epg_broadcast_change_finish
     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))
@@ -1051,6 +1053,7 @@ epg_broadcast_t *epg_broadcast_clone
     *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);
@@ -1414,7 +1417,7 @@ int epg_broadcast_set_genre
     }
     g1 = g2;
   }
-  
+
   /* Insert all entries */
   if (genre) {
     LIST_FOREACH(g1, genre, link)
@@ -1440,6 +1443,20 @@ int epg_broadcast_set_age_rating
                             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 )
 {
@@ -1476,6 +1493,11 @@ const char *epg_broadcast_get_subtitle ( epg_broadcast_t *b, const char *lang )
   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 )
 {
@@ -1561,6 +1583,10 @@ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast )
     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)
@@ -1661,6 +1687,12 @@ epg_broadcast_t *epg_broadcast_deserialize
     *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);
@@ -2010,7 +2042,7 @@ int epg_genre_list_add ( epg_genre_list_t *list, epg_genre_t *genre )
     LIST_INSERT_HEAD(list, g2, link);
   } else {
     while (g1) {
-    
+
       /* Already exists */
       if (g1->code == genre->code) return 0;
 
@@ -2059,7 +2091,7 @@ int epg_genre_list_add_by_str ( epg_genre_list_t *list, const char *str, const c
 
 // 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;
@@ -2487,7 +2519,7 @@ epg_query ( epg_query_t *eq, access_t *perm )
   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;
index b81589c869b6ae11483dbbbfc91f5ff6912c6462..da595fd397034497fd020ba951d1d5aab0823935 100644 (file)
--- a/src/epg.h
+++ b/src/epg.h
@@ -24,6 +24,7 @@
 #include "lang_str.h"
 #include "string_list.h"
 #include "access.h"
+#include "ratinglabels.h"  //Needed for the ratinglabel_t struct.
 
 /*
  * External forward decls
@@ -149,6 +150,7 @@ typedef uint64_t epg_changes_t;
 #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
@@ -163,7 +165,7 @@ struct epg_object
   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
@@ -203,7 +205,7 @@ void epg_episode_epnum_deserialize( htsmsg_t *m, epg_episode_num_t *num );
 
 /* 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)
@@ -255,7 +257,7 @@ struct epg_broadcast
   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
@@ -276,6 +278,7 @@ struct epg_broadcast
   /* 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
@@ -310,7 +313,7 @@ struct epg_broadcast
 };
 
 /* 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 );
@@ -337,7 +340,7 @@ int epg_broadcast_set_is_widescreen
 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
@@ -415,11 +418,14 @@ int epg_broadcast_set_copyright_year
 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 );
@@ -432,12 +438,14 @@ const char *epg_broadcast_get_credits_cached
   ( 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,
@@ -452,7 +460,7 @@ static inline int epg_episode_match(epg_broadcast_t *a, epg_broadcast_t *b)
 
 /* 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 );
 
 /* ************************************************************************
index 415250061c2afba56c86d3f3b7992247af419145..369651be734386218f049ebc3d89013841a734e1 100644 (file)
@@ -271,7 +271,7 @@ static void _epggrab_load ( void )
                    epggrab_conf.epgdb_periodicsave * 3600);
 
   idnode_notify_changed(&epggrab_conf.idnode);
+
   /* Load module config (channels) */
   eit_load();
   opentv_load();
@@ -426,6 +426,15 @@ const idclass_t epggrab_class = {
       .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",
@@ -536,6 +545,7 @@ void epggrab_init ( void )
   epggrab_conf.channel_reicon     = 0;
   epggrab_conf.epgdb_periodicsave = 0;
   epggrab_conf.epgdb_saveafterimport = 0;
+  epggrab_conf.epgdb_processparentallabels = 0;
 
   epggrab_cron_multi              = NULL;
 
@@ -566,7 +576,7 @@ void epggrab_init ( void )
 
   /* Initialise the OTA subsystem */
   epggrab_ota_init();
-  
+
   /* Load config */
   _epggrab_load();
 
index 5b33696e7d0c44686488baa018dc0a1ab5233821..62020a49af8c6b1377a2c5912d243258a9d617c6 100644 (file)
@@ -318,6 +318,7 @@ typedef struct epggrab_conf {
   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;
index 63df1b154585de12548796ef0e2bc96842693ddd..1b40b1dc3ba4c5e8c76784f8b2f957d5ae77ad26 100644 (file)
@@ -28,6 +28,7 @@
 #include "input.h"
 #include "input/mpegts/dvb_charset.h"
 #include "dvr/dvr.h"
+#include "ratinglabels.h"
 
 /* ************************************************************************
  * Opaque
@@ -134,6 +135,7 @@ typedef struct eit_event
   uint8_t           bw;
 
   uint8_t           parental;
+  ratinglabel_t     *rating_label;
 
   uint8_t           is_new;
   time_t            first_aired;
@@ -424,14 +426,62 @@ static int _eit_desc_parental
   ( 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);
@@ -734,6 +784,13 @@ static int _eit_process_event_one
     *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)
@@ -826,6 +883,14 @@ static int _eit_process_event
         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);
index 6f09f3e18a13f3ca6d7c213b529da838b9b7433d..f21133470d0214141a71d938adce013c1afbc7d4 100644 (file)
@@ -448,40 +448,86 @@ static int _xmltv_parse_star_rating
 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;
 }
 
@@ -589,13 +635,13 @@ _xmltv_parse_credits(htsmsg_t **out_credits, htsmsg_t *tags)
       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);
 
index 352e40c27bacd5d0dceac4b795df3f51af3a8e35..7102a9a870c3fb550c38360357be01ecc14fe13e 100644 (file)
@@ -43,6 +43,7 @@
 #endif
 
 #include "settings.h"
+#include "epggrab.h"  //Needed to be able to test for epggrab_conf.epgdb_processparentallabels
 
 /* **************************************************************************
  * Datatypes and variables
@@ -50,7 +51,7 @@
 
 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
@@ -960,6 +961,7 @@ htsp_build_dvrentry(htsp_connection_t *htsp, dvr_entry_t *de, const char *method
   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));
 
@@ -1001,10 +1003,52 @@ htsp_build_dvrentry(htsp_connection_t *htsp, dvr_entry_t *de, const char *method
 
     //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
     }
 
 
@@ -1313,8 +1357,41 @@ htsp_build_event
     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)
@@ -2056,6 +2133,7 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
   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)
@@ -2080,6 +2158,7 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
   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");
@@ -2113,7 +2192,7 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
   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();
 }
index 67d0a937ec19396c8403561b0be8985a7d1327c9..a88ef62539e97de9d6ec9efc3ee0461f4cada14a 100644 (file)
@@ -681,7 +681,7 @@ imagecache_get_id ( const char *url )
     return 0;
 
   tvh_mutex_lock(&imagecache_lock);
-  
+
   /* Skeleton */
   SKEL_ALLOC(imagecache_skel);
   imagecache_skel->url = url;
index 04c5434ceb0a58000990b1d7c11f9a03889b10e1..f02b4f8cc403546242bf4e065cf6f55c9dde8a5c 100644 (file)
@@ -71,6 +71,7 @@
 #include "transcoding/codec.h"
 #include "profile.h"
 #include "bouquet.h"
+#include "ratinglabels.h"
 #include "tvhtime.h"
 #include "packet.h"
 #include "streaming.h"
@@ -696,7 +697,7 @@ mtimer_thread(void *aux)
       cb = mti->mti_callback;
       LIST_REMOVE(mti, mti_link);
       mti->mti_callback = NULL;
-      
+
       mtimer_running = mti;
       tvh_mutex_unlock(&mtimer_lock);
 
@@ -1293,6 +1294,7 @@ main(int argc, char **argv)
   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);
@@ -1401,6 +1403,7 @@ main(int argc, char **argv)
   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);
index 57612c742b19046836ab036c700cac6cdb9d0eac..e91f75b31aad6c65d1a17d35cf1d901d2ef54314 100644 (file)
@@ -241,7 +241,7 @@ prop_write_values
         break;
       }
     }
-  
+
     /* Setter */
     if (p->set && snew)
       save = p->set(obj, snew);
@@ -298,7 +298,7 @@ prop_read_value
     assert(p->get); /* requirement */
     if (val)
       htsmsg_add_msg(m, name, (htsmsg_t*)val);
-  
+
   /* Single */
   } else {
     switch(p->type) {
@@ -380,7 +380,7 @@ prop_read_values
     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)) {
diff --git a/src/ratinglabels.c b/src/ratinglabels.c
new file mode 100644 (file)
index 0000000..52ea4f1
--- /dev/null
@@ -0,0 +1,711 @@
+/*
+ *  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);
+}
diff --git a/src/ratinglabels.h b/src/ratinglabels.h
new file mode 100644 (file)
index 0000000..50415ed
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ *  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_ */
index 82f565e2ddf8af686ade45e5ef946200b499d556..d92a959c8df8224ba9c770c6d4cb61a8b7faab6f 100644 (file)
@@ -184,7 +184,8 @@ tvhlog_subsys_t tvhlog_subsystems[] = {
 #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") },
 
 };
 
index 3b7a76fb185f59c8c51fa2f00d9c61fb1fbf16a8..c69cd8fc9bff906cb5c9bdf5179ef89e6887d2e9 100644 (file)
@@ -198,7 +198,8 @@ enum {
 #if ENABLE_DDCI
   LS_DDCI,
 #endif
-  LS_UDP,      
+  LS_UDP,
+  LS_RATINGLABELS,
   LS_LAST     /* keep this last */
 };
 
index 212f464d9046b49d2278f21c05072bf86ae7f435..5d540f564e89e317e2e6d05d45f88a6909385c03 100644 (file)
@@ -73,7 +73,10 @@ tvheadend.dvrDetails = function(grid, index) {
         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">';
@@ -138,18 +141,24 @@ tvheadend.dvrDetails = function(grid, index) {
           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
@@ -308,7 +317,7 @@ tvheadend.dvrDetails = function(grid, index) {
             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);
@@ -615,7 +624,7 @@ tvheadend.dvr_upcoming = function(panel, index) {
         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(),
@@ -805,7 +814,7 @@ tvheadend.dvr_finished = function(panel, index) {
         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(),
@@ -925,7 +934,7 @@ tvheadend.dvr_failed = function(panel, index) {
                      _('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(),
@@ -1004,7 +1013,7 @@ tvheadend.dvr_removed = function(panel, index) {
         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(),
index 04da8bd56b8abd7adbe395ab7e3c9637fa2dd3d4..636e5a8e347c2f94414353588cfd0b0fbba1aa41 100644 (file)
@@ -267,8 +267,15 @@ tvheadend.epgDetails = function(grid, index) {
         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) {
@@ -651,6 +658,8 @@ tvheadend.epg = function() {
             { name: 'category' },
             { name: 'keyword' },
             { name: 'ageRating' },
+            { name: 'ratingLabel' },
+            { name: 'ratingLabelIcon' },
             { name: 'copyright_year' },
             { name: 'new' },
             { name: 'genre' },
@@ -849,6 +858,14 @@ tvheadend.epg = function() {
                 dataIndex: 'starRating',
                 renderer: renderInt
             },
+            {
+                width: 50,
+                id: 'ratingLabel',
+                header: _("Rating"),
+                tooltip: _("Parental Rating"),
+                dataIndex: 'ratingLabel',
+                renderer: renderInt
+            },
             {
                 width: 50,
                 id: 'ageRating',
@@ -892,6 +909,7 @@ tvheadend.epg = function() {
             { 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' }
         ]
index f165d3c66da53ff1ee6f2dbd480e58122b747e28..eb6d3adaef034f6e17beaa1eea5115e74afdab20 100644 (file)
     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 {
diff --git a/src/webui/static/app/ratinglabels.js b/src/webui/static/app/ratinglabels.js
new file mode 100644 (file)
index 0000000..64413df
--- /dev/null
@@ -0,0 +1,35 @@
+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;
+
+}
index 79269f8c366060c164cb4ecbc8a916adb2e8a58d..192d15fd06e838ea66d25338ade6d61f04e8d3b0 100644 (file)
@@ -1131,6 +1131,7 @@ function accessUpdate(o) {
         tvheadend.epggrab_map(chepg);
         tvheadend.epggrab_base(chepg);
         tvheadend.epggrab_mod(chepg);
+        tvheadend.ratinglabel(chepg);
 
         cp.add(chepg);
 
old mode 100755 (executable)
new mode 100644 (file)
index be4db94..9ffa008
Binary files a/src/webui/static/img/doc/channel/epgconf_tab.png and b/src/webui/static/img/doc/channel/epgconf_tab.png differ
diff --git a/src/webui/static/img/doc/ratinglabel/epg_placeholder.png b/src/webui/static/img/doc/ratinglabel/epg_placeholder.png
new file mode 100644 (file)
index 0000000..27c46ba
Binary files /dev/null and b/src/webui/static/img/doc/ratinglabel/epg_placeholder.png differ
diff --git a/src/webui/static/img/doc/ratinglabel/rating_labels_complete.png b/src/webui/static/img/doc/ratinglabel/rating_labels_complete.png
new file mode 100644 (file)
index 0000000..a82d0c3
Binary files /dev/null and b/src/webui/static/img/doc/ratinglabel/rating_labels_complete.png differ
diff --git a/src/webui/static/img/doc/ratinglabel/rating_labels_learned.png b/src/webui/static/img/doc/ratinglabel/rating_labels_learned.png
new file mode 100644 (file)
index 0000000..5a32d78
Binary files /dev/null and b/src/webui/static/img/doc/ratinglabel/rating_labels_learned.png differ
diff --git a/src/webui/static/img/doc/ratinglabel/updated_label.png b/src/webui/static/img/doc/ratinglabel/updated_label.png
new file mode 100644 (file)
index 0000000..517ba50
Binary files /dev/null and b/src/webui/static/img/doc/ratinglabel/updated_label.png differ
diff --git a/src/webui/static/img/doc/ratinglabel/xmltv_learned.png b/src/webui/static/img/doc/ratinglabel/xmltv_learned.png
new file mode 100644 (file)
index 0000000..10333db
Binary files /dev/null and b/src/webui/static/img/doc/ratinglabel/xmltv_learned.png differ