]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add 'age rating' field to recording metadata
authorDeltaMikeCharlie <127641886+DeltaMikeCharlie@users.noreply.github.com>
Wed, 2 Aug 2023 00:13:00 +0000 (10:13 +1000)
committerFlole998 <Flole998@users.noreply.github.com>
Wed, 2 Aug 2023 17:15:36 +0000 (19:15 +0200)
src/dvr/dvr.h
src/dvr/dvr_db.c
src/htsp_server.c
src/muxer/muxer_mkv.c
src/webui/static/app/dvr.js

index 3f264a33ba3662f7e3d25366d080db4de1cf27bf..3244b93b98914bf8ac024398326b25254d49604d 100644 (file)
@@ -51,8 +51,8 @@ LIST_HEAD(dvr_vfs_list, dvr_vfs);
 #define DVR_FILESIZE_TOTAL      (1<<1)
 
 #define DVR_FINISHED_ALL             (1<<0)
-#define DVR_FINISHED_SUCCESS         (1<<1) 
-#define DVR_FINISHED_FAILED          (1<<2) 
+#define DVR_FINISHED_SUCCESS         (1<<1)
+#define DVR_FINISHED_FAILED          (1<<2)
 #define DVR_FINISHED_REMOVED_SUCCESS (1<<3) /* Removed recording, was succesful before */
 #define DVR_FINISHED_REMOVED_FAILED  (1<<4) /* Removed recording, was failed before */
 
