Add multi-lingual support to the EPG. Fixes #227.

This commit is contained in:
Adam Sutton 2012-08-14 12:57:18 +01:00
parent 7b5cf6bfe6
commit 00902f4613
26 changed files with 1387 additions and 282 deletions

View file

@ -99,6 +99,8 @@ SRCS = src/main.c \
src/filebundle.c \
src/muxes.c \
src/config2.c \
src/lang_codes.c \
src/lang_str.c \
SRCS += src/epggrab/module.c\
src/epggrab/channel.c\

View file

@ -43,6 +43,22 @@ htsmsg_t *config_get_all ( void )
return htsmsg_copy(config);
}
const char *config_get_language ( void )
{
return htsmsg_get_str(config, "language");
}
int config_set_language ( const char *lang )
{
const char *c = config_get_language();
if (!c || strcmp(c, lang)) {
if (c) htsmsg_delete_field(config, "language");
htsmsg_add_str(config, "language", lang);
return 1;
}
return 0;
}
const char *config_get_muxconfpath ( void )
{
return htsmsg_get_str(config, "muxconfpath");

View file

@ -32,4 +32,8 @@ const char *config_get_muxconfpath ( void );
int config_set_muxconfpath ( const char *str )
__attribute__((warn_unused_result));
const char *config_get_language ( void );
int config_set_language ( const char *str )
__attribute__((warn_unused_result));
#endif /* __TVH_CONFIG__H__ */

View file

@ -24,6 +24,7 @@
#include "channels.h"
#include "subscriptions.h"
#include "muxer.h"
#include "lang_str.h"
typedef struct dvr_config {
char *dvr_config_name;
@ -129,10 +130,8 @@ typedef struct dvr_entry {
char *de_creator;
char *de_filename; /* Initially null if no filename has been
generated yet */
char *de_title; /* Title in UTF-8 (from EPG) */
char *de_ititle; /* Internal title optionally with channelname
date and time pre/post/fixed */
char *de_desc; /* Description in UTF-8 (from EPG) */
lang_str_t *de_title; /* Title in UTF-8 (from EPG) */
lang_str_t *de_desc; /* Description in UTF-8 (from EPG) */
epg_genre_t de_content_type; /* Content type (from EPG) */
dvr_prio_t de_pri;
@ -235,6 +234,8 @@ typedef struct dvr_autorec_entry {
* Prototypes
*/
void dvr_make_title(char *output, size_t outlen, dvr_entry_t *de);
dvr_config_t *dvr_config_find_by_name(const char *name);
dvr_config_t *dvr_config_find_by_name_default(const char *name);
@ -266,7 +267,9 @@ dvr_entry_t *dvr_entry_create(const char *dvr_config_name,
const char *creator, dvr_autorec_entry_t *dae,
dvr_prio_t pri);
dvr_entry_t *dvr_entry_update(dvr_entry_t *de, const char* de_title, int de_start, int de_stop);
dvr_entry_t *dvr_entry_update
(dvr_entry_t *de, const char* de_title, const char *lang,
int de_start, int de_stop);
void dvr_init(void);

View file

@ -89,9 +89,11 @@ autorec_cmp(dvr_autorec_entry_t *dae, epg_broadcast_t *e)
if (!e->episode->brand || dae->dae_brand != e->episode->brand) return 0;
if(dae->dae_title != NULL && dae->dae_title[0] != '\0') {
if(e->episode->title == NULL ||
regexec(&dae->dae_title_preg, e->episode->title, 0, NULL, 0))
return 0;
lang_str_ele_t *ls;
if(!e->episode->title) return 0;
RB_FOREACH(ls, e->episode->title, link)
if (!regexec(&dae->dae_title_preg, ls->str, 0, NULL, 0)) break;
if (!ls) return 0;
}
// Note: ignore channel test if we allow quality unlocking
@ -560,7 +562,7 @@ void dvr_autorec_add_series_link
epg_episode_get_epnum(ee, &epnum);
epnump = &epnum;
}
_dvr_autorec_add(dvr_config_name, event->episode->title,
_dvr_autorec_add(dvr_config_name, NULL,/*TODO DVR lang_str event->episode->title,*/
cfg->dvr_sl_channel_lock ? event->channel : NULL,
NULL, 0, // tag/content type
cfg->dvr_sl_brand_lock ? ee->brand : NULL,

View file

@ -153,7 +153,7 @@ dvr_entry_notify(dvr_entry_t *de)
/**
*
*/
static void
void
dvr_make_title(char *output, size_t outlen, dvr_entry_t *de)
{
struct tm tm;
@ -167,7 +167,7 @@ dvr_make_title(char *output, size_t outlen, dvr_entry_t *de)
output[0] = 0;
snprintf(output + strlen(output), outlen - strlen(output),
"%s", de->de_title);
"%s", lang_str_get(de->de_title, NULL));
localtime_r(&de->de_start, &tm);
@ -210,13 +210,8 @@ static void
dvr_entry_link(dvr_entry_t *de)
{
time_t now, preamble;
char buf[100];
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
dvr_make_title(buf, sizeof(buf), de);
de->de_ititle = strdup(buf);
de->de_refcnt = 1;
LIST_INSERT_HEAD(&dvrentries, de, de_global_link);
@ -301,8 +296,22 @@ static dvr_entry_t *_dvr_entry_create (
de->de_stop_extra = cfg->dvr_extra_time_post;
de->de_config_name = strdup(cfg->dvr_config_name);
de->de_creator = strdup(creator);
de->de_title = strdup(title);
de->de_desc = description ? strdup(description) : NULL;
de->de_desc = NULL;
if (e && e->episode) {
de->de_title = lang_str_copy(e->episode->title);
if (e->episode->description)
de->de_desc = lang_str_copy(e->episode->description);
else if (e->episode->summary)
de->de_desc = lang_str_copy(e->episode->summary);
} else if (title) {
de->de_title = lang_str_create();
lang_str_add(de->de_title, title, NULL, 0);
if (description) {
de->de_desc = lang_str_create();
lang_str_add(de->de_desc, description, NULL, 0);
}
}
if (content_type) de->de_content_type = *content_type;
de->de_bcast = e;
if (e) e->getref((epg_object_t*)e);
@ -320,7 +329,7 @@ static dvr_entry_t *_dvr_entry_create (
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\" starting at %s, "
"scheduled for recording by \"%s\"",
de->de_title, de->de_channel->ch_name, tbuf, creator);
lang_str_get(de->de_title, NULL), de->de_channel->ch_name, tbuf, creator);
dvrdb_changed();
dvr_entry_save(de);
@ -362,9 +371,7 @@ dvr_entry_create_by_event(const char *config_name,
return _dvr_entry_create(config_name, e,
e->channel, e->start, e->stop,
start_extra, stop_extra,
e->episode->title,
e->episode->description ? e->episode->description
: e->episode->summary,
NULL, NULL,
LIST_FIRST(&e->episode->genre),
creator, dae, pri);
}
@ -416,9 +423,8 @@ dvr_entry_dec_ref(dvr_entry_t *de)
free(de->de_config_name);
free(de->de_creator);
free(de->de_title);
free(de->de_ititle);
free(de->de_desc);
if (de->de_title) lang_str_destroy(de->de_title);
if (de->de_desc) lang_str_destroy(de->de_desc);
if(de->de_bcast) de->de_bcast->putref((epg_object_t*)de->de_bcast);
free(de);
@ -456,11 +462,12 @@ static void
dvr_db_load_one(htsmsg_t *c, int id)
{
dvr_entry_t *de;
const char *s, *title, *creator;
const char *s, *creator;
channel_t *ch;
uint32_t start, stop, bcid;
int d;
dvr_config_t *cfg;
lang_str_t *title, *ls;
if(htsmsg_get_u32(c, "start", &start))
return;
@ -475,7 +482,7 @@ dvr_db_load_one(htsmsg_t *c, int id)
s = htsmsg_get_str(c, "config_name");
cfg = dvr_config_find_by_name_default(s);
if((title = htsmsg_get_str(c, "title")) == NULL)
if(!(title = lang_str_deserialize(c, "title")))
return;
if((creator = htsmsg_get_str(c, "creator")) == NULL)
@ -493,7 +500,7 @@ dvr_db_load_one(htsmsg_t *c, int id)
de->de_stop = stop;
de->de_config_name = strdup(cfg->dvr_config_name);
de->de_creator = strdup(creator);
de->de_title = strdup(title);
de->de_title = title;
de->de_pri = dvr_pri2val(htsmsg_get_str(c, "pri"));
if(htsmsg_get_s32(c, "start_extra", &d))
@ -513,7 +520,8 @@ dvr_db_load_one(htsmsg_t *c, int id)
de->de_stop_extra = d;
tvh_str_set(&de->de_desc, htsmsg_get_str(c, "description"));
if ((ls = lang_str_deserialize(c, "description")))
de->de_desc = ls;
tvh_str_set(&de->de_filename, htsmsg_get_str(c, "filename"));
htsmsg_get_u32(c, "errorcode", &de->de_last_error);
@ -592,10 +600,10 @@ dvr_entry_save(dvr_entry_t *de)
if(de->de_filename != NULL)
htsmsg_add_str(m, "filename", de->de_filename);
htsmsg_add_str(m, "title", de->de_title);
lang_str_serialize(de->de_title, m, "title");
if(de->de_desc != NULL)
htsmsg_add_str(m, "description", de->de_desc);
lang_str_serialize(de->de_desc, m, "description");
htsmsg_add_str(m, "pri", dvr_val2pri(de->de_pri));
@ -635,64 +643,59 @@ dvr_timer_expire(void *aux)
}
static dvr_entry_t *_dvr_entry_update
( dvr_entry_t *de, epg_broadcast_t *e, const char *de_title,
int de_start, int de_stop )
( dvr_entry_t *de, epg_broadcast_t *e,
const char *title, const char *lang,
int start, int stop )
{
int save = 0;
const char *title;
int start, stop;
/* Start/Stop */
if (e) {
if (e->episode)
title = e->episode->title;
else
title = NULL;
start = e->start;
stop = e->stop;
} else {
title = de_title;
start = de_start;
stop = de_stop;
}
if (title && (!de->de_title || strcmp(de->de_title, title))) {
free(de->de_title);
de->de_title = strdup(title);
save = 1;
}
if (stop != de->de_stop) {
de->de_stop = stop;
save = 1;
}
if (start != de->de_start) {
if (start && (start != de->de_start)) {
de->de_start = start;
save = 1;
}
if (stop && (stop != de->de_stop)) {
de->de_stop = stop;
save = 1;
}
if (e) {
epg_genre_t *g;
if (e->episode && (g = LIST_FIRST(&e->episode->genre))) {
if (g->code != de->de_content_type.code) {
de->de_content_type.code = g->code;
save = 1;
}
}
if (de->de_bcast != e) {
de->de_bcast->putref((epg_object_t*)de->de_bcast);
de->de_bcast = e;
e->getref((epg_object_t*)e);
/* Title */
if (e && e->episode && e->episode->title) {
if (de->de_title) lang_str_destroy(de->de_title);
de->de_title = lang_str_copy(e->episode->title);
} else if (title) {
if (!de->de_title) de->de_title = lang_str_create();
save = lang_str_add(de->de_title, title, lang, 1);
}
/* Genre */
if (e && e->episode) {
epg_genre_t *g = LIST_FIRST(&e->episode->genre);
if (g && (g->code != de->de_content_type.code)) {
de->de_content_type.code = g->code;
save = 1;
}
}
/* Broadcast */
if (e && (de->de_bcast != e)) {
de->de_bcast->putref(de->de_bcast);
de->de_bcast = e;
e->getref(e);
save = 1;
}
/* Save changes */
if (save) {
dvr_entry_save(de);
htsp_dvr_entry_update(de);
dvr_entry_notify(de);
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\": Updated Timer", de->de_title, de->de_channel->ch_name);
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\": Updated Timer",
lang_str_get(de->de_title, NULL), de->de_channel->ch_name);
}
return de;
@ -702,9 +705,11 @@ static dvr_entry_t *_dvr_entry_update
*
*/
dvr_entry_t *
dvr_entry_update(dvr_entry_t *de, const char* de_title, int de_start, int de_stop)
dvr_entry_update
(dvr_entry_t *de, const char* de_title, const char *lang,
int de_start, int de_stop)
{
return _dvr_entry_update(de, NULL, de_title, de_start, de_stop);
return _dvr_entry_update(de, NULL, de_title, lang, de_start, de_stop);
}
/**
@ -725,7 +730,7 @@ dvr_event_replaced(epg_broadcast_t *e, epg_broadcast_t *new_e)
if (ude == NULL && de->de_sched_state == DVR_SCHEDULED)
dvr_entry_cancel(de);
else if(new_e->episode && new_e->episode->title)
_dvr_entry_update(de, new_e, NULL, 0, 0);
_dvr_entry_update(de, new_e, NULL, NULL, 0, 0);
}
}
@ -733,7 +738,7 @@ void dvr_event_updated ( epg_broadcast_t *e )
{
dvr_entry_t *de;
de = dvr_entry_find_by_event(e);
if (de) _dvr_entry_update(de, e, NULL, 0, 0);
if (de) _dvr_entry_update(de, e, NULL, NULL, 0, 0);
}
/**

View file

@ -69,7 +69,7 @@ dvr_rec_subscribe(dvr_entry_t *de)
else
weight = 300;
snprintf(buf, sizeof(buf), "DVR: %s", de->de_title);
snprintf(buf, sizeof(buf), "DVR: %s", lang_str_get(de->de_title, NULL));
if(de->de_mc == MC_PASS) {
streaming_queue_init(&de->de_sq, SMT_PACKET);
@ -196,11 +196,11 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
char path[500];
int tally = 0;
struct stat st;
char *filename;
char filename[1000];
struct tm tm;
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
filename = strdup(de->de_ititle);
dvr_make_title(filename, sizeof(filename), de);
cleanupfilename(filename,cfg->dvr_flags);
snprintf(path, sizeof(path), "%s", cfg->dvr_storage);
@ -232,7 +232,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
if(cfg->dvr_flags & DVR_DIR_PER_TITLE) {
char *title = strdup(de->de_title);
char *title = strdup(lang_str_get(de->de_title, NULL));
cleanupfilename(title,cfg->dvr_flags);
snprintf(path + strlen(path), sizeof(path) - strlen(path),
"/%s", title);
@ -242,7 +242,6 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
/* */
if(makedirs(path) != 0) {
free(filename);
return -1;
}
@ -270,7 +269,6 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
tvh_str_set(&de->de_filename, fullname);
free(filename);
return 0;
}
@ -290,7 +288,7 @@ dvr_rec_fatal_error(dvr_entry_t *de, const char *fmt, ...)
tvhlog(LOG_ERR, "dvr",
"Recording error: \"%s\": %s",
de->de_filename ?: de->de_title, msgbuf);
de->de_filename ?: lang_str_get(de->de_title, NULL), msgbuf);
}
@ -336,7 +334,7 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
return;
}
if(muxer_init(de->de_mux, ss, de->de_ititle)) {
if(muxer_init(de->de_mux, ss, lang_str_get(de->de_title, NULL))) {
dvr_rec_fatal_error(de, "Unable to init file");
return;
}
@ -352,7 +350,7 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
"adapter: \"%s\", "
"network: \"%s\", mux: \"%s\", provider: \"%s\", "
"service: \"%s\"",
de->de_ititle,
de->de_filename ?: lang_str_get(de->de_title, NULL),
si->si_adapter ?: "<N/A>",
si->si_network ?: "<N/A>",
si->si_mux ?: "<N/A>",
@ -459,7 +457,7 @@ dvr_thread(void *aux)
tvhlog(LOG_INFO,
"dvr", "Recording completed: \"%s\"",
de->de_filename ?: de->de_title);
de->de_filename ?: lang_str_get(de->de_title, NULL));
} else {
@ -468,7 +466,7 @@ dvr_thread(void *aux)
tvhlog(LOG_ERR,
"dvr", "Recording stopped: \"%s\": %s",
de->de_filename ?: de->de_title,
de->de_filename ?: lang_str_get(de->de_title, NULL),
streaming_code2txt(sm->sm_code));
}
}
@ -494,7 +492,7 @@ dvr_thread(void *aux)
dvr_rec_set_state(de, DVR_RS_ERROR, code);
tvhlog(LOG_ERR,
"dvr", "Streaming error: \"%s\": %s",
de->de_filename ?: de->de_title,
de->de_filename ?: lang_str_get(de->de_title, NULL),
streaming_code2txt(code));
}
}
@ -507,7 +505,7 @@ dvr_thread(void *aux)
tvhlog(LOG_ERR,
"dvr", "Recording unable to start: \"%s\": %s",
de->de_filename ?: de->de_title,
de->de_filename ?: lang_str_get(de->de_title, NULL),
streaming_code2txt(sm->sm_code));
}
break;
@ -531,7 +529,7 @@ dvr_thread(void *aux)
static void
dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc)
{
char *fmap[256];
const char *fmap[256];
char **args;
char start[16];
char stop[16];
@ -554,8 +552,8 @@ dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc)
fmap['b'] = basename(fbasename); /* basename of recoding */
fmap['c'] = de->de_channel->ch_name; /* channel name */
fmap['C'] = de->de_creator; /* user who created this recording */
fmap['t'] = de->de_title; /* program title */
fmap['d'] = de->de_desc; /* program description */
fmap['t'] = lang_str_get(de->de_title, NULL); /* program title */
fmap['d'] = lang_str_get(de->de_desc, NULL); /* program description */
/* error message, empty if no error (FIXME:?) */
fmap['e'] = tvh_strdupa(streaming_code2txt(de->de_last_error));
fmap['S'] = start; /* start time, unix epoch */

