Merge pull request #408 from ProfYaffle/durationfilter

Concept of filtering EPG on duration
This commit is contained in:
perexg 2014-07-07 20:39:17 +02:00
commit 018baf54e1
23 changed files with 503 additions and 159 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/docresources/epg3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -1,5 +1,5 @@
<div class="hts-doc-text">
The 'Automatic Recorder' is used to create rules that will trig
The 'Automatic Recorder' is used to create rules that will trigger
automatic recording of events. You can use this to record you favorite
TV show(s), record all movies on a specific channel, etc.
<p>
@ -62,6 +62,10 @@ The columns have the following functions:
<dd>
Only match events belonging to the given content group.
<dt>Duration
<dd>
Only match events that fall within the specified duration range.
<dt>Weekdays
<dd>
Only record events if they occur on one of these days. By default all days

View file

@ -1,27 +1,45 @@
<div class="hts-doc-text">
The DVR log is split into a series of paged grids:
<p></p>
<ul>
<li>Upcoming Recordings - stuff scheduled to be recorded in the future
<li>Finished Recordings - stuff that has succesfully finished recording
<li>Failed Recordings - stuff that failed to record
</ul>
<li><b>Upcoming Recordings</b> - events that are scheduled to be recorded in the future</li>
<img src="docresources/dvrlog.png">
<li><b>Finished Recordings</b> - events that have succesfully finished recording</li>
<img src="docresources/dvrlog2.png">
<p>
Use the bottom toolbar (not displayed in this manual) to navigate
between pages in the grid.
<img src="docresources/dvrlog.png">
Once the recording is completed there will be a clickable link to a playlist
for the recorded file (XSPF or M3U as per your startup options) so you can watch
it.</p>
<li><b>Failed Recordings</b> - events that failed to record</li>
<img src="docresources/dvrlog3.png">
</ul>
<p>
Use the bottom toolbar to navigate between pages in the grid.</p>
<img src="docresources/dvrlog4.png">
<p>Note that the columns are sortable, but only the current view will be sorted (by
default, this will be the first page of the most recent events). Select more events
per page if you want to sort a longer selection.</p>
<p>
To see more details about a recorded event, just click on it and a pop
up will appear:
<p>
up will appear:</p>
<p></p>
<img src="docresources/dvrlogentry.png">
<p></p>
<p>
In this pop up you can cancel a scheduled recording or abort a
recording in progress. To close the pop up, just close it with the
[X] window button.
<p>
Once the recording is completed there will be a clickable link to the
recorded matroska file so you can download it directly from the
interface.
[X] window button.</p>
</div>

View file

@ -1,95 +1,130 @@
<div class="hts-doc-text">
Tvheadend has a built in Electronic Program Guide. The EPG is an in memory
<p>Tvheadend has a built-in Electronic Program Guide. The EPG is an in-memory
database populated with all the information about events received from
the DVB networks or from XMLTV.
<p>
The EPG tab displays a filterable paged grid containing all the events
sorted based on start time.
the DVB networks over-the-air or from external grabbers such as XMLTV.</p>
<p></p>
<p>The EPG tab displays a filterable grid containing all events,
sorted based on start time.</p>
<img src="docresources/epg.png">
<p>
<dl>
<dt>Filtering (or searching)
<dd>
In the EPG top tool bar you can access four input fields.
<p></p>
<hr>
<b>Filtering (or searching)</b>
<hr>
<p>In the EPG top tool bar you can access five input fields.
These are used to filter/search for events. The form uses implicit AND
between the input fields. This means that all filters must match
for an event to be displayed.
for an event to be displayed.</p>
<dl>
<dt>[Search title...]
<dd>
Filter on the event title. The filter uses case insensitive regular
expression. If you don't know what a regular expression is this means
that you can type just parts of the title and filter on that too.
(No need for exact matching).
<dt>[Filter channel...]
Only display events that match the given title. The filter uses case-insensitive
regular expressions. If you don't know what a regular expression is, this simply
means that you can type just parts of the title and filter on that - there's no need
for full, exact matching.</dd>
<dt>[Filter channel...]</dt>
<dd>
Only display events from the selected channel. Channels in the drop down are
ordered by channel number and can be filtered (by name) by typing in the box.
<dt>[Filter tag...]
ordered by channel number and can be filtered (by name) by typing in the box.</dd>
<dt>[Filter tag...]</dt>
<dd>
Only display events from the channels which are included in the selected tag.
Tags are used for grouping channels and is configured by the administrator.
<dt>[Filter content type...]
Only display events from channels which are included in the selected tag.
Tags are used for grouping channels together - such as 'Radio' or 'HDTV' - and are
configured by the administrator. You can start typing a tag name to filter the list.</dd>
<dt>[Filter content type...]</dt>
<dd>
Most DVB networks classify their events into content groups. This field
allows you to filter based on content type.
Only display events that match the given content type tag. Most DVB networks
classify their events into content groups. This field allows you to filter based
on content type (e.g. "Sports" or "Game Show"). Supported tags are determined by
your broadcaster. Again, simply start typing to filter the entries if you have a
long list to choose from.</dd>
<dt>[Filter duration...]</dt>
<dd>
Only display events that fall between the given minimum and maximum durations.
This allows you to filter for or against, say, a daily broadcast and a weekly omnibus
edition of a programme, or only look for short news bulletins and not the 24-hour
rolling broadcasts.</dd>
<dd>&nbsp;</dd>
<dd>Options are:</dd>
<table class="hts-doc-text" border="0">
<tr><td>00:00:01 to 00:15:00 - for very short news bulletins, children's programmes, etc.</td></tr>
<tr><td>00:15:01 to 00:30:00 - for short programmes, e.g. daily soap operas</td></tr>
<tr><td>00:30:01 to 01:30:00 - for medium-length programmes, e.g. documentaries</td></tr>
<tr><td>01:30:01 to 03:00:00 - for longer programmes, e.g. films</td></tr>
<tr><td>03:00:00 to no maximum - for very long programmes, e.g. major sporting events</td></tr>
</table>
</dl>
Thus, if you only would like to browse Movies from your HD-channels you
would select 'HDTV' in the [Filter tag...]-field, and select
'Movie / Drama' in the [Only include content...]-field.
<p>
Notice that you don't have to press a 'Search' button, the grid immediately
updates itself as you change the filters.
<p>
If you would like to clear all filters, just press the [Reset] button.
<dt>Paging
<p>So, if you only want to see Movies from your available HD channels, you
would select 'HDTV' in the <i>[Filter tag...]</i> field, and select
'Movie / Drama' in the <i>[Filter content type...]</i> field. If you wish, you
could then further limit the search to programmes of between 90 minutes and 3 hours by
selecting '01:30:01 to 03:00:00' in the <i>[Filter duration...]</i> field.</p>
<p></p>
<p>Note that you don't have to press a 'Search' button: the grid immediately
updates itself as you change the filters.</p>
<p></p>
<p>You can clear an individual filter by simply deleting its contents, or by selecting
<i>'(Clear filter)'</i> as appropriate on all except the title filter. If you want to
clear all filters, just press the <i>[Reset All]</i> button.</p>
<br>&nbsp;</br>
<dd>
In large installations with many channels and full EPG feed there could be
tens of thousands of events in the database.
Therefore the EPG display employs a paging bar at the bottom of the grid.
Use it to browse backwards and forwards in the EPG. It also displays the
total amount of events matched by the current query.
<hr>
<b>Event details and recording</b>
<hr>
<dt>Event details and recording
<dd>
If you click on a single event, a popup will display detailed information
about the event. It also allows the user to schedule the event for recording
by clicking on the [Record program] button.
<p>If you click on a single event, a popup will display detailed information
about the event. It also allows you to schedule the event for recording
by clicking on the <i>[Record program]</i> button.</p>
<p>
For EPG providers that supply series link information there will also be a
[Record series] button that will record all entries in the series.
<i>[Record series]</i> button that will record all entries in the series.</p>
<p>
For events without any series link information, a [Autorec] button will be
provided to create a pseudo series link using the Autorec feature.
<p>
<img src="docresources/epg2.png">
<p>
To close the popup, just close it with the [X] window button.
The popup is not modal and you can open as many detailed information popups
as you want.
For events without any series link information, an <i>[Autorec]</i> button will be
provided to create a pseudo-series link using the autorec feature.</p>
<p></p>
<img src="docresources/epg2.png"> <img src="docresources/epg3.png">
<p></p>
<p>If you schedule any kind of recording from this point, you can choose a specific
DVR profile that will apply to the recording or autorec rule. This will normally show
as <i>(default)</i>, but you can define different profiles in the <b>Configuration ->
Recording -> Digital Video Recorder</b> tab. This allows you to set, for example, more post-
broadcast padding for a channel that always runs late, or perhaps define a different
post-processing command to strip adverts out on a commercial channel.</p>
<p>You will also see a <i>Search IMDB</i> link to look for the programme by name on
imdb.com, and a <i>Play</i> link to watch a programme that's already in progress. This
second link downloads a playlist file (XSPF or M3U depending on your startup options);
if your system is configured for it, this will automatically launch an appropriate
player, otherwise you will need to manually open the playlist to start watching (normally a
double-click on the downloaded file).</p>
<p></p>
<p>To close the popup, just click on the [X] window button.
The popup isn't modal, so you don't have to close it before doing something else,
and you can open as many detailed information popups as you want.</p>
<br>&nbsp;</br>
<dt>Autorecordings
<dd>
Should you wish to record all events matching a specific query. (Record
your favorite TV-show, etc) you can press the [Create Autorec] button
in the top toolbar.
<p>
A popup with details about the to-be-created autorecording rule needs to
be confirmed before the rule takes effect.
<p>
<hr>
<b>Autorecordings</b>
<hr>
<p>Should you wish to record all events matching a specific query (to record
your favourite show every week, for example) you can press the <i>[Create AutoRec]</i>
button in the top toolbar.</p>
<p></p>
<p>A popup with details about the to-be-created autorecording rule needs to
be confirmed before the rule takes effect.</p>
<p></p>
<img src="docresources/autorecpopup.png">
<p>
The autorecordings can later be changed/deleted in under the
'Digital Video Recorder'-tag. Use that editor if you temporary want
to disable an autorecording or make adjustments, etc.
<p></p>
<p>You can change or delete the autorec rules in the
<b>Digital Video Recorder</b> tab. Use that editor if you temporarily want
to disable an autorecording or make adjustments to the channel, tag, or similar.</p>
<br>&nbsp;</br>
<dt>Watch TV
<dd>
If you want to watch live TV in the web UI, the [Watch TV] button will pop-up
<hr>
<b>Watch TV</b>
<hr>
<p>If you want to watch live TV in the web UI, the <i>[Watch TV]</i> button will pop up
a VLC plugin window (if you don't have the plugin installed a direct URL should be
provided to load into your preferred media player).
</dl>
provided to load into your preferred media player).</p>
</div>