@@ -196,7 +196,7 @@ typedef struct dvr_entry {
    */
 
   LIST_ENTRY(dvr_entry) de_global_link;
-  
+
   channel_t *de_channel;
   LIST_ENTRY(dvr_entry) de_channel_link;
 
@@ -235,7 +235,7 @@ typedef struct dvr_entry {
   char *de_image;               /* Programme Image */
   char *de_fanart_image;        /* Programme fanart image */
   htsmsg_t *de_files; /* List of all used files */
-  char *de_directory; /* Can be set for autorec entries, will override any 
+  char *de_directory; /* Can be set for autorec entries, will override any
                          directory setting from the configuration */
   lang_str_t *de_title;      /* Title in UTF-8 (from EPG) */
   lang_str_t *de_subtitle;   /* Subtitle in UTF-8 (from EPG) */
@@ -244,6 +244,7 @@ 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) */
 
   int de_pri;
   int de_dont_reschedule;
@@ -285,7 +286,7 @@ typedef struct dvr_entry {
    * Last error, see SM_CODE_ defines
    */
   uint32_t de_last_error;
-  
+
 
   /**
    * Autorec linkage
@@ -380,7 +381,7 @@ typedef struct dvr_autorec_entry {
   char *dae_title;
   tvh_regex_t dae_title_regex;
   int dae_fulltext;
-  
+
   uint32_t dae_content_type;
   /* These categories (mainly from xmltv) such as Cooking, Dog racing, Movie.
    * This allows user to easily do filtering such as '"Movie" "Martial arts"'
@@ -423,9 +424,9 @@ typedef struct dvr_autorec_entry {
 
   time_t dae_start_extra;
   time_t dae_stop_extra;
-  
+
   int dae_record;
-  
+
 } dvr_autorec_entry_t;
 
 extern struct dvr_autorec_entry_queue autorec_entries;
@@ -591,7 +592,7 @@ 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 playcount, int playposition, int age_rating);
 
 void dvr_destroy_by_channel(channel_t *ch, int delconf);
 
index 68614c09258616f4680b2777ec61982483596c91..a7554a3750f94cf95bf0779eba3a20dc692f86f0 100644 (file)
@@ -1207,6 +1207,8 @@ dvr_entry_create_from_htsmsg(htsmsg_t *conf, epg_broadcast_t *e)
     genre = LIST_FIRST(&e->genre);
     if (genre)
       htsmsg_add_u32(conf, "content_type", genre->code / 16);
+    if(e->age_rating)
+      htsmsg_add_u32(conf, "age_rating", e->age_rating);
   }
 
   de = dvr_entry_create(NULL, conf, 0);
@@ -1907,7 +1909,7 @@ dvr_is_better_recording_timeslot(const epg_broadcast_t *new_bcast, const dvr_ent
     if (svf == PROFILE_SVF_UHD && !old_has_svf) {
       old_has_svf = channel_has_correct_service_filter(old_channel, PROFILE_SVF_FHD);
       new_has_svf = channel_has_correct_service_filter(new_channel, PROFILE_SVF_FHD);
-      
+
       if (!old_has_svf && new_has_svf)
         return 1;
 
@@ -2366,6 +2368,7 @@ dvr_timer_remove_files(void *aux)
 #define DVR_UPDATED_CONFIG       (1<<17)
 #define DVR_UPDATED_PLAYPOS      (1<<18)
 #define DVR_UPDATED_PLAYCOUNT    (1<<19)
+#define DVR_UPDATED_AGE_RATING   (1<<20)
 
 static char *dvr_updated_str(char *buf, size_t buflen, int flags)
 {
@@ -2413,7 +2416,7 @@ 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 playcount, int playposition, int age_rating)
 {
   char buf[40];
   int save = 0, updated = 0;
@@ -2529,6 +2532,11 @@ static dvr_entry_t *_dvr_entry_update
     updated = 1;
     dvr_entry_set_timer(de);
   }
+  /* Manual Age Rating */
+  if (age_rating != de->de_age_rating) {
+    de->de_age_rating = age_rating;
+    save |= DVR_UPDATED_AGE_RATING;
+  }
 
   /* Title */
   if (e && e->title) {
@@ -2557,6 +2565,12 @@ static dvr_entry_t *_dvr_entry_update
     save |= DVR_UPDATED_EID;
   }
 
+  /* Age Rating from EPG*/
+  if (e && e->age_rating != de->de_age_rating) {
+    de->de_age_rating = e->age_rating;
+    save |= DVR_UPDATED_AGE_RATING;
+  }
+
   /* Description */
   if (e && e->description) {
     save |= lang_str_set2(&de->de_desc, e->description) ? DVR_UPDATED_DESCRIPTION : 0;
@@ -2639,12 +2653,14 @@ dvr_entry_update
     const char *summary, const char *desc, 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 )
+    dvr_prio_t pri, int retention, int removal, int playcount, int playposition,
+    int age_rating )
 {
   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);
+                           pri, retention, removal, playcount, playposition,
+                           age_rating);
 }
 
 /**
@@ -2698,7 +2714,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);
+                            NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1, 0);
           return;
         }
       }
@@ -2739,7 +2755,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);
+                      NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1, 0);
   }
   LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link) {
     if (de->de_sched_state != DVR_SCHEDULED) continue;
@@ -2751,7 +2767,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);
+                        NULL, 0, 0, 0, 0, DVR_PRIO_NOTSET, 0, 0, -1, -1, 0);
     }
   }
 }
@@ -4508,7 +4524,7 @@ const idclass_t dvr_entry_class = {
     {
       .type     = PT_U16,
       .id       = "copyright_year",
-      .name     = N_("The copyright year of the program."),
+      .name     = N_("Copyright year"),
       .desc     = N_("The copyright year of the program."),
       .off      = offsetof(dvr_entry_t, de_copyright_year),
       .opts     = PO_RDONLY | PO_EXPERT,
@@ -4624,6 +4640,14 @@ const idclass_t dvr_entry_class = {
       .get      = dvr_entry_class_genre_get,
       .opts     = PO_RDONLY | PO_NOSAVE,
     },
+    {
+      .type     = PT_U16,
+      .id       = "age_rating",
+      .name     = N_("Age Rating"),
+      .desc     = N_("The age rating of the program."),
+      .off      = offsetof(dvr_entry_t, de_age_rating),
+      .opts     = PO_RDONLY | PO_EXPERT,
+    },
     {}
   }
 };
index 53c37d89a6a1905f4a6be1176a58d9e7c1230605..352e40c27bacd5d0dceac4b795df3f51af3a8e35 100644 (file)
@@ -422,7 +422,7 @@ htsp_send(htsp_connection_t *htsp, htsmsg_t *m, pktbuf_t *pb,
   if(pb != NULL)
     pktbuf_ref_inc(pb);
   hm->hm_payloadsize = payloadsize;
-  
+
   tvh_mutex_lock(&htsp->htsp_out_mutex);
 
   assert(!hmq->hmq_dead);
@@ -480,7 +480,7 @@ htsp_send_message(htsp_connection_t *htsp, htsmsg_t *m, htsp_msg_q_t *hmq)
   htsp_send(htsp, m, NULL, hmq ?: &htsp->htsp_hmq_ctrl, 0);
 }
 
-/** 
+/**
  * Simple function to respond with an error
  */
 static htsmsg_t *