View file

@ -416,7 +416,7 @@ mk_write_segment_header(mk_mux_t *mkm, int64_t size)
*
*/
static htsbuf_queue_t *
build_tag_string(const char *name, const char *value,
build_tag_string(const char *name, const char *value, const char *lang,
int targettype, const char *targettypename)
{
htsbuf_queue_t *q = htsbuf_queue_alloc(0);
@ -432,7 +432,7 @@ build_tag_string(const char *name, const char *value,
ebml_append_string(st, 0x45a3, name);
ebml_append_string(st, 0x4487, value);
ebml_append_uint(st, 0x4484, 1);
ebml_append_string(st, 0x447a, "und");
ebml_append_string(st, 0x447a, lang ?: "und");
ebml_append_master(q, 0x67c8, st);
return q;
@ -448,7 +448,7 @@ build_tag_int(const char *name, int value,
{
char str[64];
snprintf(str, sizeof(str), "%d", value);
return build_tag_string(name, str, targettype, targettypename);
return build_tag_string(name, str, NULL, targettype, targettypename);
}
@ -475,6 +475,7 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
localtime_r(de ? &de->de_start : &ebc->start, &tm);
epg_episode_t *ee = NULL;
channel_t *ch;
lang_str_t *ls = NULL;
if (ebc) ee = ebc->episode;
else if (de->de_bcast) ee = de->de_bcast->episode;
@ -491,9 +492,9 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
tm.tm_min,
tm.tm_sec);
addtag(q, build_tag_string("DATE_BROADCASTED", datestr, 0, NULL));
addtag(q, build_tag_string("DATE_BROADCASTED", datestr, NULL, 0, NULL));
addtag(q, build_tag_string("ORIGINAL_MEDIA_TYPE", "TV", 0, NULL));
addtag(q, build_tag_string("ORIGINAL_MEDIA_TYPE", "TV", NULL, 0, NULL));
if(de && de->de_content_type.code) {
eg = &de->de_content_type;
@ -501,17 +502,22 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
eg = LIST_FIRST(&ee->genre);
}
if(eg && epg_genre_get_str(eg, 1, 0, ctype, 100))
addtag(q, build_tag_string("CONTENT_TYPE", ctype, 0, NULL));
addtag(q, build_tag_string("CONTENT_TYPE", ctype, NULL, 0, NULL));
if(ch)
addtag(q, build_tag_string("TVCHANNEL", ch->ch_name, 0, NULL));
addtag(q, build_tag_string("TVCHANNEL", ch->ch_name, NULL, 0, NULL));
if(de && de->de_desc)
addtag(q, build_tag_string("SUMMARY", de->de_desc, 0, NULL));
ls = de->de_desc;
else if (ee && ee->description)
addtag(q, build_tag_string("SUMMARY", ee->description, 0, NULL));
ls = ee->description;
else if (ee && ee->summary)
addtag(q, build_tag_string("SUMMARY", ee->summary, 0, NULL));
ls = ee->summary;
if (ls) {
lang_str_ele_t *e;
RB_FOREACH(e, ls, link)
addtag(q, build_tag_string("SUMMARY", e->str, e->lang, 0, NULL));
}
if (ee) {
epg_episode_num_t num;
@ -527,7 +533,7 @@ _mk_build_metadata(const dvr_entry_t *de, const epg_broadcast_t *ebc)
40, "PART"));
if (num.text)
addtag(q, build_tag_string("SYNOPSIS",
num.text, 0, NULL));
num.text, NULL, 0, NULL));
}
return q;

232
src/epg.c
View file

