--- /dev/null
+
+
+This setting controls which Event Information Table (EIT) sub-tables
+are accepted when building the EPG for this service.
+
+DVB EIT is broadcast in two forms, distinguished by `table_id`:
+
+- **Actual transport stream EIT** (`0x4e` / `0x50–0x5f`) — broadcast
+ by the service's *own* multiplex. Describes the service itself.
+- **Other transport stream EIT** (`0x4f` / `0x60–0x6f`) — broadcast
+ by *another* multiplex describing this one. Often coarser; mostly
+ used so a receiver tuned to one multiplex can still show some EPG
+ for services on neighbouring multiplexes.
+
+Without a precedence rule, the two sources can overwrite each other
+on every grab cycle, making the EPG flip between a detailed
+schedule (from actual-TS) and a coarser placeholder (from other-TS).
+
+| Value | Behaviour |
+| --- | --- |
+| **Default** | Defer to the global *EIT processing (default)* setting under EPG Grabber → OTA. Use this for most services. |
+| **None** | Ignore all EIT for this service. Equivalent to the legacy *Ignore EPG (EIT)* toggle. |
+| **Actual transport stream only** | Accept only actual-TS sub-tables (`0x4e`, `0x50–0x5f`). |
+| **Other transport stream only** | Accept only other-TS sub-tables (`0x4f`, `0x60–0x6f`). |
+| **Either** | Accept both, no precedence. Today's default behaviour — what tvheadend has always done. |
+| **Adaptive** | Accept both, but once this service's own actual-TS schedule has been seen this session, drop further other-TS for it. A neighbour's coarse description cannot overwrite the service's detailed schedule, while services whose actual-TS is never received keep using other-TS (so a dedicated EPG multiplex still works). |
+
+The Adaptive policy is self-correcting per service: it suppresses
+other-TS only once detailed actual-TS data has actually been
+received. Inspired by VDR's adaptive EIT handling.
+
+**One operational caveat for Adaptive.** The "actual-TS observed"
+state is per session and resets when tvheadend restarts. For a
+short window at startup, other-TS may briefly overwrite the
+detailed EPG until the service's own actual-TS schedule is re-
+observed in the new session. DVR entries whose link to an EPG
+event was made before the restart are reconnected by the existing
+fuzzy-match fallback in `dvr_entry_fuzzy_match` once actual-TS
+data returns. The one remaining edge is a recording already in
+progress at restart that happens to receive a stop-time-extension
+update during this swap window — the extension lands on the
+placeholder rather than the resumed entry, so the recording stops
+at its originally-scheduled time. Across normal operation (no
+restart), Adaptive self-heals after the first actual-TS arrival
+and the placeholder is then permanently filtered for that service.
+
+Most installations should leave this at *Default* and pick the
+policy globally. Per-service overrides are useful for diagnosing or
+working around broadcaster-specific quirks — a service whose own
+actual-TS EIT is intermittent, or one where you trust the
+neighbour's other-TS more than the service's own.
--- /dev/null
+
+
+This setting is the default EIT (Event Information Table)
+processing policy applied to every DVB service whose own *EIT
+processing* is left at *Default*.
+
+DVB EIT is broadcast in two forms, distinguished by `table_id`:
+
+- **Actual transport stream EIT** (`0x4e` / `0x50–0x5f`) — broadcast
+ by the service's *own* multiplex. Describes the service itself.
+- **Other transport stream EIT** (`0x4f` / `0x60–0x6f`) — broadcast
+ by *another* multiplex describing this one. Often coarser.
+
+Without a precedence rule, the two sources can overwrite each other
+on every grab cycle, making the EPG flip between a detailed
+schedule (from actual-TS) and a coarser placeholder (from other-TS).
+
+| Value | Behaviour |
+| --- | --- |
+| **None** | Ignore all EIT for services set to Default. |
+| **Actual transport stream only** | Accept only actual-TS sub-tables (`0x4e`, `0x50–0x5f`). |
+| **Other transport stream only** | Accept only other-TS sub-tables (`0x4f`, `0x60–0x6f`). |
+| **Either** | Accept both, no precedence. Today's default behaviour — what tvheadend has always done. |
+| **Adaptive** | Accept both, but once a service's own actual-TS schedule has been seen this session, drop further other-TS for it. A neighbouring multiplex's coarse description cannot overwrite the service's detailed schedule. Services whose actual-TS is never received keep using other-TS, so a dedicated EPG multiplex still works. |
+
+Fresh installations default to **Either**, which preserves the
+historical behaviour. Switch to **Adaptive** to opt into the
+actual-TS-precedence heuristic without configuring services
+individually.
+
+**One operational caveat for Adaptive.** The "actual-TS observed"
+state is per session and resets when tvheadend restarts. For a
+short window at startup, other-TS may briefly overwrite the
+detailed EPG until each service's own actual-TS schedule is re-
+observed in the new session. DVR entries whose link to an EPG
+event was made before the restart are reconnected by the existing
+fuzzy-match fallback in `dvr_entry_fuzzy_match` once actual-TS
+data returns. The one remaining edge is a recording already in
+progress at restart that happens to receive a stop-time-extension
+update during this swap window — the extension lands on the
+placeholder rather than the resumed entry, so the recording stops
+at its originally-scheduled time. Across normal operation (no
+restart), Adaptive self-heals after the first actual-TS arrival.
+
+Any service can override this global default via its own *EIT
+processing* setting.
epggrab_ota_set_genre_translation();
}
+static htsmsg_t *
+epggrab_class_eit_processing_default_list(void *o, const char *lang)
+{
+ /* Per-service EIT_PROCESSING_DEFAULT defers here, so the global
+ * choice list omits Default itself. */
+ static const struct strtab tab[] = {
+ { N_("None"), EIT_PROCESSING_NONE },
+ { N_("Actual transport stream only"), EIT_PROCESSING_ACTUAL_ONLY },
+ { N_("Other transport stream only"), EIT_PROCESSING_OTHER_ONLY },
+ { N_("Either"), EIT_PROCESSING_EITHER },
+ { N_("Adaptive"), EIT_PROCESSING_ADAPTIVE },
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
CLASS_DOC(epgconf)
PROP_DOC(cron)
PROP_DOC(ota_genre_translation)
+PROP_DOC(eit_processing_default)
const idclass_t epggrab_class = {
.ic_snode = &epggrab_conf.idnode,
.opts = PO_ADVANCED,
.group = 3,
},
+ {
+ .type = PT_INT,
+ .id = "eit_processing_default",
+ .name = N_("EIT processing (default)"),
+ .desc = N_("Default EIT processing policy applied to "
+ "services whose own \"EIT processing\" is set "
+ "to Default. See Help for the full policy "
+ "descriptions."),
+ .doc = prop_doc_eit_processing_default,
+ .off = offsetof(epggrab_conf_t, eit_processing_default),
+ .opts = PO_ADVANCED | PO_DOC_NLIST,
+ .list = epggrab_class_eit_processing_default_list,
+ .group = 3,
+ },
{
.type = PT_STR,
.id = "ota_cron",
epggrab_conf.epgdb_periodicsave = 0;
epggrab_conf.epgdb_saveafterimport = 0;
epggrab_conf.epgdb_processparentallabels = 0;
+ epggrab_conf.eit_processing_default = EIT_PROCESSING_EITHER;
epggrab_cron_multi = NULL;
/*
*
*/
+/*
+ * EIT processing policy. Per-service value selects which EIT sub-tables
+ * are accepted for that service; the per-service Default value defers
+ * to the global default (which therefore omits Default from its choice
+ * list). Adaptive accepts both actual- and other-TS but, once the
+ * service's own actual-TS schedule has been received, drops further
+ * other-TS for it so a neighbouring multiplex's coarse description
+ * cannot overwrite the service's detailed schedule.
+ */
+#define EIT_PROCESSING_DEFAULT 0 /* per-service only: use global */
+#define EIT_PROCESSING_NONE 1
+#define EIT_PROCESSING_ACTUAL_ONLY 2
+#define EIT_PROCESSING_OTHER_ONLY 3
+#define EIT_PROCESSING_EITHER 4
+#define EIT_PROCESSING_ADAPTIVE 5
+
typedef struct epggrab_conf {
idnode_t idnode;
char *cron;
char *ota_genre_translation;
uint32_t ota_timeout;
uint32_t ota_initial;
+ uint32_t eit_processing_default;
uint32_t int_initial;
} epggrab_conf_t;
if (!LIST_FIRST(&svc->s_channels))
goto done;
- if (svc->s_dvb_ignore_eit)
+ /* Apply the effective EIT processing policy for this service:
+ * the per-service value, falling back to the global default when
+ * the service is set to Default. Tableid ranges in this callback
+ * (see the bounds check earlier in this function) are
+ * 0x4e actual-TS present/following
+ * 0x50-0x5f actual-TS schedule
+ * 0x4f, 0x60-0x6f other-TS present/following + schedule. */
+ int processing = svc->s_dvb_eit_processing;
+ if (processing == EIT_PROCESSING_DEFAULT)
+ processing = epggrab_conf.eit_processing_default;
+
+ switch (processing) {
+ case EIT_PROCESSING_NONE:
goto done;
+ case EIT_PROCESSING_ACTUAL_ONLY:
+ if (tableid == 0x4f || tableid >= 0x60)
+ goto done;
+ break;
+ case EIT_PROCESSING_OTHER_ONLY:
+ if (tableid == 0x4e || (tableid >= 0x50 && tableid < 0x60))
+ goto done;
+ break;
+ case EIT_PROCESSING_ADAPTIVE:
+ /* Track actual-TS schedule arrival per service. Once seen,
+ * drop further other-TS for it so a neighbouring multiplex's
+ * coarse description cannot overwrite the service's own
+ * detailed schedule. Services whose actual-TS is never
+ * received keep using other-TS, so a dedicated EPG multiplex
+ * still works. P/F (0x4e / 0x4f) is only two events per
+ * service and is not used as the "actual-TS seen" trigger. */
+ if (tableid >= 0x50 && tableid < 0x60) {
+ if (!svc->s_dvb_eit_actual_seen) {
+ svc->s_dvb_eit_actual_seen = 1;
+ tvhtrace(LS_TBL_EIT, "%s: %s actual-TS schedule seen",
+ mt->mt_name, svc->s_nicename);
+ }
+ } else if ((tableid == 0x4f || tableid >= 0x60) &&
+ svc->s_dvb_eit_actual_seen) {
+ tvhtrace(LS_TBL_EIT,
+ "%s: skip other-TS tid 0x%02X for %s, actual-TS seen",
+ mt->mt_name, tableid, svc->s_nicename);
+ goto done;
+ }
+ break;
+ case EIT_PROCESSING_EITHER:
+ default:
+ break;
+ }
/* Queue events */
len -= 11;
char *s_dvb_provider;
char *s_dvb_cridauth;
uint16_t s_dvb_servicetype;
- int s_dvb_ignore_eit;
+ int s_dvb_eit_processing; //EIT processing policy (EIT_PROCESSING_* in epggrab.h); Default defers to the global setting
int s_dvb_subtitle_processing; //Various options for replacing/augmenting the desc from the sub-title
int s_dvb_ignore_matching_subtitle; //Ignore the sub-title if same as title
char *s_dvb_charset;
*/
int s_dvb_eit_enable;
+ /* Runtime flag: this service's own actual-TS schedule EIT
+ * (table_id 0x50-0x5f) has been received this session. Used by
+ * the EIT_PROCESSING_ADAPTIVE policy to start dropping other-TS
+ * once detailed actual-TS data is available. Not persisted. */
+ int s_dvb_eit_actual_seen;
uint64_t s_dvb_opentv_chnum;
uint16_t s_dvb_opentv_id;
uint16_t s_atsc_source_id;
return strtab2htsmsg(tab, 1, lang);
}
+static htsmsg_t *
+mpegts_service_eit_processing_list ( void *o, const char *lang )
+{
+ static const struct strtab tab[] = {
+ { N_("Default (use global setting)"), EIT_PROCESSING_DEFAULT },
+ { N_("None"), EIT_PROCESSING_NONE }, //Ignore EIT for this service.
+ { N_("Actual transport stream only"), EIT_PROCESSING_ACTUAL_ONLY },
+ { N_("Other transport stream only"), EIT_PROCESSING_OTHER_ONLY },
+ { N_("Either"), EIT_PROCESSING_EITHER },
+ { N_("Adaptive"), EIT_PROCESSING_ADAPTIVE }, //Drop other-TS once actual-TS schedule has been seen.
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
+/* Translate the legacy dvb_ignore_eit bool into dvb_eit_processing on
+ * first load of an existing config. true → None (ignore EIT for this
+ * service), false → Default (defer to the global setting). Only run
+ * when the new key is absent, so already-migrated configs are
+ * untouched; the old key naturally disappears on next save (no
+ * matching prop on the class). */
+static void
+mpegts_service_class_load(struct idnode *self, htsmsg_t *c)
+{
+ mpegts_service_t *s = (mpegts_service_t *)self;
+ int b;
+
+ service_load((service_t *)self, c);
+
+ if (!htsmsg_field_find(c, "dvb_eit_processing") &&
+ !htsmsg_get_bool(c, "dvb_ignore_eit", &b))
+ s->s_dvb_eit_processing = b ? EIT_PROCESSING_NONE
+ : EIT_PROCESSING_DEFAULT;
+}
+
CLASS_DOC(mpegts_service)
+PROP_DOC(eit_processing)
const idclass_t mpegts_service_class =
{
.ic_caption = N_("DVB Inputs - Services"),
.ic_doc = tvh_doc_mpegts_service_class,
.ic_order = "enabled,channel,svcname",
+ .ic_load = mpegts_service_class_load,
.ic_properties = (const property_t[]){
{
.type = PT_STR,
.off = offsetof(mpegts_service_t, s_dvb_servicetype),
},
{
- .type = PT_BOOL,
- .id = "dvb_ignore_eit",
- .name = N_("Ignore EPG (EIT)"),
- .desc = N_("Enable or disable ignoring of Event Information "
- "Table (EIT) data for this service."),
- .off = offsetof(mpegts_service_t, s_dvb_ignore_eit),
- .opts = PO_EXPERT,
+ .type = PT_INT,
+ .id = "dvb_eit_processing",
+ .name = N_("EIT processing"),
+ .desc = N_("Which EIT sub-tables are accepted for this "
+ "service. Default defers to the global setting. "
+ "See Help for the full policy descriptions."),
+ .doc = prop_doc_eit_processing,
+ .off = offsetof(mpegts_service_t, s_dvb_eit_processing),
+ .opts = PO_EXPERT | PO_DOC_NLIST,
+ .list = mpegts_service_eit_processing_list,
},
{
.type = PT_INT,