View file

@ -130,6 +130,8 @@ api_epg_grid
const char *ch, *tag, *title, *lang/*, *genre*/;
uint32_t start, limit, end;
htsmsg_t *l = NULL, *e;
int min_duration;
int max_duration;
*resp = htsmsg_create_map();
@ -141,13 +143,16 @@ api_epg_grid
lang = htsmsg_get_str(args, "lang");
// TODO: support multiple tag/genre/channel?
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);
epg_query(&eqr, ch, tag, NULL, /*genre,*/ title, lang, min_duration, max_duration);
epg_query_sort(&eqr);
// TODO: optional sorting

View file

@ -247,6 +247,8 @@ typedef struct dvr_autorec_entry {
epg_serieslink_t *dae_serieslink;
epg_episode_num_t dae_epnum;
int dae_minduration;
int dae_maxduration;
} dvr_autorec_entry_t;
@ -386,12 +388,13 @@ int dvr_sort_start_ascending(const void *A, const void *B);
*/
void dvr_autorec_add(const char *dvr_config_name,
const char *title, const char *channel,
const char *tag, epg_genre_t *content_type,
const char *creator, const char *comment);
const char *tag, epg_genre_t *content_type,
const int min_duration, const int max_duration,
const char *creator, const char *comment);
void dvr_autorec_add_series_link(const char *dvr_config_name,
epg_broadcast_t *event,
const char *creator, const char *comment);
const char *creator, const char *comment);
void dvr_autorec_check_event(epg_broadcast_t *e);
void dvr_autorec_check_brand(epg_brand_t *b);

