WebUI: Add programme duration filtering
This commit is contained in:
parent
0c9e9b2461
commit
423617a426
7 changed files with 203 additions and 60 deletions
|
@ -130,7 +130,10 @@ api_epg_grid
|
|||
const char *ch, *tag, *title, *lang/*, *genre*/;
|
||||
uint32_t start, limit, end;
|
||||
htsmsg_t *l = NULL, *e;
|
||||
|
||||
//IH
|
||||
int min_duration;
|
||||
int max_duration;
|
||||
|
||||
*resp = htsmsg_create_map();
|
||||
|
||||
/* Query params */
|
||||
|
@ -141,13 +144,19 @@ api_epg_grid
|
|||
lang = htsmsg_get_str(args, "lang");
|
||||
// TODO: support multiple tag/genre/channel?
|
||||
|
||||
//IH
|
||||
min_duration = htsmsg_get_u32_or_default(args, "minduration", 0);
|
||||
max_duration = htsmsg_get_u32_or_default(args, "maxduration", INT_MAX);
|
||||
|
||||
/* Pagination settings */
|
||||
start = htsmsg_get_u32_or_default(args, "start", 0);
|
||||
limit = htsmsg_get_u32_or_default(args, "limit", 50);
|
||||
|
||||
/* Query the EPG */
|
||||
pthread_mutex_lock(&global_lock);
|
||||
epg_query(&eqr, ch, tag, NULL, /*genre,*/ title, lang);
|
||||
//IH
|
||||
// epg_query(&eqr, ch, tag, NULL, /*genre,*/ title, lang);
|
||||
epg_query(&eqr, ch, tag, NULL, /*genre,*/ title, lang, min_duration, max_duration);
|
||||
epg_query_sort(&eqr);
|
||||
// TODO: optional sorting
|
||||
|
||||
|
|
44
src/epg.c
44
src/epg.c
|
@ -2205,16 +2205,25 @@ htsmsg_t *epg_genres_list_all ( int major_only, int major_prefix )
|
|||
|
||||
static void _eqr_add
|
||||
( epg_query_result_t *eqr, epg_broadcast_t *e,
|
||||
epg_genre_t *genre, regex_t *preg, time_t start, const char *lang )
|
||||
//IH
|
||||
// epg_genre_t *genre, regex_t *preg, time_t start, const char *lang )
|
||||
epg_genre_t *genre, regex_t *preg, time_t start, const char *lang, int min_duration, int max_duration )
|
||||
{
|
||||
const char *title;
|
||||
int duration;
|
||||
|
||||
/* Ignore */
|
||||
if ( e->stop < start ) return;
|
||||
if ( !(title = epg_episode_get_title(e->episode, lang)) ) return;
|
||||
if ( genre && !epg_genre_list_contains(&e->episode->genre, genre, 1) ) return;
|
||||
if ( preg && regexec(preg, title, 0, NULL, 0)) return;
|
||||
|
||||
|
||||
//IH
|
||||
duration = (int)e->stop - (int)e->start;
|
||||
if ( duration < min_duration || duration > max_duration) return;
|
||||
tvhlog(LOG_INFO, "_eqr_add", "episode duration %d vs min_duration %d and max_duration %d", duration, min_duration, max_duration);
|
||||
//
|
||||
|
||||
/* More space */
|
||||
if ( eqr->eqr_entries == eqr->eqr_alloced ) {
|
||||
eqr->eqr_alloced = MAX(100, eqr->eqr_alloced * 2);
|
||||
|
@ -2228,17 +2237,23 @@ static void _eqr_add
|
|||
|
||||
static void _eqr_add_channel
|
||||
( epg_query_result_t *eqr, channel_t *ch, epg_genre_t *genre,
|
||||
regex_t *preg, time_t start, const char *lang )
|
||||
//IH
|
||||
// regex_t *preg, time_t start, const char *lang )
|
||||
regex_t *preg, time_t start, const char *lang, int min_duration, int max_duration )
|
||||
{
|
||||
epg_broadcast_t *ebc;
|
||||
RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) {
|
||||
if ( ebc->episode ) _eqr_add(eqr, ebc, genre, preg, start, lang);
|
||||
//IH
|
||||
// if ( ebc->episode ) _eqr_add(eqr, ebc, genre, preg, start, lang);
|
||||
if ( ebc->episode ) _eqr_add(eqr, ebc, genre, preg, start, lang, min_duration, max_duration);
|
||||
}
|
||||
}
|
||||
|
||||
void epg_query0
|
||||
( epg_query_result_t *eqr, channel_t *channel, channel_tag_t *tag,
|
||||
epg_genre_t *genre, const char *title, const char *lang )
|
||||
//IH
|
||||
// epg_genre_t *genre, const char *title, const char *lang )
|
||||
epg_genre_t *genre, const char *title, const char *lang, int min_duration, int max_duration )
|
||||
{
|
||||
time_t now;
|
||||
channel_tag_mapping_t *ctm;
|
||||
|
@ -2259,19 +2274,25 @@ void epg_query0
|
|||
|
||||
/* Single channel */
|
||||
if (channel && !tag) {
|
||||
_eqr_add_channel(eqr, channel, genre, preg, now, lang);
|
||||
//IH
|
||||
// _eqr_add_channel(eqr, channel, genre, preg, now, lang);
|
||||
_eqr_add_channel(eqr, channel, genre, preg, now, lang, min_duration, max_duration);
|
||||
|
||||
/* Tag based */
|
||||
} else if ( tag ) {
|
||||
LIST_FOREACH(ctm, &tag->ct_ctms, ctm_tag_link) {
|
||||
if(channel == NULL || ctm->ctm_channel == channel)
|
||||
_eqr_add_channel(eqr, ctm->ctm_channel, genre, preg, now, lang);
|
||||
//IH
|
||||
// _eqr_add_channel(eqr, ctm->ctm_channel, genre, preg, now, lang);
|
||||
_eqr_add_channel(eqr, ctm->ctm_channel, genre, preg, now, lang, min_duration, max_duration);
|
||||
}
|
||||
|
||||
/* All channels */
|
||||
} else {
|
||||
CHANNEL_FOREACH(channel)
|
||||
_eqr_add_channel(eqr, channel, genre, preg, now, lang);
|
||||
//IH
|
||||
// _eqr_add_channel(eqr, channel, genre, preg, now, lang);
|
||||
_eqr_add_channel(eqr, channel, genre, preg, now, lang, min_duration, max_duration);
|
||||
}
|
||||
if (preg) regfree(preg);
|
||||
|
||||
|
@ -2279,11 +2300,14 @@ void epg_query0
|
|||
}
|
||||
|
||||
void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
|
||||
epg_genre_t *genre, const char *title, const char *lang)
|
||||
//IH
|
||||
// epg_genre_t *genre, const char *title, const char *lang)
|
||||
epg_genre_t *genre, const char *title, const char *lang, int min_duration, int max_duration)
|
||||
{
|
||||
channel_t *ch = channel ? channel_find(channel) : NULL;
|
||||
channel_tag_t *ct = tag ? channel_tag_find_by_name(tag, 0) : NULL;
|
||||
epg_query0(eqr, ch, ct, genre, title, lang);
|
||||
|
||||
epg_query0(eqr, ch, ct, genre, title, lang, min_duration, max_duration);
|
||||
}
|
||||
|
||||
void epg_query_free(epg_query_result_t *eqr)
|
||||
|
|
|
@ -544,11 +544,19 @@ void epg_query_free(epg_query_result_t *eqr);
|
|||
void epg_query_sort(epg_query_result_t *eqr);
|
||||
|
||||
/* Query routines */
|
||||
//IH
|
||||
/*
|
||||
void epg_query0(epg_query_result_t *eqr, struct channel *ch,
|
||||
struct channel_tag *ct, epg_genre_t *genre, const char *title,
|
||||
const char *lang);
|
||||
void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
|
||||
epg_genre_t *genre, const char *title, const char *lang);
|
||||
*/
|
||||
void epg_query0(epg_query_result_t *eqr, struct channel *ch,
|
||||
struct channel_tag *ct, epg_genre_t *genre, const char *title,
|
||||
const char *lang, int min_duration, int max_duration);
|
||||
void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
|
||||
epg_genre_t *genre, const char *title, const char *lang, int min_duration, int max_duration);
|
||||
|
||||
|
||||
/* ************************************************************************
|
||||
|
|
|
@ -57,6 +57,8 @@
|
|||
#include <sys/statvfs.h>
|
||||
#include "settings.h"
|
||||
#include <sys/time.h>
|
||||
//IH
|
||||
#include <limits.h>
|
||||
|
||||
/* **************************************************************************
|
||||
* Datatypes and variables
|
||||
|
@ -1059,7 +1061,10 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
|
|||
epg_query_result_t eqr;
|
||||
epg_genre_t genre, *eg = NULL;
|
||||
const char *lang;
|
||||
|
||||
//IH
|
||||
int min_duration;
|
||||
int max_duration;
|
||||
|
||||
/* Required */
|
||||
if( (query = htsmsg_get_str(in, "query")) == NULL )
|
||||
return htsp_error("Missing argument 'query'");
|
||||
|
@ -1079,12 +1084,20 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
|
|||
lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
|
||||
full = htsmsg_get_u32_or_default(in, "full", 0);
|
||||
|
||||
//IH
|
||||
min_duration = htsmsg_get_u32_or_default(in, "minduration", 0);
|
||||
max_duration = htsmsg_get_u32_or_default(in, "maxduration", INT_MAX);
|
||||
tvhlog(LOG_INFO, "htsp_server", "min_duration %d and max_duration %d", min_duration, max_duration);
|
||||
//
|
||||
|
||||
/* Check access */
|
||||
if (!htsp_user_access_channel(htsp, ch))
|
||||
return htsp_error("User does not have access");
|
||||
|
||||
//do the query
|
||||
epg_query0(&eqr, ch, ct, eg, query, lang);
|
||||
//IH
|
||||
// epg_query0(&eqr, ch, ct, eg, query, lang);
|
||||
epg_query0(&eqr, ch, ct, eg, query, lang, min_duration, max_duration);
|
||||
|
||||
// create reply
|
||||
out = htsmsg_create_map();
|
||||
|
|
|
@ -758,10 +758,27 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
|
|||
const char *tag = http_arg_get(&hc->hc_req_args, "tag");
|
||||
const char *title = http_arg_get(&hc->hc_req_args, "title");
|
||||
const char *lang = http_arg_get(&hc->hc_args, "Accept-Language");
|
||||
|
||||
//IH
|
||||
int min_duration;
|
||||
int max_duration;
|
||||
//
|
||||
|
||||
if(channel && !channel[0]) channel = NULL;
|
||||
if(tag && !tag[0]) tag = NULL;
|
||||
|
||||
//IH
|
||||
if((s = http_arg_get(&hc->hc_req_args, "minduration")) != NULL)
|
||||
min_duration = atoi(s);
|
||||
else
|
||||
min_duration = 0;
|
||||
|
||||
if((s = http_arg_get(&hc->hc_req_args, "maxduration")) != NULL)
|
||||
max_duration = atoi(s);
|
||||
else
|
||||
max_duration = INT_MAX;
|
||||
//
|
||||
|
||||
if((s = http_arg_get(&hc->hc_req_args, "start")) != NULL)
|
||||
start = atoi(s);
|
||||
|
||||
|
@ -780,7 +797,7 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
|
|||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
epg_query(&eqr, channel, tag, eg, title, lang);
|
||||
epg_query(&eqr, channel, tag, eg, title, lang, min_duration, max_duration);
|
||||
|
||||
epg_query_sort(&eqr);
|
||||
|
||||
|
|
|
@ -87,8 +87,11 @@ page_simple(http_connection_t *hc,
|
|||
|
||||
|
||||
if(s != NULL) {
|
||||
|
||||
epg_query(&eqr, NULL, NULL, NULL, s, lang);
|
||||
|
||||
//IH
|
||||
// epg_query(&eqr, NULL, NULL, NULL, s, lang);
|
||||
//IH Force all programme durations for this interface (0 to INT_MAX seconds)
|
||||
epg_query(&eqr, NULL, NULL, NULL, s, lang, 0, INT_MAX);
|
||||
epg_query_sort(&eqr);
|
||||
|
||||
c = eqr.eqr_entries;
|
||||
|
|
|
@ -332,12 +332,6 @@ tvheadend.epg = function() {
|
|||
header: "Duration",
|
||||
dataIndex: 'duration',
|
||||
renderer: renderDuration
|
||||
//IH
|
||||
}, {
|
||||
width: 100,
|
||||
id: 'duration',
|
||||
header: "Duration",
|
||||
dataIndex: 'duration'
|
||||
}, {
|
||||
width: 250,
|
||||
id: 'channel',
|
||||
|
@ -414,36 +408,74 @@ tvheadend.epg = function() {
|
|||
emptyText: 'Filter content type...'
|
||||
});
|
||||
|
||||
//IH
|
||||
var epgFilterDuration = new Ext.form.ComboBox({
|
||||
loadingText: 'Loading...',
|
||||
width: 150,
|
||||
displayField: 'label',
|
||||
store: new Ext.data.ArrayStore({
|
||||
fields: ['label','value'],
|
||||
data: [['Short (< 30m)', 30],['Medium (30m - 90m)', 60],['Long (> 90m)', 21600]]
|
||||
}),
|
||||
mode: 'local',
|
||||
editable: true,
|
||||
forceSelection: true,
|
||||
triggerAction: 'all',
|
||||
emptyText: 'Filter duration...'
|
||||
//IH
|
||||
// Slider for duration selection, including tooltip function to display the appropriate string
|
||||
|
||||
var tip = new Ext.slider.Tip({
|
||||
getText: function(thumb){
|
||||
return thumb.slider.durations[thumb.value];
|
||||
}
|
||||
});
|
||||
|
||||
var durationSlider = new Ext.slider.MultiSlider({
|
||||
width: 100,
|
||||
values: [0, 7],
|
||||
minValue: 0,
|
||||
maxValue: 7,
|
||||
increment: 1,
|
||||
durations: {
|
||||
0: '0 min',
|
||||
1: '20 min',
|
||||
2: '45 min',
|
||||
3: '90 min',
|
||||
4: '3 hrs',
|
||||
5: '6 hrs',
|
||||
6: '12 hrs',
|
||||
7: '24 hrs'
|
||||
},
|
||||
seconds: {
|
||||
0: 0,
|
||||
1: 1200,
|
||||
2: 2700,
|
||||
3: 5400,
|
||||
4: 10800,
|
||||
5: 21600,
|
||||
6: 43200,
|
||||
7: 86400
|
||||
},
|
||||
plugins: tip,
|
||||
listeners: {
|
||||
change: function(slider, thumb, value){
|
||||
// setduration(slider, thumb, value);
|
||||
setduration(slider);
|
||||
}}
|
||||
});
|
||||
//
|
||||
|
||||
function epgQueryClear() {
|
||||
delete epgStore.baseParams.channel;
|
||||
delete epgStore.baseParams.tag;
|
||||
delete epgStore.baseParams.contenttype;
|
||||
//IH
|
||||
delete epgStore.baseParams.contenttypestring;
|
||||
//
|
||||
delete epgStore.baseParams.title;
|
||||
//IH
|
||||
delete epgStore.baseParams.duration;
|
||||
delete epgStore.baseParams.minduration;
|
||||
delete epgStore.baseParams.mindurationstring;
|
||||
delete epgStore.baseParams.maxduration;
|
||||
delete epgStore.baseParams.maxdurationstring;
|
||||
//
|
||||
|
||||
epgFilterChannels.setValue("");
|
||||
epgFilterChannelTags.setValue("");
|
||||
epgFilterContentGroup.setValue("");
|
||||
epgFilterTitle.setValue("");
|
||||
|
||||
//IH
|
||||
epgFilterDuration.setValue("");
|
||||
durationSlider.setValue(0,0);
|
||||
durationSlider.setValue(1,7);
|
||||
//
|
||||
|
||||
epgStore.reload();
|
||||
}
|
||||
|
@ -465,19 +497,34 @@ tvheadend.epg = function() {
|
|||
epgFilterContentGroup.on('select', function(c, r) {
|
||||
if (epgStore.baseParams.contenttype !== r.data.code) {
|
||||
epgStore.baseParams.contenttype = r.data.code;
|
||||
//IH
|
||||
epgStore.baseParams.contenttypestring = tvheadend.contentGroupLookupName(r.data.code);
|
||||
console.log('contentype is',epgStore.baseParams.contenttype,'contenttypestring is',epgStore.baseParams.contenttypestring);
|
||||
//
|
||||
epgStore.reload();
|
||||
}
|
||||
});
|
||||
|
||||
//IH ------------------
|
||||
epgFilterDuration.on('select', function(c, r) {
|
||||
if (epgStore.baseParams.duration !== r.data.value) {
|
||||
console.log('Duration filter triggered with value',r.data.value);
|
||||
console.log('epgStore.baseParams.duration was ',epgStore.baseParams.duration);
|
||||
epgStore.baseParams.duration = r.data.value;
|
||||
console.log('epgStore.baseParams.duration is ',epgStore.baseParams.duration);
|
||||
}
|
||||
});
|
||||
// setduration = function(slider, thumb, value) {
|
||||
setduration = function(slider) {
|
||||
|
||||
console.log('durationSlider fired');
|
||||
|
||||
var min = slider.getValue(0);
|
||||
var max = slider.getValue(1);
|
||||
|
||||
console.log('Min:', min, "Duration:", slider.durations[min], slider.seconds[min]);
|
||||
console.log('Max:', max, "Duration:", slider.durations[max], slider.seconds[max]);
|
||||
|
||||
epgStore.baseParams.minduration = slider.seconds[min];
|
||||
epgStore.baseParams.mindurationstring = slider.durations[min];
|
||||
epgStore.baseParams.maxduration = slider.seconds[max];
|
||||
epgStore.baseParams.maxdurationstring = slider.durations[max];
|
||||
|
||||
epgStore.reload();
|
||||
};
|
||||
//
|
||||
|
||||
epgFilterTitle.on('valid', function(c) {
|
||||
var value = c.getValue();
|
||||
|
@ -519,8 +566,10 @@ tvheadend.epg = function() {
|
|||
epgFilterContentGroup,
|
||||
'-',
|
||||
//IH
|
||||
epgFilterDuration,
|
||||
'-',
|
||||
'Duration (test env): 0h ',
|
||||
durationSlider,
|
||||
'24h','-',
|
||||
//
|
||||
{
|
||||
text: 'Reset',
|
||||
handler: epgQueryClear
|
||||
|
@ -567,26 +616,46 @@ tvheadend.epg = function() {
|
|||
: "<i>Don't care</i>";
|
||||
var tag = epgStore.baseParams.tag ? epgStore.baseParams.tag
|
||||
: "<i>Don't care</i>";
|
||||
var contenttype = epgStore.baseParams.contenttype ? epgStore.baseParams.contenttype
|
||||
//IH - correct to display content type as a string as opposed to the underlying (lookup) code
|
||||
// var contenttype = epgStore.baseParams.contenttype ? epgStore.baseParams.contenttype
|
||||
var contenttype = epgStore.baseParams.contenttypestring ? epgStore.baseParams.contenttypestring
|
||||
//
|
||||
: "<i>Don't care</i>";
|
||||
//IH
|
||||
var minduration = epgStore.baseParams.mindurationstring ? epgStore.baseParams.mindurationstring
|
||||
: "<i>Don't care</i>";
|
||||
var maxduration = epgStore.baseParams.maxdurationstring ? epgStore.baseParams.maxdurationstring
|
||||
: "<i>Don't care</i>";
|
||||
//
|
||||
|
||||
Ext.MessageBox.confirm('Auto Recorder',
|
||||
'This will create an automatic rule that '
|
||||
Ext.MessageBox.confirm('Auto Recorder', 'This will create an automatic rule that '
|
||||
+ 'continuously scans the EPG for programmes '
|
||||
+ 'to record that matches this query: ' + '<br><br>'
|
||||
//IH - correct typo
|
||||
+ 'to record that match this query: ' + '<br><br>'
|
||||
+ '<div class="x-smallhdr">Title:</div>' + title + '<br>'
|
||||
+ '<div class="x-smallhdr">Channel:</div>' + channel + '<br>'
|
||||
+ '<div class="x-smallhdr">Tag:</div>' + tag + '<br>'
|
||||
+ '<div class="x-smallhdr">Genre:</div>' + contenttype + '<br>'
|
||||
+ '<br>' + 'Currently this will match (and record) '
|
||||
//IH
|
||||
+ '<div class="x-smallhdr">Min duration:</div>' + minduration + '<br>'
|
||||
+ '<div class="x-smallhdr">Max duration:</div>' + maxduration + '<br>'
|
||||
//
|
||||
+ '<br><br>' + 'Currently this will match (and record) '
|
||||
+ epgStore.getTotalCount() + ' events. ' + 'Are you sure?',
|
||||
function(button) {
|
||||
if (button === 'no')
|
||||
return;
|
||||
createAutoRec2(epgStore.baseParams);
|
||||
});
|
||||
function(button) {
|
||||
if (button === 'no')
|
||||
return;
|
||||
createAutoRec2(epgStore.baseParams);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// IH: TO DO
|
||||
//
|
||||
// Check that contenttype is still passed correctly (epgStore.baseParams.contenttype) to autorec vs the new contenttypestring
|
||||
// Add min/max to autorec rules (C)
|
||||
// Check they're displayed (js)
|
||||
//
|
||||
function createAutoRec2(params) {
|
||||
/* Really do it */
|
||||
params.op = 'createAutoRec';
|
||||
|
@ -597,4 +666,4 @@ tvheadend.epg = function() {
|
|||
}
|
||||
|
||||
return panel;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue