From: E.Smith <31170571+azlm8t@users.noreply.github.com> Date: Sat, 19 Aug 2017 09:26:44 +0000 (+0100) Subject: DVR: Record segmented programmes identified by EIT. X-Git-Tag: v4.2.4~59 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fa4487a033e58a8df87d79b58a8be9b78aaa1160;p=thirdparty%2Ftvheadend.git DVR: Record segmented programmes identified by EIT. A broadcaster can split a programme in to multiple segments. These are identified by the segments having a CRID containing an IMI (a hash character followed by an ID). Segments have identical values for this CRID and IMI and the segments start within three hours of the end of the previous segment. These rules are documented in this spec in section 7.1.7: http://www.freeviewnz.tv/media/1055/freeview_dtt_transmission_rules_2_1.pdf This document is based on the UK transmission specification. For example, a movie may be broadcast as: 21:00--22:00 movie segment 1 22:00--22:05 five minute news 22:05--23:30 movie segment 2 The xmltv guides typically merges this segments in to one programme such as: 21:00--23:30 movie (including news) In theory, a programme can be split in to numerous segments. In practice I have only seen a programme split in to two segments as shown above. To simplify recording these programmes, we identify segmented programmes and extend the stop time. So, in the above case, if the user records the 9pm showing then we will automatically extend the stop time to be 23:30 instead of 22:00. This patch explicitly disables "epg running state" for stopping the recording. This is because the recording is tied to the first showing and we don't want the recording to stop at 22:00 in the above example. We cache the calculated stop time to avoid any overheads, but explicitly recalculate it at the start of the programme. This ensures we detect any recent changes. No modification is done of the actual EPG data to attempt to merge the programme segments. The consequence of this is that the EPG will only show a "recording" marker against the first segment of the programme and not against the second segment, which is unfortunate, however it is consistent with recordings which have an extra stop time. The upcoming recordings tab correctly shows the end time. The duration of the finished recording is currently incorrectly reported due to #3706. So the movie above would be reported as 60 minutes instead of 2h30. Although the CRID processing is believed to be a global standard, if other countries do not follow the UK/NZ specification then the dvr_entry_get_segment_stop_extra could be updated to check a (bitmask) config variable to enable/disable specific CRID processing. I believe the overhead of the strcmp for the CRID check is minimal, even on low-spec machines. If necessary we could cache to indicate the CRID check has failed. Issue: #1303 --- diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index 6618cf25e..685ea9dfc 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -191,6 +191,7 @@ typedef struct dvr_entry { time_t de_start_extra; time_t de_stop_extra; + time_t de_segment_stop_extra; /* Automatic extra time for segmented prog (from EPG) */ time_t de_running_start; time_t de_running_stop; diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index a89cd4dc8..de226db21 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -53,6 +53,7 @@ static void dvr_entry_start_recording(dvr_entry_t *de, int clone); static void dvr_timer_start_recording(void *aux); static void dvr_timer_stop_recording(void *aux); static int dvr_entry_rerecord(dvr_entry_t *de); +static time_t dvr_entry_get_segment_stop_extra(dvr_entry_t *de); /* * @@ -341,7 +342,7 @@ dvr_entry_get_start_time( dvr_entry_t *de, int warm ) time_t dvr_entry_get_stop_time( dvr_entry_t *de ) { - return time_t_out_of_range((int64_t)de->de_stop + dvr_entry_get_extra_time_post(de)); + return time_t_out_of_range((int64_t)de->de_stop + dvr_entry_get_segment_stop_extra(de) + dvr_entry_get_extra_time_post(de)); } time_t @@ -1064,6 +1065,84 @@ dvr_entry_create_htsp(int enabled, const char *config_uuid, comment); } +/** + * Determine stop time for the broadcast taking in + * to account segmented programmes via EIT. + */ +static time_t dvr_entry_get_segment_stop_extra( dvr_entry_t *de ) +{ + if (!de) + return 0; + + /* Return any cached value we have previous calculated. */ + time_t segment_stop_extra = de->de_segment_stop_extra; + if (segment_stop_extra) { + return segment_stop_extra; + } + + /* If we have no broadcast data then can not search for matches */ + epg_broadcast_t *e = de->de_bcast; + if (!e) { + return 0; + } + + const char *ep_uri = e->episode->uri; + + /* If not a segmented programme then no segment extra time */ + if (!ep_uri || strncmp(ep_uri, "crid://", 7) || !strstr(ep_uri, "#")) + return 0; + + /* This URI is a CRID (from EIT) which contains an IMI so the + * programme is segmented such as part1, <5 minute news>, part2. So + * we need to check if the next few programmes have the same + * crid+imi in which case we extend our stop time to include that. + * + * We record the "news" programme too since often these segmented + * programmes have incorrect start/stop times. This is also + * consistent with xmltv that normally just merges these programmes + * together. + * + * The Freeview NZ documents say we only need to check for segments + * on the same channel and where each segment is within three hours + * of the end of the previous segment. We'll use that as best + * practice. + * + * We also put a couple of safety checks on the number of programmes + * we check in case broadcast data is bad. + */ + enum + { + THREE_HOURS = 3 * 60 * 60, + MAX_STOP_TIME = 9 * 60 * 60 + }; + + const time_t start = e->start; + const time_t stop = e->stop; + const time_t maximum_stop_time = start + MAX_STOP_TIME; + int max_progs_to_check = 10; + epg_broadcast_t *next; + for (next = epg_broadcast_get_next(e); + --max_progs_to_check && stop < maximum_stop_time && + next && next->episode && next->start < stop + THREE_HOURS; + next = epg_broadcast_get_next(next)) { + const char *next_uri = next->episode->uri; + if (next_uri && strcmp(ep_uri, next_uri) == 0) { + /* Identical CRID+IMI. So that means that programme is a + * segment part of this programme. So extend our stop time + * to include this programme. + */ + segment_stop_extra = next->stop - stop; + tvhinfo(LS_DVR, "Increasing stop for \"%s\" on \"%s\" \"%s\" by %"PRId64" seconds at start %"PRId64" and original stop %"PRId64, + lang_str_get(e->episode->title, NULL), DVR_CH_NAME(de), ep_uri, (int64_t)segment_stop_extra, (int64_t)start, (int64_t)stop); + } + } + + /* Cache the value */ + de->de_segment_stop_extra = segment_stop_extra; + return segment_stop_extra; +} + + /** * */ @@ -2033,6 +2112,15 @@ void dvr_event_running(epg_broadcast_t *e, epg_source_t esrc, epg_running_t runn } } else if ((running == EPG_RUNNING_STOP && de->de_dvb_eid == e->dvb_eid) || running == EPG_RUNNING_NOW) { + /* Don't stop recording if we are a segmented programme since + * (by definition) the first segment will be marked as stop long + * before the second segment is marked as started. Otherwise if we + * processed the stop then we will stop the dvr_thread from + * recording tv packets. + */ + if (de->de_segment_stop_extra) { + continue; + } /* * make checking more robust * sometimes, the running bits are parsed randomly for a few moments @@ -2129,7 +2217,15 @@ dvr_timer_stop_recording(void *aux) "rstop", de->de_running_stop, "stop recording timer called"); /* EPG thinks that the program is running */ - if (de->de_running_start > de->de_running_stop) { + if (de->de_segment_stop_extra) { + const time_t now = gclk(); + const time_t stop = dvr_entry_get_stop_time(de); + tvhinfo(LS_DVR, "dvr_timer_stop_recording - forcing stop of programme \"%s\" on \"%s\" due to exceeding stop time with running_start %"PRId64" and running_stop %"PRId64" segment stop %"PRId64" now %"PRId64" stop %"PRId64, + lang_str_get(de->de_title, NULL), DVR_CH_NAME(de), (int64_t)de->de_running_start, (int64_t)de->de_running_stop, (int64_t)de->de_segment_stop_extra, (int64_t)now, (int64_t)stop); + /* no-op. We fall through to the final dvr_stop_recording below. + * This path is here purely to get a log. + */ + } else if (de->de_running_start > de->de_running_stop) { gtimer_arm_rel(&de->de_timer, dvr_timer_stop_recording, de, 10); return; } @@ -2163,6 +2259,20 @@ dvr_entry_start_recording(dvr_entry_t *de, int clone) return; } + /* Reset cached value for segment stopping so it can be recalculated + * immediately at start of recording. This handles case where a + * programme is split in to three-or-more segments and we want to + * ensure we correctly pick up all segments at the start of a + * recording. Otherwise if a broadcaster has an EIT window of say + * 24h and we get the first two segments when we first calculate the + * stop time then we would not recalculate the stop time when + * further segments enter the EIT window. + */ + de->de_segment_stop_extra = 0; + const time_t stop = dvr_entry_get_stop_time(de); + tvhinfo(LS_DVR, "About to set stop timer for \"%s\" on \"%s\" at start %"PRId64" and original stop %"PRId64" and overall stop at %"PRId64, + lang_str_get(de->de_title, NULL), DVR_CH_NAME(de), (int64_t)de->de_start, (int64_t)de->de_stop, (int64_t)stop); + gtimer_arm_absn(&de->de_timer, dvr_timer_stop_recording, de, dvr_entry_get_stop_time(de)); }