tvheadend/src/api/api_epg.c
Adam Sutton 0b3fcdce58 channel: added support for getting icons from underlying services
Also added an initial implementation of picon support.
2014-10-01 17:27:53 +02:00

571 lines
16 KiB
C

/*
* API - EPG related calls
*
* Copyright (C) 2013 Adam Sutton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; withm even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tvheadend.h"
#include "channels.h"
#include "access.h"
#include "api.h"
#include "epg.h"
#include "imagecache.h"
#include "dvr/dvr.h"
static htsmsg_t *
api_epg_get_list ( const char *s )
{
htsmsg_t *m = NULL;
char *r, *saveptr = NULL;
if (s && s[0] != '\0') {
s = r = strdup(s);
r = strtok_r(r, ";", &saveptr);
while (r) {
while (*r != '\0' && *r <= ' ')
r++;
if (*r != '\0') {
if (m == NULL)
m = htsmsg_create_list();
htsmsg_add_str(m, NULL, r);
}
r = strtok_r(NULL, ";", &saveptr);
}
free((char *)s);
}
return m;
}
static void
api_epg_add_channel ( htsmsg_t *m, channel_t *ch )
{
int64_t chnum;
char buf[32];
const char *s;
htsmsg_add_str(m, "channelName", channel_get_name(ch));
htsmsg_add_str(m, "channelUuid", channel_get_uuid(ch));
if ((chnum = channel_get_number(ch)) >= 0) {
uint32_t maj = chnum / CHANNEL_SPLIT;
uint32_t min = chnum % CHANNEL_SPLIT;
if (min)
snprintf(buf, sizeof(buf), "%u.%u", maj, min);
else
snprintf(buf, sizeof(buf), "%u", maj);
htsmsg_add_str(m, "channelNumber", buf);
}
if ((s = channel_get_icon(ch)) != NULL)
htsmsg_add_str(m, "channelIcon", s);
}
static htsmsg_t *
api_epg_entry ( epg_broadcast_t *eb, const char *lang )
{
const char *s;
char buf[64];
epg_episode_t *ee = eb->episode;
channel_t *ch = eb->channel;
htsmsg_t *m, *m2;
epg_episode_num_t epnum;
epg_genre_t *eg;
dvr_entry_t *de;
if (!ee || !ch) return NULL;
m = htsmsg_create_map();
/* EPG IDs */
htsmsg_add_u32(m, "eventId", eb->id);
if (ee) {
htsmsg_add_u32(m, "episodeId", ee->id);
if (ee->uri && strncasecmp(ee->uri, "tvh://", 6))
htsmsg_add_str(m, "episodeUri", ee->uri);
}
if (eb->serieslink) {
htsmsg_add_u32(m, "serieslinkId", eb->serieslink->id);
if (eb->serieslink->uri)
htsmsg_add_str(m, "serieslinkUri", eb->serieslink->uri);
}
/* Channel Info */
api_epg_add_channel(m, ch);
/* Time */
htsmsg_add_s64(m, "start", eb->start);
htsmsg_add_s64(m, "stop", eb->stop);
/* Title/description */
if ((s = epg_broadcast_get_title(eb, lang)))
htsmsg_add_str(m, "title", s);
if ((s = epg_broadcast_get_subtitle(eb, lang)))
htsmsg_add_str(m, "subtitle", s);
if ((s = epg_broadcast_get_summary(eb, lang)))
htsmsg_add_str(m, "summary", s);
if ((s = epg_broadcast_get_description(eb, lang)))
htsmsg_add_str(m, "description", s);
/* Episode info */
if (ee) {
/* Number */
epg_episode_get_epnum(ee, &epnum);
if (epnum.s_num) {
htsmsg_add_u32(m, "seasonNumber", epnum.s_num);
if (epnum.s_cnt)
htsmsg_add_u32(m, "seasonCount", epnum.s_cnt);
}
if (epnum.e_num) {
htsmsg_add_u32(m, "episodeNumber", epnum.e_num);
if (epnum.s_cnt)
htsmsg_add_u32(m, "episodeCount", epnum.e_cnt);
}
if (epnum.p_num) {
htsmsg_add_u32(m, "partNumber", epnum.p_num);
if (epnum.p_cnt)
htsmsg_add_u32(m, "partCount", epnum.p_cnt);
}
if (epnum.text)
htsmsg_add_str(m, "episodeOnscreen", epnum.text);
else if (epg_episode_number_format(ee, buf, sizeof(buf), NULL,
"s%02d", ".", "e%02d", ""))
htsmsg_add_str(m, "episodeOnscreen", buf);
/* Image */
if (ee->image)
htsmsg_add_str(m, "image", ee->image);
/* Rating */
if (ee->star_rating)
htsmsg_add_u32(m, "starRating", ee->star_rating);
if (ee->age_rating)
htsmsg_add_u32(m, "ageRating", ee->age_rating);
/* Content Type */
m2 = NULL;
LIST_FOREACH(eg, &ee->genre, link) {
if (m2 == NULL)
m2 = htsmsg_create_list();
htsmsg_add_u32(m2, NULL, eg->code);
}
if (m2)
htsmsg_add_msg(m, "genre", m2);
}
/* Recording */
if ((de = dvr_entry_find_by_event(eb))) {
htsmsg_add_str(m, "dvrUuid", idnode_uuid_as_str(&de->de_id));
htsmsg_add_str(m, "dvrState", dvr_entry_schedstatus(de));
}
/* Next event */
if ((eb = epg_broadcast_get_next(eb)))
htsmsg_add_u32(m, "nextEventId", eb->id);
return m;
}
static void
api_epg_filter_set_str
( epg_filter_str_t *f, const char *str, int comp )
{
f->str = strdup(str);
f->comp = comp;
}
static void
api_epg_filter_add_str
( epg_query_t *eq, const char *k, const char *v, int comp )
{
if (!strcmp(k, "channelName"))
api_epg_filter_set_str(&eq->channel_name, v, comp);
else if (!strcmp(k, "title"))
api_epg_filter_set_str(&eq->title, v, comp);
else if (!strcmp(k, "subtitle"))
api_epg_filter_set_str(&eq->subtitle, v, comp);
else if (!strcmp(k, "summary"))
api_epg_filter_set_str(&eq->summary, v, comp);
else if (!strcmp(k, "description"))
api_epg_filter_set_str(&eq->description, v, comp);
}
static void
api_epg_filter_set_num
( epg_filter_num_t *f, int64_t v1, int64_t v2, int comp )
{
/* Range? */
if (f->comp == EC_LT && comp == EC_GT) {
f->val2 = f->val1;
f->val1 = v1;
f->comp = EC_RG;
return;
}
if (f->comp == EC_GT && comp == EC_LT) {
f->val2 = v1;
f->comp = EC_RG;
return;
}
f->val1 = v1;
f->val2 = v2;
f->comp = comp;
}
static void
api_epg_filter_add_num
( epg_query_t *eq, const char *k, int64_t v1, int64_t v2, int comp )
{
if (!strcmp(k, "start"))
api_epg_filter_set_num(&eq->start, v1, v2, comp);
else if (!strcmp(k, "stop"))
api_epg_filter_set_num(&eq->stop, v1, v2, comp);
else if (!strcmp(k, "duration"))
api_epg_filter_set_num(&eq->duration, v1, v2, comp);
else if (!strcmp(k, "episode"))
api_epg_filter_set_num(&eq->episode, v1, v2, comp);
else if (!strcmp(k, "stars"))
api_epg_filter_set_num(&eq->episode, v1, v2, comp);
else if (!strcmp(k, "age"))
api_epg_filter_set_num(&eq->age, v1, v2, comp);
}
static struct strtab sortcmptab[] = {
{ "start", ESK_START },
{ "stop", ESK_STOP },
{ "duration", ESK_DURATION },
{ "title", ESK_TITLE },
{ "subtitle", ESK_SUBTITLE },
{ "summary", ESK_SUMMARY },
{ "description", ESK_DESCRIPTION },
{ "channelName", ESK_CHANNEL },
{ "channelNumber", ESK_CHANNEL_NUM },
{ "starRating", ESK_STARS },
{ "ageRating", ESK_AGE },
{ "genre", ESK_GENRE }
};
static struct strtab filtcmptab[] = {
{ "gt", EC_GT },
{ "lt", EC_LT },
{ "eq", EC_EQ },
{ "regex", EC_RE },
{ "range", EC_RG }
};
static int64_t
api_epg_decode_channel_num ( const char *s )
{
int64_t v = atol(s);
const char *s1 = strchr(s, '.');
if (s1)
v += atol(s1 + 1) % CHANNEL_SPLIT;
return v;
}
static int
api_epg_grid
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
int i;
epg_query_t eq;
const char *lang, *str;
uint32_t start, limit, end, genre;
int64_t duration_min, duration_max;
htsmsg_field_t *f, *f2;
htsmsg_t *l = NULL, *e, *filter;
memset(&eq, 0, sizeof(eq));
lang = htsmsg_get_str(args, "lang");
eq.lang = lang ? strdup(lang) : NULL;
str = htsmsg_get_str(args, "title");
if (str)
eq.stitle = strdup(str);
str = htsmsg_get_str(args, "channel");
if (str)
eq.channel = strdup(str);
str = htsmsg_get_str(args, "channelTag");
if (str)
eq.channel_tag = strdup(str);
duration_min = -1;
duration_max = -1;
htsmsg_get_s64(args, "durationMin", &duration_min);
htsmsg_get_s64(args, "durationMax", &duration_max);
if (duration_min > 0 || duration_max > 0) {
eq.duration.comp = EC_RG;
eq.duration.val1 = duration_min < 0 ? 0 : duration_min;
eq.duration.val2 = duration_max < 0 ? 0 : duration_max;
}
if (!htsmsg_get_u32(args, "contentType", &genre)) {
eq.genre = eq.genre_static;
eq.genre[0] = genre;
eq.genre_count = 1;
}
/* Filter params */
if ((filter = htsmsg_get_list(args, "filter"))) {
HTSMSG_FOREACH(f, filter) {
const char *k, *t, *v;
int comp;
if (!(e = htsmsg_get_map_by_field(f))) continue;
if (!(k = htsmsg_get_str(e, "field"))) continue;
if (!(t = htsmsg_get_str(e, "type"))) continue;
comp = str2val(htsmsg_get_str(e, "comparison") ?: "", filtcmptab);
if (comp == -1) comp = EC_EQ;
if (!strcmp(k, "channelNumber")) {
if (!strcmp(t, "numeric")) {
f2 = htsmsg_field_find(e, "value");
if (f2) {
int64_t v1, v2 = 0;
if (f2->hmf_type == HMF_STR) {
const char *s = htsmsg_field_get_str(f2);
const char *z = s ? strchr(s, ';') : NULL;
if (s) {
v1 = api_epg_decode_channel_num(s);
if (z)
v2 = api_epg_decode_channel_num(z);
api_epg_filter_set_num(&eq.channel_num, v1, v2, comp);
}
} else {
if (!htsmsg_field_get_s64(f2, &v1)) {
if (v1 < CHANNEL_SPLIT)
v1 *= CHANNEL_SPLIT;
api_epg_filter_set_num(&eq.channel_num, v1, 0, comp);
}
}
}
}
} else if (!strcmp(k, "genre")) {
if (!strcmp(t, "numeric")) {
f2 = htsmsg_field_find(e, "value");
if (f2) {
int64_t v;
if (f2->hmf_type == HMF_STR) {
htsmsg_t *z = api_epg_get_list(htsmsg_field_get_str(f2));
if (z) {
htsmsg_field_t *f3;
uint32_t count = 0;
HTSMSG_FOREACH(f3, z)
count++;
if (ARRAY_SIZE(eq.genre_static) > count)
eq.genre = malloc(sizeof(eq.genre[0]) * count);
else
eq.genre = eq.genre_static;
HTSMSG_FOREACH(f3, z)
if (!htsmsg_field_get_s64(f3, &v))
eq.genre[eq.genre_count++] = v;
}
} else {
if (!htsmsg_field_get_s64(f2, &v)) {
eq.genre_count = 1;
eq.genre = eq.genre_static;
eq.genre[0] = v;
}
}
}
}
} else if (!strcmp(t, "string")) {
if ((v = htsmsg_get_str(e, "value")))
api_epg_filter_add_str(&eq, k, v, EC_RE);
} else if (!strcmp(t, "numeric")) {
f2 = htsmsg_field_find(e, "value");
if (f2) {
int64_t v1, v2 = 0;
if (f2->hmf_type == HMF_STR) {
const char *z = htsmsg_field_get_str(f2);
if (z) {
const char *z2 = strchr(z, ';');
if (z2)
v2 = strtoll(z2 + 1, NULL, 0);
}
v1 = strtoll(z, NULL, 0);
api_epg_filter_add_num(&eq, k, v1, v2, comp);
} else {
if (!htsmsg_field_get_s64(f2, &v1))
api_epg_filter_add_num(&eq, k, v1, v2, comp);
}
}
}
}
}
/* Sort */
if ((str = htsmsg_get_str(args, "sort"))) {
int skey = str2val(str, sortcmptab);
if (skey >= 0) {
eq.sort_key = skey;
if ((str = htsmsg_get_str(args, "dir")) && !strcasecmp(str, "DESC"))
eq.sort_dir = IS_DSC;
else
eq.sort_dir = IS_ASC;
}
} /* else.. keep default start time ascending sorting */
/* 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(&eq);
/* Build response */
start = MIN(eq.entries, start);
end = MIN(eq.entries, start + limit);
l = htsmsg_create_list();
for (i = start; i < end; i++) {
if (!(e = api_epg_entry(eq.result[i], lang))) continue;
htsmsg_add_msg(l, NULL, e);
}
pthread_mutex_unlock(&global_lock);
epg_query_free(&eq);
/* Build response */
*resp = htsmsg_create_map();
htsmsg_add_u32(*resp, "totalCount", eq.entries);
htsmsg_add_msg(*resp, "entries", l);
return 0;
}
static void
api_epg_episode_broadcasts
( htsmsg_t *l, const char *lang, epg_episode_t *ep,
uint32_t *entries, epg_broadcast_t *ebc_skip )
{
epg_broadcast_t *ebc;
channel_t *ch;
htsmsg_t *m;
LIST_FOREACH(ebc, &ep->broadcasts, ep_link) {
ch = ebc->channel;
if (ch == NULL) continue;
if (ebc == ebc_skip) continue;
m = api_epg_entry(ebc, lang);
htsmsg_add_msg(l, NULL, m);
(*entries)++;
}
}
static int
api_epg_alternative
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
uint32_t id, entries = 0;
htsmsg_t *l = htsmsg_create_list();
epg_broadcast_t *e;
const char *lang = htsmsg_get_str(args, "lang");
if (!htsmsg_get_u32(args, "eventId", &id))
return -EINVAL;
/* Main Job */
pthread_mutex_lock(&global_lock);
e = epg_broadcast_find_by_id(id, NULL);
if (e && e->episode)
api_epg_episode_broadcasts(l, lang, e->episode, &entries, e);
pthread_mutex_unlock(&global_lock);
/* Build response */
htsmsg_add_u32(*resp, "totalCount", entries);
htsmsg_add_msg(*resp, "entries", l);
return 0;
}
static int
api_epg_related
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
uint32_t id, entries = 0;
htsmsg_t *l = htsmsg_create_list();
epg_broadcast_t *e;
epg_episode_t *ep, *ep2;
const char *lang = htsmsg_get_str(args, "lang");
if (!htsmsg_get_u32(args, "eventId", &id))
return -EINVAL;
/* Main Job */
pthread_mutex_lock(&global_lock);
e = epg_broadcast_find_by_id(id, NULL);
ep = e ? e->episode : NULL;
if (ep && ep->brand) {
LIST_FOREACH(ep2, &ep->brand->episodes, blink) {
if (ep2 == ep) continue;
if (!ep2->title) continue;
api_epg_episode_broadcasts(l, lang, ep2, &entries, e);
entries++;
}
} else if (ep && ep->season) {
LIST_FOREACH(ep2, &ep->season->episodes, slink) {
if (ep2 == ep) continue;
if (!ep2->title) continue;
api_epg_episode_broadcasts(l, lang, ep2, &entries, e);
}
}
pthread_mutex_unlock(&global_lock);
/* Build response */
htsmsg_add_u32(*resp, "totalCount", entries);
htsmsg_add_msg(*resp, "entries", l);
return 0;
}
static int
api_epg_brand_list(access_t *perm, void *opaque, const char *op,
htsmsg_t *args, htsmsg_t **resp)
{
htsmsg_t *array;
*resp = htsmsg_create_map();
pthread_mutex_lock(&global_lock);
array = epg_brand_list();
pthread_mutex_unlock(&global_lock);
htsmsg_add_msg(*resp, "entries", array);
return 0;
}
static int
api_epg_content_type_list(access_t *perm, void *opaque, const char *op,
htsmsg_t *args, htsmsg_t **resp)
{
htsmsg_t *array;
int full = 0;
htsmsg_get_bool(args, "full", &full);
*resp = htsmsg_create_map();
array = epg_genres_list_all(full ? 0 : 1, 0);
htsmsg_add_msg(*resp, "entries", array);
return 0;
}
void api_epg_init ( void )
{
static api_hook_t ah[] = {
{ "epg/events/grid", ACCESS_ANONYMOUS, api_epg_grid, NULL },
{ "epg/events/alternative", ACCESS_ANONYMOUS, api_epg_alternative, NULL },
{ "epg/events/related", ACCESS_ANONYMOUS, api_epg_related, NULL },
{ "epg/brand/list", ACCESS_ANONYMOUS, api_epg_brand_list, NULL },
{ "epg/content_type/list", ACCESS_ANONYMOUS, api_epg_content_type_list, NULL },
{ NULL },
};
api_register_all(ah);
}