View file

@ -26,6 +26,7 @@
#include <stdarg.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include "tvheadend.h"
#include "settings.h"
@ -70,6 +71,7 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
{
channel_tag_mapping_t *ctm;
dvr_config_t *cfg;
double duration;
if (!e->channel) return 0;
if (!e->episode) return 0;
@ -83,6 +85,8 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
dae->dae_title[0] == '\0') &&
dae->dae_brand == NULL &&
dae->dae_season == NULL &&
&dae->dae_minduration == NULL &&
&dae->dae_maxduration == NULL &&
dae->dae_serieslink == NULL)
return 0; // Avoid super wildcard match
@ -136,6 +140,16 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
return 0;
}
duration = difftime(e->stop,e->start);
if(dae->dae_minduration) {
if(duration < dae->dae_minduration) return 0;
}
if(dae->dae_maxduration) {
if(duration > dae->dae_maxduration) return 0;
}
if(dae->dae_weekdays != 0x7f) {
struct tm tm;
localtime_r(&e->start, &tm);
@ -283,6 +297,11 @@ autorec_record_build(dvr_autorec_entry_t *dae)
build_weekday_tags(l, dae->dae_weekdays);
htsmsg_add_msg(e, "weekdays", l);
if (dae->dae_minduration)
htsmsg_add_u32(e, "minduration", dae->dae_minduration);
if (dae->dae_maxduration)
htsmsg_add_u32(e, "maxduration", dae->dae_maxduration);
htsmsg_add_str(e, "pri", dvr_val2pri(dae->dae_pri));
if (dae->dae_brand)
@ -407,6 +426,12 @@ autorec_record_update(void *opaque, const char *id, htsmsg_t *values,
}
}
if(!htsmsg_get_u32(values, "minduration", &u32))
dae->dae_minduration = u32;
if(!htsmsg_get_u32(values, "maxduration", &u32))
dae->dae_maxduration = u32;
if((l = htsmsg_get_list(values, "weekdays")) != NULL)
dae->dae_weekdays = build_weekday_mask(l);
@ -506,7 +531,8 @@ dvr_autorec_update(void)
static void
_dvr_autorec_add(const char *config_name,
const char *title, channel_t *ch,
const char *tag, epg_genre_t *content_type,
const char *tag, epg_genre_t *content_type,
const int min_duration, const int max_duration,
epg_brand_t *brand, epg_season_t *season,
epg_serieslink_t *serieslink,
int approx_time, epg_episode_num_t *epnum,
@ -543,6 +569,12 @@ _dvr_autorec_add(const char *config_name,
if (content_type)
dae->dae_content_type.code = content_type->code;
if (min_duration)
dae->dae_minduration = min_duration;
if (max_duration)
dae->dae_maxduration = max_duration;
if(serieslink) {
serieslink->getref(serieslink);
dae->dae_serieslink = serieslink;
@ -564,12 +596,14 @@ _dvr_autorec_add(const char *config_name,
void
dvr_autorec_add(const char *config_name,
const char *title, const char *channel,
const char *tag, epg_genre_t *content_type,
const char *creator, const char *comment)
const char *tag, epg_genre_t *content_type,
const int min_duration, const int max_duration,
const char *creator, const char *comment)
{
channel_t *ch = NULL;
if(channel != NULL) ch = channel_find(channel);
_dvr_autorec_add(config_name, title, ch, tag, content_type,
min_duration, max_duration,
NULL, NULL, NULL, 0, NULL, creator, comment);
}
@ -584,6 +618,7 @@ void dvr_autorec_add_series_link
title,
event->channel,
NULL, 0, // tag/content type
0,INT_MAX,
NULL,
NULL,
event->serieslink,

View file

@ -23,6 +23,7 @@
#include <regex.h>
#include <assert.h>
#include <inttypes.h>
#include <time.h>
#include "tvheadend.h"
#include "queue.h"
@ -2205,9 +2206,10 @@ 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 )
epg_genre_t *genre, regex_t *preg, time_t start, const char *lang, int min_duration, int max_duration )
{
const char *title;
double duration;
/* Ignore */
if ( e->stop < start ) return;
@ -2215,6 +2217,9 @@ static void _eqr_add
if ( genre && !epg_genre_list_contains(&e->episode->genre, genre, 1) ) return;
if ( preg && regexec(preg, title, 0, NULL, 0)) return;
duration = difftime(e->stop,e->start);
if ( duration < min_duration || duration > max_duration ) return;
/* More space */
if ( eqr->eqr_entries == eqr->eqr_alloced ) {
eqr->eqr_alloced = MAX(100, eqr->eqr_alloced * 2);
@ -2228,17 +2233,17 @@ 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 )
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);
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 )
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 +2264,19 @@ void epg_query0
/* Single channel */
if (channel && !tag) {
_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);
_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);
_eqr_add_channel(eqr, channel, genre, preg, now, lang, min_duration, max_duration);
}
if (preg) regfree(preg);
@ -2279,11 +2284,12 @@ 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)
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)