@@ -526,7 +526,7 @@ htsp_generate_challenge(htsp_connection_t *htsp)
 
   if((fd = tvh_open("/dev/urandom", O_RDONLY, 0)) < 0)
     return -1;
-  
+
   n = read(fd, &htsp->htsp_challenge, 32);
   close(fd);
   return n != 32;
@@ -926,7 +926,7 @@ htsp_build_tag(htsp_connection_t *htsp, channel_tag_t *ct, const char *method, i
   idnode_list_mapping_t *ilm;
   htsmsg_t *out = htsmsg_create_map();
   htsmsg_t *members = include_channels ? htsmsg_create_list() : NULL;
+
   htsmsg_add_u32(out, "tagId", htsp_channel_tag_get_identifier(ct));
   htsmsg_add_u32(out, "tagIndex", ct->ct_index);
 
@@ -999,6 +999,15 @@ htsp_build_dvrentry(htsp_connection_t *htsp, dvr_entry_t *de, const char *method
     htsmsg_add_u32(out, "priority",    u32);
     htsmsg_add_u32(out, "contentType", de->de_content_type);
 
+    //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)
+    {
+      htsmsg_add_u32(out, "ageRating", de->de_age_rating);
+    }
+
+
     if (de->de_sched_state == DVR_RECORDING || de->de_sched_state == DVR_COMPLETED) {
       htsmsg_add_u32(out, "playcount",    de->de_playcount);
       htsmsg_add_u32(out, "playposition", de->de_playposition);
@@ -1406,7 +1415,7 @@ htsp_method_authenticate(htsp_connection_t *htsp, htsmsg_t *in)
     htsmsg_add_str(r, "uilanguage",     htsp->htsp_granted_access->aa_lang_ui ?
         htsp->htsp_granted_access->aa_lang_ui : (config.language_ui ? config.language_ui : ""));
   }
-  
+
   return r;
 }
 
@@ -1469,7 +1478,7 @@ htsp_method_getDiskSpace(htsp_connection_t *htsp, htsmsg_t *in)
 
   if (dvr_get_disk_space(&bfree, &bused, &btotal))
     return htsp_error(htsp, N_("Unable to stat path"));
-  
+
   out = htsmsg_create_map();
   htsmsg_add_s64(out, "freediskspace", bfree);
   htsmsg_add_s64(out, "useddiskspace", bused);
@@ -1563,7 +1572,7 @@ htsp_method_async(htsp_connection_t *htsp, htsmsg_t *in)
   }
 
   /* First, just OK the async request */
-  htsp_reply(htsp, in, htsmsg_create_map()); 
+  htsp_reply(htsp, in, htsmsg_create_map());
 
   /* Set epg */
   if(epg)
@@ -1584,12 +1593,12 @@ htsp_method_async(htsp_connection_t *htsp, htsmsg_t *in)
   TAILQ_FOREACH(ct, &channel_tags, ct_link)
     if(channel_tag_access(ct, htsp->htsp_granted_access, 0))
       htsp_send_message(htsp, htsp_build_tag(htsp, ct, "tagAdd", 0), NULL);
-  
+
   /* Send all channels */
   CHANNEL_FOREACH(ch)
     if (htsp_user_access_channel(htsp,ch))
       htsp_send_message(htsp, htsp_build_channel(ch, "channelAdd", htsp), NULL);
-  
+
   /* Send all enabled and external tags (now with channel mappings) */
   TAILQ_FOREACH(ct, &channel_tags, ct_link)
     if(channel_tag_access(ct, htsp->htsp_granted_access, 0))
@@ -1651,7 +1660,7 @@ htsp_method_getEvent(htsp_connection_t *htsp, htsmsg_t *in)
   uint32_t eventId;
   epg_broadcast_t *e;
   const char *lang;
-  
+
   if(htsmsg_get_u32(in, "eventId", &eventId))
     return htsp_error(htsp, N_("Invalid arguments"));
   lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
@@ -1663,7 +1672,7 @@ htsp_method_getEvent(htsp_connection_t *htsp, htsmsg_t *in)
 }
 
 /**
- * Get information about the given event + 
+ * Get information about the given event +
  * n following events
  */
 static htsmsg_t *
@@ -1725,7 +1734,7 @@ htsp_method_getEvents(htsp_connection_t *htsp, htsmsg_t *in)
     }
 
   }
-  
+
   /* Send */
   out = htsmsg_create_map();
   htsmsg_add_msg(out, "events", events);
