From ac24fe6e82da8e299b4821f37aa423c0c9c841f2 Mon Sep 17 00:00:00 2001 From: Adam Sutton Date: Sat, 17 May 2014 00:04:03 +0100 Subject: [PATCH] channel: added support for getting icons from underlying services Also added an initial implementation of picon support. --- src/channels.c | 85 +++++++++++++++++++++---------- src/channels.h | 2 +- src/config.c | 37 +++++++++++++- src/config.h | 4 ++ src/htsp_server.c | 40 +++++++-------- src/imagecache.h | 12 ----- src/input/mpegts/mpegts_service.c | 51 +++++++++++++++++++ src/service.c | 11 ++++ src/service.h | 2 + src/webui/extjs.c | 15 +++--- src/webui/statedump.c | 2 +- src/webui/static/app/config.js | 24 ++++++++- 12 files changed, 214 insertions(+), 71 deletions(-) diff --git a/src/channels.c b/src/channels.c index 22ff546d..926a1c29 100644 --- a/src/channels.c +++ b/src/channels.c @@ -29,6 +29,7 @@ #include #include "settings.h" +#include "config.h" #include "tvheadend.h" #include "epg.h" @@ -181,29 +182,15 @@ channel_class_tags_enum ( void *obj ) static void channel_class_icon_notify ( void *obj ) { - channel_t *ch = obj; - if (ch->ch_icon) - imagecache_get_id(ch->ch_icon); + (void)channel_get_icon(obj); } static const void * -channel_class_get_imagecache ( void *obj ) +channel_class_get_icon ( void *obj ) { - static char buf[512], *r; - uint32_t id; - channel_t *ch = obj; - - if (!ch->ch_icon) { - r = NULL; - } else if ((id = imagecache_get_id(ch->ch_icon))) { - snprintf(buf, sizeof(buf), "imagecache/%d", id); - r = buf; - } else { - strncpy(buf, ch->ch_icon, sizeof(buf)); - r = buf; - } - - return &r; + static const char *s; + s = channel_get_icon(obj); + return &s; } static const char * @@ -318,16 +305,16 @@ const idclass_t channel_class = { }, { .type = PT_STR, - .id = "icon", - .name = "Icon", - .off = offsetof(channel_t, ch_icon), + .id = "usericon", + .name = "User Icon", + .off = offsetof(channel_t, ch_usericon), .notify = channel_class_icon_notify, }, { .type = PT_STR, - .id = "icon_public_url", - .name = "Icon URL", - .get = channel_class_get_imagecache, + .id = "icon", + .name = "Icon", + .get = channel_class_get_icon, .opts = PO_RDONLY | PO_NOSAVE | PO_HIDDEN, }, { @@ -502,6 +489,52 @@ channel_get_number ( channel_t *ch ) return 0; } +const char * +channel_get_icon ( channel_t *ch ) +{ + static __thread char buf[512], buf2[512]; + channel_service_mapping_t *csm; + const char *picon = config_get_picon_path(), + *icon = ch->ch_usericon; + uint32_t id; + + /* No user icon - try access from services */ + if (!icon && picon) { + LIST_FOREACH(csm, &ch->ch_services, csm_chn_link) { + if (!(icon = service_get_channel_icon(csm->csm_svc))) continue; + if (strncmp(icon, "picon://", 8)) { + icon = NULL; + continue; + } + sprintf(buf2, "%s/%s", picon, icon+8); + ch->ch_usericon = strdup(icon); + channel_save(ch); + idnode_updated(&ch->ch_id); + } + } + + /* Nothing */ + if (!icon || !*icon) + return NULL; + + /* Picon? */ + if (!strncmp(icon, "picon://", 8)) { + if (!picon) return NULL; + sprintf(buf2, "%s/%s", picon, icon+8); + icon = buf2; + } + + /* Lookup imagecache ID */ + if ((id = imagecache_get_id(icon))) { + snprintf(buf, sizeof(buf), "imagecache/%d", id); + + } else { + strncpy(buf, icon, sizeof(buf)); + } + + return buf; +} + /* ************************************************************************** * Creation/Deletion * *************************************************************************/ @@ -582,7 +615,7 @@ channel_delete ( channel_t *ch, int delconf ) RB_REMOVE(&channels, ch, ch_link); idnode_unlink(&ch->ch_id); free(ch->ch_name); - free(ch->ch_icon); + free(ch->ch_usericon); free(ch); } diff --git a/src/channels.h b/src/channels.h index 370ab7c5..0996d0b2 100644 --- a/src/channels.h +++ b/src/channels.h @@ -47,7 +47,7 @@ typedef struct channel /* Channel info */ char *ch_name; // Note: do not access directly! int ch_number; - char *ch_icon; + char *ch_usericon; struct channel_tag_mapping_list ch_ctms; /* Service/subscriptions */ diff --git a/src/config.c b/src/config.c index f7e97bd3..49ab8808 100644 --- a/src/config.c +++ b/src/config.c @@ -492,7 +492,7 @@ config_migrate_v3 ( void ) } /* - * v3 -> v4 : fix broken DVB network / mux files + * v4 -> v5 : fix broken DVB network / mux files */ static void config_migrate_v5 ( void ) @@ -517,6 +517,28 @@ config_migrate_v5 ( void ) } } +/* + * v5 -> v6 : change channel icon param + */ +static void +config_migrate_v6 ( void ) +{ + htsmsg_t *c, *e; + htsmsg_field_t *f; + const char *str; + + /* Remove linux prefix from class */ + if ((c = hts_settings_load_r(1, "channel"))) { + HTSMSG_FOREACH(f, c) { + if (!(e = htsmsg_field_get_map(f))) continue; + if (!(str = htsmsg_get_str(e, "icon"))) continue; + htsmsg_add_str(e, "usericon", str); + htsmsg_delete_field(e, "icon"); + hts_settings_save(e, "channel/%s", f->hmf_name); + } + } +} + /* * Migration table */ @@ -525,7 +547,8 @@ static const config_migrate_t config_migrate_table[] = { config_migrate_v2, config_migrate_v3, config_migrate_v3, // Re-run due to bug in previous version of function - config_migrate_v5 + config_migrate_v5, + config_migrate_v6, }; /* @@ -670,3 +693,13 @@ int config_set_muxconfpath ( const char *path ) { return _config_set_str("muxconfpath", path); } + +const char *config_get_picon_path ( void ) +{ + return htsmsg_get_str(config, "piconpath"); +} + +int config_set_picon_path ( const char *str ) +{ + return _config_set_str("piconpath", str); +} diff --git a/src/config.h b/src/config.h index 4473947d..0542d238 100644 --- a/src/config.h +++ b/src/config.h @@ -37,4 +37,8 @@ const char *config_get_language ( void ); int config_set_language ( const char *str ) __attribute__((warn_unused_result)); +const char *config_get_picon_path ( void ); +int config_set_picon_path ( const char *str ) + __attribute__((warn_unused_result)); + #endif /* __TVH_CONFIG__H__ */ diff --git a/src/htsp_server.c b/src/htsp_server.c index 6b47911b..38b68304 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -530,6 +530,7 @@ htsp_build_channel(channel_t *ch, const char *method, htsp_connection_t *htsp) channel_tag_t *ct; service_t *t; epg_broadcast_t *now, *next = NULL; + const char *icon; htsmsg_t *out = htsmsg_create_map(); htsmsg_t *tags = htsmsg_create_list(); @@ -539,31 +540,30 @@ htsp_build_channel(channel_t *ch, const char *method, htsp_connection_t *htsp) htsmsg_add_u32(out, "channelNumber", channel_get_number(ch)); htsmsg_add_str(out, "channelName", channel_get_name(ch)); - if(ch->ch_icon != NULL) { - uint32_t id; - struct sockaddr_storage addr; - socklen_t addrlen; - if ((id = imagecache_get_id(ch->ch_icon))) { + if ((icon = channel_get_icon(ch))) { + + /* Handle older clients */ + if ((strstr(icon, "imagecache") == icon) && htsp->htsp_version < 8) { + struct sockaddr_storage addr; + socklen_t addrlen; size_t p = 0; char url[256]; char buf[50]; - if (htsp->htsp_version < 8) { - addrlen = sizeof(addr); - getsockname(htsp->htsp_fd, (struct sockaddr*)&addr, &addrlen); - tcp_get_ip_str((struct sockaddr*)&addr, buf, 50); - strcpy(url, "http://"); - p = strlen(url); - p += snprintf(url+p, sizeof(url)-p, "%s%s%s:%d%s", - (addr.ss_family == AF_INET6)?"[":"", - buf, - (addr.ss_family == AF_INET6)?"]":"", - tvheadend_webui_port, - tvheadend_webroot ?: ""); - } - snprintf(url+p, sizeof(url)-p, "/imagecache/%d", id); + addrlen = sizeof(addr); + getsockname(htsp->htsp_fd, (struct sockaddr*)&addr, &addrlen); + tcp_get_ip_str((struct sockaddr*)&addr, buf, 50); + strcpy(url, "http://"); + p = strlen(url); + p += snprintf(url+p, sizeof(url)-p, "%s%s%s:%d%s", + (addr.ss_family == AF_INET6)?"[":"", + buf, + (addr.ss_family == AF_INET6)?"]":"", + tvheadend_webui_port, + tvheadend_webroot ?: ""); + snprintf(url+p, sizeof(url)-p, "/%s", icon); htsmsg_add_str(out, "channelIcon", url); } else { - htsmsg_add_str(out, "channelIcon", ch->ch_icon); + htsmsg_add_str(out, "channelIcon", icon); } } diff --git a/src/imagecache.h b/src/imagecache.h index c5cdc4e6..e0009974 100644 --- a/src/imagecache.h +++ b/src/imagecache.h @@ -44,16 +44,4 @@ uint32_t imagecache_get_id ( const char *url ); int imagecache_open ( uint32_t id ); -#define htsmsg_add_imageurl(_msg, _fld, _fmt, _url)\ - {\ - char _tmp[64];\ - uint32_t _id = imagecache_get_id(_url);\ - if (_id) {\ - snprintf(_tmp, sizeof(_tmp), _fmt, _id);\ - htsmsg_add_str(_msg, _fld, _tmp);\ - } else {\ - htsmsg_add_str(_msg, _fld, _url);\ - }\ - } - #endif /* __IMAGE_CACHE_H__ */ diff --git a/src/input/mpegts/mpegts_service.c b/src/input/mpegts/mpegts_service.c index 15f4a9a2..087915e8 100644 --- a/src/input/mpegts/mpegts_service.c +++ b/src/input/mpegts/mpegts_service.c @@ -23,6 +23,7 @@ #include "input.h" #include "settings.h" #include "dvb_charset.h" +#include "config.h" /* ************************************************************************** * Class definition @@ -356,6 +357,55 @@ mpegts_service_provider_name ( service_t *s ) return ((mpegts_service_t*)s)->s_dvb_provider; } +static const char * +mpegts_service_channel_icon ( service_t *s ) +{ + mpegts_service_t *ms = (mpegts_service_t*)s; + + /* DVB? */ +#if ENABLE_MPEGTS_DVB + extern const idclass_t dvb_mux_class; + if (ms->s_dvb_mux && + idnode_is_instance(&ms->s_dvb_mux->mm_id, &dvb_mux_class)) { + int32_t hash = 0; + static __thread char buf[1024]; + dvb_mux_t *mmd = (dvb_mux_t*)ms->s_dvb_mux; + + switch ( mmd->lm_tuning.dmc_fe_type) { + case DVB_TYPE_S: + if (mmd->lm_tuning.u.dmc_fe_qpsk.orbital_dir == 'E') + hash = mmd->lm_tuning.u.dmc_fe_qpsk.orbital_pos; + else + hash = 0xFFFF - mmd->lm_tuning.u.dmc_fe_qpsk.orbital_pos; + hash <<= 16; + break; + case DVB_TYPE_C: + hash = 0xFFFF0000; + break; + case DVB_TYPE_T: + hash = 0xEEEE0000; + break; + case DVB_TYPE_ATSC: + hash = 0xDDDD0000; + break; + default: + return NULL; + } + + snprintf(buf, sizeof(buf), + "picon://1_0_%X_%X_%X_%X_%X_0_0_0.png", + ms->s_dvb_servicetype, + ms->s_dvb_service_id, + ms->s_dvb_mux->mm_tsid, + ms->s_dvb_mux->mm_onid, + hash); + return buf; + } +#endif + + return NULL; +} + void mpegts_service_delete ( service_t *t, int delconf ) { @@ -419,6 +469,7 @@ mpegts_service_create0 s->s_channel_number = mpegts_service_channel_number; s->s_channel_name = mpegts_service_channel_name; s->s_provider_name = mpegts_service_provider_name; + s->s_channel_icon = mpegts_service_channel_icon; pthread_mutex_lock(&s->s_stream_mutex); service_make_nicename((service_t*)s); diff --git a/src/service.c b/src/service.c index 01e26cb5..adba2e58 100644 --- a/src/service.c +++ b/src/service.c @@ -1393,6 +1393,17 @@ service_get_channel_number ( service_t *s ) return 0; } +/* + * Get name for channel from service + */ +const char * +service_get_channel_icon ( service_t *s ) +{ + const char *r = NULL; + if (s->s_channel_icon) r = s->s_channel_icon(s); + return r; +} + /** * Get the encryption CAID from a service * only the first CA stream in a service is returned diff --git a/src/service.h b/src/service.h index 977d2912..0f51c601 100644 --- a/src/service.h +++ b/src/service.h @@ -294,6 +294,7 @@ typedef struct service { int (*s_channel_number) (struct service *); const char *(*s_channel_name) (struct service *); const char *(*s_provider_name) (struct service *); + const char *(*s_channel_icon) (struct service *); /** * Name usable for displaying to user @@ -538,5 +539,6 @@ void sort_elementary_streams(service_t *t); const char *service_get_channel_name (service_t *s); int service_get_channel_number (service_t *s); +const char *service_get_channel_icon (service_t *s); #endif // SERVICE_H__ diff --git a/src/webui/extjs.c b/src/webui/extjs.c index fc8a57fe..cd250705 100755 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -789,8 +789,8 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque) htsmsg_add_str(m, "channel", channel_get_name(ch)); htsmsg_add_u32(m, "channelid", channel_get_id(ch)); - if(ch->ch_icon != NULL) - htsmsg_add_imageurl(m, "chicon", "imagecache/%d", ch->ch_icon); + if ((s = channel_get_icon(ch))) + htsmsg_add_str(m, "chicon", s); if((s = epg_episode_get_title(ee, lang))) htsmsg_add_str(m, "title", s); @@ -876,8 +876,8 @@ extjs_epgrelated(http_connection_t *hc, const char *remain, void *opaque) m = htsmsg_create_map(); htsmsg_add_u32(m, "id", ebc->id); htsmsg_add_str(m, "channel", channel_get_name(ch)); - if (ch->ch_icon) - htsmsg_add_imageurl(m, "chicon", "imagecache/%d", ch->ch_icon); + if ((s = channel_get_icon(ch))) + htsmsg_add_str(m, "chicon", s); htsmsg_add_u32(m, "start", ebc->start); htsmsg_add_msg(array, NULL, m); } @@ -1331,9 +1331,8 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque, htsmsg_add_str(m, "channel", DVR_CH_NAME(de)); if(de->de_channel != NULL) { htsmsg_add_str(m, "channelid", channel_get_uuid(de->de_channel)); - if (de->de_channel->ch_icon) - htsmsg_add_imageurl(m, "chicon", "imagecache/%d", - de->de_channel->ch_icon); + if ((s = channel_get_icon(de->de_channel))) + htsmsg_add_str(m, "chicon", s); } htsmsg_add_str(m, "config_name", de->de_config_name); @@ -1599,6 +1598,8 @@ extjs_config(http_connection_t *hc, const char *remain, void *opaque) save |= config_set_muxconfpath(str); if ((str = http_arg_get(&hc->hc_req_args, "language"))) save |= config_set_language(str); + if ((str = http_arg_get(&hc->hc_req_args, "piconpath"))) + save |= config_set_picon_path(str); if (save) config_save(); diff --git a/src/webui/statedump.c b/src/webui/statedump.c index 79dfc0fd..f3be1374 100644 --- a/src/webui/statedump.c +++ b/src/webui/statedump.c @@ -66,7 +66,7 @@ dumpchannels(htsbuf_queue_t *hq) ch->ch_refcount, ch->ch_zombie, channel_get_number(ch), - ch->ch_icon ?: ""); + channel_get_icon(ch) ?: ""); } } diff --git a/src/webui/static/app/config.js b/src/webui/static/app/config.js index cd92fc58..ee3c2382 100644 --- a/src/webui/static/app/config.js +++ b/src/webui/static/app/config.js @@ -43,7 +43,8 @@ tvheadend.miscconf = function() { [ 'muxconfpath', 'language', 'tvhtime_update_enabled', 'tvhtime_ntp_enabled', - 'tvhtime_tolerance', 'transcoding_enabled' + 'tvhtime_tolerance', 'transcoding_enabled', + 'piconpath' ]); /* **************************************************************** @@ -126,6 +127,25 @@ tvheadend.miscconf = function() { items: [tvhtimeUpdateEnabled, tvhtimeNtpEnabled, tvhtimeTolerance] }); + /* + * Picons + */ + + var piconPath = new Ext.form.TextField({ + name: 'piconpath', + fieldLabel: 'Picon path (e.g. file:///tmp/picons)', + width: 400 + }); + + var piconPanel = new Ext.form.FieldSet({ + title: 'Picon', + width: 700, + autoHeight: true, + collapsible: true, + animCollapse: true, + items: [piconPath] + }); + /* * Image cache */ @@ -232,7 +252,7 @@ tvheadend.miscconf = function() { layout: 'form', defaultType: 'textfield', autoHeight: true, - items: [languageWrap, dvbscanWrap, tvhtimePanel, transcodingPanel] + items: [languageWrap, dvbscanWrap, tvhtimePanel, transcodingPanel, piconPanel] }); var _items = [confpanel];