*/
LIST_ENTRY(dvr_entry) de_bcast_link;
epg_broadcast_t *de_bcast;
- char *de_episode;
+ epg_episode_num_t de_epnum;
/**
* Major State
{
time_t t1, t2;
const char *title1, *title2;
- char buf[64];
+ epg_episode_num_t epnum;
/* Wrong length (+/-20%) */
t1 = de->de_stop - de->de_start;
return 0;
/* episode check */
- if (dvr_entry_get_episode(e, buf, sizeof(buf)) && de->de_episode)
- if (strcmp(buf, de->de_episode))
- return 0;
+ epg_episode_get_epnum(e->episode, &epnum);
+ if (epg_episode_number_cmpfull(&epnum, &de->de_epnum))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Expect season and episode from an input string of
+ * "Season A/B.Episode Y/Z"
+ * or "Season A.Episode Y/Z"
+ * or "Season A.Episode Y"
+ * or some combination.
+ */
+static int extract_season_episode(epg_episode_num_t *epnum, const char *text)
+{
+ uint32_t s = 0, sc = 0, e = 0, ec = 0;
+ const char *ch = text;
+
+ memset(epnum, 0, sizeof(*epnum));
+
+ /* Extract season and season count */
+ if (strncasecmp(ch, "Season", 7))
+ goto _episode;
+
+ ch += 7;
+ for (; *ch == ' '; ch++);
+ for (; isdigit(*ch); ch++)
+ s = (s * 10) + (*ch - '0');
+ if (*ch == '/') {
+ for (ch++; isdigit(*ch); ch++)
+ sc = (sc * 10) + (*ch - '0');
+ }
+
+ /* Sanity check */
+ if (*ch != '.')
+ return 0;
+ /* Extract episode and episode count */
+_episode:
+ if (strncasecmp(ch, "Episode", 7))
+ return 0;
+ ch += 7;
+ for (; *ch == ' '; ch++);
+ for (; isdigit(*ch); ch++)
+ e = (e * 10) + (*ch - '0');
+ if (*ch == '/') {
+ for (ch++; isdigit(*ch); ch++)
+ ec = (ec * 10) + (*ch - '0');
+ }
+
+ /* end-of-string check */
+ if (*ch)
+ return 0;
+
+ if (e == 0)
+ return 0;
+
+ epnum->s_num = s;
+ epnum->s_cnt = sc;
+ epnum->e_num = e;
+ epnum->e_cnt = ec;
return 1;
}
int64_t start, stop;
htsmsg_t *m;
char ubuf[UUID_HEX_SIZE];
+ const char *s;
if (conf) {
if (htsmsg_get_s64(conf, "start", &start))
idnode_load(&de->de_id, conf);
+ /* Extract episode info */
+ s = htsmsg_get_str(conf, "episode");
+ if (s) {
+ extract_season_episode(&de->de_epnum, s);
+ } else {
+ m = htsmsg_get_map(conf, "episode");
+ if (m)
+ epg_episode_epnum_deserialize(m, &de->de_epnum);
+ }
+
/* filenames */
m = htsmsg_get_list(conf, "files");
if (m)
static int _dvr_duplicate_epnum(dvr_entry_t *de, dvr_entry_t *de2, void **aux)
{
- return !strempty(de2->de_episode) && !strcmp(de->de_episode, de2->de_episode);
+ if (de->de_epnum.e_num && de2->de_epnum.e_num)
+ return de->de_epnum.e_num == de2->de_epnum.e_num;
+ if (de->de_epnum.text && de2->de_epnum.text)
+ return strcmp(de->de_epnum.text, de2->de_epnum.text) == 0;
+ return 0;
}
static int _dvr_duplicate_title(dvr_entry_t *de, dvr_entry_t *de2, void **aux)
return NULL;
}
-/// Expect season and episode from an input string of
-/// "Season A/B.Episode Y/Z"
-/// or "Season A.Episode Y/Z"
-/// or "Season A.Episode Y"
-/// or some combination.
-static int64_t extract_season_episode(const char* ep)
-{
- /* Go to first digit of season */
- const char *ch = ep;
- while (*ch && !isdigit(*ch))
- ++ch;
-
- /* atoi on the season */
- int s=0;
- while (isdigit(*ch))
- s = (s * 10) + (*ch++ - '0');
-
- /* Now we're either on / or . */
- while (*ch && *ch != '.')
- ++ch;
-
- /* Now we're on Episode */
- while (*ch && !isdigit(*ch))
- ++ch;
- int e=0;
- while (isdigit(*ch))
- e = (e * 10) + (*ch++ - '0');
-
- /* Now combine it together */
- return ((int64_t)s) << 32 | e;
-}
-
/// @return 1 if dup.
static int _dvr_duplicate_unique_match(dvr_entry_t *de1, dvr_entry_t *de2, void **aux)
{
/* Titles not equal? Can't be a dup then */
if (lang_str_compare(de1->de_title, de2->de_title)) return NOT_DUP;
- /* Season and/or episode is stored in episode. But the numbers are
- * not saved separately. We want to dup match only if both season
+ /* We want to dup match only if both season
* AND episode are present since OTA often have just "Ep 1" without
* giving the season.
*/
- const char *s_ep1 = de1->de_episode;
- const char *s_ep2 = de2->de_episode;
-
- /* Are season AND episode both in the string? */
- const int is_s_and_ep1 = s_ep1 && strstr(s_ep1, "Season") && strstr(s_ep1, "Episode");
- const int is_s_and_ep2 = s_ep2 && strstr(s_ep2, "Season") && strstr(s_ep2, "Episode");
-
- /* Season and episode are the same (for the same title) so must be a DUP.
- * If they differ then must not be a DUP.
- *
- * We only compare up to the character before the slash since the
- * default display is "Season X.Episode Y/Z" for xmltv, but OTA
- * rarely has the Z component which means that we will frequently
- * fail to match xmltv vs OTA unless we compare only the season and
- * episode components and ignore the total number of episodes
- * component.
- *
- * Newer tv_grab gives "Season X/X.Episode Y/Z" so we have to
- * be careful with the compare.
- */
- if (is_s_and_ep1 && is_s_and_ep2) {
- const int64_t sepnum1 = extract_season_episode(s_ep1);
- const int64_t sepnum2 = extract_season_episode(s_ep2);
- return sepnum1 == sepnum2 ? DUP : NOT_DUP;
- }
+ if (de1->de_epnum.e_num && de2->de_epnum.e_num)
+ return de1->de_epnum.e_num == de2->de_epnum.e_num ? DUP : NOT_DUP;
/* Only one has season and episode? Then can't be a dup with the
* other one that doesn't have season+episode
*/
- if ((is_s_and_ep1 && !is_s_and_ep2) || (!is_s_and_ep1 && is_s_and_ep2)) return NOT_DUP;
+ if (de1->de_epnum.e_num || de2->de_epnum.e_num)
+ return NOT_DUP;
/* Now, near the end, we can check for unequal programme ids. We do
* this check relatively late since we want dup checking above to
* for different region broadcasts so we want to match on episode details
* first.
*/
- if (do_progid_check && progid1 && progid2 && strcmp(progid1, progid2)) return NOT_DUP;
+ if (do_progid_check && progid1 && progid2 && strcmp(progid1, progid2))
+ return NOT_DUP;
/* Only one side has an id? We do nothing for this case since programmes in old dvr/log
* don't have a programid persisted.
* different crid so would be NOT_DUP at first check. But if everything is
* identical then user should resort to using ONCE_PER_DAY rules, etc.
*/
- if (!lang_str_compare(de1->de_subtitle, de2->de_subtitle) && !lang_str_compare(de1->de_desc, de2->de_desc))
+ if (!lang_str_compare(de1->de_subtitle, de2->de_subtitle) &&
+ !lang_str_compare(de1->de_desc, de2->de_desc))
return DUP;
/* If all tests have finished then we assume not a dup */
break;
case DVR_AUTOREC_RECORD_DIFFERENT_EPISODE_NUMBER:
case DVR_AUTOREC_LRECORD_DIFFERENT_EPISODE_NUMBER:
- if (strempty(de->de_episode))
+ if (de->de_epnum.e_num == 0 && de->de_epnum.text == NULL)
return NULL;
break;
case DVR_AUTOREC_RECORD_DIFFERENT_SUBTITLE:
if (de->de_desc) lang_str_destroy(de->de_desc);
dvr_entry_assign_broadcast(de, NULL);
free(de->de_channel_name);
- free(de->de_episode);
+ free(de->de_epnum.text);
free(de->de_image);
free(de->de_uri);
{
char buf[40];
int save = 0, updated = 0;
+ epg_episode_num_t epnum;
if (enabled >= 0) {
enabled = !!enabled;
}
/* Episode */
- if (!dvr_entry_get_episode(de->de_bcast, buf, sizeof(buf)))
- buf[0] = '\0';
- if (strcmp(de->de_episode ?: "", buf)) {
- free(de->de_episode);
- de->de_episode = strdup(buf);
+ if (de->de_bcast->episode) {
+ epg_episode_get_epnum(de->de_bcast->episode, &epnum);
+ } else {
+ memset(&epnum, 0, sizeof(epnum));
+ }
+ if (epg_episode_number_cmpfull(&de->de_epnum, &epnum)) {
+ de->de_epnum = epnum;
+ if (epnum.text)
+ de->de_epnum.text = strdup(epnum.text);
save |= DVR_UPDATED_EPISODE;
}
int64_t s64;
idnode_save(&de->de_id, m);
+ htsmsg_add_msg(m, "episode", epg_episode_epnum_serialize(&de->de_epnum));
if (de->de_files) {
l = htsmsg_create_list();
HTSMSG_FOREACH(f, de->de_files)
return &prop_ptr;
}
+static int
+dvr_entry_class_disp_episode_set(void *o, const void *v)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ epg_episode_num_t epnum;
+ if (!extract_season_episode(&epnum, (const char *)v))
+ epnum.text = (char *)v;
+ if (epg_episode_number_cmpfull(&de->de_epnum, &epnum)) {
+ free(de->de_epnum.text);
+ de->de_epnum = epnum;
+ if (epnum.text)
+ de->de_epnum.text = strdup(epnum.text);
+ return 1;
+ }
+ return 0;
+}
+
+static const void *
+dvr_entry_class_disp_episode_get(void *o)
+{
+ dvr_entry_t *de = (dvr_entry_t *)o;
+ const char *lang;
+ char buf1[32], buf2[32];
+ if (de->de_epnum.e_num) {
+ lang = idnode_lang(o);
+ snprintf(buf1, sizeof(buf1), "%s %%d", tvh_gettext_lang(lang, N_("Season")));
+ snprintf(buf1, sizeof(buf1), "%s %%d", tvh_gettext_lang(lang, N_("Episode")));
+ epg_episode_epnum_format(&de->de_epnum, prop_sbuf, PROP_SBUF_LEN, NULL,
+ buf1, ".", buf2, "/%d");
+ return &prop_sbuf_ptr;
+ } else if (de->de_epnum.text) {
+ prop_ptr = de->de_epnum.text;
+ } else {
+ prop_ptr = "";
+ }
+ return &prop_ptr;
+}
+
static const void *
dvr_entry_class_url_get(void *o)
{
},
{
.type = PT_STR,
- .id = "episode",
+ .id = "episode_disp",
.name = N_("Episode"),
.desc = N_("Episode number/ID."),
- .off = offsetof(dvr_entry_t, de_episode),
- .opts = PO_RDONLY | PO_HIDDEN,
+ .set = dvr_entry_class_disp_episode_set,
+ .get = dvr_entry_class_disp_episode_get,
+ .opts = PO_RDONLY | PO_HIDDEN | PO_NOSAVE,
},
{
.type = PT_STR,
* Episode
* *************************************************************************/
-static htsmsg_t *epg_episode_num_serialize ( epg_episode_num_t *num )
+htsmsg_t *epg_episode_epnum_serialize ( epg_episode_num_t *num )
{
htsmsg_t *m;
if (!num) return NULL;
return m;
}
-static void epg_episode_num_deserialize
+void epg_episode_epnum_deserialize
( htsmsg_t *m, epg_episode_num_t *num )
{
const char *str;
_epg_object_putref(episode);
}
-size_t epg_episode_number_format
- ( epg_episode_t *episode, char *buf, size_t len,
+size_t epg_episode_epnum_format
+ ( epg_episode_num_t *epnum, char *buf, size_t len,
const char *pre, const char *sfmt,
const char *sep, const char *efmt,
const char *cfmt )
{
size_t i = 0;
- if (!episode || !buf || !len) return 0;
- epg_episode_num_t num;
- epg_episode_get_epnum(episode, &num);
+ if (!epnum || !buf || !len) return 0;
buf[0] = '\0';
- if (num.e_num) {
+ if (epnum->e_num) {
if (pre) tvh_strlcatf(buf, len, i, "%s", pre);
- if (sfmt && num.s_num) {
- tvh_strlcatf(buf, len, i, sfmt, num.s_num);
- if (cfmt && num.s_cnt)
- tvh_strlcatf(buf, len, i, cfmt, num.s_cnt);
+ if (sfmt && epnum->s_num) {
+ tvh_strlcatf(buf, len, i, sfmt, epnum->s_num);
+ if (cfmt && epnum->s_cnt)
+ tvh_strlcatf(buf, len, i, cfmt, epnum->s_cnt);
if (sep) tvh_strlcatf(buf, len, i, "%s", sep);
}
- tvh_strlcatf(buf, len, i, efmt, num.e_num);
- if (cfmt && num.e_cnt)
- tvh_strlcatf(buf, len, i, cfmt, num.e_cnt);
- } else if (num.text) {
+ tvh_strlcatf(buf, len, i, efmt, epnum->e_num);
+ if (cfmt && epnum->e_cnt)
+ tvh_strlcatf(buf, len, i, cfmt, epnum->e_cnt);
+ } else if (epnum->text) {
if (pre) tvh_strlcatf(buf, len, i, "%s", pre);
- tvh_strlcatf(buf, len, i, "%s", num.text);
+ tvh_strlcatf(buf, len, i, "%s", epnum->text);
}
return i;
}
-void epg_episode_get_epnum ( epg_episode_t *ee, epg_episode_num_t *num )
+size_t epg_episode_number_format
+ ( epg_episode_t *episode, char *buf, size_t len,
+ const char *pre, const char *sfmt,
+ const char *sep, const char *efmt,
+ const char *cfmt )
{
- if (!ee || !num) return;
+ if (!episode) return 0;
+ epg_episode_num_t num;
+ epg_episode_get_epnum(episode, &num);
+ return epg_episode_epnum_format(&num, buf, len, pre,
+ sfmt, sep, efmt, cfmt);
+}
+
+void epg_episode_get_epnum ( const epg_episode_t *ee, epg_episode_num_t *num )
+{
+ if (!ee || !num) {
+ memset(num, 0, sizeof(*num));
+ return;
+ }
*num = ee->epnum;
if (ee->season) {
num->e_cnt = ee->season->episode_count;
}
}
-int epg_episode_number_cmp ( epg_episode_num_t *a, epg_episode_num_t *b )
+int epg_episode_number_cmp ( const epg_episode_num_t *a, const epg_episode_num_t *b )
{
if (a->e_num) {
if (a->s_num != b->s_num) {
return 0;
}
+int epg_episode_number_cmpfull ( const epg_episode_num_t *a, const epg_episode_num_t *b )
+{
+ int i = a->s_cnt - b->s_cnt;
+ if (i) return i;
+ i = a->s_num - b->s_num;
+ if (i) return i;
+ i = a->e_cnt - b->e_cnt;
+ if (i) return i;
+ i = a->e_num - b->e_num;
+ if (i) return i;
+ i = a->p_cnt - b->p_cnt;
+ if (i) return i;
+ i = a->p_num - b->p_num;
+ if (i) return i;
+ return strcasecmp(a->text ?: "", b->text ?: "");
+}
+
// 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.
lang_str_serialize(episode->summary, m, "summary");
if (episode->description)
lang_str_serialize(episode->description, m, "description");
- htsmsg_add_msg(m, "epnum", epg_episode_num_serialize(&episode->epnum));
+ htsmsg_add_msg(m, "epnum", epg_episode_epnum_serialize(&episode->epnum));
LIST_FOREACH(eg, &episode->genre, link) {
if (!a) a = htsmsg_create_list();
htsmsg_add_u32(a, NULL, eg->code);
lang_str_destroy(ls);
}
if ((sub = htsmsg_get_map(m, "epnum"))) {
- epg_episode_num_deserialize(sub, &num);
+ epg_episode_epnum_deserialize(sub, &num);
*save |= epg_episode_set_epnum(ee, &num, &changes);
if (num.text) free(num.text);
}
// Note: this does NOT strdup the text field
void epg_episode_get_epnum
- ( epg_episode_t *e, epg_episode_num_t *epnum );
+ ( const epg_episode_t *e, epg_episode_num_t *epnum );
/* EpNum format helper */
// output string will be:
// if (episode_num)
// ret += sprintf(efmt, episode_num)
// if (episode_cnt) ret += sprintf(cfmt, episode_cnt)
// and will return num chars written
+size_t epg_episode_epnum_format
+ ( epg_episode_num_t *epnum, char *buf, size_t len,
+ const char *pre, const char *sfmt,
+ const char *sep, const char *efmt,
+ const char *cfmt );
size_t epg_episode_number_format
( epg_episode_t *e, char *buf, size_t len,
const char *pre, const char *sfmt,
const char *sep, const char *efmt,
const char *cfmt );
-int epg_episode_number_cmp
- ( epg_episode_num_t *a, epg_episode_num_t *b );
+int epg_episode_number_cmp
+ ( const epg_episode_num_t *a, const epg_episode_num_t *b );
+int epg_episode_number_cmpfull
+ ( const epg_episode_num_t *a, const epg_episode_num_t *b );
+
+htsmsg_t *epg_episode_epnum_serialize( epg_episode_num_t *num );
+void epg_episode_epnum_deserialize( htsmsg_t *m, epg_episode_num_t *num );
/* Matching */
int epg_episode_fuzzy_match
return conf;
}
+/*
+ *
+ */
+static void htsp_serialize_epnum
+ (htsmsg_t *out, epg_episode_num_t *epnum, const char *textname)
+{
+ if (epnum->s_num) {
+ htsmsg_add_u32(out, "seasonNumber", epnum->s_num);
+ if (epnum->s_cnt)
+ htsmsg_add_u32(out, "seasonCount", epnum->s_cnt);
+ }
+ if (epnum->e_num) {
+ htsmsg_add_u32(out, "episodeNumber", epnum->e_num);
+ if (epnum->e_cnt)
+ htsmsg_add_u32(out, "episodeCount", epnum->e_cnt);
+ }
+ if (epnum->p_num) {
+ htsmsg_add_u32(out, "partNumber", epnum->p_num);
+ if (epnum->p_cnt)
+ htsmsg_add_u32(out, "partCount", epnum->p_cnt);
+ }
+ if (epnum->text)
+ htsmsg_add_str(out, textname ?: "episodeOnscreen", epnum->text);
+}
+
/* **************************************************************************
* File helpers
* *************************************************************************/
htsmsg_add_str(out, "subtitle", s);
if(de->de_desc && (s = lang_str_get(de->de_desc, lang)))
htsmsg_add_str(out, "description", s);
- if(de->de_episode)
- htsmsg_add_str(out, "episode", de->de_episode);
+ htsp_serialize_epnum(out, &de->de_epnum, "episode");
if(de->de_owner)
htsmsg_add_str(out, "owner", de->de_owner);
if(de->de_creator)
if (ee->first_aired)
htsmsg_add_s64(out, "firstAired", ee->first_aired);
epg_episode_get_epnum(ee, &epnum);
- if (epnum.s_num) {
- htsmsg_add_u32(out, "seasonNumber", epnum.s_num);
- if (epnum.s_cnt)
- htsmsg_add_u32(out, "seasonCount", epnum.s_cnt);
- }
- if (epnum.e_num) {
- htsmsg_add_u32(out, "episodeNumber", epnum.e_num);
- if (epnum.e_cnt)
- htsmsg_add_u32(out, "episodeCount", epnum.e_cnt);
- }
- if (epnum.p_num) {
- htsmsg_add_u32(out, "partNumber", epnum.p_num);
- if (epnum.p_cnt)
- htsmsg_add_u32(out, "partCount", epnum.p_cnt);
- }
- if (epnum.text)
- htsmsg_add_str(out, "episodeOnscreen", epnum.text);
+ htsp_serialize_epnum(out, &epnum, NULL);
if (ee->image)
htsmsg_add_str(out, "image", ee->image);
}