@ -272,7 +272,7 @@ static int _epg_object_set_str
( void *o, char **old, const char *new, epggrab_module_t *src )
{
int save = 0;
epg_object_t *eo = (epg_object_t*)o;
epg_object_t *eo = o;
if ( !eo || !new ) return 0;
if ( !_epg_object_set_grabber(eo, src) && *old ) return 0;
if ( !*old || strcmp(*old, new) ) {
@ -284,6 +284,21 @@ static int _epg_object_set_str
return save;
}
static int _epg_object_set_lang_str
( void *o, lang_str_t **old, const char *newstr, const char *newlang,
epggrab_module_t *src )
{
int update, save;
epg_object_t *eo = o;
if ( !eo || !newstr ) return 0;
update = _epg_object_set_grabber(eo, src);
if (!*old) *old = lang_str_create();
save = lang_str_add(*old, newstr, newlang, update);
if (save)
_epg_object_set_updated(eo);
return save;
}
static int _epg_object_set_u8
( void *o, uint8_t *old, const uint8_t new, epggrab_module_t *src )
{
@ -326,8 +341,8 @@ static void _epg_brand_destroy ( void *eo )
assert(0);
}
_epg_object_destroy(eo, &epg_brands);
if (eb->title) free(eb->title);
if (eb->summary) free(eb->summary);
if (eb->title) lang_str_destroy(eb->title);
if (eb->summary) lang_str_destroy(eb->summary);
if (eb->image) free(eb->image);
free(eb);
}
@ -364,17 +379,19 @@ epg_brand_t *epg_brand_find_by_id ( uint64_t id )
}
int epg_brand_set_title
( epg_brand_t *brand, const char *title, epggrab_module_t *src )
( epg_brand_t *brand, const char *title, const char *lang,
epggrab_module_t *src )
{
if (!brand || !title) return 0;
return _epg_object_set_str(brand, &brand->title, title, src);
if (!brand || !title || !*title) return 0;
return _epg_object_set_lang_str(brand, &brand->title, title, lang, src);
}
int epg_brand_set_summary
( epg_brand_t *brand, const char *summary, epggrab_module_t *src )
( epg_brand_t *brand, const char *summary, const char *lang,
epggrab_module_t *src )
{
if (!brand || !summary) return 0;
return _epg_object_set_str(brand, &brand->summary, summary, src);
if (!brand || !summary || !*summary) return 0;
return _epg_object_set_lang_str(brand, &brand->summary, summary, lang, src);
}
int epg_brand_set_image
@ -429,9 +446,9 @@ htsmsg_t *epg_brand_serialize ( epg_brand_t *brand )
if ( !brand || !brand->uri ) return NULL;
if ( !(m = _epg_object_serialize(brand)) ) return NULL;
if (brand->title)
htsmsg_add_str(m, "title", brand->title);
lang_str_serialize(brand->title, m, "title");
if (brand->summary)
htsmsg_add_str(m, "summary", brand->summary);
lang_str_serialize(brand->summary, m, "summary");
if (brand->season_count)
htsmsg_add_u32(m, "season-count", brand->season_count);
return m;
@ -442,15 +459,22 @@ epg_brand_t *epg_brand_deserialize ( htsmsg_t *m, int create, int *save )
epg_object_t **skel = _epg_brand_skel();
epg_brand_t *eb;
uint32_t u32;
const char *str;
lang_str_t *ls;
lang_str_ele_t *e;
if ( !_epg_object_deserialize(m, *skel) ) return NULL;
if ( !(eb = epg_brand_find_by_uri((*skel)->uri, create, save)) ) return NULL;
if ( (str = htsmsg_get_str(m, "title")) )
*save |= epg_brand_set_title(eb, str, NULL);
if ( (str = htsmsg_get_str(m, "summary")) )
*save |= epg_brand_set_summary(eb, str, NULL);
if ((ls = lang_str_deserialize(m, "title"))) {
RB_FOREACH(e, ls, link)
*save |= epg_brand_set_title(eb, e->str, e->lang, NULL);
lang_str_destroy(ls);
}
if ((ls = lang_str_deserialize(m, "summary"))) {
RB_FOREACH(e, ls, link)
*save |= epg_brand_set_summary(eb, e->str, e->lang, NULL);
lang_str_destroy(ls);
}
if ( !htsmsg_get_u32(m, "season-count", &u32) )
*save |= epg_brand_set_season_count(eb, u32, NULL);
@ -470,6 +494,18 @@ htsmsg_t *epg_brand_list ( void )
return a;
}
const char *epg_brand_get_title ( const epg_brand_t *b, const char *lang )
{
if (!b || !b->title) return NULL;
return lang_str_get(b->title, lang);
}
const char *epg_brand_get_summary ( const epg_brand_t *b, const char *lang )
{
if (!b || !b->summary) return NULL;
return lang_str_get(b->summary, lang);
}
/* **************************************************************************
* Season
* *************************************************************************/
@ -482,8 +518,8 @@ static void _epg_season_destroy ( void *eo )
assert(0);
}
_epg_object_destroy(eo, &epg_seasons);
if (es->brand) _epg_brand_rem_season(es->brand, es);
if (es->summary) free(es->summary);
if (es->brand) _epg_brand_rem_season(es->brand, es);
if (es->summary) lang_str_destroy(es->summary);
if (es->image) free(es->image);
free(es);
}
@ -520,10 +556,11 @@ epg_season_t *epg_season_find_by_id ( uint64_t id )
}
int epg_season_set_summary
( epg_season_t *season, const char *summary, epggrab_module_t *src )
( epg_season_t *season, const char *summary, const char *lang,
epggrab_module_t *src )
{
if (!season || !summary) return 0;
return _epg_object_set_str(season, &season->summary, summary, src);
if (!season || !summary || !*summary) return 0;
return _epg_object_set_lang_str(season, &season->summary, summary, lang, src);
}
int epg_season_set_image
@ -585,7 +622,7 @@ htsmsg_t *epg_season_serialize ( epg_season_t *season )
if (!season || !season->uri) return NULL;
if (!(m = _epg_object_serialize((epg_object_t*)season))) return NULL;
if (season->summary)
htsmsg_add_str(m, "summary", season->summary);
lang_str_serialize(season->summary, m, "summary");
if (season->number)
htsmsg_add_u32(m, "number", season->number);
if (season->episode_count)
@ -602,12 +639,18 @@ epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save )
epg_brand_t *eb;
uint32_t u32;
const char *str;
lang_str_t *ls;
lang_str_ele_t *e;
if ( !_epg_object_deserialize(m, *skel) ) return NULL;
if ( !(es = epg_season_find_by_uri((*skel)->uri, create, save)) ) return NULL;
if ( (str = htsmsg_get_str(m, "summary")) )
*save |= epg_season_set_summary(es, str, NULL);
if ((ls = lang_str_deserialize(m, "summary"))) {
RB_FOREACH(e, ls, link) {
*save |= epg_season_set_summary(es, e->str, e->lang, NULL);
}
lang_str_destroy(ls);
}
if ( !htsmsg_get_u32(m, "number", &u32) )
*save |= epg_season_set_number(es, u32, NULL);
if ( !htsmsg_get_u32(m, "episode-count", &u32) )
@ -620,6 +663,13 @@ epg_season_t *epg_season_deserialize ( htsmsg_t *m, int create, int *save )
return es;
}
const char *epg_season_get_summary
( const epg_season_t *s, const char *lang )
{
if (!s || !s->summary) return NULL;
return lang_str_get(s->summary, lang);
}
/* **************************************************************************
* Episode
* *************************************************************************/
@ -684,10 +734,10 @@ static void _epg_episode_destroy ( void *eo )
_epg_object_destroy(eo, &epg_episodes);
if (ee->brand) _epg_brand_rem_episode(ee->brand, ee);
if (ee->season) _epg_season_rem_episode(ee->season, ee);
if (ee->title) free(ee->title);
if (ee->subtitle) free(ee->subtitle);
if (ee->summary) free(ee->summary);
if (ee->description) free(ee->description);
if (ee->title) lang_str_destroy(ee->title);
if (ee->subtitle) lang_str_destroy(ee->subtitle);
if (ee->summary) lang_str_destroy(ee->summary);
if (ee->description) lang_str_destroy(ee->description);
while ((g = LIST_FIRST(&ee->genre))) {
LIST_REMOVE(g, link);
free(g);
@ -728,31 +778,38 @@ epg_episode_t *epg_episode_find_by_id ( uint64_t id )
}
int epg_episode_set_title
( epg_episode_t *episode, const char *title, epggrab_module_t *src )
( epg_episode_t *episode, const char *title, const char *lang,
epggrab_module_t *src )
{
if (!episode || !title) return 0;
return _epg_object_set_str(episode, &episode->title, title, src);
if (!episode || !title || !*title) return 0;
return _epg_object_set_lang_str(episode, &episode->title, title, lang, src);
}
int epg_episode_set_subtitle
( epg_episode_t *episode, const char *subtitle, epggrab_module_t *src )
( epg_episode_t *episode, const char *subtitle, const char *lang,
epggrab_module_t *src )
{
if (!episode || !subtitle) return 0;
return _epg_object_set_str(episode, &episode->subtitle, subtitle, src);
if (!episode || !subtitle || !*subtitle) return 0;
return _epg_object_set_lang_str(episode, &episode->subtitle,
subtitle, lang, src);
}
int epg_episode_set_summary
( epg_episode_t *episode, const char *summary, epggrab_module_t *src )
( epg_episode_t *episode, const char *summary, const char *lang,
epggrab_module_t *src )
{
if (!episode || !summary) return 0;
return _epg_object_set_str(episode, &episode->summary, summary, src);
if (!episode || !summary || !*summary) return 0;
return _epg_object_set_lang_str(episode, &episode->summary,
summary, lang, src);
}
int epg_episode_set_description
( epg_episode_t *episode, const char *desc, epggrab_module_t *src )
( epg_episode_t *episode, const char *desc, const char *lang,
epggrab_module_t *src )
{
if (!episode || !desc) return 0;
return _epg_object_set_str(episode, &episode->description, desc, src);
if (!episode || !desc || !*desc) return 0;
return _epg_object_set_lang_str(episode, &episode->description,
desc, lang, src);
}
int epg_episode_set_image
@ -948,6 +1005,7 @@ int epg_episode_number_cmp ( epg_episode_num_t *a, epg_episode_num_t *b )
// WIBNI: this could do with soem proper matching, maybe some form of
// fuzzy string match. I did try a few things, but none of them
// were very reliable.
#if TODO_FUZZY_MATCH
int epg_episode_fuzzy_match
( epg_episode_t *episode, const char *uri, const char *title,
const char *summary, const char *description )
@ -957,6 +1015,7 @@ int epg_episode_fuzzy_match
if ( title && episode->title && (strstr(title, episode->title) || strstr(episode->title, title)) ) return 1;
return 0;
}
#endif
htsmsg_t *epg_episode_serialize ( epg_episode_t *episode )
{
@ -965,13 +1024,13 @@ htsmsg_t *epg_episode_serialize ( epg_episode_t *episode )
if (!episode || !episode->uri) return NULL;
if (!(m = _epg_object_serialize((epg_object_t*)episode))) return NULL;
if (episode->title)
htsmsg_add_str(m, "title", episode->title);
lang_str_serialize(episode->title, m, "title");
if (episode->subtitle)
htsmsg_add_str(m, "subtitle", episode->subtitle);
lang_str_serialize(episode->subtitle, m, "subtitle");
if (episode->summary)
htsmsg_add_str(m, "summary", episode->summary);
lang_str_serialize(episode->summary, m, "summary");
if (episode->description)
htsmsg_add_str(m, "description", episode->description);
lang_str_serialize(episode->description, m, "description");
htsmsg_add_msg(m, "epnum", epg_episode_num_serialize(&episode->epnum));
LIST_FOREACH(eg, &episode->genre, link) {
if (!a) a = htsmsg_create_list();
@ -998,18 +1057,33 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
htsmsg_t *sub;
htsmsg_field_t *f;
uint32_t u32;
lang_str_t *ls;
lang_str_ele_t *e;
if ( !_epg_object_deserialize(m, *skel) ) return NULL;
if ( !(ee = epg_episode_find_by_uri((*skel)->uri, create, save)) ) return NULL;
if ( !(ee = epg_episode_find_by_uri((*skel)->uri, create, save)) )
return NULL;
if ( (str = htsmsg_get_str(m, "title")) )
*save |= epg_episode_set_title(ee, str, NULL);
if ( (str = htsmsg_get_str(m, "subtitle")) )
*save |= epg_episode_set_subtitle(ee, str, NULL);
if ( (str = htsmsg_get_str(m, "summary")) )
*save |= epg_episode_set_summary(ee, str, NULL);
if ( (str = htsmsg_get_str(m, "description")) )
*save |= epg_episode_set_description(ee, str, NULL);
if ((ls = lang_str_deserialize(m, "title"))) {
RB_FOREACH(e, ls, link)
*save |= epg_episode_set_title(ee, e->str, e->lang, NULL);
lang_str_destroy(ls);
}
if ((ls = lang_str_deserialize(m, "subtitle"))) {
RB_FOREACH(e, ls, link)
*save |= epg_episode_set_subtitle(ee, e->str, e->lang, NULL);
lang_str_destroy(ls);
}
if ((ls = lang_str_deserialize(m, "summary"))) {
RB_FOREACH(e, ls, link)
*save |= epg_episode_set_summary(ee, e->str, e->lang, NULL);
lang_str_destroy(ls);
}
if ((ls = lang_str_deserialize(m, "description"))) {
RB_FOREACH(e, ls, link)
*save |= epg_episode_set_description(ee, e->str, e->lang, NULL);
lang_str_destroy(ls);
}
if ( (sub = htsmsg_get_map(m, "epnum")) ) {
epg_episode_num_deserialize(sub, &num);
*save |= epg_episode_set_epnum(ee, &num, NULL);
@ -1039,6 +1113,34 @@ epg_episode_t *epg_episode_deserialize ( htsmsg_t *m, int create, int *save )
return ee;
}
const char *epg_episode_get_title
( const epg_episode_t *e, const char *lang )
{
if (!e || !e->title) return NULL;
return lang_str_get(e->title, lang);
}
const char *epg_episode_get_subtitle
( const epg_episode_t *e, const char *lang )
{
if (!e || !e->subtitle) return NULL;
return lang_str_get(e->subtitle, lang);
}
const char *epg_episode_get_summary
( const epg_episode_t *e, const char *lang )
{
if (!e || !e->summary) return NULL;
return lang_str_get(e->summary, lang);
}
const char *epg_episode_get_description
( const epg_episode_t *e, const char *lang )
{
if (!e || !e->description) return NULL;
return lang_str_get(e->description, lang);
}
/* **************************************************************************
* Channel
* *************************************************************************/
@ -1757,13 +1859,15 @@ 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 )
epg_genre_t *genre, regex_t *preg, time_t start, const char *lang )
{
const char *title;
/* Ignore */
if ( e->stop < start ) return;
if ( !e->episode->title ) 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, e->episode->title, 0, NULL, 0) ) return;
if ( preg && regexec(preg, title, 0, NULL, 0)) return;
/* More space */
if ( eqr->eqr_entries == eqr->eqr_alloced ) {
@ -1778,17 +1882,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 )
regex_t *preg, time_t start, const char *lang )
{
epg_broadcast_t *ebc;
RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) {
if ( ebc->episode ) _eqr_add(eqr, ebc, genre, preg, start);
if ( ebc->episode ) _eqr_add(eqr, ebc, genre, preg, start, lang);
}
}
void epg_query0
( epg_query_result_t *eqr, channel_t *channel, channel_tag_t *tag,
epg_genre_t *genre, const char *title )
epg_genre_t *genre, const char *title, const char *lang )
{
time_t now;
channel_tag_mapping_t *ctm;
@ -1809,19 +1913,19 @@ void epg_query0
/* Single channel */
if (channel && !tag) {
_eqr_add_channel(eqr, channel, genre, preg, now);
_eqr_add_channel(eqr, channel, genre, preg, now, lang);
/* 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);
_eqr_add_channel(eqr, ctm->ctm_channel, genre, preg, now, lang);
}
/* All channels */
} else {
RB_FOREACH(channel, &channel_name_tree, ch_name_link) {
_eqr_add_channel(eqr, channel, genre, preg, now);
_eqr_add_channel(eqr, channel, genre, preg, now, lang);
}
}
if (preg) regfree(preg);
@ -1830,11 +1934,11 @@ void epg_query0
}
void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
epg_genre_t *genre, const char *title)
epg_genre_t *genre, const char *title, const char *lang)
{
channel_t *ch = channel ? channel_find_by_name(channel, 0, 0) : NULL;
channel_tag_t *ct = tag ? channel_tag_find_by_name(tag, 0) : NULL;
epg_query0(eqr, ch, ct, genre, title);
epg_query0(eqr, ch, ct, genre, title, lang);
}
void epg_query_free(epg_query_result_t *eqr)

View file

