MythTV master
recordingquality.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <utility>
3
7
8#include "recordinginfo.h"
9#include "recordingquality.h"
10
11static void merge_overlapping(RecordingGaps &gaps);
12static double score_gaps(const RecordingInfo& /*ri*/, const RecordingGaps& /*gaps*/);
13static QDateTime get_start(const RecordingInfo& /*ri*/);
14static QDateTime get_end(const RecordingInfo& /*ri*/);
15
18 : m_recordingGaps(std::move(rg))
19{
20 if (!ri)
21 return;
22
23 // QList doesn't play well with std::ranges
24 // NOLINTNEXTLINE(modernize-use-ranges)
25 std::stable_sort(m_recordingGaps.begin(), m_recordingGaps.end());
27
29
30 LOG(VB_RECORD, LOG_INFO,
31 QString("RecordingQuality() score(%3)").arg(m_overallScore));
32}
33
35 const RecordingInfo *ri, RecordingGaps rg,
36 const QDateTime &first, const QDateTime &latest) :
37 m_recordingGaps(std::move(rg))
38{
39 if (!ri)
40 return;
41
43 int max_start = gCoreContext->GetNumSetting("MaxStartGap", 15);
44 int max_end = gCoreContext->GetNumSetting("MaxEndGap", 15);
45
46 // trim start
47 QDateTime start = get_start(*ri);
48 while (!m_recordingGaps.empty() &&
49 m_recordingGaps.first().GetStart() < start)
50 {
51 RecordingGap &firstGap = m_recordingGaps.first();
52 if (start < firstGap.GetEnd())
53 firstGap = RecordingGap(start, firstGap.GetEnd());
54 else
55 m_recordingGaps.pop_front();
56 }
57
58 // trim end
59 QDateTime end = get_end(*ri).addSecs(-max_end);
60 while (!m_recordingGaps.empty() &&
61 m_recordingGaps.back().GetEnd() > end)
62 {
64 if (back.GetStart() < end)
65 back = RecordingGap(back.GetStart(), end);
66 else
67 m_recordingGaps.pop_back();
68 }
69
70 // account for late start
71 int start_gap = (first.isValid()) ? start.secsTo(first) : 0;
72 if (start_gap > max_start)
73 m_recordingGaps.push_front(RecordingGap(start, first));
74
75 // account for missing end
76 int end_gap = (latest.isValid()) ? latest.secsTo(end) : 0;
77 if (end_gap > max_end)
78 m_recordingGaps.push_back(RecordingGap(latest, end));
79
80 // QList doesn't play well with std::ranges
81 // NOLINTNEXTLINE(modernize-use-ranges)
82 std::stable_sort(m_recordingGaps.begin(), m_recordingGaps.end());
84
86
87 LOG(VB_RECORD, LOG_INFO,
88 QString("RecordingQuality() start(%1) end(%2) score(%3)")
91 QString::number(m_overallScore)));
92}
93
95 int continuity_error_count, int packet_count)
96{
97 m_continuityErrorCount = continuity_error_count;
98 m_packetCount = packet_count;
99 if (!m_packetCount)
100 return;
101
102 double er = double(m_continuityErrorCount) / double(m_packetCount);
103 if (er >= 0.01)
104 m_overallScore = std::max(m_overallScore * 0.60, 0.0);
105 else if (er >= 0.001)
106 m_overallScore = std::max(m_overallScore * 0.80, 0.0);
107 else if (er >= 0.0001)
108 m_overallScore = std::max(m_overallScore * 0.90, 0.0);
109
110 if (er >= 0.01)
111 m_overallScore = std::min(m_overallScore, 0.5);
112}
113
115{
116 return (m_overallScore * 100) <
117 gCoreContext->GetNumSetting("MinimumRecordingQuality", 95);
118}
119
121{
122 QString str =
123 QString(R"(<RecordingQuality overall_score="%1" key="%2")")
124 .arg(m_overallScore).arg(m_programKey);
125
126 if (m_packetCount)
127 {
128 str += QString(R"( continuity_error_count="%1" packet_count="%2")")
130 }
131
132 if (m_recordingGaps.empty())
133 return str + " />";
134
135 str += ">\n";
136
137 auto add_gap = [](const QString& s, const auto & gap) {
138 return s + StringUtil::indentSpaces(1) +
139 QString("<Gap start=\"%1\" end=\"%2\" duration=\"%3\" />\n")
140 .arg(gap.GetStart().toString(Qt::ISODate))
141 .arg(gap.GetEnd().toString(Qt::ISODate))
142 .arg(gap.GetStart().secsTo(gap.GetEnd()));
143 };
144 str = std::accumulate(m_recordingGaps.cbegin(),m_recordingGaps.cend(),
145 str, add_gap);
146
147 return str + "</RecordingQuality>";
148}
149
151{
152 if (gaps.empty())
153 return;
154
155 RecordingGaps::iterator it = gaps.begin();
156 RecordingGaps::iterator next = it; ++next;
157 while (next != gaps.end())
158 {
159 if ((*it).GetEnd() >= (*next).GetStart())
160 {
161 (*it) = RecordingGap((*it).GetStart(), (*next).GetEnd());
162 next = gaps.erase(next);
163 }
164 else
165 {
166 it = next;
167 ++next;
168 }
169 }
170}
171
172static double score_gaps(const RecordingInfo &ri, const RecordingGaps &gaps)
173{
174 if (gaps.empty())
175 return 1.0;
176
177 QDateTime start = get_start(ri);
178
179 double program_length = start.secsTo(get_end(ri));
180 if (program_length < 1.0)
181 return 0.0;
182
183 double score = 1.0;
184 for (const auto & gap : gaps)
185 {
186 double gap_start = start.secsTo(gap.GetStart());
187 double gap_end = start.secsTo(gap.GetEnd());
188 double gap_length = gap_end - gap_start;
189 double rel_start = gap_start / program_length;
190 double rel_end = gap_end / program_length;
191 double rel_center = (rel_start + rel_end) * 0.5;
192 double rel_length = rel_end - rel_start;
193
194 /*
195 LOG(VB_GENERAL, LOG_INFO,
196 QString("%1 gap(%2,%3,%4) rel(%5,%6,%7)")
197 .arg((*it).toString())
198 .arg(gap_start).arg(gap_end).arg(gap_length)
199 .arg(rel_start).arg(rel_end).arg(rel_length));
200 */
201
202 if (rel_center >= 0.9 || rel_end >= 0.95)
203 rel_length *= 4;
204
205 if (rel_center < 0.1)
206 rel_length *= 2;
207
208 if (gap_length > 5) // 5 secs
209 rel_length *= 1.5;
210
211 if (gap_length > 120) // 2 minutes
212 rel_length *= 5;
213
214 // NOTE: many more scoring adjustments could be made here
215 // and we may want to tune this differently depending on
216 // program length.
217
218 score -= rel_length;
219 }
220
221 return (score > 0.0) ? score : 0.0;
222}
223
224static QDateTime get_start(const RecordingInfo &ri)
225{
226 if (ri.GetDesiredStartTime().isValid())
227 {
228 return (ri.GetScheduledStartTime() > ri.GetDesiredStartTime()) ?
230 }
231 return ri.GetScheduledStartTime();
232}
233
234static QDateTime get_end(const RecordingInfo &ri)
235{
236 if (ri.GetDesiredEndTime().isValid())
237 {
238 return (ri.GetScheduledEndTime() < ri.GetDesiredEndTime()) ?
240 }
241 return ri.GetScheduledEndTime();
242}
int GetNumSetting(const QString &key, int defaultval=0)
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:405
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:398
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
Definition: programinfo.h:346
QDateTime GetEnd(void) const
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:36
QDateTime GetDesiredEndTime(void) const
QDateTime GetDesiredStartTime(void) const
RecordingGaps m_recordingGaps
bool IsDamaged(void) const
void AddTSStatistics(int continuity_error_count, int packet_count)
QString toStringXML(void) const
RecordingQuality(const RecordingInfo *ri, RecordingGaps rg)
static std::vector< uint32_t > back
Definition: goom_core.cpp:27
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ ISODate
Default UTC.
Definition: mythdate.h:17
QString indentSpaces(unsigned int level, unsigned int size=4)
Definition: stringutil.h:40
static QDateTime get_start(const RecordingInfo &)
static double score_gaps(const RecordingInfo &, const RecordingGaps &)
static void merge_overlapping(RecordingGaps &gaps)
static QDateTime get_end(const RecordingInfo &)
QList< RecordingGap > RecordingGaps