From: E.Smith <31170571+azlm8t@users.noreply.github.com> Date: Wed, 20 Sep 2017 01:05:39 +0000 (+0100) Subject: xmltv: Parse credits, category, keyword and more age ratings. (#4441) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=db437039114c62c5973a2c8cba8dca8a497ecb1c;p=thirdparty%2Ftvheadend.git xmltv: Parse credits, category, keyword and more age ratings. (#4441) The xmltv provides additional information about programmes such as keywords ("Zookeeper", "Newscast", "Lion", "Mystery"), and categories ("Crime drama", "Movie", "Series"). It can also provide detailed information about actors, writers, editors, composers, etc. We parse this information and allow it to be searched from the GUI. We make this configurable since having 20+ actors per movie can increase memory usage of the server and the clients to which we send this information. We also offer an option to append this information on to the description. This allows people with old clients to see the information. We cache this information in to a csv string so users can search across multiple actors such as "Douglas.*Stallone" to find movies where both actors starred, rather than searching across each actor individually. The category is not currently searchable via regex since I think that should probably be a search box similar to content type. We currently only parse and store a few of the credits, viz., actor, guest, presenter, writer, and director. If people really search for films based on the composer or editor then we can add it in the future. This information is stored on the epg_broadcast rather than the epg_episode since theoretically a programme could have different information for different showings of the same programme. For example, my broadcaster shows the same film in the same week but prefixes the description of some showings with a keyword (such as "Frightfest") with other film of the same genre to create a pseudo-boxset. Thus if we ever scrape keywords from EIT we'd probably tag the particular films with this keyword as a tag on which people could search. Similarly for credits, a daytime showing of a programme can contain edits for violence, swearing that are not in the late night showing, thus potentially changing the cast despite being the same "episode", or perhaps one showing is dubbed. We also parse a few more age ratings since a number of programmes only have "word" ratings rather than age ratings (TV-14 instead of 14). Also the existing age could underflow since one rating system uses negative numbers which don't fit in our unsigned byte. Issue: #4441 --- diff --git a/src/epg.c b/src/epg.c index a15037dca..301fd8ed4 100644 --- a/src/epg.c +++ b/src/epg.c @@ -365,6 +365,8 @@ static int FNNAME \ EPG_OBJECT_SET_FN(_epg_object_set_lang_str, lang_str_t, lang_str_destroy, lang_str_compare, lang_str_copy) +EPG_OBJECT_SET_FN(_epg_object_set_string_list, string_list_t, string_list_destroy, string_list_cmp, string_list_copy) +EPG_OBJECT_SET_FN(_epg_object_set_htsmsg, htsmsg_t, htsmsg_destroy, htsmsg_cmp, htsmsg_copy) #undef EPG_OBJECT_SET_FN static int _epg_object_set_u8 @@ -1883,6 +1885,11 @@ static void _epg_broadcast_destroy ( void *eo ) if (ebc->serieslink) _epg_serieslink_rem_broadcast(ebc->serieslink, ebc); if (ebc->summary) lang_str_destroy(ebc->summary); if (ebc->description) lang_str_destroy(ebc->description); + if (ebc->credits) htsmsg_destroy(ebc->credits); + if (ebc->credits_cached) lang_str_destroy(ebc->credits_cached); + if (ebc->category) string_list_destroy(ebc->category); + if (ebc->keyword) string_list_destroy(ebc->keyword); + if (ebc->keyword_cached) lang_str_destroy(ebc->keyword_cached); _epg_object_destroy(eo, NULL); free(ebc); } @@ -1979,6 +1986,12 @@ int epg_broadcast_change_finish save |= epg_broadcast_set_summary(broadcast, NULL, NULL); if (!(changes & EPG_CHANGED_DESCRIPTION)) save |= epg_broadcast_set_description(broadcast, NULL, NULL); + if (!(changes & EPG_CHANGED_CREDITS)) + save |= epg_broadcast_set_credits(broadcast, NULL, NULL); + if (!(changes & EPG_CHANGED_CATEGORY)) + save |= epg_broadcast_set_category(broadcast, NULL, NULL); + if (!(changes & EPG_CHANGED_KEYWORD)) + save |= epg_broadcast_set_keyword(broadcast, NULL, NULL); return save; } @@ -2004,6 +2017,9 @@ epg_broadcast_t *epg_broadcast_clone *save |= epg_broadcast_set_is_new(ebc, src->is_new, &changes); *save |= epg_broadcast_set_is_repeat(ebc, src->is_repeat, &changes); *save |= epg_broadcast_set_summary(ebc, src->summary, &changes); + *save |= epg_broadcast_set_credits(ebc, src->credits, &changes); + *save |= epg_broadcast_set_category(ebc, src->category, &changes); + *save |= epg_broadcast_set_keyword(ebc, src->keyword, &changes); *save |= epg_broadcast_set_description(ebc, src->description, &changes); *save |= epg_broadcast_set_serieslink(ebc, src->serieslink, &changes); *save |= epg_broadcast_set_episode(ebc, src->episode, &changes); @@ -2177,6 +2193,72 @@ int epg_broadcast_set_description changed, EPG_CHANGED_DESCRIPTION); } +int epg_broadcast_set_credits +( epg_broadcast_t *b, htsmsg_t *credits, uint32_t *changed ) +{ + if (!b) return 0; + const int mod = _epg_object_set_htsmsg(b, &b->credits, credits, changed, EPG_CHANGED_CREDITS); + if (mod) { + /* Copy in to cached csv for regex searching in autorec/GUI. + * We use just one string (rather than regex across each entry + * separately) so you could do a regex of "Douglas.*Stallone" + * to match the movies with the two actors. + */ + if (!b->credits_cached) { + b->credits_cached = lang_str_create(); + } + lang_str_set(&b->credits_cached, "", NULL); + + if (b->credits) { + int add_sep = 0; + htsmsg_field_t *f; + HTSMSG_FOREACH(f, b->credits) { + if (add_sep) { + lang_str_append(b->credits_cached, ", ", NULL); + } else { + add_sep = 1; + } + lang_str_append(b->credits_cached, f->hmf_name, NULL); + } + } else { + if (b->credits_cached) { + lang_str_destroy(b->credits_cached); + b->credits_cached = NULL; + } + } + } + return mod; +} + +int epg_broadcast_set_category +( epg_broadcast_t *b, string_list_t *msg, uint32_t *changed ) +{ + if (!b) return 0; + return _epg_object_set_string_list(b, &b->category, msg, changed, EPG_CHANGED_CATEGORY); +} + +int epg_broadcast_set_keyword +( epg_broadcast_t *b, string_list_t *msg, uint32_t *changed ) +{ + if (!b) return 0; + const int mod = _epg_object_set_string_list(b, &b->keyword, msg, changed, EPG_CHANGED_KEYWORD); + if (mod) { + /* Copy in to cached csv for regex searching in autorec/GUI. */ + if (msg) { + /* 1==>human readable */ + char *str = string_list_2_csv(msg, ',', 1); + lang_str_set(&b->keyword_cached, str, NULL); + free(str); + } else { + if (b->keyword_cached) { + lang_str_destroy(b->keyword_cached); + b->keyword_cached = NULL; + } + } + } + return mod; +} + epg_broadcast_t *epg_broadcast_get_next ( epg_broadcast_t *broadcast ) { if ( !broadcast ) return NULL; @@ -2201,6 +2283,18 @@ const char *epg_broadcast_get_summary ( epg_broadcast_t *b, const char *lang ) return lang_str_get(b->summary, lang); } +const char *epg_broadcast_get_credits_cached ( epg_broadcast_t *b, const char *lang) +{ + if (!b || !b->credits_cached) return NULL; + return lang_str_get(b->credits_cached, lang); +} + +const char *epg_broadcast_get_keyword_cached ( epg_broadcast_t *b, const char *lang) +{ + if (!b || !b->keyword_cached) return NULL; + return lang_str_get(b->keyword_cached, lang); +} + const char *epg_broadcast_get_description ( epg_broadcast_t *b, const char *lang ) { if (!b || !b->description) return NULL; @@ -2243,6 +2337,15 @@ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *broadcast ) lang_str_serialize(broadcast->summary, m, "summary"); if (broadcast->description) lang_str_serialize(broadcast->description, m, "description"); + if (broadcast->credits) + htsmsg_add_msg(m, "credits", htsmsg_copy(broadcast->credits)); + /* No need to serialize credits_cached since it is rebuilt from credits. */ + if (broadcast->category) + string_list_serialize(broadcast->category, m, "category"); + if (broadcast->keyword) + string_list_serialize(broadcast->keyword, m, "keyword"); + /* No need to serialize keyword_cached since it is rebuilt from keyword */ + if (broadcast->serieslink) htsmsg_add_str(m, "serieslink", broadcast->serieslink->uri); @@ -2257,6 +2360,8 @@ epg_broadcast_t *epg_broadcast_deserialize epg_episode_t *ee; epg_serieslink_t *esl; lang_str_t *ls; + htsmsg_t *hm; + string_list_t *sl; const char *str; uint32_t eid, u32, changes = 0, changes2 = 0; int64_t start, stop; @@ -2318,6 +2423,18 @@ epg_broadcast_t *epg_broadcast_deserialize lang_str_destroy(ls); } + if ((hm = htsmsg_get_map(m, "credits"))) { + *save |= epg_broadcast_set_credits(ebc, hm, &changes); + } + + if ((sl = string_list_deserialize(m, "keyword"))) { + *save |= epg_broadcast_set_keyword(ebc, sl, &changes); + } + + if ((sl = string_list_deserialize(m, "category"))) { + *save |= epg_broadcast_set_category(ebc, sl, &changes); + } + /* Series link */ if ((str = htsmsg_get_str(m, "serieslink"))) if ((esl = epg_serieslink_find_by_uri(str, ebc->grabber, 1, save, &changes2))) { @@ -2783,7 +2900,13 @@ _eq_add ( epg_query_t *eq, epg_broadcast_t *e ) regex_match(&eq->stitle_re, s)) { if ((s = epg_broadcast_get_description(e, lang)) == NULL || regex_match(&eq->stitle_re, s)) { - return; + if ((s = epg_broadcast_get_credits_cached(e, lang)) == NULL || + regex_match(&eq->stitle_re, s)) { + if ((s = epg_broadcast_get_keyword_cached(e, lang)) == NULL || + regex_match(&eq->stitle_re, s)) { + return; + } + } } } } diff --git a/src/epg.h b/src/epg.h index 7be7b245a..299dd28c9 100644 --- a/src/epg.h +++ b/src/epg.h @@ -30,6 +30,7 @@ struct channel; struct channel_tag; struct epggrab_module; +struct string_list; /* * Map/List types @@ -126,6 +127,9 @@ typedef enum epg_object_type #define EPG_CHANGED_SUMMARY (1<<3) #define EPG_CHANGED_DESCRIPTION (1<<4) #define EPG_CHANGED_IMAGE (1<<5) +#define EPG_CHANGED_CREDITS (1<<6) +#define EPG_CHANGED_CATEGORY (1<<7) +#define EPG_CHANGED_KEYWORD (1<<8) #define EPG_CHANGED_SLAST 2 typedef struct epg_object_ops { @@ -511,7 +515,13 @@ struct epg_broadcast /* Broadcast level text */ lang_str_t *summary; ///< Summary lang_str_t *description; ///< Description - + htsmsg_t *credits; ///< Cast/Credits map of name -> role type (actor, presenter, director, etc). + lang_str_t *credits_cached; ///< Comma separated cast (for regex searching in GUI/autorec). Kept in sync with cast_map + struct string_list *category; ///< Extra categories (typically from xmltv) such as "Western" or "Sumo Wrestling". + ///< These extra categories are often a superset of our EN 300 468 DVB genre. + ///< Currently not explicitly searchable in GUI. + struct string_list *keyword; ///< Extra keywords (typically from xmltv) such as "Wild West" or "Unicorn". + lang_str_t *keyword_cached; ///< Cached CSV version for regex searches. RB_ENTRY(epg_broadcast) sched_link; ///< Schedule link LIST_ENTRY(epg_broadcast) ep_link; ///< Episode link epg_episode_t *episode; ///< Episode shown @@ -578,6 +588,15 @@ int epg_broadcast_set_summary int epg_broadcast_set_description ( epg_broadcast_t *b, const lang_str_t *str, uint32_t *changed ) __attribute__((warn_unused_result)); +int epg_broadcast_set_credits +( epg_broadcast_t *b, htsmsg_t* msg, uint32_t *changed ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_category +( epg_broadcast_t *b, struct string_list* msg, uint32_t *changed ) + __attribute__((warn_unused_result)); +int epg_broadcast_set_keyword +( epg_broadcast_t *b, struct string_list* msg, uint32_t *changed ) + __attribute__((warn_unused_result)); int epg_broadcast_set_serieslink ( epg_broadcast_t *b, epg_serieslink_t *sl, uint32_t *changed ) __attribute__((warn_unused_result)); @@ -592,6 +611,11 @@ const char *epg_broadcast_get_summary ( epg_broadcast_t *b, const char *lang ); const char *epg_broadcast_get_description ( epg_broadcast_t *b, const char *lang ); +/* Get the cached (csv) version for regex searching */ +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 ); /* Serialization */ htsmsg_t *epg_broadcast_serialize ( epg_broadcast_t *b ); diff --git a/src/epggrab.h b/src/epggrab.h index f65d67570..7944794a4 100644 --- a/src/epggrab.h +++ b/src/epggrab.h @@ -179,6 +179,9 @@ struct epggrab_module_int const char *args; ///< Extra arguments int xmltv_chnum; + int xmltv_scrape_extra; ///< Scrape actors and extra details + int xmltv_scrape_onto_desc; ///< Include scraped actors + ///< and extra details on to programme description for viewing by legacy clients. /* Handle data */ char* (*grab) ( void *mod ); diff --git a/src/epggrab/module/xmltv.c b/src/epggrab/module/xmltv.c index a519a54e7..b96bb669f 100644 --- a/src/epggrab/module/xmltv.c +++ b/src/epggrab/module/xmltv.c @@ -35,6 +35,7 @@ #include "spawn.h" #include "file.h" #include "htsstr.h" +#include "string_list.h" #include "lang_str.h" #include "epg.h" @@ -380,7 +381,15 @@ static int _xmltv_parse_star_rating /* * Tries to get age ratingform element. * Expects integer representing minimal age of watcher. - * Other rating types (non-integer, for example MPAA or VCHIP) are ignored. + * Other rating types (non-integer, for example MPAA or VCHIP) are + * mostly ignored, but we have some basic mappings for common + * ratings such as TV-MA which may only be the only ratings for + * some movies. + * + * We use the first rating that we find that returns a usable age. Of + * course that means some programmes might not have the rating you + * expect for your country. For example one episode of a cooking + * programme has BBFC 18 but VCHIP TV-G. * * Attribute system is ignored. * @@ -388,9 +397,8 @@ static int _xmltv_parse_star_rating * 16 * * Currently non-working example: - * - * PG - * + * + * -12 * * * TODO - support for other rating systems: @@ -406,13 +414,36 @@ static int _xmltv_parse_age_rating const char *s1; if (!ee || !body) return 0; - if (!(rating = htsmsg_get_map(body, "rating"))) return 0; - if (!(tags = htsmsg_get_map(rating, "tags"))) return 0; - if (!(s1 = htsmsg_xml_get_cdata_str(tags, "value"))) return 0; - age = atoi(s1); - - return epg_episode_set_age_rating(ee, age, changes); + htsmsg_field_t *f; + HTSMSG_FOREACH(f, body) { + if (!strcmp(f->hmf_name, "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_episode_set_age_rating(ee, age, changes); + } + } + } + } + return 0; } /* @@ -454,6 +485,80 @@ _xmltv_parse_lang_str ( lang_str_t **ls, htsmsg_t *tags, const char *tname ) } } +/// Make a string list from the contents of all tags in the message +/// that have tagname. +__attribute__((warn_unused_result)) +static string_list_t * + _xmltv_make_str_list_from_matching(htsmsg_t *tags, const char *tagname) +{ + htsmsg_t *e; + htsmsg_field_t *f; + string_list_t *tag_list = NULL; + + HTSMSG_FOREACH(f, tags) { + if (!strcmp(f->hmf_name, tagname) && (e = htsmsg_get_map_by_field(f))) { + const char *str = htsmsg_get_str(e, "cdata"); + if (str && *str) { + if (!tag_list) tag_list = string_list_create(); + string_list_insert(tag_list, str); + } + } + } + + return tag_list; +} + + +/// Parse credits from the message tags and store the name/type (such +/// as actor, director) in to out_credits (created if necessary). +/// Also return a string list of the names only. +/// +/// Sample input: +/// +/// Fred Foo +/// Vic Vicson +/// Simon Scott +/// +/// +/// Returns string list of {"Fred Foo", "Simon Scott", "Vic Vicson"} and +/// out_credits containing the names and actor/director. +__attribute__((warn_unused_result)) +static string_list_t * +_xmltv_parse_credits(htsmsg_t **out_credits, htsmsg_t *tags) +{ + htsmsg_t *credits = htsmsg_get_map(tags, "credits"); + if (!credits) + return NULL; + htsmsg_t *credits_tags; + if (!(credits_tags = htsmsg_get_map(credits, "tags"))) + return NULL; + + string_list_t *credits_names = NULL; + htsmsg_t *e; + htsmsg_field_t *f; + + HTSMSG_FOREACH(f, credits_tags) { + if ((!strcmp(f->hmf_name, "actor") || + !strcmp(f->hmf_name, "director") || + !strcmp(f->hmf_name, "guest") || + !strcmp(f->hmf_name, "presenter") || + !strcmp(f->hmf_name, "writer") + ) && + (e = htsmsg_get_map_by_field(f))) { + const char* str = htsmsg_get_str(e, "cdata"); + if (str) { + if (!credits_names) credits_names = string_list_create(); + string_list_insert(credits_names, str); + + if (!*out_credits) *out_credits = htsmsg_create_map(); + htsmsg_add_str(*out_credits, str, f->hmf_name); + } + } + } + + return credits_names; +} + /** * Parse tags inside of a programme */ @@ -462,6 +567,8 @@ static int _xmltv_parse_programme_tags time_t start, time_t stop, const char *icon, epggrab_stats_t *stats) { + const int scrape_extra = ((epggrab_module_ext_t *)mod)->xmltv_scrape_extra; + const int scrape_onto_desc = ((epggrab_module_ext_t *)mod)->xmltv_scrape_onto_desc; int save = 0, save2 = 0, save3 = 0; epg_episode_t *ee = NULL; epg_serieslink_t *es = NULL; @@ -488,8 +595,64 @@ static int _xmltv_parse_programme_tags /* Description (wait for episode first) */ _xmltv_parse_lang_str(&desc, tags, "desc"); - if (desc) + + /* If user has requested it then retrieve additional information + * from programme such as credits and keywords. + */ + if (scrape_extra || scrape_onto_desc) { + htsmsg_t *credits = NULL; + string_list_t *credits_names = _xmltv_parse_credits(&credits, tags); + string_list_t *category = _xmltv_make_str_list_from_matching(tags, "category"); + string_list_t *keyword = _xmltv_make_str_list_from_matching(tags, "keyword"); + + if (scrape_extra && credits) { + save3 |= epg_broadcast_set_credits(ebc, credits, &changes); + } + + if (scrape_extra && category) { + save3 |= epg_broadcast_set_category(ebc, category, &changes); + } + + if (scrape_extra && keyword) { + save3 |= epg_broadcast_set_keyword(ebc, keyword, &changes); + } + + /* Convert the string list VAR to a human-readable csv and append + * it to the desc with a prefix of NAME. + */ +#define APPENDIT(VAR,NAME) \ + if (VAR) { \ + char *str = string_list_2_csv(VAR, ',', 1); \ + if (str) { \ + lang_str_append(desc, "\n\n", NULL); \ + lang_str_append(desc, NAME, NULL); \ + lang_str_append(desc, str, NULL); \ + free(str); \ + } \ + } + + /* Append the details on to the description, mainly for legacy + * clients. This allow you to view the details in the description + * on old boxes/tablets that don't parse the newer fields or + * don't display them. + */ + if (desc && scrape_onto_desc) { + APPENDIT(credits_names, N_("Credits: ")); + APPENDIT(category, N_("Categories: ")); + APPENDIT(keyword, N_("Keywords: ")); + } + + if (credits) htsmsg_destroy(credits); + if (credits_names) string_list_destroy(credits_names); + if (category) string_list_destroy(category); + if (keyword) string_list_destroy(keyword); + +#undef APPENDIT + } /* desc */ + + if (desc) { save3 |= epg_broadcast_set_description(ebc, desc, &changes); + } /* desc */ /* summary */ _xmltv_parse_lang_str(&summary, tags, "summary"); @@ -583,7 +746,6 @@ static int _xmltv_parse_programme_tags if (subtitle) lang_str_destroy(subtitle); if (desc) lang_str_destroy(desc); if (summary) lang_str_destroy(summary); - return save | save2 | save3; } @@ -786,6 +948,29 @@ static int _xmltv_parse N_("Try to obtain channel numbers from the display-name xml tag. " \ "If the first word is number, it is used as the channel number.") +#define SCRAPE_EXTRA_NAME N_("Scrape credits and extra information") +#define SCRAPE_EXTRA_DESC \ + N_("Obtain list of credits (actors, etc.), keywords and extra information from the xml tags (if available). " \ + "Some xmltv providers supply a list of actors and additional keywords to " \ + "describe programmes. This option will retrieve this additional information. " \ + "This can be very detailed (20+ actors per movie) " \ + "and will take a lot of memory and resources on this box, and will " \ + "pass this information to your client machines and GUI too, using " \ + "memory and resources on those boxes too. " \ + "Do not enable on low-spec machines.") + +#define SCRAPE_ONTO_DESC_NAME N_("Alter programme description to include detailed information") +#define SCRAPE_ONTO_DESC_DESC \ + N_("If enabled then this will alter the programme descriptions to " \ + "include information about actors, keywords and categories (if available from the xmltv file). " \ + "This is useful for legacy clients that can not parse newer Tvheadend messages " \ + "containing this information or do not display the information. "\ + "For example the modified description might include 'Starring: Lorem Ipsum'. " \ + "The description is altered for all clients, both legacy, modern, and GUI. "\ + "Enabling scraping of detailed information can use significant resources (memory and CPU). "\ + "You should not enable this if you use 'duplicate detect if different description' " \ + "since the descriptions will change due to added information.") + static htsmsg_t * xmltv_dn_chnum_list ( void *o, const char *lang ) { @@ -812,6 +997,22 @@ const idclass_t epggrab_mod_int_xmltv_class = { .opts = PO_DOC_NLIST, .group = 1 }, + { + .type = PT_BOOL, + .id = "scrape_extra", + .name = SCRAPE_EXTRA_NAME, + .desc = SCRAPE_EXTRA_DESC, + .off = offsetof(epggrab_module_int_t, xmltv_scrape_extra), + .group = 1 + }, + { + .type = PT_BOOL, + .id = "scrape_onto_desc", + .name = SCRAPE_ONTO_DESC_NAME, + .desc = SCRAPE_ONTO_DESC_DESC, + .off = offsetof(epggrab_module_int_t, xmltv_scrape_onto_desc), + .group = 1 + }, {} } }; @@ -830,6 +1031,22 @@ const idclass_t epggrab_mod_ext_xmltv_class = { .opts = PO_DOC_NLIST, .group = 1 }, + { + .type = PT_BOOL, + .id = "scrape_extra", + .name = SCRAPE_EXTRA_NAME, + .desc = SCRAPE_EXTRA_DESC, + .off = offsetof(epggrab_module_int_t, xmltv_scrape_extra), + .group = 1 + }, + { + .type = PT_BOOL, + .id = "scrape_onto_desc", + .name = SCRAPE_ONTO_DESC_NAME, + .desc = SCRAPE_ONTO_DESC_DESC, + .off = offsetof(epggrab_module_int_t, xmltv_scrape_onto_desc), + .group = 1 + }, {} } }; diff --git a/src/htsp_server.c b/src/htsp_server.c index 018f64b5e..667f70dad 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -35,6 +35,7 @@ #include "descrambler/caid.h" #include "notify.h" #include "htsmsg_json.h" +#include "string_list.h" #include "lang_codes.h" #if ENABLE_TIMESHIFT #include "timeshift.h" @@ -1224,6 +1225,17 @@ htsp_build_event htsmsg_add_str(out, "summary", str); } else if((str = epg_broadcast_get_summary(e, lang))) htsmsg_add_str(out, "description", str); + + if (e->credits) { + htsmsg_add_msg(out, "credits", htsmsg_copy(e->credits)); + } + if (e->category) { + htsmsg_add_msg(out, "category", string_list_to_htsmsg(e->category)); + } + if (e->keyword) { + htsmsg_add_msg(out, "keyword", string_list_to_htsmsg(e->keyword)); + } + if (e->serieslink) { htsmsg_add_u32(out, "serieslinkId", e->serieslink->id); if (e->serieslink->uri) diff --git a/src/lang_str.h b/src/lang_str.h index d31dbddfd..d0beebdb1 100644 --- a/src/lang_str.h +++ b/src/lang_str.h @@ -64,9 +64,11 @@ lang_str_t *lang_str_deserialize /* Compare */ int lang_str_compare ( const lang_str_t *ls1, const lang_str_t *ls2 ); -/* Empty */ -int strempty(const char* c); -int lang_str_empty(lang_str_t* str); +/* Is string empty? */ +int strempty(const char* c) + __attribute__((warn_unused_result)); +int lang_str_empty(lang_str_t* str) + __attribute__((warn_unused_result)); /* Size in bytes */ size_t lang_str_size ( const lang_str_t *ls );