View file

@ -546,9 +546,9 @@ void epg_query_sort(epg_query_result_t *eqr);
/* Query routines */
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);
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);
epg_genre_t *genre, const char *title, const char *lang, int min_duration, int max_duration);
/* ************************************************************************

View file

@ -57,6 +57,7 @@
#include <sys/statvfs.h>
#include "settings.h"
#include <sys/time.h>
#include <limits.h>
/* **************************************************************************
* Datatypes and variables
@ -1059,7 +1060,9 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
epg_query_result_t eqr;
epg_genre_t genre, *eg = NULL;
const char *lang;
int min_duration;
int max_duration;
/* Required */
if( (query = htsmsg_get_str(in, "query")) == NULL )
return htsp_error("Missing argument 'query'");
@ -1079,12 +1082,16 @@ 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);
min_duration = htsmsg_get_u32_or_default(in, "minduration", 0);
max_duration = htsmsg_get_u32_or_default(in, "maxduration", INT_MAX);
tvhtrace("htsp", "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);
epg_query0(&eqr, ch, ct, eg, query, lang, min_duration, max_duration);
// create reply
out = htsmsg_create_map();

View file

@ -759,9 +759,22 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
const char *title = http_arg_get(&hc->hc_req_args, "title");
const char *lang = http_arg_get(&hc->hc_args, "Accept-Language");
int min_duration;
int max_duration;
if(channel && !channel[0]) channel = NULL;
if(tag && !tag[0]) tag = NULL;
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 +793,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);
@ -1117,18 +1130,31 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque)
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "createAutoRec")) {
int min_duration;
int max_duration;
epg_genre_t genre, *eg = NULL;
if ((s = http_arg_get(&hc->hc_req_args, "contenttype"))) {
genre.code = atoi(s);
eg = &genre;
}
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;
dvr_autorec_add(http_arg_get(&hc->hc_req_args, "config_name"),
http_arg_get(&hc->hc_req_args, "title"),
http_arg_get(&hc->hc_req_args, "channel"),
http_arg_get(&hc->hc_req_args, "tag"),
eg,
hc->hc_representative, "Created from EPG query");
http_arg_get(&hc->hc_req_args, "channel"),
http_arg_get(&hc->hc_req_args, "tag"),
eg, min_duration,max_duration,
hc->hc_representative, "Created from EPG query");
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 1);

View file