@@ -1760,7 +1769,7 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
   if(htsmsg_get_bool_or_default(in, "fulltext", 0))
     eq.fulltext = 1;
   eq.stitle = strdup(query);
-  
+
   /* Optional */
   if(!(htsmsg_get_u32(in, "channelId", &u32))) {
     if (!(ch = channel_find_by_id(u32)))
@@ -1811,9 +1820,9 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
     }
     htsmsg_add_msg(out, full ? "events" : "eventIds", array);
   }
-  
+
   epg_query_free(&eq);
-  
+
   return out;
 }
 
@@ -1893,7 +1902,7 @@ htsp_method_getDvrConfigs(htsp_connection_t *htsp, htsmsg_t *in)
 /**
  * add a Dvrentry
  */
-static htsmsg_t * 
+static htsmsg_t *
 htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
 {
   htsmsg_t *conf, *out;
@@ -1974,6 +1983,9 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
     s = htsmsg_get_str(in, "description");
     if (s)
       lang_str_serialize_one(conf, "description", s, lang);
+    u32 = htsmsg_get_u32_or_default(in, "ageRating", 0);
+    if(u32)
+      htsmsg_add_u32(conf, "age_rating", u32);
   }
 
   /* Create the dvr entry */
@@ -1982,10 +1994,10 @@ htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
   htsmsg_destroy(conf);
 
   dvr_status = de != NULL ? de->de_sched_state : DVR_NOSTATE;
-  
+
   /* Create response */
   out = htsmsg_create_map();
-  
+
   switch(dvr_status) {
   case DVR_SCHEDULED:
   case DVR_RECORDING:
@@ -2043,11 +2055,12 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
   const char *dvr_config_name, *title, *subtitle, *summary, *desc, *lang;
   channel_t *channel = NULL;
   int enabled, retention, removal, playcount = -1, playposition = -1;
+  int age_rating;
 
   de = htsp_findDvrEntry(htsp, in, &out, 0);
   if (de == NULL)
     return out;
-  
+
   if(!htsmsg_get_u32(in, "channelId", &u32))
     channel = channel_find_by_id(u32);
   if (!channel)
@@ -2066,6 +2079,7 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
   retention   = htsmsg_get_u32_or_default(in, "retention",  DVR_RET_REM_DVRCONFIG);
   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);
   title       = htsmsg_get_str(in, "title");
   subtitle    = htsmsg_get_str(in, "subtitle");
   summary     = htsmsg_get_str(in, "summary");
@@ -2098,7 +2112,8 @@ 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);
+                        priority, retention, removal, playcount, playposition,
+                        age_rating);
 
   return htsp_success();
 }
@@ -2265,7 +2280,7 @@ htsp_method_deleteAutorecEntry(htsp_connection_t *htsp, htsmsg_t *in)
     return htsp_error(htsp, N_("User does not have access"));
 
   autorec_destroy_by_id(daeId, 1);
-  
+
   return htsp_success();
 }
 
@@ -2380,20 +2395,20 @@ htsp_method_deleteTimerecEntry(htsp_connection_t *htsp, htsmsg_t *in)
 }
 
 /**
- * Return cutpoint data for a recording (if present). 
+ * Return cutpoint data for a recording (if present).
  *
  * Request message fields:
  * id                 u32    required   DVR entry id
  *
  * Result message fields:
- * cutpoints          msg[]  optional   List of cutpoint entries, if a file is 
+ * cutpoints          msg[]  optional   List of cutpoint entries, if a file is
  *                                      found and has some valid data.
  *
  * Cutpoint fields:
  * start              u32    required   Cut start time in ms.
  * end                u32    required   Cut end time in ms.
- * type               u32    required   Action type: 
- *                                      0=Cut, 1=Mute, 2=Scene, 
+ * type               u32    required   Action type:
+ *                                      0=Cut, 1=Mute, 2=Scene,
  *                                      3=Commercial break.
  **/
 static htsmsg_t *
@@ -2427,7 +2442,7 @@ htsp_method_getDvrCutpoints(htsp_connection_t *htsp, htsmsg_t *in)
     }
     htsmsg_add_msg(msg, "cutpoints", cutpoint_list);
   }
-  
+
   // Cleanup...
   dvr_cutpoint_list_destroy(list);
 
@@ -2632,7 +2647,7 @@ htsp_method_unsubscribe(htsp_connection_t *htsp, htsmsg_t *in)
   LIST_FOREACH(s, &htsp->htsp_subscriptions, hs_link)
     if(s->hs_sid == sid)
       break;