@ -20,6 +20,7 @@
#define EPG_H
#include "settings.h"
#include "lang_str.h"
/*
* External forward decls
@ -136,8 +137,8 @@ struct epg_brand
{
epg_object_t; ///< Base object
char *title; ///< Brand name
char *summary; ///< Brand summary
lang_str_t *title; ///< Brand name
lang_str_t *summary; ///< Brand summary
uint16_t season_count; ///< Total number of seasons
char *image; ///< Brand image
@ -150,12 +151,20 @@ epg_brand_t *epg_brand_find_by_uri
( const char *uri, int create, int *save );
epg_brand_t *epg_brand_find_by_id ( uint64_t id );
/* Accessors */
const char *epg_brand_get_title
( const epg_brand_t *b, const char *lang );
const char *epg_brand_get_summary
( const epg_brand_t *b, const char *lang );
/* Mutators */
int epg_brand_set_title
( epg_brand_t *b, const char *title, struct epggrab_module *src )
( epg_brand_t *b, const char *title, const char *lang,
struct epggrab_module *src )
__attribute__((warn_unused_result));
int epg_brand_set_summary
( epg_brand_t *b, const char *summary, struct epggrab_module *src )
( epg_brand_t *b, const char *summary, const char *lang,
struct epggrab_module *src )
__attribute__((warn_unused_result));
int epg_brand_set_season_count
( epg_brand_t *b, uint16_t season_count, struct epggrab_module *src )
@ -180,7 +189,7 @@ struct epg_season
{
epg_object_t; ///< Parent object
char *summary; ///< Season summary
lang_str_t *summary; ///< Season summary
uint16_t number; ///< The season number
uint16_t episode_count; ///< Total number of episodes
char *image; ///< Season image
@ -196,9 +205,14 @@ epg_season_t *epg_season_find_by_uri
( const char *uri, int create, int *save );
epg_season_t *epg_season_find_by_id ( uint64_t id );
/* Accessors */
const char *epg_season_get_summary
( const epg_season_t *s, const char *lang );
/* Mutators */
int epg_season_set_summary
( epg_season_t *s, const char *summary, struct epggrab_module *src )
( epg_season_t *s, const char *summary, const char *lang,
struct epggrab_module *src )
__attribute__((warn_unused_result));
int epg_season_set_number
( epg_season_t *s, uint16_t number, struct epggrab_module *src )
@ -240,10 +254,10 @@ struct epg_episode
{
epg_object_t; ///< Parent object
char *title; ///< Title
char *subtitle; ///< Sub-title
char *summary; ///< Summary
char *description; ///< An extended description
lang_str_t *title; ///< Title
lang_str_t *subtitle; ///< Sub-title
lang_str_t *summary; ///< Summary
lang_str_t *description; ///< An extended description
char *image; ///< Episode image
epg_genre_list_t genre; ///< Episode genre(s)
epg_episode_num_t epnum; ///< Episode numbering
@ -265,18 +279,32 @@ epg_episode_t *epg_episode_find_by_uri
( const char *uri, int create, int *save );
epg_episode_t *epg_episode_find_by_id ( uint64_t id );
/* Accessors */
const char *epg_episode_get_title
( const epg_episode_t *e, const char *lang );
const char *epg_episode_get_subtitle
( const epg_episode_t *e, const char *lang );
const char *epg_episode_get_summary
( const epg_episode_t *e, const char *lang );
const char *epg_episode_get_description
( const epg_episode_t *e, const char *lang );
/* Mutators */
int epg_episode_set_title
( epg_episode_t *e, const char *title, struct epggrab_module *src )
( epg_episode_t *e, const char *title, const char *lang,
struct epggrab_module *src )
__attribute__((warn_unused_result));
int epg_episode_set_subtitle
( epg_episode_t *e, const char *subtitle, struct epggrab_module *src )
( epg_episode_t *e, const char *subtitle, const char *lang,
struct epggrab_module *src )
__attribute__((warn_unused_result));
int epg_episode_set_summary
( epg_episode_t *e, const char *summary, struct epggrab_module *src )
( epg_episode_t *e, const char *summary, const char *lang,
struct epggrab_module *src )
__attribute__((warn_unused_result));
int epg_episode_set_description
( epg_episode_t *e, const char *description, struct epggrab_module *src )
( epg_episode_t *e, const char *description, const char *lang,
struct epggrab_module *src )
__attribute__((warn_unused_result));
int epg_episode_set_number
( epg_episode_t *e, uint16_t number, struct epggrab_module *src )
@ -444,9 +472,10 @@ 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);
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);
epg_genre_t *genre, const char *title, const char *lang);
/* ************************************************************************

View file

@ -77,9 +77,9 @@ static void _epgdb_v1_process ( htsmsg_t *c, epggrab_stats_t *stats )
if (!ee) return;
if (save) stats->episodes.total++;
if (title)
save |= epg_episode_set_title(ee, title, NULL);
save |= epg_episode_set_title(ee, title, NULL, NULL);
if (desc)
save |= epg_episode_set_summary(ee, desc, NULL);
save |= epg_episode_set_summary(ee, desc, NULL, NULL);
if (!htsmsg_get_u32(c, "episode", &u32))
save |= epg_episode_set_number(ee, u32, NULL);
if (!htsmsg_get_u32(c, "part", &u32))

View file

@ -93,10 +93,10 @@ typedef struct eit_event
{
char uri[257];
char suri[257];
char title[257];
char summary[257];
char desc[2000];
lang_str_t *title;
lang_str_t *summary;
lang_str_t *desc;
char *default_charset;
@ -169,26 +169,38 @@ static int _eit_desc_short_event
( epggrab_module_t *mod, uint8_t *ptr, int len, eit_event_t *ev )
{
int r;
char lang[4];
char buf[256];
if ( len < 5 ) return -1;
/* Language (skip) */
/* Language */
memcpy(lang, ptr, 3);
lang[3] = '\0';
len -= 3;
ptr += 3;
/* Title */
if ( (r = _eit_get_string_with_len(mod, ev->title, sizeof(ev->title),
ptr, len, ev->default_charset)) < 0 )
if ( (r = _eit_get_string_with_len(mod, buf, sizeof(buf),
ptr, len, ev->default_charset)) < 0 ) {
return -1;
} else {
if (!ev->title) ev->title = lang_str_create();
lang_str_add(ev->title, buf, lang, 0);
}
len -= r;
ptr += r;
if ( len < 1 ) return -1;
/* Summary */
if ( (r = _eit_get_string_with_len(mod, ev->summary, sizeof(ev->summary),
ptr, len, ev->default_charset)) < 0 )
if ( (r = _eit_get_string_with_len(mod, buf, sizeof(buf),
ptr, len, ev->default_charset)) < 0 ) {
return -1;
} else {
if (!ev->summary) ev->summary = lang_str_create();
lang_str_add(ev->summary, buf, lang, 0);
}
return 0;
}
@ -201,6 +213,7 @@ static int _eit_desc_ext_event
{
int r, nitem;
char ikey[256], ival[256];
char buf[256], lang[4];
if (len < 6) return -1;
@ -208,7 +221,9 @@ static int _eit_desc_ext_event
len -= 1;
ptr += 1;
/* Language (skip) */
/* Language */
memcpy(lang, ptr, 3);
lang[3] = '\0';
len -= 3;
ptr += 3;
@ -248,11 +263,14 @@ static int _eit_desc_ext_event
/* Description */
if ( _eit_get_string_with_len(mod,
ev->desc + strlen(ev->desc),
sizeof(ev->desc) - strlen(ev->desc),
buf, sizeof(buf),
ptr, len,
ev->default_charset) < 0 )
ev->default_charset) < 0 ) {
return -1;
} else {
if (!ev->desc) ev->desc = lang_str_create();
lang_str_append(ev->desc, buf, lang);
}
return 0;
}
@ -394,6 +412,7 @@ static int _eit_process_event
epg_episode_t *ee;
epg_season_t *es;
eit_event_t ev;
lang_str_ele_t *ls;
if ( len < 12 ) return -1;
@ -432,7 +451,7 @@ static int _eit_process_event
dllen -= 2;
ptr += 2;
if (dllen < dlen) return ret;
if (dllen < dlen) break;
switch (dtag) {
case DVB_DESC_SHORT_EVENT:
@ -461,7 +480,7 @@ static int _eit_process_event
break;
}
if (r < 0) return ret;
if (r < 0) break;
dllen -= dlen;
ptr += dlen;
}
@ -478,7 +497,9 @@ static int _eit_process_event
ee = epg_episode_find_by_uri(ev.uri, 1, save);
} else if ( !(ee = ebc->episode) ) {
char *uri;
uri = epg_hash(ev.title, ev.summary, ev.desc);
uri = epg_hash(lang_str_get(ev.title, NULL),
lang_str_get(ev.summary, NULL),
lang_str_get(ev.desc, NULL));
if (uri) {
ee = epg_episode_find_by_uri(uri, 1, save);
free(uri);
@ -491,12 +512,18 @@ static int _eit_process_event
/* Update Episode */
if (ee) {
*save |= epg_episode_set_is_bw(ee, ev.bw, mod);
if ( *ev.title )
*save |= epg_episode_set_title(ee, ev.title, mod);
if ( *ev.summary )
*save |= epg_episode_set_summary(ee, ev.summary, mod);
if ( *ev.desc )
*save |= epg_episode_set_description(ee, ev.desc, mod);
if ( ev.title ) {
RB_FOREACH(ls, ev.title, link)
*save |= epg_episode_set_title(ee, ls->str, ls->lang, mod);
}
if ( ev.summary ) {
RB_FOREACH(ls, ev.summary, link)
*save |= epg_episode_set_summary(ee, ls->str, ls->lang, mod);
}
if ( ev.desc ) {
RB_FOREACH(ls, ev.desc, link)
*save |= epg_episode_set_description(ee, ls->str, ls->lang, mod);
}
if ( ev.genre )
*save |= epg_episode_set_genre(ee, ev.genre, mod);
#if TODO_ADD_EXTRA
@ -514,9 +541,12 @@ static int _eit_process_event
/* Tidy up */
#if TODO_ADD_EXTRA
if (ev.extra) htsmsg_destroy(ev.extra);
if (ev.extra) htsmsg_destroy(ev.extra);
#endif
if (ev.genre) epg_genre_list_destroy(ev.genre);
if (ev.genre) epg_genre_list_destroy(ev.genre);
if (ev.title) lang_str_destroy(ev.title);
if (ev.summary) lang_str_destroy(ev.summary);
if (ev.desc) lang_str_destroy(ev.desc);
return ret;
}

View file

@ -355,6 +355,12 @@ static int _opentv_parse_event_section
epg_season_t *es;
opentv_event_t ev;
epggrab_module_t *src = (epggrab_module_t*)mod;
const char *lang = NULL;
const char *str;
/* Get language (bit of a hack) */
if (!strcmp(mod->dict->id, "skyit")) lang = "it";
else if (!strcmp(mod->dict->id, "skyeng")) lang = "eng";
/* Channel */
cid = ((int)buf[0] << 8) | buf[1];
@ -398,14 +404,15 @@ static int _opentv_parse_event_section
/* Update */
if (ee) {
if (!ev.title && ebc->episode)
save |= epg_episode_set_title(ee, ebc->episode->title, src);
else if (ev.title)
save |= epg_episode_set_title(ee, ev.title, src);
if (!ev.title && ebc->episode) {
if ((str = epg_episode_get_title(ebc->episode, NULL)))
save |= epg_episode_set_title(ee, str, lang, NULL);
} else if (ev.title)
save |= epg_episode_set_title(ee, ev.title, lang, src);
if (ev.summary)
save |= epg_episode_set_summary(ee, ev.summary, src);
save |= epg_episode_set_summary(ee, ev.summary, lang, src);
if (ev.desc)
save |= epg_episode_set_description(ee, ev.desc, src);
save |= epg_episode_set_description(ee, ev.desc, lang, src);
if (ev.cat) {
epg_genre_list_t *egl = calloc(1, sizeof(epg_genre_list_t));
epg_genre_list_add_by_eit(egl, ev.cat);

View file

@ -127,12 +127,12 @@ static int _pyepg_parse_brand
/* Set title */
if ((str = htsmsg_xml_get_cdata_str(tags, "title"))) {
save |= epg_brand_set_title(brand, str, mod);
save |= epg_brand_set_title(brand, str, NULL, mod);
}
/* Set summary */
if ((str = htsmsg_xml_get_cdata_str(tags, "summary"))) {
save |= epg_brand_set_summary(brand, str, mod);
save |= epg_brand_set_summary(brand, str, NULL, mod);
}
/* Set image */
@ -182,7 +182,7 @@ static int _pyepg_parse_season
/* Set summary */
if ((str = htsmsg_xml_get_cdata_str(tags, "summary"))) {
save |= epg_season_set_summary(season, str, mod);
save |= epg_season_set_summary(season, str, NULL, mod);
}
/* Set image */
@ -246,15 +246,15 @@ static int _pyepg_parse_episode
/* Set title/subtitle */
if ((str = htsmsg_xml_get_cdata_str(tags, "title"))) {
save |= epg_episode_set_title(episode, str, mod);
save |= epg_episode_set_title(episode, str, NULL, mod);
}
if ((str = htsmsg_xml_get_cdata_str(tags, "subtitle"))) {
save |= epg_episode_set_subtitle(episode, str, mod);
save |= epg_episode_set_subtitle(episode, str, NULL, mod);
}
/* Set summary */
if ((str = htsmsg_xml_get_cdata_str(tags, "summary"))) {
save |= epg_episode_set_summary(episode, str, mod);
save |= epg_episode_set_summary(episode, str, NULL, mod);
}
/* Number */

View file

@ -34,6 +34,7 @@
#include "spawn.h"
#include "htsstr.h"
#include "lang_str.h"
#include "epg.h"
#include "epggrab.h"
#include "epggrab/private.h"
@ -323,6 +324,27 @@ static epg_genre_list_t
return egl;
}
/*
* Parse a series of language strings
*/
static void
_xmltv_parse_lang_str ( lang_str_t **ls, htsmsg_t *tags, const char *tname )
{
htsmsg_t *e, *attrib;
htsmsg_field_t *f;
const char *lang;
HTSMSG_FOREACH(f, tags) {
if (!strcmp(f->hmf_name, tname) && (e = htsmsg_get_map_by_field(f))) {
if (!*ls) *ls = lang_str_create();
lang = NULL;
if ((attrib = htsmsg_get_map(e, "attrib")))
lang = htsmsg_get_str(attrib, "lang");
lang_str_add(*ls, htsmsg_get_str(e, "cdata"), lang, 0);
}
}
}
/**
* Parse tags inside of a programme
*/
@ -338,11 +360,9 @@ static int _xmltv_parse_programme_tags
int sn = 0, sc = 0, en = 0, ec = 0, pn = 0, pc = 0;
const char *onscreen = NULL;
char *suri = NULL, *uri = NULL;
const char *title = htsmsg_xml_get_cdata_str(tags, "title");
const char *desc = htsmsg_xml_get_cdata_str(tags, "desc");
/* Ignore */
if (!title) return 0;
lang_str_t *title = NULL;
lang_str_t *desc = NULL;
lang_str_ele_t *ls;
/*
* Broadcast
@ -369,6 +389,8 @@ static int _xmltv_parse_programme_tags
/* Get episode info */
get_episode_info(mod, tags, &uri, &suri, &onscreen,
&sn, &sc, &en, &ec, &pn, &pc);
_xmltv_parse_lang_str(&title, tags, "title");
_xmltv_parse_lang_str(&desc, tags, "desc");
/*
* Season
@ -383,12 +405,15 @@ static int _xmltv_parse_programme_tags
/*
* Episode
*/
if (!uri)
uri = epg_hash(title, NULL, desc);
if (!uri && title) {
uri = epg_hash(lang_str_get(title, NULL), NULL, lang_str_get(desc, NULL));
}
if (uri) {
ee = epg_episode_find_by_uri(uri, 1, &save);
free(uri);
}
/* Update */
if (ee) {
stats->episodes.total++;
if (save) stats->episodes.created++;
@ -397,10 +422,17 @@ static int _xmltv_parse_programme_tags
if (es)
save |= epg_episode_set_season(ee, es, mod);
if (title)
save |= epg_episode_set_title(ee, title, mod);
if (desc)
save |= epg_episode_set_description(ee, desc, mod);
if (title) {
RB_FOREACH(ls, title, link) {
save |= epg_episode_set_title(ee, ls->str, ls->lang, mod);
}
}
if (desc) {
RB_FOREACH(ls, desc, link) {
save |= epg_episode_set_description(ee, ls->str, ls->lang, mod);
}
}
if ((egl = _xmltv_parse_categories(tags))) {
save |= epg_episode_set_genre(ee, egl, mod);
epg_genre_list_destroy(egl);
@ -417,6 +449,10 @@ static int _xmltv_parse_programme_tags
/* Stats */
if (save2) stats->broadcasts.modified++;
/* Cleanup */
if (title) lang_str_destroy(title);
if (desc) lang_str_destroy(desc);
return save | save2 | save3;
}

View file

@ -382,10 +382,10 @@ htsp_build_dvrentry(dvr_entry_t *de, const char *method)
htsmsg_add_s32(out, "start", de->de_start);
htsmsg_add_s32(out, "stop", de->de_stop);
if( de->de_title != NULL )
htsmsg_add_str(out, "title", de->de_title);
if( de->de_desc != NULL )
htsmsg_add_str(out, "description", de->de_desc);
if( de->de_title && (s = lang_str_get(de->de_title, NULL)))
htsmsg_add_str(out, "title", s);
if( de->de_desc && (s = lang_str_get(de->de_desc, NULL)))
htsmsg_add_str(out, "description", s);
switch(de->de_sched_state) {
case DVR_SCHEDULED:
@ -629,17 +629,11 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
if( (de = dvr_entry_find_by_id(dvrEntryId)) == NULL)
return htsp_error("id not found");
if(htsmsg_get_u32(in, "start", &start))
start = de->de_start;
if(htsmsg_get_u32(in, "stop", &stop))
stop = de->de_stop;
start = htsmsg_get_u32_or_default(in, "start", 0);
stop = htsmsg_get_u32_or_default(in, "stop", 0);
title = htsmsg_get_str(in, "title");
if (title == NULL)
title = de->de_title;
de = dvr_entry_update(de, title, start, stop);
de = dvr_entry_update(de, title, NULL, start, stop);
//create response
out = htsmsg_create_map();
@ -730,7 +724,7 @@ htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
}
//do the query
epg_query0(&eqr, ch, ct, eg, query);
epg_query0(&eqr, ch, ct, eg, query, NULL);
c = eqr.eqr_entries;
// create reply
@ -758,6 +752,7 @@ htsp_build_event(epg_broadcast_t *e)
epg_broadcast_t *n;
dvr_entry_t *de;
epg_genre_t *g;
const char *str;
out = htsmsg_create_map();
@ -766,12 +761,12 @@ htsp_build_event(epg_broadcast_t *e)
htsmsg_add_u32(out, "start", e->start);
htsmsg_add_u32(out, "stop", e->stop);
if (e->episode) {
if(e->episode->title != NULL)
htsmsg_add_str(out, "title", e->episode->title);
if(e->episode->description != NULL)
htsmsg_add_str(out, "description", e->episode->description);
else if(e->episode->summary != NULL)
htsmsg_add_str(out, "description", e->episode->summary);
if ((str = epg_episode_get_title(e->episode, NULL)))
htsmsg_add_str(out, "title", str);
if ((str = epg_episode_get_description(e->episode, NULL)))
htsmsg_add_str(out, "description", str);
else if((str = epg_episode_get_summary(e->episode, NULL)))
htsmsg_add_str(out, "description", str);
if((g = LIST_FIRST(&e->episode->genre)))
htsmsg_add_u32(out, "contentType", g->code);

View file

@ -24,7 +24,7 @@
static void htsstr_argsplit_add(char ***argv, int *argc, char *s);
static int htsstr_format0(const char *str, char *out, char **map);
static int htsstr_format0(const char *str, char *out, const char **map);
char *
hts_strndup(const char *src, size_t len)
@ -150,9 +150,9 @@ htsstr_argsplit_free(char **argv) {
}
static int
htsstr_format0(const char *str, char *out, char **map) {
htsstr_format0(const char *str, char *out, const char **map) {
const char *s = str;
char *f;
const char *f;
int n = 0;
while(*s) {
@ -183,7 +183,7 @@ htsstr_format0(const char *str, char *out, char **map) {
}
char *
htsstr_format(const char *str, char **map)
htsstr_format(const char *str, const char **map)
{
char *s;

View file

@ -29,6 +29,6 @@ char **htsstr_argsplit(const char *str);
void htsstr_argsplit_free(char **argv);
char *htsstr_format(const char *str, char **map);
char *htsstr_format(const char *str, const char **map);
#endif /* HTSSTR_H__ */

561
src/lang_codes.c Normal file
View file

@ -0,0 +1,561 @@
/*
* Multi-language Support - language codes
* Copyright (C) 2012 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; without 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 <string.h>
#include <stdlib.h>
#include "lang_codes.h"
#include "config2.h"
/* **************************************************************************
* Code list
* *************************************************************************/
const lang_code_t lang_codes[] = {
{ "und", NULL, NULL , "Undetermined" },
{ "aar", "aa", NULL , "Afar" },
{ "abk", "ab", NULL , "Abkhazian" },
{ "ace", NULL, NULL , "Achinese" },
{ "ach", NULL, NULL , "Acoli" },
{ "ada", NULL, NULL , "Adangme" },
{ "ady", NULL, NULL , "Adyghe; Adygei" },
{ "afa", NULL, NULL , "Afro-Asiatic languages" },
{ "afh", NULL, NULL , "Afrihili" },
{ "afr", "af", NULL , "Afrikaans" },
{ "ain", NULL, NULL , "Ainu" },
{ "aka", "ak", NULL , "Akan" },
{ "akk", NULL, NULL , "Akkadian" },
{ "alb", "sq", "sqi", "Albanian" },
{ "ale", NULL, NULL , "Aleut" },
{ "alg", NULL, NULL , "Algonquian languages" },
{ "alt", NULL, NULL , "Southern Altai" },
{ "amh", "am", NULL , "Amharic" },
{ "anp", NULL, NULL , "Angika" },
{ "apa", NULL, NULL , "Apache languages" },
{ "ara", "ar", NULL , "Arabic" },
{ "arc", NULL, NULL , "Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)" },
{ "arg", "an", NULL , "Aragonese" },
{ "arm", "hy", "hye", "Armenian" },
{ "arn", NULL, NULL , "Mapudungun; Mapuche" },
{ "arp", NULL, NULL , "Arapaho" },
{ "art", NULL, NULL , "Artificial languages" },
{ "arw", NULL, NULL , "Arawak" },
{ "asm", "as", NULL , "Assamese" },
{ "ast", NULL, NULL , "Asturian; Bable; Leonese; Asturleonese" },
{ "ath", NULL, NULL , "Athapascan languages" },
{ "aus", NULL, NULL , "Australian languages" },
{ "ava", "av", NULL , "Avaric" },
{ "ave", "ae", NULL , "Avestan" },
{ "awa", NULL, NULL , "Awadhi" },
{ "aym", "ay", NULL , "Aymara" },
{ "aze", "az", NULL , "Azerbaijani" },
{ "bad", NULL, NULL , "Banda languages" },
{ "bai", NULL, NULL , "Bamileke languages" },
{ "bak", "ba", NULL , "Bashkir" },
{ "bal", NULL, NULL , "Baluchi" },
{ "bam", "bm", NULL , "Bambara" },
{ "ban", NULL, NULL , "Balinese" },
{ "baq", "eu", "eus", "Basque" },
{ "bas", NULL, NULL , "Basa" },
{ "bat", NULL, NULL , "Baltic languages" },
{ "bej", NULL, NULL , "Beja; Bedawiyet" },
{ "bel", "be", NULL , "Belarusian" },
{ "bem", NULL, NULL , "Bemba" },
{ "ben", "bn", NULL , "Bengali" },
{ "ber", NULL, NULL , "Berber languages" },
{ "bho", NULL, NULL , "Bhojpuri" },
{ "bih", "bh", NULL , "Bihari languages" },
{ "bik", NULL, NULL , "Bikol" },
{ "bin", NULL, NULL , "Bini; Edo" },
{ "bis", "bi", NULL , "Bislama" },
{ "bla", NULL, NULL , "Siksika" },
{ "bnt", NULL, NULL , "Bantu languages" },
{ "bos", "bs", NULL , "Bosnian" },
{ "bra", NULL, NULL , "Braj" },
{ "bre", "br", NULL , "Breton" },
{ "btk", NULL, NULL , "Batak languages" },
{ "bua", NULL, NULL , "Buriat" },
{ "bug", NULL, NULL , "Buginese" },
{ "bul", "bg", NULL , "Bulgarian" },
{ "bur", "my", "mya", "Burmese" },
{ "byn", NULL, NULL , "Blin; Bilin" },
{ "cad", NULL, NULL , "Caddo" },
{ "cai", NULL, NULL , "Central American Indian languages" },
{ "car", NULL, NULL , "Galibi Carib" },
{ "cat", "ca", NULL , "Catalan; Valencian" },
{ "cau", NULL, NULL , "Caucasian languages" },
{ "ceb", NULL, NULL , "Cebuano" },
{ "cel", NULL, NULL , "Celtic languages" },
{ "cha", "ch", NULL , "Chamorro" },
{ "chb", NULL, NULL , "Chibcha" },
{ "che", "ce", NULL , "Chechen" },
{ "chg", NULL, NULL , "Chagatai" },
{ "chi", "zh", "zho", "Chinese" },
{ "chk", NULL, NULL , "Chuukese" },
{ "chm", NULL, NULL , "Mari" },
{ "chn", NULL, NULL , "Chinook jargon" },
{ "cho", NULL, NULL , "Choctaw" },
{ "chp", NULL, NULL , "Chipewyan; Dene Suline" },
{ "chr", NULL, NULL , "Cherokee" },
{ "chu", "cu", NULL , "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic" },
{ "chv", "cv", NULL , "Chuvash" },
{ "chy", NULL, NULL , "Cheyenne" },
{ "cmc", NULL, NULL , "Chamic languages" },
{ "cop", NULL, NULL , "Coptic" },
{ "cor", "kw", NULL , "Cornish" },
{ "cos", "co", NULL , "Corsican" },
{ "cre", "cr", NULL , "Cree" },
{ "crh", NULL, NULL , "Crimean Tatar; Crimean Turkish" },
{ "crp", NULL, NULL , "Creoles and pidgins" },
{ "csb", NULL, NULL , "Kashubian" },
{ "cus", NULL, NULL , "Cushitic languages" },
{ "cze", "cs", "ces", "Czech" },
{ "dak", NULL, NULL , "Dakota" },
{ "dan", "da", NULL , "Danish" },
{ "dar", NULL, NULL , "Dargwa" },
{ "day", NULL, NULL , "Land Dayak languages" },
{ "del", NULL, NULL , "Delaware" },
{ "den", NULL, NULL , "Slave (Athapascan)" },
{ "dgr", NULL, NULL , "Dogrib" },
{ "din", NULL, NULL , "Dinka" },
{ "div", "dv", NULL , "Divehi; Dhivehi; Maldivian" },
{ "doi", NULL, NULL , "Dogri" },
{ "dra", NULL, NULL , "Dravidian languages" },
{ "dsb", NULL, NULL , "Lower Sorbian" },
{ "dua", NULL, NULL , "Duala" },
{ "dut", "nl", "nld", "Dutch; Flemish" },
{ "dyu", NULL, NULL , "Dyula" },
{ "dzo", "dz", NULL , "Dzongkha" },
{ "efi", NULL, NULL , "Efik" },
{ "egy", NULL, NULL , "Egyptian (Ancient)" },
{ "eka", NULL, NULL , "Ekajuk" },
{ "elx", NULL, NULL , "Elamite" },
{ "eng", "en", NULL , "English" },
{ "epo", "eo", NULL , "Esperanto" },
{ "est", "et", NULL , "Estonian" },
{ "ewe", "ee", NULL , "Ewe" },
{ "ewo", NULL, NULL , "Ewondo" },
{ "fan", NULL, NULL , "Fang" },
{ "fao", "fo", NULL , "Faroese" },
{ "fat", NULL, NULL , "Fanti" },
{ "fij", "fj", NULL , "Fijian" },
{ "fil", NULL, NULL , "Filipino; Pilipino" },
{ "fin", "fi", NULL , "Finnish" },
{ "fiu", NULL, NULL , "Finno-Ugrian languages" },
{ "fon", NULL, NULL , "Fon" },
{ "fre", "fr", "fra", "French" },
{ "frr", NULL, NULL , "Northern Frisian" },
{ "frs", NULL, NULL , "Eastern Frisian" },
{ "fry", "fy", NULL , "Western Frisian" },
{ "ful", "ff", NULL , "Fulah" },
{ "fur", NULL, NULL , "Friulian" },
{ "gaa", NULL, NULL , "Ga" },
{ "gay", NULL, NULL , "Gayo" },
{ "gba", NULL, NULL , "Gbaya" },
{ "gem", NULL, NULL , "Germanic languages" },
{ "geo", "ka", "kat", "Georgian" },
{ "ger", "de", "deu", "German" },
{ "gez", NULL, NULL , "Geez" },
{ "gil", NULL, NULL , "Gilbertese" },
{ "gla", "gd", NULL , "Gaelic; Scottish Gaelic" },
{ "gle", "ga", NULL , "Irish" },
{ "glg", "gl", NULL , "Galician" },
{ "glv", "gv", NULL , "Manx" },
{ "gon", NULL, NULL , "Gondi" },
{ "gor", NULL, NULL , "Gorontalo" },
{ "got", NULL, NULL , "Gothic" },
{ "grb", NULL, NULL , "Grebo" },
{ "grn", "gn", NULL , "Guarani" },
{ "gsw", NULL, NULL , "Swiss German; Alemannic; Alsatian" },
{ "guj", "gu", NULL , "Gujarati" },
{ "gwi", NULL, NULL , "Gwich'in" },
{ "hai", NULL, NULL , "Haida" },
{ "hat", "ht", NULL , "Haitian; Haitian Creole" },
{ "hau", "ha", NULL , "Hausa" },
{ "haw", NULL, NULL , "Hawaiian" },
{ "heb", "he", NULL , "Hebrew" },
{ "her", "hz", NULL , "Herero" },
{ "hil", NULL, NULL , "Hiligaynon" },
{ "him", NULL, NULL , "Himachali languages; Western Pahari languages" },
{ "hin", "hi", NULL , "Hindi" },
{ "hit", NULL, NULL , "Hittite" },
{ "hmn", NULL, NULL , "Hmong; Mong" },
{ "hmo", "ho", NULL , "Hiri Motu" },
{ "hrv", "hr", NULL , "Croatian" },
{ "hsb", NULL, NULL , "Upper Sorbian" },
{ "hun", "hu", NULL , "Hungarian" },
{ "hup", NULL, NULL , "Hupa" },
{ "iba", NULL, NULL , "Iban" },
{ "ibo", "ig", NULL , "Igbo" },
{ "ice", "is", "isl", "Icelandic" },
{ "ido", "io", NULL , "Ido" },
{ "iii", "ii", NULL , "Sichuan Yi; Nuosu" },
{ "ijo", NULL, NULL , "Ijo languages" },
{ "iku", "iu", NULL , "Inuktitut" },
{ "ile", "ie", NULL , "Interlingue; Occidental" },
{ "ilo", NULL, NULL , "Iloko" },
{ "ina", "ia", NULL , "Interlingua (International Auxiliary Language Association)" },
{ "inc", NULL, NULL , "Indic languages" },
{ "ind", "id", NULL , "Indonesian" },
{ "ine", NULL, NULL , "Indo-European languages" },
{ "inh", NULL, NULL , "Ingush" },
{ "ipk", "ik", NULL , "Inupiaq" },
{ "ira", NULL, NULL , "Iranian languages" },
{ "iro", NULL, NULL , "Iroquoian languages" },
{ "ita", "it", NULL , "Italian" },
{ "jav", "jv", NULL , "Javanese" },
{ "jbo", NULL, NULL , "Lojban" },
{ "jpn", "ja", NULL , "Japanese" },
{ "jpr", NULL, NULL , "Judeo-Persian" },
{ "jrb", NULL, NULL , "Judeo-Arabic" },
{ "kaa", NULL, NULL , "Kara-Kalpak" },
{ "kab", NULL, NULL , "Kabyle" },
{ "kac", NULL, NULL , "Kachin; Jingpho" },
{ "kal", "kl", NULL , "Kalaallisut; Greenlandic" },
{ "kam", NULL, NULL , "Kamba" },
{ "kan", "kn", NULL , "Kannada" },
{ "kar", NULL, NULL , "Karen languages" },
{ "kas", "ks", NULL , "Kashmiri" },
{ "kau", "kr", NULL , "Kanuri" },
{ "kaw", NULL, NULL , "Kawi" },
{ "kaz", "kk", NULL , "Kazakh" },
{ "kbd", NULL, NULL , "Kabardian" },
{ "kha", NULL, NULL , "Khasi" },
{ "khi", NULL, NULL , "Khoisan languages" },
{ "khm", "km", NULL , "Central Khmer" },
{ "kho", NULL, NULL , "Khotanese; Sakan" },
{ "kik", "ki", NULL , "Kikuyu; Gikuyu" },
{ "kin", "rw", NULL , "Kinyarwanda" },
{ "kir", "ky", NULL , "Kirghiz; Kyrgyz" },
{ "kmb", NULL, NULL , "Kimbundu" },
{ "kok", NULL, NULL , "Konkani" },
{ "kom", "kv", NULL , "Komi" },
{ "kon", "kg", NULL , "Kongo" },
{ "kor", "ko", NULL , "Korean" },
{ "kos", NULL, NULL , "Kosraean" },
{ "kpe", NULL, NULL , "Kpelle" },
{ "krc", NULL, NULL , "Karachay-Balkar" },
{ "krl", NULL, NULL , "Karelian" },
{ "kro", NULL, NULL , "Kru languages" },
{ "kru", NULL, NULL , "Kurukh" },
{ "kua", "kj", NULL , "Kuanyama; Kwanyama" },
{ "kum", NULL, NULL , "Kumyk" },
{ "kur", "ku", NULL , "Kurdish" },
{ "kut", NULL, NULL , "Kutenai" },
{ "lad", NULL, NULL , "Ladino" },
{ "lah", NULL, NULL , "Lahnda" },
{ "lam", NULL, NULL , "Lamba" },
{ "lao", "lo", NULL , "Lao" },
{ "lat", "la", NULL , "Latin" },
{ "lav", "lv", NULL , "Latvian" },
{ "lez", NULL, NULL , "Lezghian" },
{ "lim", "li", NULL , "Limburgan; Limburger; Limburgish" },
{ "lin", "ln", NULL , "Lingala" },
{ "lit", "lt", NULL , "Lithuanian" },
{ "lol", NULL, NULL , "Mongo" },
{ "loz", NULL, NULL , "Lozi" },
{ "ltz", "lb", NULL , "Luxembourgish; Letzeburgesch" },
{ "lua", NULL, NULL , "Luba-Lulua" },
{ "lub", "lu", NULL , "Luba-Katanga" },
{ "lug", "lg", NULL , "Ganda" },
{ "lui", NULL, NULL , "Luiseno" },
{ "lun", NULL, NULL , "Lunda" },
{ "luo", NULL, NULL , "Luo (Kenya and Tanzania)" },
{ "lus", NULL, NULL , "Lushai" },
{ "mac", "mk", "mkd", "Macedonian" },
{ "mad", NULL, NULL , "Madurese" },
{ "mag", NULL, NULL , "Magahi" },
{ "mah", "mh", NULL , "Marshallese" },
{ "mai", NULL, NULL , "Maithili" },
{ "mak", NULL, NULL , "Makasar" },
{ "mal", "ml", NULL , "Malayalam" },
{ "man", NULL, NULL , "Mandingo" },
{ "mao", "mi", "mri", "Maori" },
{ "map", NULL, NULL , "Austronesian languages" },
{ "mar", "mr", NULL , "Marathi" },
{ "mas", NULL, NULL , "Masai" },
{ "may", "ms", "msa", "Malay" },
{ "mdf", NULL, NULL , "Moksha" },
{ "mdr", NULL, NULL , "Mandar" },
{ "men", NULL, NULL , "Mende" },
{ "mic", NULL, NULL , "Mi'kmaq; Micmac" },
{ "min", NULL, NULL , "Minangkabau" },
{ "mis", NULL, NULL , "Uncoded languages" },
{ "mkh", NULL, NULL , "Mon-Khmer languages" },
{ "mlg", "mg", NULL , "Malagasy" },
{ "mlt", "mt", NULL , "Maltese" },
{ "mnc", NULL, NULL , "Manchu" },
{ "mni", NULL, NULL , "Manipuri" },
{ "mno", NULL, NULL , "Manobo languages" },
{ "moh", NULL, NULL , "Mohawk" },
{ "mon", "mn", NULL , "Mongolian" },
{ "mos", NULL, NULL , "Mossi" },
{ "mul", NULL, NULL , "Multiple languages" },
{ "mun", NULL, NULL , "Munda languages" },
{ "mus", NULL, NULL , "Creek" },
{ "mwl", NULL, NULL , "Mirandese" },
{ "mwr", NULL, NULL , "Marwari" },
{ "myn", NULL, NULL , "Mayan languages" },
{ "myv", NULL, NULL , "Erzya" },
{ "nah", NULL, NULL , "Nahuatl languages" },
{ "nai", NULL, NULL , "North American Indian languages" },
{ "nap", NULL, NULL , "Neapolitan" },
{ "nau", "na", NULL , "Nauru" },
{ "nav", "nv", NULL , "Navajo; Navaho" },
{ "ndo", "ng", NULL , "Ndonga" },
{ "nep", "ne", NULL , "Nepali" },
{ "new", NULL, NULL , "Nepal Bhasa; Newari" },
{ "nia", NULL, NULL , "Nias" },
{ "nic", NULL, NULL , "Niger-Kordofanian languages" },
{ "niu", NULL, NULL , "Niuean" },
{ "nog", NULL, NULL , "Nogai" },
{ "nor", "no", NULL , "Norwegian" },
{ "nqo", NULL, NULL , "N'Ko" },
{ "nso", NULL, NULL , "Pedi; Sepedi; Northern Sotho" },
{ "nub", NULL, NULL , "Nubian languages" },
{ "nwc", NULL, NULL , "Classical Newari; Old Newari; Classical Nepal Bhasa" },
{ "nya", "ny", NULL , "Chichewa; Chewa; Nyanja" },
{ "nym", NULL, NULL , "Nyamwezi" },
{ "nyn", NULL, NULL , "Nyankole" },
{ "nyo", NULL, NULL , "Nyoro" },
{ "nzi", NULL, NULL , "Nzima" },
{ "oci", "oc", NULL , "Occitan (post 1500)" },
{ "oji", "oj", NULL , "Ojibwa" },
{ "ori", "or", NULL , "Oriya" },
{ "orm", "om", NULL , "Oromo" },
{ "osa", NULL, NULL , "Osage" },
{ "oss", "os", NULL , "Ossetian; Ossetic" },
{ "oto", NULL, NULL , "Otomian languages" },
{ "paa", NULL, NULL , "Papuan languages" },
{ "pag", NULL, NULL , "Pangasinan" },
{ "pal", NULL, NULL , "Pahlavi" },
{ "pam", NULL, NULL , "Pampanga; Kapampangan" },
{ "pan", "pa", NULL , "Panjabi; Punjabi" },
{ "pap", NULL, NULL , "Papiamento" },
{ "pau", NULL, NULL , "Palauan" },
{ "per", "fa", "fas", "Persian" },
{ "phi", NULL, NULL , "Philippine languages" },
{ "phn", NULL, NULL , "Phoenician" },
{ "pli", "pi", NULL , "Pali" },
{ "pol", "pl", NULL , "Polish" },
{ "pon", NULL, NULL , "Pohnpeian" },
{ "por", "pt", NULL , "Portuguese" },
{ "pra", NULL, NULL , "Prakrit languages" },
{ "pus", "ps", NULL , "Pushto; Pashto" },
{ "que", "qu", NULL , "Quechua" },
{ "raj", NULL, NULL , "Rajasthani" },
{ "rap", NULL, NULL , "Rapanui" },
{ "rar", NULL, NULL , "Rarotongan; Cook Islands Maori" },
{ "roa", NULL, NULL , "Romance languages" },
{ "roh", "rm", NULL , "Romansh" },
{ "rom", NULL, NULL , "Romany" },
{ "rum", "ro", "ron", "Romanian; Moldavian; Moldovan" },
{ "run", "rn", NULL , "Rundi" },
{ "rup", NULL, NULL , "Aromanian; Arumanian; Macedo-Romanian" },
{ "rus", "ru", NULL , "Russian" },
{ "sad", NULL, NULL , "Sandawe" },
{ "sag", "sg", NULL , "Sango" },
{ "sah", NULL, NULL , "Yakut" },
{ "sai", NULL, NULL , "South American Indian languages" },
{ "sal", NULL, NULL , "Salishan languages" },
{ "sam", NULL, NULL , "Samaritan Aramaic" },
{ "san", "sa", NULL , "Sanskrit" },
{ "sas", NULL, NULL , "Sasak" },
{ "sat", NULL, NULL , "Santali" },
{ "scn", NULL, NULL , "Sicilian" },
{ "sco", NULL, NULL , "Scots" },
{ "sel", NULL, NULL , "Selkup" },
{ "sem", NULL, NULL , "Semitic languages" },
{ "sgn", NULL, NULL , "Sign Languages" },
{ "shn", NULL, NULL , "Shan" },
{ "sid", NULL, NULL , "Sidamo" },
{ "sin", "si", NULL , "Sinhala; Sinhalese" },
{ "sio", NULL, NULL , "Siouan languages" },
{ "sit", NULL, NULL , "Sino-Tibetan languages" },
{ "sla", NULL, NULL , "Slavic languages" },
{ "slo", "sk", "slk", "Slovak" },
{ "slv", "sl", NULL , "Slovenian" },
{ "sma", NULL, NULL , "Southern Sami" },
{ "sme", "se", NULL , "Northern Sami" },
{ "smi", NULL, NULL , "Sami languages" },
{ "smj", NULL, NULL , "Lule Sami" },
{ "smn", NULL, NULL , "Inari Sami" },
{ "smo", "sm", NULL , "Samoan" },
{ "sms", NULL, NULL , "Skolt Sami" },
{ "sna", "sn", NULL , "Shona" },
{ "snd", "sd", NULL , "Sindhi" },
{ "snk", NULL, NULL , "Soninke" },
{ "sog", NULL, NULL , "Sogdian" },
{ "som", "so", NULL , "Somali" },
{ "son", NULL, NULL , "Songhai languages" },
{ "spa", "es", NULL , "Spanish; Castilian" },
{ "srd", "sc", NULL , "Sardinian" },
{ "srn", NULL, NULL , "Sranan Tongo" },
{ "srp", "sr", NULL , "Serbian" },
{ "srr", NULL, NULL , "Serer" },
{ "ssa", NULL, NULL , "Nilo-Saharan languages" },
{ "ssw", "ss", NULL , "Swati" },
{ "suk", NULL, NULL , "Sukuma" },
{ "sun", "su", NULL , "Sundanese" },
{ "sus", NULL, NULL , "Susu" },
{ "sux", NULL, NULL , "Sumerian" },
{ "swa", "sw", NULL , "Swahili" },
{ "swe", "sv", NULL , "Swedish" },
{ "syc", NULL, NULL , "Classical Syriac" },
{ "syr", NULL, NULL , "Syriac" },
{ "tah", "ty", NULL , "Tahitian" },
{ "tai", NULL, NULL , "Tai languages" },
{ "tam", "ta", NULL , "Tamil" },
{ "tat", "tt", NULL , "Tatar" },
{ "tel", "te", NULL , "Telugu" },
{ "tem", NULL, NULL , "Timne" },
{ "ter", NULL, NULL , "Tereno" },
{ "tet", NULL, NULL , "Tetum" },
{ "tgk", "tg", NULL , "Tajik" },
{ "tgl", "tl", NULL , "Tagalog" },
{ "tha", "th", NULL , "Thai" },
{ "tib", "bo", "bod", "Tibetan" },
{ "tig", NULL, NULL , "Tigre" },
{ "tir", "ti", NULL , "Tigrinya" },
{ "tiv", NULL, NULL , "Tiv" },
{ "tkl", NULL, NULL , "Tokelau" },
{ "tlh", NULL, NULL , "Klingon; tlhIngan-Hol" },
{ "tli", NULL, NULL , "Tlingit" },
{ "tmh", NULL, NULL , "Tamashek" },
{ "tog", NULL, NULL , "Tonga (Nyasa)" },
{ "ton", "to", NULL , "Tonga (Tonga Islands)" },
{ "tpi", NULL, NULL , "Tok Pisin" },
{ "tsi", NULL, NULL , "Tsimshian" },
{ "tsn", "tn", NULL , "Tswana" },
{ "tso", "ts", NULL , "Tsonga" },
{ "tuk", "tk", NULL , "Turkmen" },
{ "tum", NULL, NULL , "Tumbuka" },
{ "tup", NULL, NULL , "Tupi languages" },
{ "tur", "tr", NULL , "Turkish" },
{ "tut", NULL, NULL , "Altaic languages" },
{ "tvl", NULL, NULL , "Tuvalu" },
{ "twi", "tw", NULL , "Twi" },
{ "tyv", NULL, NULL , "Tuvinian" },
{ "udm", NULL, NULL , "Udmurt" },
{ "uga", NULL, NULL , "Ugaritic" },
{ "uig", "ug", NULL , "Uighur; Uyghur" },
{ "ukr", "uk", NULL , "Ukrainian" },
{ "umb", NULL, NULL , "Umbundu" },
{ "urd", "ur", NULL , "Urdu" },
{ "uzb", "uz", NULL , "Uzbek" },
{ "vai", NULL, NULL , "Vai" },
{ "ven", "ve", NULL , "Venda" },
{ "vie", "vi", NULL , "Vietnamese" },
{ "vol", "vo", NULL , "Volapük" },
{ "vot", NULL, NULL , "Votic" },
{ "wak", NULL, NULL , "Wakashan languages" },
{ "wal", NULL, NULL , "Wolaitta; Wolaytta" },
{ "war", NULL, NULL , "Waray" },
{ "was", NULL, NULL , "Washo" },
{ "wel", "cy", "cym", "Welsh" },
{ "wen", NULL, NULL , "Sorbian languages" },
{ "wln", "wa", NULL , "Walloon" },
{ "wol", "wo", NULL , "Wolof" },
{ "xal", NULL, NULL , "Kalmyk; Oirat" },
{ "xho", "xh", NULL , "Xhosa" },
{ "yao", NULL, NULL , "Yao" },
{ "yap", NULL, NULL , "Yapese" },
{ "yid", "yi", NULL , "Yiddish" },
{ "yor", "yo", NULL , "Yoruba" },
{ "ypk", NULL, NULL , "Yupik languages" },
{ "zap", NULL, NULL , "Zapotec" },
{ "zbl", NULL, NULL , "Blissymbols; Blissymbolics; Bliss" },
{ "zen", NULL, NULL , "Zenaga" },
{ "zha", "za", NULL , "Zhuang; Chuang" },
{ "znd", NULL, NULL , "Zande languages" },
{ "zul", "zu", NULL , "Zulu" },
{ "zun", NULL, NULL , "Zuni" },
{ "zza", NULL, NULL , "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki" },
{ NULL, NULL, NULL, NULL }
};
/* **************************************************************************
* Functions
* *************************************************************************/
const char *lang_code_get ( const char *code )
{
int i;
char tmp[4];
if (code && *code) {
/* Extract the code (lowercase) */
i = 0;
while (i < 3 && *code) {
if (*code == ';' || *code == ',' || *code == '-') break;
if (*code != ' ')
tmp[i++] = *code | 0x20; // |0x20 = lower case
code++;
}
tmp[i] = '\0';
/* Search */
if (i) {
const lang_code_t *c = lang_codes;
while (c->code2b) {
if ( !strcmp(tmp, c->code2b) ) return c->code2b;
if ( c->code1 && !strcmp(tmp, c->code1) ) return c->code2b;
if ( c->code2t && !strcmp(tmp, c->code2t) ) return c->code2b;
c++;
}
}
}
return lang_codes[0].code2b;
}
const char **lang_code_split ( const char *codes )
{
int n;
const char *c, *p, **ret;
/* Defaults */
if (!codes) codes = config_get_language();
/* No config */
if (!codes) return NULL;
/* Count entries */
n = 0;
c = codes;
while (*c) {
if (*c == ',') n++;
c++;
}
ret = calloc(2+n, sizeof(char*));
/* Create list */
n = 0;
p = c = codes;
while (*c) {
if (*c == ',') {
ret[n++] = lang_code_get(p);
p = c + 1;
}
c++;
}
if (*p) ret[n++] = lang_code_get(p);
ret[n] = NULL;
return ret;
}

38
src/lang_codes.h Normal file
View file

@ -0,0 +1,38 @@
/*
* Multi-language Support - language codes
* Copyright (C) 2012 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; without 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/>.
*/
#ifndef __TVH_LANG_CODES_H__
#define __TVH_LANG_CODES_H__
typedef struct lang_code
{
const char *code2b; ///< ISO 639-2 B
const char *code1; ///< ISO 639-1
const char *code2t; ///< ISO 639-2 T
const char *desc; ///< Description
} lang_code_t;
extern const lang_code_t lang_codes[];
/* Convert code to preferred internal code */
const char *lang_code_get ( const char *code );
/* Split list of codes as per HTTP Language-Accept spec */
const char **lang_code_split ( const char *codes );
#endif /* __TVH_LANG_CODES_H__ */

191
src/lang_str.c Normal file
View file

@ -0,0 +1,191 @@
/*
* Multi-language String support
* Copyright (C) 2012 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; without 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 <string.h>
#include <stdlib.h>
#include "redblack.h"
#include "lang_codes.h"
#include "lang_str.h"
/* ************************************************************************
* Support
* ***********************************************************************/
/* Compare language codes */
static int _lang_cmp ( void *a, void *b )
{
return strcmp(((lang_str_ele_t*)a)->lang, ((lang_str_ele_t*)b)->lang);
}
/* ************************************************************************
* Language String
* ***********************************************************************/
/* Create new instance */
lang_str_t *lang_str_create ( void )
{
return calloc(1, sizeof(lang_str_t));
}
/* Destroy (free memory) */
void lang_str_destroy ( lang_str_t *ls )
{
lang_str_ele_t *e;
while ((e = RB_FIRST(ls))) {
if (e->str) free(e->str);
RB_REMOVE(ls, e, link);
free(e);
}
free(ls);
}
/* Copy the lang_str instance */
lang_str_t *lang_str_copy ( const lang_str_t *ls )
{
lang_str_t *ret = lang_str_create();
lang_str_ele_t *e;
RB_FOREACH(e, ls, link)
lang_str_add(ret, e->str, e->lang, 0);
return ret;
}
/* Get language element */
lang_str_ele_t *lang_str_get2
( lang_str_t *ls, const char *lang )
{
int i;
const char **langs;
lang_str_ele_t skel, *e = NULL;
if (!ls) return NULL;
/* Check config/requested langs */
if ((langs = lang_code_split(lang))) {
i = 0;
while (langs[i]) {
skel.lang = langs[i];
if ((e = RB_FIND(ls, &skel, link, _lang_cmp)))
break;
i++;
}
free(langs);
}
/* Use first available */
if (!e) e = RB_FIRST(ls);
/* Return */
return e;
}
/* Get string */
const char *lang_str_get
( lang_str_t *ls, const char *lang )
{
lang_str_ele_t *e = lang_str_get2(ls, lang);
return e ? e->str : NULL;
}
/* Internal insertion routine */
static int _lang_str_add
( lang_str_t *ls, const char *str, const char *lang, int update, int append )
{
int save = 0;
static lang_str_ele_t *skel = NULL;
lang_str_ele_t *e;
if (!str) return 0;
/* Get proper code */
if (!(lang = lang_code_get(lang))) return 0;
/* Create skel */
if (!skel) skel = calloc(1, sizeof(lang_str_ele_t));
skel->lang = lang;
/* Create */
e = RB_INSERT_SORTED(ls, skel, link, _lang_cmp);
if (!e) {
skel->str = strdup(str);
skel = NULL;
save = 1;
/* Append */
} else if (append) {
e->str = realloc(e->str, strlen(e->str) + strlen(str) + 1);
strcat(e->str, str);
save = 1;
/* Update */
} else if (update && strcmp(str, e->str)) {
free(e->str);
e->str = strdup(str);
save = 1;
}
return save;
}
/* Add new string (or replace existing one) */
int lang_str_add
( lang_str_t *ls, const char *str, const char *lang, int update )
{
return _lang_str_add(ls, str, lang, update, 0);
}
/* Append to existing string (or add new one) */
int lang_str_append
( lang_str_t *ls, const char *str, const char *lang )
{
return _lang_str_add(ls, str, lang, 0, 1);
}
/* Serialize */
void lang_str_serialize ( lang_str_t *ls, htsmsg_t *m, const char *f )
{
lang_str_ele_t *e;
if (!ls) return;
htsmsg_t *a = htsmsg_create_map();
RB_FOREACH(e, ls, link) {
htsmsg_add_str(a, e->lang, e->str);
}
htsmsg_add_msg(m, f, a);
}
/* De-serialize */
lang_str_t *lang_str_deserialize ( htsmsg_t *m, const char *n )
{
lang_str_t *ret = NULL;
htsmsg_t *a;
htsmsg_field_t *f;
const char *str;
if ((a = htsmsg_get_map(m, n))) {
ret = lang_str_create();
HTSMSG_FOREACH(f, a) {
if ((str = htsmsg_field_get_string(f))) {
lang_str_add(ret, str, f->hmf_name, 0);
}
}
} else if ((str = htsmsg_get_str(m, n))) {
ret = lang_str_create();
lang_str_add(ret, str, NULL, 0);
}
return ret;
}

55
src/lang_str.h Normal file
View file

@ -0,0 +1,55 @@
/*
* Multi-language String support
* Copyright (C) 2012 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; without 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/>.
*/
#ifndef __TVH_LANG_STR_H__
#define __TVH_LANG_STR_H__
#include "redblack.h"
#include "htsmsg.h"
typedef struct lang_str_ele
{
RB_ENTRY(lang_str_ele) link;
const char *lang;
char *str;
} lang_str_ele_t;
typedef RB_HEAD(lang_str, lang_str_ele) lang_str_t;
/* Create/Destroy */
void lang_str_destroy ( lang_str_t *ls );
lang_str_t *lang_str_create ( void );
lang_str_t *lang_str_copy ( const lang_str_t *ls );
/* Get elements */
const char *lang_str_get ( lang_str_t *ls, const char *lang );
lang_str_ele_t *lang_str_get2 ( lang_str_t *ls, const char *lang );
/* Add/Update elements */
int lang_str_add
( lang_str_t *ls, const char *str, const char *lang, int update );
int lang_str_append
( lang_str_t *ls, const char *str, const char *lang );
/* Serialize/Deserialize */
void lang_str_serialize
( lang_str_t *ls, htsmsg_t *msg, const char *f );
lang_str_t *lang_str_deserialize
( htsmsg_t *m, const char *f );
#endif /* __TVH_LANG_STR_H__ */

View file

@ -718,6 +718,7 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
const char *channel = http_arg_get(&hc->hc_req_args, "channel");
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");
if(channel && !channel[0]) channel = NULL;
if(tag && !tag[0]) tag = NULL;
@ -740,7 +741,7 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
pthread_mutex_lock(&global_lock);
epg_query(&eqr, channel, tag, eg, title);
epg_query(&eqr, channel, tag, eg, title, lang);
epg_query_sort(&eqr);
@ -763,17 +764,18 @@ extjs_epg(http_connection_t *hc, const char *remain, void *opaque)
if(ch->ch_icon != NULL)
htsmsg_add_str(m, "chicon", ch->ch_icon);
if(ee->title != NULL)
htsmsg_add_str(m, "title", ee->title);
if(ee->subtitle)
htsmsg_add_str(m, "subtitle", ee->subtitle);
if((s = epg_episode_get_title(ee, lang)))
htsmsg_add_str(m, "title", s);
if((s = epg_episode_get_subtitle(ee, lang)))
htsmsg_add_str(m, "subtitle", s);
if(ee->description != NULL)
htsmsg_add_str(m, "description", ee->description);
else if(ee->summary != NULL)
htsmsg_add_str(m, "description", ee->summary);
if((s = epg_episode_get_description(ee, lang)))
htsmsg_add_str(m, "description", s);
else if((s = epg_episode_get_summary(ee, lang)))
htsmsg_add_str(m, "description", s);
if (epg_episode_number_format(ee, buf, 100, NULL, "Season %d", ".", "Episode %d", "/%d"))
if (epg_episode_number_format(ee, buf, 100, NULL, "Season %d", ".",
"Episode %d", "/%d"))
htsmsg_add_str(m, "episode", buf);
htsmsg_add_u32(m, "id", e->id);
@ -813,11 +815,12 @@ extjs_epgrelated(http_connection_t *hc, const char *remain, void *opaque)
epg_episode_t *ee, *ee2;
channel_t *ch;
uint32_t count = 0;
const char *id, *type;
const char *s;
char buf[100];
id = http_arg_get(&hc->hc_req_args, "id");
type = http_arg_get(&hc->hc_req_args, "type");
const char *lang = http_arg_get(&hc->hc_args, "Accept-Language");
const char *id = http_arg_get(&hc->hc_req_args, "id");
const char *type = http_arg_get(&hc->hc_req_args, "type");
out = htsmsg_create_map();
array = htsmsg_create_list();
@ -852,9 +855,12 @@ extjs_epgrelated(http_connection_t *hc, const char *remain, void *opaque)
count++;
m = htsmsg_create_map();
htsmsg_add_str(m, "uri", ee2->uri);
htsmsg_add_str(m, "title", ee2->title);
if (ee2->subtitle) htsmsg_add_str(m, "subtitle", ee2->subtitle);
if (epg_episode_number_format(ee2, buf, 100, NULL, "Season %d", ".", "Episode %d", "/%d"))
if ((s = epg_episode_get_title(ee2, lang)))
htsmsg_add_str(m, "title", s);
if ((s = epg_episode_get_subtitle(ee2, lang)))
htsmsg_add_str(m, "subtitle", s);
if (epg_episode_number_format(ee2, buf, 100, NULL, "Season %d",
".", "Episode %d", "/%d"))
htsmsg_add_str(m, "episode", buf);
htsmsg_add_msg(array, NULL, m);
}
@ -865,9 +871,12 @@ extjs_epgrelated(http_connection_t *hc, const char *remain, void *opaque)
count++;
m = htsmsg_create_map();
htsmsg_add_str(m, "uri", ee2->uri);
htsmsg_add_str(m, "title", ee2->title);
if (ee2->subtitle) htsmsg_add_str(m, "subtitle", ee2->subtitle);
if (epg_episode_number_format(ee2, buf, 100, NULL, "Season %d", ".", "Episode %d", "/%d"))
if ((s = epg_episode_get_title(ee2, lang)))
htsmsg_add_str(m, "title", s);
if ((s = epg_episode_get_subtitle(ee2, lang)))
htsmsg_add_str(m, "subtitle", s);
if (epg_episode_number_format(ee2, buf, 100, NULL, "Season %d",
".", "Episode %d", "/%d"))
htsmsg_add_str(m, "episode", buf);
htsmsg_add_msg(array, NULL, m);
}
@ -1235,10 +1244,10 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque)
htsmsg_add_str(m, "config_name", de->de_config_name);
if(de->de_title != NULL)
htsmsg_add_str(m, "title", de->de_title);
htsmsg_add_str(m, "title", lang_str_get(de->de_title, NULL));
if(de->de_desc != NULL)
htsmsg_add_str(m, "description", de->de_desc);
htsmsg_add_str(m, "description", lang_str_get(de->de_desc, NULL));
if (de->de_bcast && de->de_bcast->episode)
if (epg_episode_number_format(de->de_bcast->episode, buf, 100, NULL, "Season %d", ".", "Episode %d", "/%d"))
@ -1753,6 +1762,8 @@ extjs_config(http_connection_t *hc, const char *remain, void *opaque)
pthread_mutex_lock(&global_lock);
if ((str = http_arg_get(&hc->hc_req_args, "muxconfpath")))
save |= config_set_muxconfpath(str);
if ((str = http_arg_get(&hc->hc_req_args, "language")))
save |= config_set_language(str);
if (save) config_save();
pthread_mutex_unlock(&global_lock);
out = htsmsg_create_map();

View file

@ -67,6 +67,7 @@ page_simple(http_connection_t *hc,
dvr_query_result_t dqr;
const char *rstatus = NULL;
epg_query_result_t eqr;
const char *lang = http_arg_get(&hc->hc_args, "Accept-Language");
htsbuf_qprintf(hq, "<html>");
htsbuf_qprintf(hq, "<body>");
@ -86,7 +87,7 @@ page_simple(http_connection_t *hc,
if(s != NULL) {
epg_query(&eqr, NULL, NULL, NULL, s);
epg_query(&eqr, NULL, NULL, NULL, s, lang);
epg_query_sort(&eqr);
c = eqr.eqr_entries;
@ -123,12 +124,13 @@ page_simple(http_connection_t *hc,
rstatus = de != NULL ? val2str(de->de_sched_state,
recstatustxt) : NULL;
s = epg_episode_get_title(e->episode, lang);
htsbuf_qprintf(hq,
"<a href=\"/eventinfo/%d\">"
"%02d:%02d-%02d:%02d&nbsp;%s%s%s</a><br>",
e->id,
a.tm_hour, a.tm_min, b.tm_hour, b.tm_min,
e->episode ? e->episode->title : "",
s ?: "",
rstatus ? "&nbsp;" : "", rstatus ?: "");
}
}
@ -199,6 +201,8 @@ page_einfo(http_connection_t *hc, const char *remain, void *opaque)
dvr_entry_t *de;
const char *rstatus;
dvr_entry_sched_state_t dvr_status;
const char *lang = http_arg_get(&hc->hc_args, "Accept-Language");
const char *s;
pthread_mutex_lock(&global_lock);
@ -227,8 +231,9 @@ page_einfo(http_connection_t *hc, const char *remain, void *opaque)
days[a.tm_wday], a.tm_mday, a.tm_mon + 1,
a.tm_hour, a.tm_min, b.tm_hour, b.tm_min);
s = epg_episode_get_title(e->episode, lang);
htsbuf_qprintf(hq, "<hr><b>\"%s\": \"%s\"</b><br><br>",
e->channel->ch_name, e->episode ? e->episode->title : "");
e->channel->ch_name, s ?: "");
dvr_status = de != NULL ? de->de_sched_state : DVR_NOSTATE;

View file

@ -7,7 +7,7 @@ tvheadend.miscconf = function() {
var confreader = new Ext.data.JsonReader(
{ root: 'config' },
[
'muxconfpath',
'muxconfpath', 'language',
]
);
@ -21,6 +21,12 @@ tvheadend.miscconf = function() {
allowBlank : true,
});
var language = new Ext.form.TextField({
fieldLabel : 'Default Language(s)',
name : 'language',
allowBlank : true,
});
/* ****************************************************************
* Form
* ***************************************************************/
@ -53,7 +59,8 @@ tvheadend.miscconf = function() {
defaultType : 'textfield',
autoHeight : true,
items : [
dvbscanPath
language,
dvbscanPath,
],
tbar: [
saveButton,

View file

@ -774,7 +774,7 @@ page_dvrfile(http_connection_t *hc, const char *remain, void *opaque)
if(de->de_title != NULL) {
snprintf(disposition, sizeof(disposition),
"attachment; filename=%s.%s", de->de_title, postfix);
"attachment; filename=%s.%s", lang_str_get(de->de_title, NULL), postfix);
i = 20;
while(disposition[i]) {
if(disposition[i] == ' ')