@ -87,8 +87,9 @@ page_simple(http_connection_t *hc,
if(s != NULL) {
epg_query(&eqr, NULL, NULL, NULL, s, lang);
//Note: force min/max durations for this interface to 0 and INT_MAX seconds respectively
epg_query(&eqr, NULL, NULL, NULL, s, lang, 0, INT_MAX);
epg_query_sort(&eqr);
c = eqr.eqr_entries;

View file

@ -1,6 +1,11 @@
/**
* Channel tags
*/
insertChannelTagsClearOption = function( scope, records, options ){
var placeholder = Ext.data.Record.create(['identifier', 'name']);
scope.insert(0,new placeholder({identifier: '-1', name: '(Clear filter)'}));
};
tvheadend.channelTags = new Ext.data.JsonStore({
autoLoad: true,
root: 'entries',
@ -9,6 +14,9 @@ tvheadend.channelTags = new Ext.data.JsonStore({
url: 'channeltags',
baseParams: {
op: 'listTags'
},
listeners: {
'load': insertChannelTagsClearOption
}
});
@ -26,6 +34,11 @@ tvheadend.channelrec = new Ext.data.Record.create(
['name', 'chid', 'epggrabsrc', 'tags', 'ch_icon', 'epg_pre_start',
'epg_post_end', 'number']);
insertChannelClearOption = function( scope, records, options ){
var placeholder = Ext.data.Record.create(['key', 'val']);
scope.insert(0,new placeholder({key: '-1', val: '(Clear filter)'}));
};
tvheadend.channels = new Ext.data.JsonStore({
url: 'api/channel/list',
root: 'entries',
@ -35,6 +48,9 @@ tvheadend.channels = new Ext.data.JsonStore({
sortInfo: {
field: 'val',
direction: 'ASC'
},
listeners: {
'load': insertChannelClearOption
}
});

View file

@ -507,10 +507,6 @@ tvheadend.dvrschedule = function(title, iconCls, dvrStore) {
return panel;
};
/**
*
*/
/**
*
*/
@ -543,16 +539,15 @@ tvheadend.autoreceditor = function() {
valueField: 'key',
store: tvheadend.channels,
mode: 'local',
editable: false,
editable: true,
forceSelection: true,
typeAhead: true,
triggerAction: 'all',
emptyText: 'Only include channel...'
}),
renderer: function(v, m, r) {
var i = tvheadend.channels.find('key', v);
if (i !== -1)
v = tvheadend.channels.getAt(i).get('val');
return v;
}
renderer: function(v) {
return tvheadend.channelLookupName(v);
},
},
{
header: "SeriesLink",
@ -568,7 +563,9 @@ tvheadend.autoreceditor = function() {
displayField: 'name',
store: tvheadend.channelTags,
mode: 'local',
editable: false,
editable: true,
forceSelection: true,
typeAhead: true,
triggerAction: 'all',
emptyText: 'Only include tag...'
})
@ -584,11 +581,31 @@ tvheadend.autoreceditor = function() {
displayField: 'name',
store: tvheadend.ContentGroupStore,
mode: 'local',
editable: false,
editable: true,
forceSelection: true,
typeAhead: true,
triggerAction: 'all',
emptyText: 'Only include content...'
})
},
{
header: "Duration",
dataIndex: 'minduration',
renderer: function(v) {
return tvheadend.durationLookupRange(v);
},
editor: durationCombo = new Ext.form.ComboBox({
store: tvheadend.DurationStore,
mode: 'local',
valueField: 'minvalue',
displayField: 'label',
editable: true,
forceSelection: true,
typeAhead: true,
triggerAction: 'all',
id: 'minfield'
})
},
{
header: "Weekdays",
dataIndex: 'weekdays',
@ -685,10 +702,35 @@ tvheadend.autoreceditor = function() {
})
}]});
tvheadend.autorecStore.on('update', function (store, record, operation) {
if (operation == 'edit') {
if (record.isModified('minduration')) {
if (record.data.minduration == "")
record.set('maxduration',"");
else {
var index = tvheadend.DurationStore.find('minvalue', record.data.minduration);
if (index !== -1)
record.set('maxduration', tvheadend.DurationStore.getById(index).data.maxvalue);
}
}
if (record.isModified('channel') && record.data.channel == -1)
record.set('channel',"");
if (record.isModified('tag') && record.data.tag == '(Clear filter)')
record.set('tag',"");
if (record.isModified('contenttype') && record.data.contenttype == -1)
record.set('contenttype',"");
}
});
return new tvheadend.tableEditor('Automatic Recorder', 'autorec', cm,
tvheadend.autorecRecord, [], tvheadend.autorecStore,
'autorec.html', 'wand');
};
/**
*
*/
@ -786,6 +828,7 @@ tvheadend.dvr = function() {
tvheadend.autorecRecord = Ext.data.Record.create(['enabled', 'title',
'serieslink', 'channel', 'tag', 'creator', 'contenttype', 'comment',
'minduration', 'maxduration',
'weekdays', 'pri', 'approx_time', 'config_name']);
tvheadend.autorecStore = new Ext.data.JsonStore({

View file

@ -7,13 +7,22 @@ tvheadend.brands = new Ext.data.JsonStore({
op: 'brandList'
}
});
insertContentGroupClearOption = function( scope, records, options ){
var placeholder = Ext.data.Record.create(['name', 'code']);
scope.insert(0,new placeholder({name: '(Clear filter)', code: '-1'}));
};
//WIBNI: might want this store to periodically update
tvheadend.ContentGroupStore = new Ext.data.JsonStore({
root: 'entries',
fields: ['name', 'code'],
autoLoad: true,
url: 'ecglist'
url: 'ecglist',
listeners: {
'load': insertContentGroupClearOption
}
});
tvheadend.contentGroupLookupName = function(code) {
@ -29,6 +38,45 @@ tvheadend.contentGroupLookupName = function(code) {
tvheadend.ContentGroupStore.setDefaultSort('code', 'ASC');
tvheadend.channelLookupName = function(key) {
channelString = "";
var index = tvheadend.channels.find('key', key);
if (index !== -1)
var channelString = tvheadend.channels.getAt(index).get('val');
return channelString;
};
// Store for duration filters - EPG, autorec dialog and autorec rules in the DVR grid
// NB: 'no max' is defined as 9999999s, or about 3 months...
tvheadend.DurationStore = new Ext.data.SimpleStore({
storeId: 'durationnames',
idIndex: 0,
fields: ['identifier','label','minvalue','maxvalue'],
data: [['-1', '(Clear filter)',"",""],
['1','00:00:01 - 00:15:00',1, 900],
['2','00:15:01 - 00:30:00', 901, 1800],
['3','00:30:01 - 01:30:00', 1801, 5400],
['4','01:30:01 - 03:00:00', 5401, 10800],
['5','03:00:01 - No maximum', 10801, 9999999]]
});
// Function to convert numeric duration to corresponding label string
// Note: triggered by minimum duration only. This would fail if ranges
// had the same minimum (e.g. 15-30 mins and 15-60 minutes) (which we don't have).
tvheadend.durationLookupRange = function(value) {
durationString = "";
var index = tvheadend.DurationStore.find('minvalue', value);
if (index !== -1)
var durationString = tvheadend.DurationStore.getAt(index).data.label;
return durationString;
};
tvheadend.epgDetails = function(event) {
var content = '';
@ -378,7 +426,16 @@ tvheadend.epg = function() {
editable: true,
forceSelection: true,
triggerAction: 'all',
emptyText: 'Filter channel...'
typeAhead: true,
emptyText: 'Filter channel...',
listeners: {
blur: function () {
if(this.getRawValue() == "" ) {
clearChannelFilter();
epgStore.reload();
}
}
}
});
// Tags, uses global store
@ -391,7 +448,17 @@ tvheadend.epg = function() {
editable: true,
forceSelection: true,
triggerAction: 'all',
emptyText: 'Filter tag...'
typeAhead: true,
emptyText: 'Filter tag...',
listeners: {
blur: function () {
if(this.getRawValue() == "" ) {
clearChannelTagsFilter();
epgStore.reload();
}
}
}
});
// Content groups
@ -405,42 +472,115 @@ tvheadend.epg = function() {
editable: true,
forceSelection: true,
triggerAction: 'all',
emptyText: 'Filter content type...'
typeAhead: true,
emptyText: 'Filter content type...',
listeners: {
blur: function () {
if(this.getRawValue() == "" ) {
clearContentGroupFilter();
epgStore.reload();
}
}
}
});
function epgQueryClear() {
delete epgStore.baseParams.channel;
delete epgStore.baseParams.tag;
delete epgStore.baseParams.contenttype;
var epgFilterDuration = new Ext.form.ComboBox({
loadingText: 'Loading...',
width: 150,
displayField: 'label',
store: tvheadend.DurationStore,
mode: 'local',
editable: true,
forceSelection: true,
triggerAction: 'all',
typeAhead: true,
emptyText: 'Filter duration...',
listeners: {
blur: function () {
if(this.getRawValue() == "" ) {
clearDurationFilter();
epgStore.reload();
}
}
}
});
/*
* Clear filter functions
*/
clearTitleFilter = function() {
delete epgStore.baseParams.title;
epgFilterChannels.setValue("");
epgFilterChannelTags.setValue("");
epgFilterContentGroup.setValue("");
epgFilterTitle.setValue("");
};
clearChannelFilter = function() {
delete epgStore.baseParams.channel;
epgFilterChannels.setValue("");
};
clearChannelTagsFilter = function() {
delete epgStore.baseParams.tag;
epgFilterChannelTags.setValue("");
};
clearContentGroupFilter = function() {
delete epgStore.baseParams.contenttype;
epgFilterContentGroup.setValue("");
};
clearDurationFilter = function() {
delete epgStore.baseParams.minduration;
delete epgStore.baseParams.maxduration;
epgFilterDuration.setValue("");
};
function epgQueryClear() {
clearTitleFilter();
clearChannelFilter();
clearChannelTagsFilter();
clearDurationFilter();
clearContentGroupFilter();
epgStore.reload();
}
};
/*
* Filter selection event handlers
*/
epgFilterChannels.on('select', function(c, r) {
if (epgStore.baseParams.channel !== r.data.key) {
if (r.data.key == -1)
clearChannelFilter();
else if (epgStore.baseParams.channel !== r.data.key)
epgStore.baseParams.channel = r.data.key;
epgStore.reload();
}
epgStore.reload();
});
epgFilterChannelTags.on('select', function(c, r) {
if (epgStore.baseParams.tag !== r.data.name) {
if (r.data.identifier == -1)
clearChannelTagsFilter();
else if (epgStore.baseParams.tag !== r.data.name)
epgStore.baseParams.tag = r.data.name;
epgStore.reload();
}
epgStore.reload();
});
epgFilterContentGroup.on('select', function(c, r) {
if (epgStore.baseParams.contenttype !== r.data.code) {
if (r.data.code == -1)
clearContentGroupFilter();
else if (epgStore.baseParams.contenttype !== r.data.code)
epgStore.baseParams.contenttype = r.data.code;
epgStore.reload();
epgStore.reload();
});
epgFilterDuration.on('select', function(c, r) {
if (r.data.identifier == -1)
clearDurationFilter();
else if (epgStore.baseParams.minduration !== r.data.minvalue) {
epgStore.baseParams.minduration = r.data.minvalue;
epgStore.baseParams.maxduration = r.data.maxvalue;
}
epgStore.reload();
});
epgFilterTitle.on('valid', function(c) {
@ -482,8 +622,10 @@ tvheadend.epg = function() {
'-',
epgFilterContentGroup,
'-',
epgFilterDuration,
'-',
{
text: 'Reset',
text: 'Reset All',
handler: epgQueryClear
},
'->',
@ -524,28 +666,31 @@ tvheadend.epg = function() {
var title = epgStore.baseParams.title ? epgStore.baseParams.title
: "<i>Don't care</i>";
var channel = epgStore.baseParams.channel ? epgStore.baseParams.channel
var channel = epgStore.baseParams.channel ? tvheadend.channelLookupName(epgStore.baseParams.channel)
: "<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
var contenttype = epgStore.baseParams.contenttype ? tvheadend.contentGroupLookupName(epgStore.baseParams.contenttype)
: "<i>Don't care</i>";
var duration = epgStore.baseParams.minduration ? tvheadend.durationLookupRange(epgStore.baseParams.minduration)
: "<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>'
+ '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) '
+ '<div class="x-smallhdr">Duration:</div>' + duration + '<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);
});
}
function createAutoRec2(params) {