-  
+
   /*
    * We send the reply here or the user will get the 'subscriptionStop'
    * async message before the reply to 'unsubscribe'.
@@ -2663,7 +2678,7 @@ htsp_method_change_weight(htsp_connection_t *htsp, htsmsg_t *in)
   LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
     if(hs->hs_sid == sid)
       break;
-  
+
   if(hs == NULL)
     return htsp_error(htsp, N_("Subscription does not exist"));
 
@@ -3148,7 +3163,7 @@ htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
     privgain = (rights->aa_rights |
                 htsp->htsp_granted_access->aa_rights) !=
                   htsp->htsp_granted_access->aa_rights;
-      
+
     tvhinfo(LS_HTSP, "%s: Identified as user '%s'",
            htsp->htsp_logname, username);
     tvh_str_update(&htsp->htsp_username, username);
@@ -3187,7 +3202,7 @@ htsp_read_message(htsp_connection_t *htsp, htsmsg_t **mp, int timeout)
   uint8_t data[4];
   void *buf;
 
-  v = timeout ? tcp_read_timeout(htsp->htsp_fd, data, 4, timeout) : 
+  v = timeout ? tcp_read_timeout(htsp->htsp_fd, data, 4, timeout) :
                 tcp_read(htsp->htsp_fd, data, 4);
 
   if(v != 0)
@@ -3199,9 +3214,9 @@ htsp_read_message(htsp_connection_t *htsp, htsmsg_t **mp, int timeout)
   if((buf = malloc(len)) == NULL)
     return ENOMEM;
 
-  v = timeout ? tcp_read_timeout(htsp->htsp_fd, buf, len, timeout) : 
+  v = timeout ? tcp_read_timeout(htsp->htsp_fd, buf, len, timeout) :
                 tcp_read(htsp->htsp_fd, buf, len);
-  
+
   if(v != 0) {
     free(buf);
     return v;
@@ -3407,7 +3422,7 @@ htsp_write_scheduler(void *aux)
     r = tvh_write(htsp->htsp_fd, dptr, dlen);
     free(dptr);
     tvh_mutex_lock(&htsp->htsp_out_mutex);
-    
+
     if (r) {
       tvhinfo(LS_HTSP, "%s: Write error -- %s",
               htsp->htsp_logname, strerror(errno));
@@ -3431,7 +3446,7 @@ htsp_serve(int fd, void **opaque, struct sockaddr_storage *source,
   htsp_connection_t htsp;
   char buf[50];
   htsp_subscription_t *s;
-  
+
   // Note: global_lock held on entry
 
   if (config.dscp >= 0)
@@ -3519,7 +3534,7 @@ htsp_serve(int fd, void **opaque, struct sockaddr_storage *source,
     htsp_file_destroy(hf);
 
   close(fd);
-  
+
   /* Free memory (leave lock in place, for parent method) */
   tvh_mutex_lock(&global_lock);
   free(htsp.htsp_logname);
@@ -4053,7 +4068,7 @@ htsp_stream_deliver(htsp_subscription_t *hs, th_pkt_t *pkt)
   }
 
   m = htsmsg_create_map();
+
   htsmsg_add_str(m, "method", "muxpkt");
   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
   if (video)
@@ -4074,7 +4089,7 @@ htsp_stream_deliver(htsp_subscription_t *hs, th_pkt_t *pkt)
 
   uint32_t dur = hs->hs_90khz ? pkt->pkt_duration : ts_rescale(pkt->pkt_duration, 1000000);
   htsmsg_add_u32(m, "duration", dur);
-  
+
   /**
    * Since we will serialize directly we use 'binptr' which is a binary
    * object that just points to data, thus avoiding a copy.
@@ -4099,9 +4114,9 @@ htsp_stream_deliver(htsp_subscription_t *hs, th_pkt_t *pkt)
       htsmsg_add_u32(m, "errors", hs->hs_data_errors);
 
     /**
-     * Figure out real time queue delay 
+     * Figure out real time queue delay
      */
-    
+
     tvh_mutex_lock(&htsp->htsp_out_mutex);
 
     int64_t min_dts = PTS_UNSET;
@@ -4113,7 +4128,7 @@ htsp_stream_deliver(htsp_subscription_t *hs, th_pkt_t *pkt)
        continue;
       if(ts == PTS_UNSET)
        continue;
