forked from vdr-projects/vdr-plugin-tvscraper
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy patheventOrRec.c
417 lines (390 loc) · 19.4 KB
/
eventOrRec.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
#include "eventOrRec.h"
#include <dirent.h>
csEventOrRecording::csEventOrRecording(const cEvent *event):
m_event(event),
m_description(event?config.splitDescription(event->Description()):cSv())
{
}
csEventOrRecording::csEventOrRecording(const cStaticEvent *sEvent):
m_event(nullptr),
m_description(sEvent?config.splitDescription(sEvent->Description()):cSv())
{
}
csEventOrRecording::csEventOrRecording(const cRecordingInfo *info):
m_event(info?info->GetEvent():nullptr),
m_description(info?config.splitDescription(info->Description()):cSv())
{
}
void csEventOrRecording::AddYears(cYears &years) const {
years.addYears(Description() );
years.addYears(ShortText() );
years.addYears(Title() );
}
int csEventOrRecording::DurationDistance(int DurationInMin) {
// -1 : currently no data available, but should be available later (recording length unkown as recording is ongoing or destination of cut/copy/move
// -2 : no data available
// all others: distance ...
if (DurationInMin <=0 ) return -2; // no data
int durationInMinLow, durationInMinHigh;
int durationRange = DurationRange(durationInMinLow, durationInMinHigh);
if (durationRange < 0) return durationRange;
if (DurationInMin > durationInMinHigh) return DurationInMin - durationInMinHigh;
if (DurationInMin < durationInMinLow) return durationInMinLow - DurationInMin;
return 0;
}
int csEventOrRecording::DurationRange(int &durationInMinLow, int &durationInMinHigh) {
// 0 : data available
// -2 : no data available
// OLD !!!!! return true, if data is available
if (!EventDuration() ) return -2;
durationInMinLow = DurationLowSec() / 60 - 1;
durationInMinHigh = DurationHighSec() / 60 + (10 * DurationHighSec()) / 60 / 60; // add 10 mins for 60 min duration, more for longer durations. This is because often a cut version of the movie is broadcasted
// if (Recording() && Title() && strcmp("The Expendables 3", Title() ) == 0)
// esyslog("tvscraper: csEventOrRecording::DurationRange, title = %s, durationInMinLow = %i, durationInMinHigh = %i", Title(), durationInMinLow, durationInMinHigh);
return 0;
}
cSv csEventOrRecording::EpisodeSearchString() const {
if(ShortText() && *ShortText() ) return ShortText();
return Description().substr(0, 100);
}
std::string csEventOrRecording::ChannelName() const {
tChannelID channelID = ChannelID();
if (!channelID.Valid() ) {
esyslog("tvscraper, csEventOrRecording::ChannelName, invalid channel ID, title %s", Title() );
return m_unknownChannel;
}
#if APIVERSNUM < 20301
const cChannel *channel = Channels.GetByChannelID(channelID);
#else
LOCK_CHANNELS_READ;
const cChannel *channel = Channels->GetByChannelID(channelID);
#endif
if (!channel) {
esyslog("tvscraper, csEventOrRecording::ChannelName, no channel for channel ID %s, title %s", ChannelIDs().c_str(), Title() );
return m_unknownChannel;
}
return channel->Name();
}
// Recording
csRecording::csRecording(const cRecording *recording):
csEventOrRecording(recording?recording->Info():(cRecordingInfo*)nullptr),
m_recording(recording)
{
if (!recording ) {
esyslog("tvscraper: ERROR: csRecording::csRecording !recording");
return;
}
if (!recording->Info() ) {
esyslog("tvscraper: ERROR: csRecording::csRecording !recording->Info()");
return;
}
if (!recording->Info()->GetEvent() ) {
esyslog("tvscraper: ERROR: csRecording::csRecording !recording->Info()->GetEvent()");
}
// if (cSv(recording->Info()->Title()) == "A Fish Called Wanda") m_debug = true;
if (m_debug) esyslog("tvscraper, debug recording %s", recording->Info()->Title());
}
bool csRecording::recordingLengthIsChanging() {
int recordingUsage = m_recording->IsInUse();
// if ((recordingUsage | ruReplay) == ruReplay) return false;
if ((recordingUsage & (ruTimer | ruDst)) != 0) return true;
return false;
}
int csRecording::DurationRange(int &durationInMinLow, int &durationInMinHigh) {
// 0 : data available
// -1 : currently no data available, but should be available later (recording length unknown as recording is ongoing or destination of cut/copy/move
// -2 : no data available
// OLD !!!! return true, if data is available
if (recordingLengthIsChanging() ) return -1;
if (!m_recording->FileName() ) return -2;
if (!EventDuration() && !m_recording->LengthInSeconds() ) return -2;
if (m_recording->IsEdited() || DurationInSecMarks() != -1) {
// length of recording without adds
int durationInSec = m_recording->IsEdited()?m_recording->LengthInSeconds():DurationInSecMarks();
durationInMinLow = durationInSec / 60 - 2; // 2 min for inaccuracies
durationInMinHigh = durationInSec / 60 + (15 * durationInSec) / 60 / 60; // add 15 mins for 60 min duration, more for longer durations. This is because often a cut version of the movie is broadcasted. Also, the markad results are not really reliable
return 0;
} else {
return csEventOrRecording::DurationRange(durationInMinLow, durationInMinHigh);
}
}
int csRecording::DurationInSecMarks_int(void) {
// return -1 if no data is available
// otherwise, duration of cut recording
// also set m_durationInSecMarks, to the returned value
m_durationInSecMarks = -1;
if (!m_recording) return m_durationInSecMarks;
if (!m_recording->HasMarks() ) return m_durationInSecMarks;
cMarks marks;
marks.Load(m_recording->FileName(), m_recording->FramesPerSecond(), m_recording->IsPesRecording() );
int numSequences = marks.GetNumSequences();
if (numSequences == 0) return m_durationInSecMarks; // only one begin mark at index 0 -> no data
// do the calculation
const cMark *BeginMark = marks.GetNextBegin();
if (!BeginMark) return m_durationInSecMarks;
int durationInFrames = 0;
while (const cMark *EndMark = marks.GetNextEnd(BeginMark)) {
durationInFrames += EndMark->Position() - BeginMark->Position();
BeginMark = marks.GetNextBegin(EndMark);
}
if (BeginMark) { // the last sequence had no actual "end" mark
durationInFrames += m_recording->LengthInSeconds() * m_recording->FramesPerSecond() - BeginMark->Position();
}
int durationInSeconds = durationInFrames / m_recording->FramesPerSecond();
// calculation finished, sanity checks:
if (m_recording->LengthInSeconds() < durationInSeconds) {
esyslog("tvscraper: ERROR: recording length shorter than length of cut recording, recording length %i length of cut recording %i filename \"%s\"", m_recording->LengthInSeconds(), durationInSeconds, m_recording->FileName() );
return m_durationInSecMarks;
}
if (numSequences == 1) {
// no adds found, just recording margin at start / stop
if (EventDuration() ) {
// event duration is available, and should be equal to duration of cut recording
if (abs(EventDuration() - durationInSeconds) < 4*60) m_durationInSecMarks = durationInSeconds;
else esyslog("tvscraper: GetDurationInSecMarks: sanity check, one sequence, more than 4 mins difference to event length. Event length %i length of cut out of recording %i filename \"%s\"", EventDuration(), durationInSeconds, m_recording->FileName() );
} else {
// event duration is not available, cut recording sould be recording - timer margin at start / stop
if (DurationWithoutMarginSec() < durationInSeconds) m_durationInSecMarks = durationInSeconds;
else esyslog("tvscraper: GetDurationInSecMarks: sanity check, one sequence, more than 20 mins cut. Recording length %i length of cut out of recording %i filename \"%s\"", m_recording->LengthInSeconds(), durationInSeconds, m_recording->FileName() );
}
if (m_durationInSecMarks != -1 && config.enableDebug) esyslog("tvscraper: GetDurationInSecMarks: sanity check ok, one sequence, Recording length %i length of cut out of recording %i filename \"%s\"", m_recording->LengthInSeconds(), durationInSeconds, m_recording->FileName() );
return m_durationInSecMarks;
}
// more than one sequence, ads found
// guess a reasonable min length
if (durationInSeconds < DurationLowSec() ) {
esyslog("tvscraper: GetDurationInSecMarks: sanity check, too much cut out of recording. Recording length %i length of cut recording %i expected min length of cut recording %i filename \"%s\"", m_recording->LengthInSeconds(), durationInSeconds, DurationLowSec(), m_recording->FileName() );
return m_durationInSecMarks;
}
if (config.enableDebug) esyslog("tvscraper: GetDurationInSecMarks: sanity check ok, Recording length %i length of cut out of recording %i filename \"%s\"", m_recording->LengthInSeconds(), durationInSeconds, m_recording->FileName() );
m_durationInSecMarks = durationInSeconds;
return m_durationInSecMarks;
}
int parseMarkadVpsKeyword(std::ifstream &markad, const char *fname) {
// return 0: EOF, or error
// 1 start
// 2 pause start
// 3 pause stop
// 4 stop
if (!markad) return 0;
std::string line;
markad >> line;
if (line.empty() ) return 0;
if (line.compare(0, 6, "START:", 6) == 0) return 1;
if (line.compare(0, 5, "STOP:", 5) == 0) return 4;
if (line.compare(0, 5, "PAUSE" , 5) != 0) {
esyslog("tvscraper: ERROR parsing markad.vps, line = %s, file = %s", line.c_str(), fname );
return 0;
}
// check PAUSE_START: / PAUSE_STOP:
if (line.compare(5, 7, "_START:") == 0) return 2;
if (line.compare(5, 6, "_STOP:") == 0) return 3;
// check PAUSE START: / PAUSE STOP:
line = "";
markad >> line;
if (line.empty() ) {
esyslog("tvscraper: ERROR parsing markad.vps, line empty after PAUSE, file = %s", fname);
return 0;
}
if (line.compare(0, 6, "START:", 6) == 0) return 2;
if (line.compare(0, 5, "STOP:", 5) == 0) return 3;
esyslog("tvscraper: ERROR parsing markad.vps, line after PAUSE = %s, file = %s", line.c_str(), fname );
return 0;
}
int parseMarkadVps(std::ifstream &markad, int &time, const char *fname) {
// return 0: EOF, or error
// 1 start
// 2 pause start
// 3 pause stop
// 4 stop
// time: timestamp in sec (since recording start)
time = -1;
int keyword = parseMarkadVpsKeyword(markad, fname);
if (keyword == 0) return 0;
if (!markad) {
esyslog("tvscraper: ERROR parsing markad.vps, EOF after keyword = %d, file = %s", keyword, fname);
return 0;
}
std::string line;
markad >> line;
if (line.empty() ) {
esyslog("tvscraper: ERROR parsing markad.vps, empty line after keyword = %d, file = %s", keyword, fname);
return 0;
}
if (!markad) {
esyslog("tvscraper: ERROR parsing markad.vps, EOF after keyword = %d and line %s, file = %s", keyword, line.c_str() , fname);
return 0;
}
markad >> time;
return keyword;
}
int csRecording::getVpsLength() {
// -4: not checked. (will not be returned)
// -3: markad.vps not available or wrong format
// -2: VPS not used.
// -1: VPS used, but no time available
// >0: VPS length in seconds
if (m_vps_length > -4) return m_vps_length;
CONCATENATE(filename, m_recording->FileName(), "/markad.vps");
struct stat buffer;
if (stat (filename, &buffer) != 0) { m_vps_length = -3; return m_vps_length; }
std::ifstream markad(filename);
if (!markad) { m_vps_length = -3; return m_vps_length; }
std::string l1;
markad >> l1;
if (l1 == "VPSTIMER=NO") { m_vps_length = -2; return m_vps_length; }
if (l1 != "VPSTIMER=YES") {
// esyslog("tvscraper: ERROR: markad.vps, wrong format, first line=%s, path %s", l1.c_str(), m_recording->FileName());
// remove this error. There are old versions of markad.vps without this line
m_vps_length = -3;
return m_vps_length;
}
m_vps_length = -1; // VPS was used. Check the length
if (!markad) return m_vps_length;
int time_start_sequence, time_end_sequence;
int keyword = parseMarkadVps(markad, time_start_sequence, m_recording->FileName());
if (keyword == 0) return m_vps_length;
if (keyword != 1) {
esyslog("tvscraper: ERROR: markad.vps, first keyword = %d, path %s", keyword, m_recording->FileName());
return m_vps_length;
}
m_vps_start = time_start_sequence;
for (int cur_length = 0;;) {
keyword = parseMarkadVps(markad, time_end_sequence, m_recording->FileName());
if (keyword != 4 && keyword != 2) {
esyslog("tvscraper: ERROR: markad.vps, keyword %d after start sequence, path %s", keyword, m_recording->FileName());
return m_vps_length;
}
if (time_start_sequence >= time_end_sequence) {
esyslog("tvscraper: ERROR:time_start_sequence %d > time_end_sequence %d, keyword %d, path %s", time_start_sequence, time_end_sequence, keyword, m_recording->FileName());
return m_vps_length;
}
cur_length += (time_end_sequence - time_start_sequence);
if (keyword == 4) {
m_vps_length = cur_length;
return m_vps_length;
}
keyword = parseMarkadVps(markad, time_start_sequence, m_recording->FileName());
if (keyword != 3) {
esyslog("tvscraper: ERROR: markad.vps, keyword %d after pause start sequence, path %s", keyword, m_recording->FileName());
return m_vps_length;
}
}
}
bool csRecording::getTvscraperTimerInfo(bool &vps, int &lengthInSeconds) {
// return false if no info is available
CONCATENATE(filename_old, m_recording->FileName(), "/tvscrapper.json");
CONCATENATE(filename_new, m_recording->FileName(), "/tvscraper.json");
const char *filename = filename_old;
struct stat buffer;
if (stat (filename, &buffer) != 0) filename = filename_new;
cJsonDocumentFromFile document(filename);
if (document.HasParseError() ) return false;
rapidjson::Value::ConstMemberIterator timer_j = document.FindMember("timer");
if (timer_j == document.MemberEnd() ) return false; // timer information not available
if (!getValue(timer_j->value, "vps", vps, filename ) ) return false;
time_t start, stop;
if (!getValue(timer_j->value, "start_time", start, filename ) ) return false;
if (!getValue(timer_j->value, "stop_time", stop, filename ) ) return false;
lengthInSeconds = stop - start;
return true;
}
bool csRecording::getEpgsearchTimerInfo(bool &vps, int &lengthInSeconds) {
// false: No info available
// otherwise: Epgsearch was used, timer length
if (!m_recording->Info()->Aux() ) return false;
cSv epgsearchAux = partInXmlTag(m_recording->Info()->Aux(), "epgsearch");
if (epgsearchAux.empty()) return false;
cSv epgsearchStart = partInXmlTag(epgsearchAux, "start");
if (epgsearchStart.empty()) return false;
cSv epgsearchStop = partInXmlTag(epgsearchAux, "stop");
if (epgsearchStop.empty()) return false;
time_t start = parse_unsigned<time_t>(epgsearchStart);
time_t stop = parse_unsigned<time_t>(epgsearchStop);
lengthInSeconds = stop - start;
vps = (start == m_recording->Info()->GetEvent()->StartTime() ) &&
(lengthInSeconds == m_recording->Info()->GetEvent()->Duration() );
return true;
}
int csRecording::durationDeviationNoVps() {
// vps was NOT used.
if (m_debug) esyslog("tvscraper: csRecording::durationDeviationNoVps");
bool vps;
int lengthInSeconds;
if (getTvscraperTimerInfo(vps, lengthInSeconds) ) {
if (vps) esyslog("tvscraper: ERROR, inconsistent VPS data in %s", m_recording->FileName() );
if (m_debug) esyslog("tvscraper: csRecording::durationDeviationNoVps, getTvscraperTimerInfo lengthInSeconds = %d, m_recording->LengthInSeconds() = %d", lengthInSeconds, m_recording->LengthInSeconds());
return std::max(0, lengthInSeconds - m_recording->LengthInSeconds());
} else if (getEpgsearchTimerInfo(vps, lengthInSeconds) ) {
if (vps) esyslog("tvscraper: ERROR, inconsistent VPS data (epgsearch) in %s", m_recording->FileName() );
return std::max(0, lengthInSeconds - m_recording->LengthInSeconds());
} else
return std::max(0, m_recording->Info()->GetEvent()->Duration() + (::Setup.MarginStart + ::Setup.MarginStop) * 60 - m_recording->LengthInSeconds());
}
int csRecording::durationDeviationVps(int s_runtime, bool markadMissingTimes) {
// VPS was used, but we don't have the VPS start / end mark
int deviation = m_recording->Info()->GetEvent()->Duration() - m_recording->LengthInSeconds();
if (deviation <= 0) return 0; // recording is longer than event duration -> no error
if (!markadMissingTimes && deviation <= 6*60) return 0; // we assume VPS was used, und report only huge deviations > 6min. Except if markad found VPS, but has no VPS times. This indicates that the VPS start event was detected too late
if (s_runtime <= 0) return deviation;
return std::min(deviation, std::abs(s_runtime*60 - m_recording->LengthInSeconds()));
}
int csRecording::durationDeviation(int s_runtime) {
// return deviation between actual reording length, and planned duration (timer / vps)
// return -1 if no information is available
if (!m_recording || !m_recording->FileName() || !m_recording->Info() || !m_recording->Info()->GetEvent() ) return -1;
if (m_recording->IsEdited() || cSv(m_recording->Info()->Aux()).find("<isEdited>true</isEdited>") != std::string_view::npos) return 0; // we assume, who ever edited the recording checked for completeness
int inUse = m_recording->IsInUse();
if ((inUse != ruNone) && (inUse != ruReplay)) return -1; // still recording => incomplete, this check is useless
if (m_debug) esyslog("tvscraper: csRecording::durationDeviation 1, s_runtime %d", s_runtime);
if (!m_recording->Info()->GetEvent()->Vps() ) return durationDeviationNoVps();
if (m_debug) esyslog("tvscraper: csRecording::durationDeviation event has VPS, s_runtime %d", s_runtime);
// event has VPS. Was VPS used?
int vps_used_markad = getVpsLength();
if (vps_used_markad == -2) return durationDeviationNoVps();
if (vps_used_markad >= 0) return std::abs(vps_used_markad + m_vps_start - m_recording->LengthInSeconds() );
if (vps_used_markad == -1) return durationDeviationVps(s_runtime, true);
// still unclear whether VPS was used
bool vps;
int lengthInSeconds;
if (getTvscraperTimerInfo(vps, lengthInSeconds) ) {
// information in tvscraper.json is available
if(!vps) return std::max(0, lengthInSeconds - m_recording->LengthInSeconds());
// vps was used, but the VPS start/stop marks are missing
return durationDeviationVps(s_runtime);
}
if (getEpgsearchTimerInfo(vps, lengthInSeconds) ) {
// epgsearch was used, and information in aux is available
if(!vps) return std::max(0, lengthInSeconds - m_recording->LengthInSeconds());
// vps was used, but the VPS start/stop marks are missing
return durationDeviationVps(s_runtime);
}
// still unclear whether VPS was used, no final inforamtion is available. Guess
int deviationNoVps = m_recording->Info()->GetEvent()->Duration() + (::Setup.MarginStart + ::Setup.MarginStop) * 60 - m_recording->LengthInSeconds();
int deviationVps = m_recording->Info()->GetEvent()->Duration() - m_recording->LengthInSeconds();
if (std::abs(deviationVps) < std::abs(deviationNoVps)) return durationDeviationVps(s_runtime);
return std::max(0, deviationNoVps);
}
csEventOrRecording *GetsEventOrRecording(const cEvent *event, const cRecording *recording) {
if (event) return new csEventOrRecording(event);
if (recording && recording->Info() && recording->Info()->GetEvent() ) return new csRecording(recording);
return NULL;
}
int GetNumberOfTsFiles(const cRecording* recording) {
// find our number of ts files
if (!recording || !recording->FileName() ) return -1;
DIR *dir = opendir(recording->FileName());
if (dir == nullptr) return -1;
struct dirent *ent;
int number_ts_files = 0;
while ((ent = readdir (dir)) != NULL)
// POSIX defines it as char d_name[], a character array of unspecified
// size, with at most NAME_MAX characters preceding the terminating null byte ('\0').
if (strlen(ent->d_name) == 8 && strcmp(ent->d_name + 5, ".ts") == 0) {
bool only_digits = true;
for (int i = 0; i < 5; i++) if (ent->d_name[i] < '0' || ent->d_name[i] > '9') only_digits = false;
if (only_digits) ++number_ts_files;
}
closedir (dir);
return number_ts_files;
}