From: azlm8t <31170571+azlm8t@users.noreply.github.com> Date: Fri, 22 Sep 2017 00:09:20 +0000 (+0100) Subject: dvr: Allow selecting (xmltv) category in autorec. (#4665) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a4ce14b898a5ddedf3d03141e7dfc1d28d245cae;p=thirdparty%2Ftvheadend.git dvr: Allow selecting (xmltv) category in autorec. (#4665) The xmltv import supports categories such as "movie", "animated", "biography", so allow autorec to record via these categories. We do this by providing three drop-down selectors in the advanced settings of the autorec. This allows the user to easily discover the categories available whilst providing enough capability for reasonably advanced recordings when coupled with the existing fulltext search. Issue: #4665 --- diff --git a/src/api/api_channel.c b/src/api/api_channel.c index 0f09d7130..4183eb8fb 100644 --- a/src/api/api_channel.c +++ b/src/api/api_channel.c @@ -24,6 +24,7 @@ #include "channels.h" #include "access.h" #include "api.h" +#include "string_list.h" static int api_channel_is_all(access_t *perm, htsmsg_t *args) @@ -152,6 +153,51 @@ api_channel_tag_create return 0; } +static int +api_channel_cat_list + ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + channel_t *ch; + int cfg = api_channel_is_all(perm, args); + + htsmsg_t *l = htsmsg_create_list(); + string_list_t *sl = string_list_create(); + const string_list_item_t *item; + + pthread_mutex_lock(&global_lock); + /* Build string_list of all categories the user is allowed + * to see. + */ + CHANNEL_FOREACH(ch) { + if (!cfg && !channel_access(ch, perm, 0)) continue; + if (!ch->ch_enabled) continue; + epg_broadcast_t *e; + RB_FOREACH(e, &ch->ch_epg_schedule, sched_link) { + if (e->category) { + RB_FOREACH(item, e->category, h_link) { + const char *id = item->id; + /* Get rid of duplicates */ + string_list_insert(sl, id); + } + } + } + } + pthread_mutex_unlock(&global_lock); + + /* Now we have the unique list, convert it for GUI. */ + RB_FOREACH(item, sl, h_link) { + const char *id = item->id; + htsmsg_add_msg(l, NULL, htsmsg_create_key_val(id, id)); + } + + *resp = htsmsg_create_map(); + htsmsg_add_msg(*resp, "entries", l); + + string_list_destroy(sl); + return 0; +} + + void api_channel_init ( void ) { static api_hook_t ah[] = { @@ -165,6 +211,7 @@ void api_channel_init ( void ) { "channeltag/list", ACCESS_ANONYMOUS, api_channel_tag_list, NULL }, { "channeltag/create", ACCESS_ADMIN, api_channel_tag_create, NULL }, + { "channelcategory/list", ACCESS_ANONYMOUS, api_channel_cat_list, NULL }, { NULL }, }; diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index f6dd2d080..603e2b7de 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -347,6 +347,13 @@ typedef struct dvr_autorec_entry { 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"' + * or '"Children" "Animated" "Movie"' + */ + char *dae_cat1; /** Simple single category from drop-down selection boxes */ + char *dae_cat2; /** Simple single category from drop-down selection boxes */ + char *dae_cat3; /** Simple single category from drop-down selection boxes */ int dae_start; /* Minutes from midnight */ int dae_start_window; /* Minutes (duration) */ diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c index cc01ff2cf..726aa9fdb 100644 --- a/src/dvr/dvr_autorec.c +++ b/src/dvr/dvr_autorec.c @@ -30,6 +30,7 @@ #include "tvheadend.h" #include "settings.h" +#include "string_list.h" #include "dvr.h" #include "epg.h" #include "htsp_server.h" @@ -159,6 +160,9 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e) dae->dae_content_type == 0 && (dae->dae_title == NULL || dae->dae_title[0] == '\0') && + (dae->dae_cat1 == NULL || *dae->dae_cat1 == 0) && + (dae->dae_cat2 == NULL || *dae->dae_cat2 == 0) && + (dae->dae_cat3 == NULL || *dae->dae_cat3 == 0) && dae->dae_brand == NULL && dae->dae_season == NULL && dae->dae_minduration <= 0 && @@ -211,6 +215,15 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e) return 0; } + if (e->category) { + if (dae->dae_cat1 && *dae->dae_cat1 && !string_list_contains_string(e->category, dae->dae_cat1)) + return 0; + if (dae->dae_cat2 && *dae->dae_cat2 && !string_list_contains_string(e->category, dae->dae_cat2)) + return 0; + if (dae->dae_cat3 && *dae->dae_cat3 && !string_list_contains_string(e->category, dae->dae_cat3)) + return 0; + } + if(dae->dae_start >= 0 && dae->dae_start_window >= 0 && dae->dae_start < 24*60 && dae->dae_start_window < 24*60) { struct tm a_time, ev_time; @@ -986,6 +999,16 @@ dvr_autorec_entry_class_btype_list ( void *o, const char *lang ) return strtab2htsmsg(tab, 1, lang); } +static htsmsg_t * +dvr_autorec_entry_category_list ( void *o, const char *lang ) +{ + htsmsg_t *m = htsmsg_create_map(); + htsmsg_add_str(m, "type", "api"); + htsmsg_add_str(m, "uri", "channelcategory/list"); + return m; +} + + static uint32_t dvr_autorec_entry_class_owner_opts(void *o, uint32_t opts) { @@ -999,6 +1022,28 @@ dvr_autorec_entry_class_owner_opts(void *o, uint32_t opts) CLASS_DOC(dvrautorec) PROP_DOC(duplicate_handling) +/* We provide several category drop-downs to make it easy for user + * to select several. So abstract the properties away since they + * are nearly identical for each entry. + */ +#define CATEGORY_SELECTION_PROP(NUM) \ + .type = PT_STR, \ + .id = "cat" #NUM, \ + .name = N_("Category " #NUM " (selection)"), \ + .desc = N_("The category of the program to look for. The xmltv "\ + "providers often supply detailed categories such as "\ + "Sitcom, Movie, Track/field, etc. " \ + "This let you select from categories for current programmes. " \ + "It is then combined (AND) with other fields to limit to " \ + "programmes that match all categories. " \ + "If this selection list is empty then it means your provider does not " \ + "supply programme categories." \ + ), \ + .off = offsetof(dvr_autorec_entry_t, dae_cat ## NUM), \ + .opts = PO_EXPERT, \ + .list = dvr_autorec_entry_category_list + + const idclass_t dvr_autorec_entry_class = { .ic_class = "dvrautorec", .ic_caption = N_("DVR - Auto-recording (Autorecs)"), @@ -1046,6 +1091,21 @@ const idclass_t dvr_autorec_entry_class = { .set = dvr_autorec_entry_class_title_set, .off = offsetof(dvr_autorec_entry_t, dae_title), }, + /* We provide a small number of selection drop-downs. This is to + * make it easier for users to see what categories are available and + * make it easy to record for example '"Movie" "Martial arts" "Western"' + * without user needing a regex, and allowing them to easily see what + * categories they have available. + */ + { + CATEGORY_SELECTION_PROP(1) + }, + { + CATEGORY_SELECTION_PROP(2) + }, + { + CATEGORY_SELECTION_PROP(3) + }, { .type = PT_BOOL, .id = "fulltext", diff --git a/src/epg.h b/src/epg.h index e775f0964..8fa15a9ba 100644 --- a/src/epg.h +++ b/src/epg.h @@ -527,7 +527,7 @@ struct epg_broadcast 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. + ///< Used with drop-down lists in the 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 diff --git a/src/string_list.c b/src/string_list.c index 09d3ff441..e87ad0d5a 100644 --- a/src/string_list.c +++ b/src/string_list.c @@ -172,3 +172,16 @@ string_list_copy(const string_list_t *src) return ret; } + +int +string_list_contains_string(const string_list_t *src, const char *find) +{ + string_list_item_t skel; + skel.id = (char*)find; + + string_list_item_t *item = RB_FIND(src, &skel, h_link, string_list_item_cmp); + /* Can't just return item due to compiler settings preventing ptr to + * int conversion + */ + return item != NULL; +} diff --git a/src/string_list.h b/src/string_list.h index 8657133ad..b2205b380 100644 --- a/src/string_list.h +++ b/src/string_list.h @@ -72,4 +72,8 @@ int string_list_cmp(const string_list_t *m1, const string_list_t *m2) /// Deep clone (shares no pointers, so have to string_list_destroy both. string_list_t *string_list_copy(const string_list_t *src) __attribute__((warn_unused_result)); + +/// Searching +int string_list_contains_string(const string_list_t *src, const char *find); + #endif diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index c9838e521..20853b48e 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -745,7 +745,7 @@ tvheadend.dvr_settings = function(panel, index) { tvheadend.autorec_editor = function(panel, index) { var list = 'name,title,fulltext,channel,start,start_window,weekdays,' + - 'record,tag,btype,content_type,minduration,maxduration,' + + 'record,tag,btype,content_type,cat1,cat2,cat3,minduration,maxduration,' + 'dedup,directory,config_name,comment'; var elist = 'enabled,start_extra,stop_extra,' + (tvheadend.accessUpdate.admin ? @@ -767,6 +767,9 @@ tvheadend.autorec_editor = function(panel, index) { tag: { width: 200 }, btype: { width: 50 }, content_type: { width: 100 }, + cat1: { width: 300 }, + cat2: { width: 300 }, + cat3: { width: 300 }, minduration: { width: 100 }, maxduration: { width: 100 }, weekdays: { width: 160 }, @@ -803,7 +806,7 @@ tvheadend.autorec_editor = function(panel, index) { }, del: true, list: 'enabled,name,title,fulltext,channel,tag,start,start_window,' + - 'weekdays,minduration,maxduration,btype,content_type,' + + 'weekdays,minduration,maxduration,btype,content_type,cat1,cat2,cat3' + 'pri,dedup,directory,config_name,owner,creator,comment', sort: { field: 'name',