-  
+
       if(min_dts == PTS_UNSET)
        min_dts = ts;
       else
@@ -4184,7 +4199,7 @@ htsp_subscription_start(htsp_subscription_t *hs, const streaming_start_t *ss)
     htsmsg_add_str(c, "type", type);
     if(ssc->es_lang[0])
       htsmsg_add_str(c, "language", ssc->es_lang);
-    
+
     if(ssc->es_type == SCT_DVBSUB) {
       htsmsg_add_u32(c, "composition_id", ssc->es_composition_id);
       htsmsg_add_u32(c, "ancillary_id", ssc->es_ancillary_id);
@@ -4222,7 +4237,7 @@ htsp_subscription_start(htsp_subscription_t *hs, const streaming_start_t *ss)
 
     htsmsg_add_msg(streams, NULL, c);
   }
-  
+
   htsmsg_add_msg(m, "streams", streams);
 
   si = &ss->ss_si;
@@ -4244,9 +4259,9 @@ htsp_subscription_start(htsp_subscription_t *hs, const streaming_start_t *ss)
     htsmsg_add_str2(sourceinfo, "service",      si->si_service     );
     htsmsg_add_str2(sourceinfo, "satpos",       si->si_satpos      );
   }
-  
+
   htsmsg_add_msg(m, "sourceinfo", sourceinfo);
+
   htsmsg_add_str(m, "method", "subscriptionStart");
   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
   htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
index 5b71c006f53e46e91ac803f63141ab21daadab18..a38d15af54dd7bf66015fd127475562fa650e9d2 100644 (file)
@@ -749,6 +749,7 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc,
   const char *lang;
   const lang_code_list_t *langs;
   epg_episode_num_t num;
+  int tmp_age;
 
   if (de || ebc) {
     localtime_r(de ? &de->de_start : &ebc->start, &tm);
@@ -779,6 +780,13 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc,
   if(eg && epg_genre_get_str(eg, 1, 0, ctype, 100, NULL))
     addtag(q, build_tag_string("CONTENT_TYPE", ctype, NULL, 0, NULL));
 
+  //TODO - MKV The spec suggests that this tag can consist of multiple value types.
+  //https://www.matroska.org/technical/tagging.html
+  //==> Depending on the COUNTRY it’s the format of the rating of a movie
+  //==> (P, R, X in the USA, an age in other countries or a URI defining a logo).
+  tmp_age = ebc->age_rating;
+  addtag(q, build_tag_int("LAW_RATING", tmp_age, 0, NULL));
+
   if(ch)
     addtag(q, build_tag_string("TVCHANNEL",
                                channel_get_name(ch, channel_blank_name), NULL, 0, NULL));
index 4c93ba06a21ec145a28123a0d316e8f6d8ff5c50..8b703a97bb4d3636d41997feb1fabbb5006d1862 100644 (file)
@@ -73,6 +73,7 @@ tvheadend.dvrDetails = function(grid, index) {
         var genre = params[21].value;
         /* channelname is unused param 22 */
         var fanart_image = params[23].value;
+        var age_rating = params[25].value;
         var content = '<div class="dvr-details-dialog">' +
         '<div class="dvr-details-dialog-background-image"></div>' +
         '<div class="dvr-details-dialog-content">';
@@ -764,7 +765,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',
+              'sched_status,errors,data_errors,config_name,owner,creator,comment,genre,broadcast,age_rating',
         columns: {
             disp_title: {
                 renderer: tvheadend.displayWithYearAndDuplicateRenderer(),
@@ -866,7 +867,7 @@ tvheadend.dvr_finished = function(panel, index) {
             buttonFcn(store, select, 'api/dvr/entry/move/failed');
         }
     };
-    
+
     var removeButton = {
         name: 'remove',
         builder: function() {
@@ -954,7 +955,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,',
+              'sched_status,errors,data_errors,playcount,url,config_name,owner,creator,comment,age_rating',
         columns: {
             disp_title: {
                 renderer: tvheadend.displayWithYearRenderer(),
@@ -1074,7 +1075,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',
+              'sched_status,errors,data_errors,playcount,url,config_name,owner,creator,comment,age_rating',
         columns: {
             disp_title: {
                 renderer: tvheadend.displayWithYearRenderer(),
@@ -1153,7 +1154,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',
+              'sched_status,errors,data_errors,url,config_name,owner,creator,comment,age_rating',
         columns: {
             disp_title: {
                 renderer: tvheadend.displayWithYearRenderer(),