MythTV master
eitfixup.cpp
Go to the documentation of this file.
1// C++ headers
2#include <algorithm>
3#include <array>
4
5// Qt Headers
6#include <QRegularExpression>
7
8// MythTV headers
10
11#include "channelutil.h" // for GetDefaultAuthority()
12#include "eitfixup.h"
13#include "mpeg/dishdescriptors.h" // for dish_theme_type_to_string
14#include "programinfo.h" // for CategoryType, subtitle types and audio and video properties
15
16/*------------------------------------------------------------------------
17 * Event Fix Up Scripts - Turned on by entry in dtv_privatetype table
18 *------------------------------------------------------------------------*/
19
20static const QRegularExpression kStereo { R"(\b\‍(?[sS]tereo\)?\b)" };
21static const QRegularExpression kUKSpaceColonStart { R"(^[ |:]*)" };
22static const QRegularExpression kDotAtEnd { "\\.$" };
23
24static const QMap<QChar,quint16> r2v = {
25 {'I' , 1}, {'V' , 5}, {'X' , 10}, {'L' , 50},
26 {'C' , 100}, {'D' , 500}, {'M' , 1000},
27 {QChar(0x399), 1}, // Greek Ι
28};
29
30int EITFixUp::parseRoman (QString roman)
31{
32 if (roman.isEmpty())
33 return 0;
34
35 uint result = 0;
36 for (int i = 0; i < roman.size() - 1; i++)
37 {
38 int v1 = r2v[roman.at(i)];
39 int v2 = r2v[roman.at(i+1)];
40 result += (v1 >= v2) ? v1 : -v1;
41 }
42 return result + r2v[roman.back()];
43}
44
45
47{
48 if (event.m_fixup)
49 {
50 if (event.m_subtitle == event.m_title)
51 event.m_subtitle = QString("");
52
53 if (event.m_description.isEmpty() && !event.m_subtitle.isEmpty())
54 {
55 event.m_description = event.m_subtitle;
56 event.m_subtitle = QString("");
57 }
58 }
59
60 if (kFixHTML & event.m_fixup)
61 FixStripHTML(event);
62
63 if (kFixHDTV & event.m_fixup)
64 event.m_videoProps |= VID_HDTV;
65
66 if (kFixBell & event.m_fixup)
67 FixBellExpressVu(event);
68
69 if (kFixDish & event.m_fixup)
70 FixBellExpressVu(event);
71
72 if (kFixUK & event.m_fixup)
73 FixUK(event);
74
75 if (kFixPBS & event.m_fixup)
76 FixPBS(event);
77
78 if (kFixComHem & event.m_fixup)
79 FixComHem(event, (kFixSubtitle & event.m_fixup) != 0U);
80
81 if (kFixAUStar & event.m_fixup)
82 FixAUStar(event);
83
84 if (kFixAUDescription & event.m_fixup)
85 FixAUDescription(event);
86
87 if (kFixAUFreeview & event.m_fixup)
88 FixAUFreeview(event);
89
90 if (kFixAUNine & event.m_fixup)
91 FixAUNine(event);
92
93 if (kFixAUSeven & event.m_fixup)
94 FixAUSeven(event);
95
96 if (kFixMCA & event.m_fixup)
97 FixMCA(event);
98
99 if (kFixRTL & event.m_fixup)
100 FixRTL(event);
101
102 if (kFixP7S1 & event.m_fixup)
103 FixPRO7(event);
104
105 if (kFixATV & event.m_fixup)
106 FixATV(event);
107
108 if (kFixDisneyChannel & event.m_fixup)
109 FixDisneyChannel(event);
110
111 if (kFixFI & event.m_fixup)
112 FixFI(event);
113
114 if (kFixPremiere & event.m_fixup)
115 FixPremiere(event);
116
117 if (kFixNL & event.m_fixup)
118 FixNL(event);
119
120 if (kFixNO & event.m_fixup)
121 FixNO(event);
122
123 if (kFixNRK_DVBT & event.m_fixup)
124 FixNRK_DVBT(event);
125
126 if (kFixDK & event.m_fixup)
127 FixDK(event);
128
129 if (kFixCategory & event.m_fixup)
130 FixCategory(event);
131
132 if (kFixGreekSubtitle & event.m_fixup)
133 FixGreekSubtitle(event);
134
135 if (kFixGreekEIT & event.m_fixup)
136 FixGreekEIT(event);
137
138 if (kFixGreekCategories & event.m_fixup)
139 FixGreekCategories(event);
140
141 if (kFixUnitymedia & event.m_fixup)
142 FixUnitymedia(event);
143
144 // Clean up text strings after all fixups have been applied.
145 if (event.m_fixup)
146 {
147 static const QRegularExpression emptyParens { R"(\‍(\s*\))" };
148 if (!event.m_title.isEmpty())
149 {
150 event.m_title.remove(QChar('\0')).remove(emptyParens);
151 event.m_title = event.m_title.simplified();
152 }
153
154 if (!event.m_subtitle.isEmpty())
155 {
156 event.m_subtitle.remove(QChar('\0'));
157 event.m_subtitle.remove(emptyParens);
158 event.m_subtitle = event.m_subtitle.simplified();
159 }
160
161 if (!event.m_description.isEmpty())
162 {
163 event.m_description.remove(QChar('\0'));
164 event.m_description.remove(emptyParens);
165 event.m_description = event.m_description.simplified();
166 }
167 }
168
169 if (kFixGenericDVB & event.m_fixup)
170 {
171 event.m_programId = AddDVBEITAuthority(event.m_chanid, event.m_programId);
172 event.m_seriesId = AddDVBEITAuthority(event.m_chanid, event.m_seriesId);
173 }
174
175 // Are any items left unhandled? report them to allow fixups improvements
176 if (!event.m_items.empty())
177 {
178 for (auto i = event.m_items.begin(); i != event.m_items.end(); ++i)
179 {
180 LOG(VB_EIT, LOG_DEBUG, QString("Unhandled item in EIT for"
181 " channel id \"%1\", \"%2\": %3").arg(event.m_chanid)
182 .arg(i.key(), i.value()));
183 }
184 }
185}
186
202QString EITFixUp::AddDVBEITAuthority(uint chanid, const QString &id)
203{
204 if (id.isEmpty())
205 return id;
206
207 // CRIDs are not case sensitive, so change all to lower case
208 QString crid = id.toLower();
209
210 // remove "crid://"
211 if (crid.startsWith("crid://"))
212 crid.remove(0,7);
213
214 // if id is a CRID with authority, return it
215 if (crid.length() >= 1 && crid[0] != '/')
216 return crid;
217
218 QString authority = ChannelUtil::GetDefaultAuthority(chanid);
219 if (authority.isEmpty())
220 return ""; // no authority, not a valid CRID, return empty
221
222 return authority + crid;
223}
224
230{
231 // A 0x0D character is present between the content
232 // and the subtitle if its present
233 int position = event.m_description.indexOf('\r');
234
235 if (position != -1)
236 {
237 // Subtitle present in the title, so get
238 // it and adjust the description
239 event.m_subtitle = event.m_description.left(position);
240 event.m_description = event.m_description.right(
241 event.m_description.length() - position - 2);
242 }
243
244 // Take out the content description which is
245 // always next with a period after it
246 position = event.m_description.indexOf(".");
247 // Make sure they didn't leave it out and
248 // you come up with an odd category
249 if (position < 10)
250 {
251 }
252 else
253 {
254 event.m_category = "Unknown";
255 }
256
257 // If the content descriptor didn't come up with anything, try parsing the category
258 // out of the description.
259 if (event.m_category.isEmpty())
260 {
261 // Take out the content description which is
262 // always next with a period after it
263 position = event.m_description.indexOf(".");
264 if ((position + 1) < event.m_description.length())
265 position = event.m_description.indexOf(". ");
266 // Make sure they didn't leave it out and
267 // you come up with an odd category
268 if ((position > -1) && position < 20)
269 {
270 const QString stmp = event.m_description;
271 event.m_description = stmp.right(stmp.length() - position - 2);
272 event.m_category = stmp.left(position);
273
274 int position_p = event.m_category.indexOf("(");
275 if (position_p == -1)
276 event.m_description = stmp.right(stmp.length() - position - 2);
277 else
278 event.m_category = "Unknown";
279 }
280 else
281 {
282 event.m_category = "Unknown";
283 }
284
285 // When a channel is off air the category is "-"
286 // so leave the category as blank
287 if (event.m_category == "-")
288 event.m_category = "OffAir";
289
290 if (event.m_category.length() > 20)
291 event.m_category = "Unknown";
292 }
293 else if (event.m_categoryType)
294 {
295 QString theme = dish_theme_type_to_string(event.m_categoryType);
296 event.m_description = event.m_description.replace(theme, "");
297 if (event.m_description.startsWith("."))
298 event.m_description = event.m_description.right(event.m_description.length() - 1);
299 if (event.m_description.startsWith(" "))
300 event.m_description = event.m_description.right(event.m_description.length() - 1);
301 }
302
303 // See if a year is present as (xxxx)
304 static const QRegularExpression bellYear { R"(\‍([0-9]{4}\))" };
305 position = event.m_description.indexOf(bellYear);
306 if (position != -1 && !event.m_category.isEmpty())
307 {
308 // Parse out the year
309 bool ok = false;
310 uint y = event.m_description.mid(position + 1, 4).toUInt(&ok);
311 if (ok)
312 {
313 event.m_originalairdate = QDate(y, 1, 1);
314 event.m_airdate = y;
315 event.m_previouslyshown = true;
316 }
317
318 // Get the actors if they exist
319 if (position > 3)
320 {
321 static const QRegularExpression bellActors { R"(\set\s|,)" };
322 QString tmp = event.m_description.left(position-3);
323 QStringList actors =
324 tmp.split(bellActors, Qt::SkipEmptyParts);
325
326 /* Possible TODO: if EIT inlcude the priority and/or character
327 * names for the actors, include them in AddPerson call. */
328 for (const auto & actor : std::as_const(actors))
329 event.AddPerson(DBPerson::kActor, actor);
330 }
331 // Remove the year and actors from the description
332 event.m_description = event.m_description.right(
333 event.m_description.length() - position - 7);
334 }
335
336 // Check for (CC) in the decription and
337 // set the <subtitles type="teletext"> flag
338 position = event.m_description.indexOf("(CC)");
339 if (position != -1)
340 {
341 event.m_subtitleType |= SUB_HARDHEAR;
342 event.m_description = event.m_description.replace("(CC)", "");
343 }
344
345 // Check for (Stereo) in the decription and set the <audio> tags
346 auto match = kStereo.match(event.m_description);
347 if (match.hasMatch())
348 {
349 event.m_audioProps |= AUD_STEREO;
350 event.m_description.remove(match.capturedStart(0),
351 match.capturedLength(0));
352 }
353
354 // Check for "title (All Day, HD)" in the title
355 static const QRegularExpression bellPPVTitleAllDayHD { R"(\s*\‍(All Day\, HD\)\s*$)" };
356 match = bellPPVTitleAllDayHD.match(event.m_title);
357 if (match.hasMatch())
358 {
359 event.m_title.remove(match.capturedStart(), match.capturedLength());
360 event.m_videoProps |= VID_HDTV;
361 }
362
363 // Check for "title (All Day)" in the title
364 static const QRegularExpression bellPPVTitleAllDay { R"(\s*\‍(All Day.*\)\s*$)" };
365 match = bellPPVTitleAllDay.match(event.m_title);
366 if (match.hasMatch())
367 event.m_title.remove(match.capturedStart(), match.capturedLength());
368
369 // Check for "HD - title" in the title
370 static const QRegularExpression bellPPVTitleHD { R"(^HD\s?-\s?)" };
371 match = bellPPVTitleHD.match(event.m_title);
372 if (match.hasMatch())
373 {
374 event.m_title.remove(match.capturedStart(), match.capturedLength());
375 event.m_videoProps |= VID_HDTV;
376 }
377
378 // Check for (HD) in the decription
379 position = event.m_description.indexOf("(HD)");
380 if (position != -1)
381 {
382 event.m_description = event.m_description.replace("(HD)", "");
383 event.m_videoProps |= VID_HDTV;
384 }
385
386 // Check for (HD) in the title
387 position = event.m_title.indexOf("(HD)");
388 if (position != -1)
389 {
390 event.m_title = event.m_title.replace("(HD)", "");
391 event.m_videoProps |= VID_HDTV;
392 }
393
394 // Check for HD at the end of the title
395 static const QRegularExpression dishPPVTitleHD { R"(\sHD\s*$)" };
396 match = dishPPVTitleHD.match(event.m_title);
397 if (match.hasMatch())
398 {
399 event.m_title.remove(match.capturedStart(), match.capturedLength());
400 event.m_videoProps |= VID_HDTV;
401 }
402
403 // Check for (DD) at the end of the description
404 position = event.m_description.indexOf("(DD)");
405 if (position != -1)
406 {
407 event.m_description = event.m_description.replace("(DD)", "");
408 event.m_audioProps |= AUD_DOLBY;
409 event.m_audioProps |= AUD_STEREO;
410 }
411
412 // Remove SAP from Dish descriptions
413 position = event.m_description.indexOf("(SAP)");
414 if (position != -1)
415 {
416 event.m_description = event.m_description.replace("(SAP", "");
417 event.m_subtitleType |= SUB_HARDHEAR;
418 }
419
420 // Remove any trailing colon in title
421 static const QRegularExpression dishPPVTitleColon { R"(\:\s*$)" };
422 match = dishPPVTitleColon.match(event.m_title);
423 if (match.hasMatch())
424 event.m_title.remove(match.capturedStart(), match.capturedLength());
425
426 // Remove New at the end of the description
427 static const QRegularExpression dishDescriptionNew { R"(\s*New\.\s*)" };
428 match = dishDescriptionNew.match(event.m_description);
429 if (match.hasMatch())
430 {
431 event.m_previouslyshown = false;
432 event.m_description.remove(match.capturedStart(), match.capturedLength());
433 }
434
435 // Remove Series Finale at the end of the desciption
436 static const QRegularExpression dishDescriptionFinale { R"(\s*(Series|Season)\sFinale\.\s*)" };
437 match = dishDescriptionFinale.match(event.m_description);
438 if (match.hasMatch())
439 {
440 event.m_previouslyshown = false;
441 event.m_description.remove(match.capturedStart(), match.capturedLength());
442 }
443
444 // Remove Series Finale at the end of the desciption
445 static const QRegularExpression dishDescriptionFinale2 { R"(\s*Finale\.\s*)" };
446 match = dishDescriptionFinale2.match(event.m_description);
447 if (match.hasMatch())
448 {
449 event.m_previouslyshown = false;
450 event.m_description.remove(match.capturedStart(), match.capturedLength());
451 }
452
453 // Remove Series Premiere at the end of the description
454 static const QRegularExpression dishDescriptionPremiere { R"(\s*(Series|Season)\s(Premier|Premiere)\.\s*)" };
455 match = dishDescriptionPremiere.match(event.m_description);
456 if (match.hasMatch())
457 {
458 event.m_previouslyshown = false;
459 event.m_description.remove(match.capturedStart(), match.capturedLength());
460 }
461
462 // Remove Series Premiere at the end of the description
463 static const QRegularExpression dishDescriptionPremiere2 { R"(\s*(Premier|Premiere)\.\s*)" };
464 match = dishDescriptionPremiere2.match(event.m_description);
465 if (match.hasMatch())
466 {
467 event.m_previouslyshown = false;
468 event.m_description.remove(match.capturedStart(), match.capturedLength());
469 }
470
471 // Remove Dish's PPV code at the end of the description
472 static const QRegularExpression ppvcode { R"(\s*\‍(([A-Z]|[0-9]){5}\)\s*$)",
473 QRegularExpression::CaseInsensitiveOption };
474 match = ppvcode.match(event.m_description);
475 if (match.hasMatch())
476 event.m_description.remove(match.capturedStart(), match.capturedLength());
477
478 // Remove trailing garbage
479 static const QRegularExpression dishPPVSpacePerenEnd { R"(\s\)\s*$)" };
480 match = dishPPVSpacePerenEnd.match(event.m_description);
481 if (match.hasMatch())
482 event.m_description.remove(match.capturedStart(), match.capturedLength());
483
484 // Check for subtitle "All Day (... Eastern)" in the subtitle
485 static const QRegularExpression bellPPVSubtitleAllDay { R"(^All Day \‍(.*\sEastern\)\s*$)" };
486 match = bellPPVSubtitleAllDay.match(event.m_subtitle);
487 if (match.hasMatch())
488 event.m_subtitle.remove(match.capturedStart(), match.capturedLength());
489
490 // Check for description "(... Eastern)" in the description
491 static const QRegularExpression bellPPVDescriptionAllDay { R"(^\‍(.*\sEastern\))" };
492 match = bellPPVDescriptionAllDay.match(event.m_description);
493 if (match.hasMatch())
494 event.m_description.remove(match.capturedStart(), match.capturedLength());
495
496 // Check for description "(... ET)" in the description
497 static const QRegularExpression bellPPVDescriptionAllDay2 { R"(^\‍([0-9].*am-[0-9].*am\sET\))" };
498 match = bellPPVDescriptionAllDay2.match(event.m_description);
499 if (match.hasMatch())
500 event.m_description.remove(match.capturedStart(), match.capturedLength());
501
502 // Check for description "(nnnnn)" in the description
503 static const QRegularExpression bellPPVDescriptionEventId { R"(\‍([0-9]{5}\))" };
504 match = bellPPVDescriptionEventId.match(event.m_description);
505 if (match.hasMatch())
506 event.m_description.remove(match.capturedStart(), match.capturedLength());
507}
508
513{
514 QStringList strListColon = event.m_description.split(":");
515 QStringList strListEnd;
516
517 bool fColon = false;
518 bool fQuotedSubtitle = false;
519 QString strEnd;
520 if (strListColon.count()>1)
521 {
522 bool fDoubleDot = false;
523 bool fSingleDot = true;
524 int nLength = strListColon[0].length();
525
526 int nPosition1 = event.m_description.indexOf("..");
527 if ((nPosition1 < nLength) && (nPosition1 >= 0))
528 fDoubleDot = true;
529 nPosition1 = event.m_description.indexOf(".");
530 if (nPosition1==-1)
531 fSingleDot = false;
532 if (nPosition1 > nLength)
533 {
534 fSingleDot = false;
535 }
536 else
537 {
538 QString strTmp = event.m_description.mid(nPosition1+1,
539 nLength-nPosition1);
540
541 QStringList tmp = strTmp.split(" ");
542 if (((uint) tmp.size()) < kMaxDotToColon)
543 fSingleDot = false;
544 }
545
546 if (fDoubleDot)
547 {
548 strListEnd = strListColon;
549 fColon = true;
550 }
551 else if (!fSingleDot)
552 {
553 QStringList strListTmp;
554 uint nTitle=0;
555 int nTitleMax=-1;
556 for (int i =0; (i<strListColon.count()) && (nTitleMax==-1);i++)
557 {
558 const QStringList tmp = strListColon[i].split(" ");
559
560 nTitle += tmp.size();
561
562 if (nTitle < kMaxToTitle)
563 strListTmp.push_back(strListColon[i]);
564 else
565 nTitleMax=i;
566 }
567 QString strPartial;
568 for (int i=0;i<(nTitleMax-1);i++)
569 strPartial+=strListTmp[i]+":";
570 if (nTitleMax>0)
571 {
572 strPartial+=strListTmp[nTitleMax-1];
573 strListEnd.push_back(strPartial);
574 }
575 for (int i=nTitleMax+1;i<strListColon.count();i++)
576 strListEnd.push_back(strListColon[i]);
577 fColon = true;
578 }
579 }
580 static const QRegularExpression ukQuotedSubtitle { R"(^'([\w\s\-,]+?)\.' )" };
581 auto match = ukQuotedSubtitle.match(event.m_description);
582 if (match.hasMatch())
583 {
584 event.m_subtitle = match.captured(1);
585 event.m_description.remove(match.capturedStart(0),
586 match.capturedLength(0));
587 fQuotedSubtitle = true;
588 }
589 QStringList strListPeriod;
590 QStringList strListQuestion;
591 QStringList strListExcl;
592 if (!(fColon || fQuotedSubtitle))
593 {
594 strListPeriod = event.m_description.split(".");
595 if (strListPeriod.count() >1)
596 {
597 int nPosition1 = event.m_description.indexOf(".");
598 int nPosition2 = event.m_description.indexOf("..");
599 if ((nPosition1 < nPosition2) || (nPosition2==-1))
600 strListEnd = strListPeriod;
601 }
602
603 strListQuestion = event.m_description.split("?");
604 strListExcl = event.m_description.split("!");
605 if ((strListQuestion.size() > 1) &&
606 ((uint)strListQuestion.size() <= kMaxQuestionExclamation))
607 {
608 strListEnd = strListQuestion;
609 strEnd = "?";
610 }
611 else if ((strListExcl.size() > 1) &&
612 ((uint)strListExcl.size() <= kMaxQuestionExclamation))
613 {
614 strListEnd = strListExcl;
615 strEnd = "!";
616 }
617 else
618 {
619 strEnd.clear();
620 }
621 }
622
623 if (!strListEnd.empty())
624 {
625 QStringList strListSpace = strListEnd[0].split(
626 " ", Qt::SkipEmptyParts);
627 if (fColon && ((uint)strListSpace.size() > kMaxToTitle))
628 return;
629 if ((uint)strListSpace.size() > kDotToTitle)
630 return;
631 static const QRegularExpression ukExclusionFromSubtitle {
632 "(starring|stars\\s|drama|seres|sitcom)",
633 QRegularExpression::CaseInsensitiveOption };
634 if (strListSpace.filter(ukExclusionFromSubtitle).empty())
635 {
636 event.m_subtitle = strListEnd[0]+strEnd;
637 event.m_subtitle.remove(kUKSpaceColonStart);
638 event.m_description=
639 event.m_description.mid(strListEnd[0].length()+1);
640 event.m_description.remove(kUKSpaceColonStart);
641 }
642 }
643}
644
645
650{
651 static const QRegularExpression uk24ep { R"(^\d{1,2}:00[ap]m to \d{1,2}:00[ap]m: )" };
652 static const QRegularExpression ukTime { R"(\d{1,2}[\.:]\d{1,2}\s*(am|pm|))" };
653 QString strFull;
654
655 bool isMovie = event.m_category.startsWith("Movie",Qt::CaseInsensitive) ||
656 event.m_category.startsWith("Film",Qt::CaseInsensitive);
657 // BBC three case (could add another record here ?)
658 static const QRegularExpression ukThen { R"(\s*?(Then|Followed by) 60 Seconds\.)",
659 QRegularExpression::CaseInsensitiveOption };
660 static const QRegularExpression ukNew { R"((New\.|\s*?(Brand New|New)\s*?(Series|Episode)\s*?[:\.\-]))",
661 QRegularExpression::CaseInsensitiveOption };
662 static const QRegularExpression ukNewTitle { R"(^(Brand New|New:)\s*)",
663 QRegularExpression::CaseInsensitiveOption };
664 event.m_description = event.m_description.remove(ukThen);
665 event.m_description = event.m_description.remove(ukNew);
666 event.m_title = event.m_title.remove(ukNewTitle);
667
668 // Removal of Class TV, CBBC and CBeebies etc..
669 static const QRegularExpression ukTitleRemove { "^(?:[tT]4:|Schools\\s*?:)" };
670 static const QRegularExpression ukDescriptionRemove { R"(^(?:CBBC\s*?\.|CBeebies\s*?\.|Class TV\s*?:|BBC Switch\.))" };
671 event.m_title = event.m_title.remove(ukTitleRemove);
672 event.m_description = event.m_description.remove(ukDescriptionRemove);
673
674 // Removal of BBC FOUR and BBC THREE
675 static const QRegularExpression ukBBC34 { R"(BBC (?:THREE|FOUR) on BBC (?:ONE|TWO)\.)",
676 QRegularExpression::CaseInsensitiveOption };
677 event.m_description = event.m_description.remove(ukBBC34);
678
679 // BBC 7 [Rpt of ...] case.
680 static const QRegularExpression ukBBC7rpt { R"(\[Rptd?[^]]+?\d{1,2}\.\d{1,2}[ap]m\]\.)" };
681 event.m_description = event.m_description.remove(ukBBC7rpt);
682
683 // "All New To 4Music!
684 static const QRegularExpression ukAllNew { R"(All New To 4Music!\s?)" };
685 event.m_description = event.m_description.remove(ukAllNew);
686
687 // Removal of 'Also in HD' text
688 static const QRegularExpression ukAlsoInHD { R"(\s*Also in HD\.)",
689 QRegularExpression::CaseInsensitiveOption };
690 event.m_description = event.m_description.remove(ukAlsoInHD);
691
692 // Remove [AD,S] etc.
693 static const QRegularExpression ukCC { R"(\[(?:(AD|SL|S|W|HD),?)+\])" };
694 auto match = ukCC.match(event.m_description);
695 while (match.hasMatch())
696 {
697 QStringList tmpCCitems = match.captured(0).remove("[").remove("]").split(",");
698 if (tmpCCitems.contains("AD"))
699 event.m_audioProps |= AUD_VISUALIMPAIR;
700 if (tmpCCitems.contains("HD"))
701 event.m_videoProps |= VID_HDTV;
702 if (tmpCCitems.contains("S"))
703 event.m_subtitleType |= SUB_NORMAL;
704 if (tmpCCitems.contains("SL"))
705 event.m_subtitleType |= SUB_SIGNED;
706 if (tmpCCitems.contains("W"))
707 event.m_videoProps |= VID_WIDESCREEN;
708 event.m_description.remove(match.capturedStart(0),
709 match.capturedLength(0));
710 match = ukCC.match(event.m_description, match.capturedStart(0));
711 }
712
713 event.m_title = event.m_title.trimmed();
714 event.m_description = event.m_description.trimmed();
715
716 // Constituents of UK season regexp, decomposed for clarity
717
718 // Matches Season 2, S 2 and "Series 2," etc but not "hits 2"
719 // cap1 = season
720 static const QString seasonStr = R"(\b(?:Season|Series|S)\s*(\d+)\s*,?)";
721
722 // Work out the season and episode numbers (if any)
723 // Matching pattern "Season 2 Episode|Ep 3 of 14|3/14" etc
724
725 // Matches Episode 3, Ep 3/4, Ep 3 of 4 etc but not "step 1"
726 // cap1 = ep, cap2 = total
727 static const QString longEp = R"(\b(?:Ep|Episode)\s*(\d+)\s*(?:(?:/|of)\s*(\d*))?)";
728
729 // Matches S2 Ep 3/4, "Season 2, Ep 3 of 4", Episode 3 etc
730 // cap1 = season, cap2 = ep, cap3 = total
731 static const QString longSeasEp = QString("\\(?(?:%1)?\\s*%2").arg(seasonStr, longEp);
732
733 // Matches long seas/ep with surrounding parenthesis & trailing period
734 // cap1 = season, cap2 = ep, cap3 = total
735 static const QString longContext = QString(R"(\‍(*%1\s*\)?\s*\.?)").arg(longSeasEp);
736
737 // Matches 3/4, 3 of 4
738 // cap1 = ep, cap2 = total
739 static const QString shortEp = R"((\d+)\s*(?:/|of)\s*(\d+))";
740
741 // Matches short ep/total, ignoring Parts and idioms such as 9/11, 24/7 etc.
742 // ie. x/y in parenthesis or has no leading or trailing text in the sentence.
743 // cap0 may include previous/anchoring period
744 // cap1 = shortEp with surrounding parenthesis & trailing period (to remove)
745 // cap2 = ep, cap3 = total,
746 static const QString shortContext =
747 QString(R"((?:^|\.)(\s*\‍(*\s*%1[\s)]*(?:[).:]|$)))").arg(shortEp);
748
749 // Prefer long format resorting to short format
750 // cap0 = long match to remove, cap1 = long season, cap2 = long ep, cap3 = long total,
751 // cap4 = short match to remove, cap5 = short ep, cap6 = short total
752 static const QRegularExpression ukSeries { "(?:" + longContext + "|" + shortContext + ")",
753 QRegularExpression::CaseInsensitiveOption };
754
755 bool series = false;
756 bool fromTitle = true;
757 match = ukSeries.match(event.m_title);
758 if (!match.hasMatch())
759 {
760 fromTitle = false;
761 match = ukSeries.match(event.m_description);
762 }
763 if (match.hasMatch())
764 {
765 if (!match.captured(1).isEmpty())
766 {
767 event.m_season = match.captured(1).toUInt();
768 series = true;
769 }
770
771 if (!match.captured(2).isEmpty())
772 {
773 event.m_episode = match.captured(2).toUInt();
774 series = true;
775 }
776 else if (!match.captured(5).isEmpty())
777 {
778 event.m_episode = match.captured(5).toUInt();
779 series = true;
780 }
781
782 if (!match.captured(3).isEmpty())
783 {
784 event.m_totalepisodes = match.captured(3).toUInt();
785 series = true;
786 }
787 else if (!match.captured(6).isEmpty())
788 {
789 event.m_totalepisodes = match.captured(6).toUInt();
790 series = true;
791 }
792
793 // Remove long or short match. Short text doesn't start at position2
794 int form = match.captured(4).isEmpty() ? 0 : 4;
795
796 if (fromTitle)
797 {
798 LOG(VB_EIT, LOG_DEBUG, QString("Extracted S%1E%2/%3 from title (%4) \"%5\"")
799 .arg(event.m_season).arg(event.m_episode).arg(event.m_totalepisodes)
800 .arg(event.m_title, event.m_description));
801
802 event.m_title.remove(match.capturedStart(form),
803 match.capturedLength(form));
804 }
805 else
806 {
807 LOG(VB_EIT, LOG_DEBUG, QString("Extracted S%1E%2/%3 from description (%4) \"%5\"")
808 .arg(event.m_season).arg(event.m_episode).arg(event.m_totalepisodes)
809 .arg(event.m_title, event.m_description));
810
811 if (match.capturedStart(form) == 0)
812 {
813 // Remove from the start of the description.
814 // Otherwise it ends up in the subtitle.
815 event.m_description.remove(match.capturedStart(form),
816 match.capturedLength(form));
817 }
818 }
819 }
820
821 if (isMovie)
822 event.m_categoryType = ProgramInfo::kCategoryMovie;
823 else if (series)
824 event.m_categoryType = ProgramInfo::kCategorySeries;
825
826 // Multi-part episodes, or films (e.g. ITV film split by news)
827 // Matches Part 1, Pt 1/2, Part 1 of 2 etc.
828 static const QRegularExpression ukPart { R"([-(\:,.]\s*(?:Part|Pt)\s*(\d+)\s*(?:(?:of|/)\s*(\d+))?\s*[-):,.])",
829 QRegularExpression::CaseInsensitiveOption };
830 match = ukPart.match(event.m_title);
831 auto match2 = ukPart.match(event.m_description);
832 if (match.hasMatch())
833 {
834 event.m_partnumber = match.captured(1).toUInt();
835 event.m_parttotal = match.captured(2).toUInt();
836
837 LOG(VB_EIT, LOG_DEBUG, QString("Extracted Part %1/%2 from title (%3)")
838 .arg(event.m_partnumber).arg(event.m_parttotal).arg(event.m_title));
839
840 // Remove from the title
841 event.m_title.remove(match.capturedStart(0),
842 match.capturedLength(0));
843 }
844 else if (match2.hasMatch())
845 {
846 event.m_partnumber = match2.captured(1).toUInt();
847 event.m_parttotal = match2.captured(2).toUInt();
848
849 LOG(VB_EIT, LOG_DEBUG, QString("Extracted Part %1/%2 from description (%3) \"%4\"")
850 .arg(event.m_partnumber).arg(event.m_parttotal)
851 .arg(event.m_title, event.m_description));
852
853 // Remove from the start of the description.
854 // Otherwise it ends up in the subtitle.
855 if (match2.capturedStart(0) == 0)
856 {
857 // Retain a single colon (subtitle separator) if we remove any
858 QString sub = match2.captured(0).contains(":") ? ":" : "";
859 event.m_description = event.m_description.replace(match2.captured(0), sub);
860 }
861 }
862
863 static const QRegularExpression ukStarring { R"((?:Western\s)?[Ss]tarring ([\w\s\-']+?)[Aa]nd\s([\w\s\-']+?)[\.|,]\s*(\d{4})?(?:\.\s)?)" };
864 match = ukStarring.match(event.m_description);
865 if (match.hasMatch())
866 {
867 // if we match this we've captured 2 actors and an (optional) airdate
868 /* Possible TODO: if EIT inlcude the priority and/or character
869 * names for the actors, include them in AddPerson call. */
870 event.AddPerson(DBPerson::kActor, match.captured(1));
871 event.AddPerson(DBPerson::kActor, match.captured(2));
872 if (match.captured(3).length() > 0)
873 {
874 bool ok = false;
875 uint y = match.captured(3).toUInt(&ok);
876 if (ok)
877 {
878 event.m_airdate = y;
879 event.m_originalairdate = QDate(y, 1, 1);
880 }
881 }
882 }
883
884 static const QRegularExpression ukLaONoSplit { "^Law & Order: (?:Criminal Intent|LA|"
885 "Special Victims Unit|Trial by Jury|UK|You the Jury)" };
886 if (!event.m_title.startsWith("CSI:") && !event.m_title.startsWith("CD:") &&
887 !event.m_title.contains(ukLaONoSplit) &&
888 !event.m_title.startsWith("Mission: Impossible"))
889 {
890 static const QRegularExpression ukDoubleDotStart { R"(^\.\.+)" };
891 static const QRegularExpression ukDoubleDotEnd { R"(\.\.+$)" };
892 if ((event.m_title.indexOf(ukDoubleDotEnd) != -1) &&
893 (event.m_description.indexOf(ukDoubleDotStart) != -1))
894 {
895 QString strPart=event.m_title.remove(ukDoubleDotEnd)+" ";
896 strFull = strPart + event.m_description.remove(ukDoubleDotStart);
897 static const QRegularExpression ukCEPQ { R"([:\!\.\?]\s)" };
898 static const QRegularExpression ukSpaceStart { "^ " };
899 int position1 = strFull.indexOf(ukCEPQ,strPart.length());
900 if (isMovie && (position1 != -1))
901 {
902 if (strFull[position1] == '!' || strFull[position1] == '?'
903 || (position1>2 && strFull[position1] == '.' && strFull[position1-2] == '.'))
904 position1++;
905 event.m_title = strFull.left(position1);
906 event.m_description = strFull.mid(position1 + 1);
907 event.m_description.remove(ukSpaceStart);
908 }
909 else
910 {
911 position1 = strFull.indexOf(ukCEPQ);
912 if (position1 != -1)
913 {
914 if (strFull[position1] == '!' || strFull[position1] == '?'
915 || (position1>2 && strFull[position1] == '.' && strFull[position1-2] == '.'))
916 position1++;
917 event.m_title = strFull.left(position1);
918 event.m_description = strFull.mid(position1 + 1);
919 event.m_description.remove(ukSpaceStart);
920 SetUKSubtitle(event);
921 }
922 }
923 }
924 else if (event.m_description.indexOf(uk24ep) != -1)
925 {
926 auto match24 = uk24ep.match(event.m_description);
927 if (match24.hasMatch())
928 {
929 // Special case for episodes of 24.
930 // -2 from the length cause we don't want ": " on the end
931 event.m_subtitle = event.m_description.mid(match24.capturedStart(0),
932 match24.captured(0).length() - 2);
933 event.m_description = event.m_description.remove(match24.captured(0));
934 }
935 }
936 else if (event.m_description.indexOf(ukTime) == -1)
937 {
938 static const QRegularExpression ukYearColon { R"(^[\d]{4}:)" };
939 if (!isMovie && (event.m_title.indexOf(ukYearColon) < 0))
940 {
941 int position1 = event.m_title.indexOf(":");
942 if ((position1 != -1) &&
943 (event.m_description.indexOf(":") < 0 ))
944 {
945 static const QRegularExpression ukCompleteDots { R"(^\.\.+$)" };
946 if (event.m_title.mid(position1+1).indexOf(ukCompleteDots)==0)
947 {
948 SetUKSubtitle(event);
949 QString strTmp = event.m_title.mid(position1+1);
950 event.m_title.resize(position1);
951 event.m_subtitle = strTmp+event.m_subtitle;
952 }
953 else if ((uint)position1 < kSubtitleMaxLen)
954 {
955 event.m_subtitle = event.m_title.mid(position1 + 1);
956 event.m_title = event.m_title.left(position1);
957 }
958 }
959 else
960 {
961 SetUKSubtitle(event);
962 }
963 }
964 }
965 }
966
967 if (!isMovie && event.m_subtitle.isEmpty() &&
968 !event.m_title.startsWith("The X-Files"))
969 {
970 int position1 = event.m_description.indexOf(ukTime);
971 if (position1 != -1)
972 {
973 static const QRegularExpression ukColonPeriod { R"([:\.])" };
974 int position2 = event.m_description.indexOf(ukColonPeriod);
975 if ((position2>=0) && (position2 < (position1-2)))
976 SetUKSubtitle(event);
977 }
978 else
979 {
980 position1 = event.m_title.indexOf("-");
981 if (position1 != -1)
982 {
983 if ((uint)position1 < kSubtitleMaxLen)
984 {
985 event.m_subtitle = event.m_title.mid(position1 + 1);
986 event.m_subtitle.remove(kUKSpaceColonStart);
987 event.m_title = event.m_title.left(position1);
988 }
989 }
990 else
991 {
992 SetUKSubtitle(event);
993 }
994 }
995 }
996
997 // Work out the year (if any)
998 static const QRegularExpression ukYear { R"([\[\‍(]([\d]{4})[\)\]])" };
999 match = ukYear.match(event.m_description);
1000 if (match.hasMatch())
1001 {
1002 event.m_description.remove(match.capturedStart(0),
1003 match.capturedLength(0));
1004 bool ok = false;
1005 uint y = match.captured(1).toUInt(&ok);
1006 if (ok)
1007 {
1008 event.m_airdate = y;
1009 event.m_originalairdate = QDate(y, 1, 1);
1010 }
1011 }
1012
1013 // Trim leading/trailing '.'
1014 static const QRegularExpression ukDotSpaceStart { R"(^\. )" };
1015 static const QRegularExpression ukDotEnd { R"(\.$)" };
1016 event.m_subtitle.remove(ukDotSpaceStart);
1017 if (event.m_subtitle.lastIndexOf("..") != (event.m_subtitle.length()-2))
1018 event.m_subtitle.remove(ukDotEnd);
1019
1020 // Reverse the subtitle and empty description
1021 if (event.m_description.isEmpty() && !event.m_subtitle.isEmpty())
1022 {
1023 event.m_description=event.m_subtitle;
1024 event.m_subtitle.clear();
1025 }
1026}
1027
1032{
1033 /* Used for PBS ATSC Subtitles are separated by a colon */
1034 int position = event.m_description.indexOf(':');
1035 if (position != -1)
1036 {
1037 const QString stmp = event.m_description;
1038 event.m_subtitle = stmp.left(position);
1039 event.m_description = stmp.right(stmp.length() - position - 2);
1040 }
1041}
1042
1046void EITFixUp::FixComHem(DBEventEIT &event, bool process_subtitle)
1047{
1048 static const QRegularExpression comHemPersSeparator { R"((, |\soch\s))" };
1049
1050 // Reverse what EITFixUp::Fix() did
1051 if (event.m_subtitle.isEmpty() && !event.m_description.isEmpty())
1052 {
1053 event.m_subtitle = event.m_description;
1054 event.m_description = "";
1055 }
1056
1057 // Remove subtitle, it contains the category and we already know that
1058 event.m_subtitle = "";
1059
1060 bool isSeries = false;
1061 // Try to find episode numbers
1062 static const QRegularExpression comHemSeries1
1063 { R"(\s?(?:[dD]el|[eE]pisode)\s([0-9]+)(?:\s?(?:/|:|av)\s?([0-9]+))?\.)" };
1064 static const QRegularExpression comHemSeries2 { R"(\s?-?\s?([Dd]el\s+([0-9]+)))" };
1065 auto match = comHemSeries1.match(event.m_description);
1066 auto match2 = comHemSeries2.match(event.m_title);
1067 if (match2.hasMatch())
1068 {
1069 event.m_partnumber = match2.capturedView(2).toUInt();
1070 event.m_title.remove(match2.capturedStart(), match2.capturedLength());
1071 }
1072 else if (match.hasMatch())
1073 {
1074 if (match.capturedStart(1) != -1)
1075 event.m_partnumber = match.capturedView(1).toUInt();
1076 if (match.capturedStart(2) != -1)
1077 event.m_parttotal = match.capturedView(2).toUInt();
1078
1079 // Remove the episode numbers, but only if it's not at the begining
1080 // of the description (subtitle code might use it)
1081 if (match.capturedStart() > 0)
1082 event.m_description.remove(match.capturedStart(),
1083 match.capturedLength());
1084 isSeries = true;
1085 }
1086
1087 // Add partnumber/parttotal to subtitle
1088 // This will be overwritten if we find a better subtitle
1089 if (event.m_partnumber > 0)
1090 {
1091 event.m_subtitle = QString("Del %1").arg(event.m_partnumber);
1092 if (event.m_parttotal > 0)
1093 event.m_subtitle += QString(" av %1").arg(event.m_parttotal);
1094 }
1095
1096 // Move subtitle info from title to subtitle
1097 static const QRegularExpression comHemTSub { R"(\s+-\s+([^\-]+))" };
1098 match = comHemTSub.match(event.m_title);
1099 if (match.hasMatch())
1100 {
1101 event.m_subtitle = match.captured(1);
1102 event.m_title.remove(match.capturedStart(), match.capturedLength());
1103 }
1104
1105 // No need to continue without a description.
1106 if (event.m_description.length() <= 0)
1107 return;
1108
1109 // Try to find country category, year and possibly other information
1110 // from the begining of the description
1111 static const QRegularExpression comHemCountry
1112 { R"(^(\‍(.+\))?\s?([^ ]+)\s([^\.0-9]+)\sfrån\s([0-9]{4})(?:\smed\s([^\.]+))?\.?)" };
1113 match = comHemCountry.match(event.m_description);
1114 if (match.hasMatch())
1115 {
1116 QString replacement;
1117
1118 // Original title, usually english title
1119 // note: list[1] contains extra () around the text that needs removing
1120 if (!match.capturedView(1).isEmpty())
1121 {
1122 replacement = match.captured(1) + " ";
1123 //store it somewhere?
1124 }
1125
1126 // Countr(y|ies)
1127 if (!match.capturedView(2).isEmpty())
1128 {
1129 replacement += match.captured(2) + " ";
1130 //store it somewhere?
1131 }
1132
1133 // Category
1134 if (!match.capturedView(3).isEmpty())
1135 {
1136 replacement += match.captured(3) + ".";
1137 if(event.m_category.isEmpty())
1138 {
1139 event.m_category = match.captured(3);
1140 }
1141
1142 if(match.captured(3).indexOf("serie")!=-1)
1143 {
1144 isSeries = true;
1145 }
1146 }
1147
1148 // Year
1149 if (!match.capturedView(4).isEmpty())
1150 {
1151 bool ok = false;
1152 uint y = match.capturedView(4).trimmed().toUInt(&ok);
1153 if (ok)
1154 event.m_airdate = y;
1155 }
1156
1157 // Actors
1158 if (!match.capturedView(5).isEmpty())
1159 {
1160 const QStringList actors =
1161 match.captured(5).split(comHemPersSeparator, Qt::SkipEmptyParts);
1162 /* Possible TODO: if EIT inlcude the priority and/or character
1163 * names for the actors, include them in AddPerson call. */
1164 for (const auto & actor : std::as_const(actors))
1165 event.AddPerson(DBPerson::kActor, actor);
1166 }
1167
1168 // Remove year and actors.
1169 // The reason category is left in the description is because otherwise
1170 // the country would look wierd like "Amerikansk. Rest of description."
1171 event.m_description = event.m_description.replace(match.captured(0),replacement);
1172 }
1173
1174 if (isSeries)
1175 event.m_categoryType = ProgramInfo::kCategorySeries;
1176
1177 // Look for additional persons in the description
1178 static const QRegularExpression comHemPersons
1179 { R"(\s?([Rr]egi|[Ss]kådespelare|[Pp]rogramledare|[Ii] rollerna):\s([^\.]+)\.)" };
1180 auto iter = comHemPersons.globalMatch(event.m_description);
1181 while (iter.hasNext())
1182 {
1183 auto pmatch = iter.next();
1185
1186 static const QRegularExpression comHemDirector { "[Rr]egi" };
1187 static const QRegularExpression comHemActor { "[Ss]kådespelare|[Ii] rollerna" };
1188 static const QRegularExpression comHemHost { "[Pp]rogramledare" };
1189#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1190 auto dmatch = comHemDirector.match(pmatch.capturedView(1));
1191 auto amatch = comHemActor.match(pmatch.capturedView(1));
1192 auto hmatch = comHemHost.match(pmatch.capturedView(1));
1193#else
1194 auto dmatch = comHemDirector.matchView(pmatch.capturedView(1));
1195 auto amatch = comHemActor.matchView(pmatch.capturedView(1));
1196 auto hmatch = comHemHost.matchView(pmatch.capturedView(1));
1197#endif
1198 if (dmatch.hasMatch())
1199 {
1200 role = DBPerson::kDirector;
1201 }
1202 else if (amatch.hasMatch())
1203 {
1204 role = DBPerson::kActor;
1205 }
1206 else if (hmatch.hasMatch())
1207 {
1208 role = DBPerson::kHost;
1209 }
1210 else
1211 {
1212 event.m_description.remove(pmatch.capturedStart(), pmatch.capturedLength());
1213 continue;
1214 }
1215
1216 const QStringList actors =
1217 pmatch.captured(2).split(comHemPersSeparator, Qt::SkipEmptyParts);
1218 /* Possible TODO: if EIT inlcude the priority and/or character
1219 * names for the actors, include them in AddPerson call. */
1220 for (const auto & actor : std::as_const(actors))
1221 event.AddPerson(role, actor);
1222
1223 // Remove it
1224 event.m_description=event.m_description.replace(pmatch.captured(0),"");
1225 }
1226
1227 // Is this event on a channel we shoud look for a subtitle?
1228 // The subtitle is the first sentence in the description, but the
1229 // subtitle can't be the only thing in the description and it must be
1230 // shorter than 55 characters or we risk picking up the wrong thing.
1231 if (process_subtitle)
1232 {
1233 static const QRegularExpression comHemSub { R"([.\?\!] )" };
1234 int pos2 = event.m_description.indexOf(comHemSub);
1235 bool pvalid = pos2 != -1 && pos2 <= 55;
1236 if (pvalid && (event.m_description.length() - (pos2 + 2)) > 0)
1237 {
1238 event.m_subtitle = event.m_description.left(
1239 pos2 + (event.m_description[pos2] == '?' ? 1 : 0));
1240 event.m_description = event.m_description.mid(pos2 + 2);
1241 }
1242 }
1243
1244 // Teletext subtitles?
1245 static const QRegularExpression comHemTT { "[Tt]ext-[Tt][Vv]" };
1246 if (event.m_description.indexOf(comHemTT) != -1)
1247 event.m_subtitleType |= SUB_NORMAL;
1248
1249 // Try to findout if this is a rerun and if so the date.
1250 static const QRegularExpression comHemRerun1 { R"([Rr]epris\sfrån\s([^\.]+)(?:\.|$))" };
1251 static const QRegularExpression comHemRerun2 { R"(([0-9]+)/([0-9]+)(?:\s-\s([0-9]{4}))?)" };
1252 match = comHemRerun1.match(event.m_description);
1253 if (!match.hasMatch())
1254 return;
1255
1256 // Rerun from today
1257 if (match.captured(1) == "i dag")
1258 {
1259 event.m_originalairdate = event.m_starttime.date();
1260 return;
1261 }
1262
1263 // Rerun from yesterday afternoon
1264 if (match.captured(1) == "eftermiddagen")
1265 {
1266 event.m_originalairdate = event.m_starttime.date().addDays(-1);
1267 return;
1268 }
1269
1270 // Rerun with day, month and possibly year specified
1271#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1272 match2 = comHemRerun2.match(match.capturedView(1));
1273#else
1274 match2 = comHemRerun2.matchView(match.capturedView(1));
1275#endif
1276 if (match2.hasMatch())
1277 {
1278 int day = match2.capturedView(1).toInt();
1279 int month = match2.capturedView(2).toInt();
1280 //int year;
1281 //if (match2.capturedLength(3) > 0)
1282 // year = match2.capturedView(3).toInt();
1283 //else
1284 // year = event.m_starttime.date().year();
1285
1286 if (day > 0 && month > 0)
1287 {
1288 QDate date(event.m_starttime.date().year(), month, day);
1289 // it's a rerun so it must be in the past
1290 if (date > event.m_starttime.date())
1291 date = date.addYears(-1);
1292 event.m_originalairdate = date;
1293 }
1294 return;
1295 }
1296}
1297
1302{
1303 event.m_category = event.m_subtitle;
1304 /* Used for DVB-S Subtitles are separated by a colon */
1305 int position = event.m_description.indexOf(':');
1306 if (position != -1)
1307 {
1308 const QString stmp = event.m_description;
1309 event.m_subtitle = stmp.left(position);
1310 event.m_description = stmp.right(stmp.length() - position - 2);
1311 }
1312}
1313
1318{
1319 if (event.m_description.startsWith("[Program data ") || event.m_description.startsWith("[Program info "))//TEN
1320 {
1321 event.m_description = "";//event.m_subtitle;
1322 }
1323 if (event.m_description.endsWith("Copyright West TV Ltd. 2011)"))
1324 event.m_description.resize(event.m_description.length()-40);
1325
1326 if (event.m_description.isEmpty() && !event.m_subtitle.isEmpty())//due to ten's copyright info, this won't be caught before
1327 {
1328 event.m_description = event.m_subtitle;
1329 event.m_subtitle.clear();
1330 }
1331 if (event.m_description.startsWith(event.m_title+" - "))
1332 event.m_description.remove(0,event.m_title.length()+3);
1333 if (event.m_title.startsWith("LIVE: ", Qt::CaseInsensitive))
1334 {
1335 event.m_title.remove(0, 6);
1336 event.m_description.prepend("(Live) ");
1337 }
1338}
1339
1344{
1345 static const QRegularExpression rating { "\\((G|PG|M|MA)\\)" };
1346 auto match = rating.match(event.m_description);
1347 if (match.hasMatch())
1348 {
1349 EventRating prograting;
1350 prograting.m_system="AU"; prograting.m_rating = match.captured(1);
1351 event.m_ratings.push_back(prograting);
1352 event.m_description.remove(0,match.capturedLength()+1);
1353 }
1354 if (event.m_description.startsWith("[HD]"))
1355 {
1356 event.m_videoProps |= VID_HDTV;
1357 event.m_description.remove(0,5);
1358 }
1359 if (event.m_description.startsWith("[CC]"))
1360 {
1361 event.m_subtitleType |= SUB_NORMAL;
1362 event.m_description.remove(0,5);
1363 }
1364 if (event.m_subtitle == "Movie")
1365 {
1366 event.m_subtitle.clear();
1367 event.m_categoryType = ProgramInfo::kCategoryMovie;
1368 }
1369 if (event.m_description.startsWith(event.m_title))
1370 event.m_description.remove(0,event.m_title.length()+1);
1371}
1372
1377{
1378 if (event.m_description.endsWith(" Rpt"))
1379 {
1380 event.m_previouslyshown = true;
1381 event.m_description.resize(event.m_description.size()-4);
1382 }
1383 static const QRegularExpression year { "(\\d{4})$" };
1384 auto match = year.match(event.m_description);
1385 if (match.hasMatch())
1386 {
1387 event.m_airdate = match.capturedView(1).toUInt();
1388 event.m_description.resize(event.m_description.size()-5);
1389 }
1390 if (event.m_description.endsWith(" CC"))
1391 {
1392 event.m_subtitleType |= SUB_NORMAL;
1393 event.m_description.resize(event.m_description.size()-3);
1394 }
1395 QString advisories;//store the advisories to append later
1396 static const QRegularExpression adv { "(\\([A-Z,]+\\))$" };
1397 match = adv.match(event.m_description);
1398 if (match.hasMatch())
1399 {
1400 advisories = match.captured(1);
1401 event.m_description.remove(match.capturedStart()-1, match.capturedLength()+1);
1402 }
1403 static const QRegularExpression rating { "(C|G|PG|M|MA)$" };
1404 match = rating.match(event.m_description);
1405 if (match.hasMatch())
1406 {
1407 EventRating prograting;
1408 prograting.m_system="AU"; prograting.m_rating = match.captured(1);
1409 if (!advisories.isEmpty())
1410 prograting.m_rating.append(" ").append(advisories);
1411 event.m_ratings.push_back(prograting);
1412 event.m_description.remove(match.capturedStart()-1, match.capturedLength()+1);
1413 }
1414}
1419{
1420 // If the description has been truncated to fit within the
1421 // 'subtitle' eit field, none of the following will work (ABC)
1422 if (event.m_description.endsWith(".."))
1423 return;
1424 event.m_description = event.m_description.trimmed();
1425
1426 static const QRegularExpression auFreeviewSY { R"((.*) \‍((.+)\) \‍(([12][0-9][0-9][0-9])\)$)" };
1427 auto match = auFreeviewSY.match(event.m_description);
1428 if (match.hasMatch())
1429 {
1430 if (event.m_subtitle.isEmpty())//nine sometimes has an actual subtitle field and the brackets thingo)
1431 event.m_subtitle = match.captured(2);
1432 event.m_airdate = match.capturedView(3).toUInt();
1433 event.m_description = match.captured(1);
1434 return;
1435 }
1436 static const QRegularExpression auFreeviewY { "(.*) \\(([12][0-9][0-9][0-9])\\)$" };
1437 match = auFreeviewY.match(event.m_description);
1438 if (match.hasMatch())
1439 {
1440 event.m_airdate = match.capturedView(2).toUInt();
1441 event.m_description = match.captured(1);
1442 return;
1443 }
1444 static const QRegularExpression auFreeviewSYC { R"((.*) \‍((.+)\) \‍(([12][0-9][0-9][0-9])\) \‍((.+)\)$)" };
1445 match = auFreeviewSYC.match(event.m_description);
1446 if (match.hasMatch())
1447 {
1448 if (event.m_subtitle.isEmpty())
1449 event.m_subtitle = match.captured(2);
1450 event.m_airdate = match.capturedView(3).toUInt();
1451 QStringList actors = match.captured(4).split("/");
1452 /* Possible TODO: if EIT inlcude the priority and/or character
1453 * names for the actors, include them in AddPerson call. */
1454 for (const QString& actor : std::as_const(actors))
1455 event.AddPerson(DBPerson::kActor, actor);
1456 event.m_description = match.captured(1);
1457 return;
1458 }
1459 static const QRegularExpression auFreeviewYC { R"((.*) \‍(([12][0-9][0-9][0-9])\) \‍((.+)\)$)" };
1460 match = auFreeviewYC.match(event.m_description);
1461 if (match.hasMatch())
1462 {
1463 event.m_airdate = match.capturedView(2).toUInt();
1464 QStringList actors = match.captured(3).split("/");
1465 /* Possible TODO: if EIT inlcude the priority and/or character
1466 * names for the actors, include them in AddPerson call. */
1467 for (const QString& actor : std::as_const(actors))
1468 event.AddPerson(DBPerson::kActor, actor);
1469 event.m_description = match.captured(1);
1470 }
1471}
1472
1477{
1478 const uint SUBTITLE_PCT = 60; // % of description to allow subtitle to
1479 const uint lSUBTITLE_MAX_LEN = 128;// max length of subtitle field in db.
1480
1481 // Remove subtitle, it contains category information too specific to use
1482 event.m_subtitle = QString("");
1483
1484 // No need to continue without a description.
1485 if (event.m_description.length() <= 0)
1486 return;
1487
1488 // Replace incomplete title if the full one is in the description
1489 static const QRegularExpression mcaIncompleteTitle { R"((.*).\.\.\.$)" };
1490 auto match = mcaIncompleteTitle.match(event.m_title);
1491 if (match.hasMatch())
1492 {
1493 static const QString mcaCompleteTitlea { "^'?(" };
1494 static const QString mcaCompleteTitleb { R"([^\.\?]+[^\'])'?[\.\?]\s+(.+))" };
1495 static const QRegularExpression mcaCompleteTitle
1496 { mcaCompleteTitlea + match.captured(1) + mcaCompleteTitleb,
1497 QRegularExpression::CaseInsensitiveOption};
1498 match = mcaCompleteTitle.match(event.m_description);
1499 if (match.hasMatch())
1500 {
1501 event.m_title = match.captured(1).trimmed();
1502 event.m_description = match.captured(2).trimmed();
1503 }
1504 }
1505
1506 // Try to find subtitle in description
1507 static const QRegularExpression mcaSubtitle { R"(^'([^\.]+)'\.\s+(.+))" };
1508 match = mcaSubtitle.match(event.m_description);
1509 if (match.hasMatch())
1510 {
1511 uint matchLen = match.capturedLength(1);
1512#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1513 uint evDescLen = std::max(event.m_description.length(), 1);
1514#else
1515 uint evDescLen = std::max(event.m_description.length(), static_cast<qsizetype>(1));
1516#endif
1517
1518 if ((matchLen < lSUBTITLE_MAX_LEN) &&
1519 ((matchLen * 100 / evDescLen) < SUBTITLE_PCT))
1520 {
1521 event.m_subtitle = match.captured(1);
1522 event.m_description = match.captured(2);
1523 }
1524 }
1525
1526 // Try to find episode numbers in subtitle
1527 static const QRegularExpression mcaSeries { R"(^S?(\d+)\/E?(\d+)\s-\s(.*)$)" };
1528 match = mcaSeries.match(event.m_subtitle);
1529 if (match.hasMatch())
1530 {
1531 uint season = match.capturedView(1).toUInt();
1532 uint episode = match.capturedView(2).toUInt();
1533 event.m_subtitle = match.captured(3).trimmed();
1534 event.m_syndicatedepisodenumber =
1535 QString("S%1E%2").arg(season).arg(episode);
1536 event.m_season = season;
1537 event.m_episode = episode;
1538 event.m_categoryType = ProgramInfo::kCategorySeries;
1539 }
1540
1541 // Closed captioned?
1542 static const QRegularExpression mcaCC { R"(,?\s(HI|English) Subtitles\.?)" };
1543 int position = event.m_description.indexOf(mcaCC);
1544 if (position > 0)
1545 {
1546 event.m_subtitleType |= SUB_HARDHEAR;
1547 event.m_description.remove(mcaCC);
1548 }
1549
1550 // Dolby Digital 5.1?
1551 static const QRegularExpression mcaDD { R"(,?\sDD\.?)" };
1552 position = event.m_description.indexOf(mcaDD);
1553 if ((position > 0) && (position > event.m_description.length() - 7))
1554 {
1555 event.m_audioProps |= AUD_DOLBY;
1556 event.m_description.remove(mcaDD);
1557 }
1558
1559 // Remove bouquet tags
1560 static const QRegularExpression mcaAvail { R"(\s(Only available on [^\.]*bouquet|Not available in RSA [^\.]*)\.?)" };
1561 event.m_description.remove(mcaAvail);
1562
1563 // Try to find year and director from the end of the description
1564 bool isMovie = false;
1565 static const QRegularExpression mcaCredits { R"((.*)\s\‍((\d{4})\)\s*([^\.]+)\.?\s*$)" };
1566 match = mcaCredits.match(event.m_description);
1567 if (match.hasMatch())
1568 {
1569 isMovie = true;
1570 event.m_description = match.captured(1).trimmed();
1571 bool ok = false;
1572 uint y = match.captured(2).trimmed().toUInt(&ok);
1573 if (ok)
1574 event.m_airdate = y;
1575 event.AddPerson(DBPerson::kDirector, match.captured(3).trimmed());
1576 }
1577 else
1578 {
1579 // Try to find year only from the end of the description
1580 static const QRegularExpression mcaYear { R"((.*)\s\‍((\d{4})\)\s*$)" };
1581 match = mcaYear.match(event.m_description);
1582 if (match.hasMatch())
1583 {
1584 isMovie = true;
1585 event.m_description = match.captured(1).trimmed();
1586 bool ok = false;
1587 uint y = match.captured(2).trimmed().toUInt(&ok);
1588 if (ok)
1589 event.m_airdate = y;
1590 }
1591 }
1592
1593 if (isMovie)
1594 {
1595 static const QRegularExpression mcaActors { R"((.*\.)\s+([^\.]+\s[A-Z][^\.]+)\.\s*)" };
1596 match = mcaActors.match(event.m_description);
1597 if (match.hasMatch())
1598 {
1599 static const QRegularExpression mcaActorsSeparator { "(,\\s+)" };
1600 const QStringList actors = match.captured(2).split(
1601 mcaActorsSeparator, Qt::SkipEmptyParts);
1602 /* Possible TODO: if EIT inlcude the priority and/or character
1603 * names for the actors, include them in AddPerson call. */
1604 for (const auto & actor : std::as_const(actors))
1605 event.AddPerson(DBPerson::kActor, actor.trimmed());
1606 event.m_description = match.captured(1).trimmed();
1607 }
1608 event.m_categoryType = ProgramInfo::kCategoryMovie;
1609 }
1610}
1611
1616{
1617 // subtitle with episode number: "Folge *: 'subtitle'
1618 static const QRegularExpression superRTLSubtitle { R"(^Folge\s(\d{1,3}):\s'(.*)')" };
1619 auto match = superRTLSubtitle.match(event.m_subtitle);
1620 if (match.hasMatch())
1621 {
1622 event.m_season = 0;
1623 event.m_episode = match.capturedView(1).toUInt();
1624 event.m_subtitle = match.captured(2);
1625 }
1626
1627 // No need to continue without a description or with an subtitle.
1628 if (event.m_description.length() <= 0 || event.m_subtitle.length() > 0)
1629 return;
1630
1631 // Repeat
1632 static const QRegularExpression rtlRepeat
1633 { R"([\s\‍(]?Wiederholung.+vo[m|n].+(\d{2}\.\d{2}\.\d{4}|\d{2}[:\.]\d{2}\sUhr)\)?)" };
1634 match = rtlRepeat.match(event.m_description);
1635 if (match.hasMatch())
1636 {
1637 // remove '.' if it matches at the beginning of the description
1638 int pos = match.capturedStart(0);
1639 int length = match.capturedLength(0) + (pos ? 0 : 1);
1640 event.m_description = event.m_description.remove(pos, length).trimmed();
1641 }
1642
1643 // should be (?:\x{8a}|\\.\\s*|$) but 0x8A gets replaced with 0x20
1644 static const QRegularExpression rtlSubtitle1 { R"(^Folge\s(\d{1,4})\s*:\s+'(.*)'(?:\s|\.\s*|$))" };
1645 static const QRegularExpression rtlSubtitle2 { R"(^Folge\s(\d{1,4})\s+(.{0,5}[^\?!\.]{0,120})[\?!\.]\s*)" };
1646 static const QRegularExpression rtlSubtitle3 { R"(^(?:Folge\s)?(\d{1,4}(?:\/[IVX]+)?)\s+(.{0,5}[^\?!\.]{0,120})[\?!\.]\s*)" };
1647 static const QRegularExpression rtlSubtitle4 { R"(^Thema.{0,5}:\s([^\.]+)\.\s*)" };
1648 static const QRegularExpression rtlSubtitle5 { "^'(.+)'\\.\\s*" };
1649 static const QRegularExpression rtlEpisodeNo1 { R"(^(Folge\s\d{1,4})\.*\s*)" };
1650 static const QRegularExpression rtlEpisodeNo2 { R"(^(\d{1,2}\/[IVX]+)\.*\s*)" };
1651
1652 auto match1 = rtlSubtitle1.match(event.m_description);
1653 auto match2 = rtlSubtitle2.match(event.m_description);
1654 auto match3 = rtlSubtitle3.match(event.m_description);
1655 auto match4 = rtlSubtitle4.match(event.m_description);
1656 auto match5 = rtlSubtitle5.match(event.m_description);
1657 auto match6 = rtlEpisodeNo1.match(event.m_description);
1658 auto match7 = rtlEpisodeNo2.match(event.m_description);
1659
1660 // subtitle with episode number: "Folge *: 'subtitle'. description
1661 if (match1.hasMatch())
1662 {
1663 event.m_syndicatedepisodenumber = match1.captured(1);
1664 event.m_subtitle = match1.captured(2);
1665 event.m_description =
1666 event.m_description.remove(0, match1.capturedLength());
1667 }
1668 // episode number subtitle
1669 else if (match2.hasMatch())
1670 {
1671 event.m_syndicatedepisodenumber = match2.captured(1);
1672 event.m_subtitle = match2.captured(2);
1673 event.m_description =
1674 event.m_description.remove(0, match2.capturedLength());
1675 }
1676 // episode number subtitle
1677 else if (match3.hasMatch())
1678 {
1679 event.m_syndicatedepisodenumber = match3.captured(1);
1680 event.m_subtitle = match3.captured(2);
1681 event.m_description =
1682 event.m_description.remove(0, match3.capturedLength());
1683 }
1684 // "Thema..."
1685 else if (match4.hasMatch())
1686 {
1687 event.m_subtitle = match4.captured(1);
1688 event.m_description =
1689 event.m_description.remove(0, match4.capturedLength());
1690 }
1691 // "'...'"
1692 else if (match5.hasMatch())
1693 {
1694 event.m_subtitle = match5.captured(1);
1695 event.m_description =
1696 event.m_description.remove(0, match5.capturedLength());
1697 }
1698 // episode number
1699 else if (match6.hasMatch())
1700 {
1701 event.m_syndicatedepisodenumber = match6.captured(2);
1702 event.m_subtitle = match6.captured(1);
1703 event.m_description =
1704 event.m_description.remove(0, match6.capturedLength());
1705 }
1706 // episode number
1707 else if (match7.hasMatch())
1708 {
1709 event.m_syndicatedepisodenumber = match7.captured(2);
1710 event.m_subtitle = match7.captured(1);
1711 event.m_description =
1712 event.m_description.remove(0, match7.capturedLength());
1713 }
1714
1715 /* got an episode title now? (we did not have one at the start of this function) */
1716 if (!event.m_subtitle.isEmpty())
1718
1719 /* if we do not have an episode title by now try some guessing as last resort */
1720 if (event.m_subtitle.length() == 0)
1721 {
1722 const uint SUBTITLE_PCT = 35; // % of description to allow subtitle up to
1723 const uint lSUBTITLE_MAX_LEN = 50; // max length of subtitle field in db
1724
1725 static const QRegularExpression rtlSubtitle { R"(^([^\.]{3,})\.\s+(.+))" };
1726 match = rtlSubtitle.match(event.m_description);
1727 if (match.hasMatch())
1728 {
1729 uint matchLen = match.capturedLength(1);
1730#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1731 uint evDescLen = std::max(event.m_description.length(), 1);
1732#else
1733 uint evDescLen = std::max(event.m_description.length(), static_cast<qsizetype>(1));
1734#endif
1735
1736 if ((matchLen < lSUBTITLE_MAX_LEN) &&
1737 (matchLen * 100 / evDescLen < SUBTITLE_PCT))
1738 {
1739 event.m_subtitle = match.captured(1);
1740 event.m_description = match.captured(2);
1741 }
1742 }
1743 }
1744}
1745
1746// FIXME add more jobs
1747static const QMap<QString,DBPerson::Role> deCrewTitle {
1748 { "Regie", DBPerson::kDirector },
1749 { "Drehbuch", DBPerson::kWriter },
1750 { "Autor", DBPerson::kWriter },
1751};
1752
1757{
1758 // strip repeat info and set previouslyshown flag
1759 static const QRegularExpression pro7Repeat
1760 { R"((?<=\s|^)\‍(WH vom \w+, \d{2}\.\d{2}\.\d{4}, \d{2}:\d{2} Uhr\)$)" };
1761 auto match = pro7Repeat.match(event.m_subtitle);
1762 if (match.hasMatch())
1763 {
1764 event.m_previouslyshown = true;
1765 event.m_subtitle.remove(match.capturedStart(0),
1766 match.capturedLength(0));
1767 event.m_subtitle = event.m_subtitle.trimmed();
1768 }
1769
1770 // strip "Mit Gebärdensprache (Online-Stream)"
1771 static const QRegularExpression pro7signLanguage
1772 { R"((?<=\s|^)Mit Gebärdensprache \‍(Online\-Stream\)$)" };
1773 match = pro7signLanguage.match(event.m_subtitle);
1774 if (match.hasMatch())
1775 {
1776 event.m_subtitle.remove(match.capturedStart(0),
1777 match.capturedLength(0));
1778 event.m_subtitle = event.m_subtitle.trimmed();
1779 }
1780
1781 // move age ratings into metadata
1782 static const QRegularExpression pro7ratingAllAges
1783 { R"((?<=\s|^)Altersfreigabe: Ohne Altersbeschränkung$)" };
1784 match = pro7ratingAllAges.match(event.m_subtitle);
1785 if (match.hasMatch())
1786 {
1787 EventRating prograting;
1788 prograting.m_system="DE";
1789 prograting.m_rating = "0";
1790 event.m_ratings.push_back(prograting);
1791
1792 event.m_subtitle.remove(match.capturedStart(0),
1793 match.capturedLength(0));
1794 event.m_subtitle = event.m_subtitle.trimmed();
1795 }
1796 static const QRegularExpression pro7rating
1797 { R"((?<=\s|^)Altersfreigabe: ab (\d+)$)" };
1798 match = pro7rating.match(event.m_subtitle);
1799 if (match.hasMatch())
1800 {
1801 EventRating prograting;
1802 prograting.m_system="DE";
1803 prograting.m_rating = match.captured(1);
1804 event.m_ratings.push_back(prograting);
1805
1806 event.m_subtitle.remove(match.capturedStart(0),
1807 match.capturedLength(0));
1808 event.m_subtitle = event.m_subtitle.trimmed();
1809 }
1810
1811 // move category and (original) airdate into metadata, add country and airdate to description
1812 static const QRegularExpression pro7CategoryOriginalairdate
1813 { R"((?<=\s|^)(Late Night Show|Live Shopping|Real Crime|Real Life Doku|Romantic Comedy|Scripted Reality|\S+), ([A-Z]+(?:\/[A-Z]+)*) (\d{4})$)" };
1814 match = pro7CategoryOriginalairdate.match(event.m_subtitle);
1815 if (match.hasMatch())
1816 {
1817 event.m_category = match.captured(1);
1818
1819 event.m_description.append(" (").append(match.captured(2)).append(" ").append(match.captured(3)).append(")");
1820
1821 uint y = match.captured(3).toUInt();
1822 event.m_originalairdate = QDate(y, 1, 1);
1823 if (event.m_airdate == 0)
1824 {
1825 event.m_airdate = y;
1826 }
1827
1828 event.m_subtitle.remove(match.capturedStart(0),
1829 match.capturedLength(0));
1830 event.m_subtitle = event.m_subtitle.trimmed();
1831 }
1832
1833 // remove subtitle if equal to title
1834 if (event.m_title == event.m_subtitle) {
1835 event.m_subtitle = "";
1836 }
1837}
1838
1843{
1844 static const QRegularExpression deDisneyChannelSubtitle { R"(,([^,]+?)\s{0,1}(\d{4})$)" };
1845 auto match = deDisneyChannelSubtitle.match(event.m_subtitle);
1846 if (match.hasMatch())
1847 {
1848 if (event.m_airdate == 0)
1849 {
1850 event.m_airdate = match.captured(3).toUInt();
1851 }
1852 event.m_subtitle.remove(match.capturedStart(0),
1853 match.capturedLength(0));
1854 }
1855 static const QRegularExpression tmp { R"(\s[^\s]+?-(Serie))" };
1856 match = tmp.match(event.m_subtitle);
1857 if (match.hasMatch())
1858 {
1859 event.m_categoryType = ProgramInfo::kCategorySeries;
1860 event.m_category=match.captured(0).trimmed();
1861 event.m_subtitle.remove(match.capturedStart(0),
1862 match.capturedLength(0));
1863 }
1864}
1865
1870{
1871 static const QRegularExpression atvSubtitle { R"(,{0,1}\sFolge\s(\d{1,3})$)" };
1872 event.m_subtitle.replace(atvSubtitle, "");
1873}
1874
1875
1880{
1881 static const QRegularExpression fiRerun { R"(\s?Uusinta[a-zA-Z\s]*\.?)" };
1882 auto match = fiRerun.match(event.m_description);
1883 if (match.hasMatch())
1884 {
1885 event.m_previouslyshown = true;
1886 event.m_description.remove(match.capturedStart(), match.capturedLength());
1887 }
1888
1889 static const QRegularExpression fiRerun2 { R"(\‍([Uu]\))" };
1890 match = fiRerun2.match(event.m_description);
1891 if (match.hasMatch())
1892 {
1893 event.m_previouslyshown = true;
1894 event.m_description.remove(match.capturedStart(), match.capturedLength());
1895 }
1896
1897 // Check for (Stereo) in the decription and set the <audio> tags
1898 match = kStereo.match(event.m_description);
1899 if (match.hasMatch())
1900 {
1901 event.m_audioProps |= AUD_STEREO;
1902 event.m_description.remove(match.capturedStart(), match.capturedLength());
1903 }
1904
1905 // Remove age limit in parenthesis at end of title
1906 static const QRegularExpression fiAgeLimit { R"(\‍((\d{1,2}|[ST])\)$)" };
1907 match = fiAgeLimit.match(event.m_title);
1908 if (match.hasMatch())
1909 {
1910 EventRating prograting;
1911 prograting.m_system="FI"; prograting.m_rating = match.captured(1);
1912 event.m_ratings.push_back(prograting);
1913 event.m_title.remove(match.capturedStart(), match.capturedLength());
1914 }
1915
1916 // Remove Film or Elokuva at start of title
1917 static const QRegularExpression fiFilm { "^(Film|Elokuva): " };
1918 match = fiFilm.match(event.m_title);
1919 if (match.hasMatch())
1920 {
1921 event.m_category = "Film";
1922 event.m_categoryType = ProgramInfo::kCategoryMovie;
1923 event.m_title.remove(match.capturedStart(), match.capturedLength());
1924 }
1925}
1926
1932{
1933 QString country = "";
1934
1935 static const QRegularExpression dePremiereLength { R"(\s?[0-9]+\sMin\.)" };
1936 event.m_description = event.m_description.replace(dePremiereLength, "");
1937
1938 static const QRegularExpression dePremiereAirdate { R"(\s?([^\s^\.]+)\s((?:1|2)[0-9]{3})\.)" };
1939 auto match = dePremiereAirdate.match(event.m_description);
1940 if ( match.hasMatch())
1941 {
1942 country = match.captured(1).trimmed();
1943 bool ok = false;
1944 uint y = match.captured(2).toUInt(&ok);
1945 if (ok)
1946 event.m_airdate = y;
1947 event.m_description.remove(match.capturedStart(0),
1948 match.capturedLength(0));
1949 }
1950
1951 static const QRegularExpression dePremiereCredits { R"(\sVon\s([^,]+)(?:,|\su\.\sa\.)\smit\s([^\.]*)\.)" };
1952 match = dePremiereCredits.match(event.m_description);
1953 if (match.hasMatch())
1954 {
1955 event.AddPerson(DBPerson::kDirector, match.captured(1));
1956 const QStringList actors = match.captured(2).split(
1957 ", ", Qt::SkipEmptyParts);
1958 /* Possible TODO: if EIT inlcude the priority and/or character
1959 * names for the actors, include them in AddPerson call. */
1960 for (const auto & actor : std::as_const(actors))
1961 event.AddPerson(DBPerson::kActor, actor);
1962 event.m_description.remove(match.capturedStart(0),
1963 match.capturedLength(0));
1964 }
1965
1966 event.m_description = event.m_description.replace("\u000A$", "");
1967 event.m_description = event.m_description.replace("\u000A", " ");
1968
1969 // move the original titel from the title to subtitle
1970 static const QRegularExpression dePremiereOTitle { R"(\s*\‍(([^\)]*)\)$)" };
1971 match = dePremiereOTitle.match(event.m_title);
1972 if (match.hasMatch())
1973 {
1974 event.m_subtitle = QString("%1, %2").arg(match.captured(1), country);
1975 event.m_title.remove(match.capturedStart(0),
1976 match.capturedLength(0));
1977 }
1978
1979 // Find infos about season and episode number
1980 static const QRegularExpression deSkyDescriptionSeasonEpisode { R"(^(\d{1,2}).\sStaffel,\sFolge\s(\d{1,2}):\s)" };
1981 match = deSkyDescriptionSeasonEpisode.match(event.m_description);
1982 if (match.hasMatch())
1983 {
1984 event.m_season = match.captured(1).trimmed().toUInt();
1985 event.m_episode = match.captured(2).trimmed().toUInt();
1986 event.m_description.remove(match.capturedStart(0),
1987 match.capturedLength(0));
1988 }
1989}
1990
1991/*
1992 * Mapping table from English category names to Dutch names and types
1993 */
1995 QString name;
1997};
1998static const QMap<QString, NLMapResult> categoryTrans = {
1999 { "Documentary", { .name="Documentaire", .type=ProgramInfo::kCategoryNone } },
2000 { "News", { .name="Nieuws/actualiteiten", .type=ProgramInfo::kCategoryNone } },
2001 { "Kids", { .name="Jeugd", .type=ProgramInfo::kCategoryNone } },
2002 { "Show/game Show", { .name="Amusement", .type=ProgramInfo::kCategoryTVShow } },
2003 { "Music/Ballet/Dance", { .name="Muziek", .type=ProgramInfo::kCategoryNone } },
2004 { "News magazine", { .name="Informatief", .type=ProgramInfo::kCategoryNone } },
2005 { "Movie", { .name="Film", .type=ProgramInfo::kCategoryMovie } },
2006 { "Nature/animals/Environment", { .name="Natuur", .type=ProgramInfo::kCategoryNone } },
2007 { "Movie - Adult", { .name="Erotiek", .type=ProgramInfo::kCategoryNone } },
2008 { "Movie - Soap/melodrama/folkloric",
2009 { .name="Serie/soap", .type=ProgramInfo::kCategorySeries } },
2010 { "Arts/Culture", { .name="Kunst/Cultuur", .type=ProgramInfo::kCategoryNone } },
2011 { "Sports", { .name="Sport", .type=ProgramInfo::kCategorySports } },
2012 { "Cartoons/Puppets", { .name="Animatie", .type=ProgramInfo::kCategoryNone } },
2013 { "Movie - Comedy", { .name="Comedy", .type=ProgramInfo::kCategorySeries } },
2014 { "Movie - Detective/Thriller", { .name="Misdaad", .type=ProgramInfo::kCategoryNone } },
2015 { "Social/Spiritual Sciences", { .name="Religieus", .type=ProgramInfo::kCategoryNone } },
2016};
2017
2022{
2023 QString fullinfo = event.m_subtitle + event.m_description;
2024 event.m_subtitle = "";
2025
2026 // Convert categories to Dutch categories Myth knows.
2027 // nog invoegen: comedy, sport, misdaad
2028
2029 if (categoryTrans.contains(event.m_category))
2030 {
2031 auto [name, type] = categoryTrans[event.m_category];
2032 event.m_category = name;
2033 event.m_categoryType = type;
2034 }
2035
2036 // Film - categories are usually not Films
2037 if (event.m_category.startsWith("Film -"))
2038 event.m_categoryType = ProgramInfo::kCategorySeries;
2039
2040 // Get stereo info
2041 auto match = kStereo.match(fullinfo);
2042 if (match.hasMatch())
2043 {
2044 event.m_audioProps |= AUD_STEREO;
2045 fullinfo.remove(match.capturedStart(), match.capturedLength());
2046 }
2047
2048 //Get widescreen info
2049 static const QRegularExpression nlWide { "breedbeeld" };
2050 match = nlWide.match(fullinfo);
2051 if (match.hasMatch())
2052 {
2053 event.m_videoProps |= VID_WIDESCREEN;
2054 fullinfo = fullinfo.replace("breedbeeld", ".");
2055 }
2056
2057 // Get repeat info
2058 static const QRegularExpression nlRepeat { "herh." };
2059 match = nlRepeat.match(fullinfo);
2060 if (match.hasMatch())
2061 fullinfo = fullinfo.replace("herh.", ".");
2062
2063 // Get teletext subtitle info
2064 static const QRegularExpression nlTxt { "txt" };
2065 match = nlTxt.match(fullinfo);
2066 if (match.hasMatch())
2067 {
2068 event.m_subtitleType |= SUB_NORMAL;
2069 fullinfo = fullinfo.replace("txt", ".");
2070 }
2071
2072 // Get HDTV information
2073 static const QRegularExpression nlHD { R"(\sHD$)" };
2074 match = nlHD.match(event.m_title);
2075 if (match.hasMatch())
2076 {
2077 event.m_videoProps |= VID_HDTV;
2078 event.m_title.remove(match.capturedStart(), match.capturedLength());
2079 }
2080
2081 // Try to make subtitle from Afl.:
2082 static const QRegularExpression nlSub { R"(\sAfl\.:\s([^\.]+)\.)" };
2083 match = nlSub.match(fullinfo);
2084 if (match.hasMatch())
2085 {
2086 QString tmpSubString = match.captured(0);
2087 tmpSubString = tmpSubString.right(match.capturedLength() - 7);
2088 event.m_subtitle = tmpSubString.left(tmpSubString.length() -1);
2089 fullinfo.remove(match.capturedStart(), match.capturedLength());
2090 }
2091
2092 // Try to make subtitle from " "
2093 static const QRegularExpression nlSub2 { R"(\s\"([^\"]+)\")" };
2094 match = nlSub2.match(fullinfo);
2095 if (match.hasMatch())
2096 {
2097 QString tmpSubString = match.captured(0);
2098 tmpSubString = tmpSubString.right(match.capturedLength() - 2);
2099 event.m_subtitle = tmpSubString.left(tmpSubString.length() -1);
2100 fullinfo.remove(match.capturedStart(), match.capturedLength());
2101 }
2102
2103
2104 // This is trying to catch the case where the subtitle is in the main title
2105 // but avoid cases where it isn't a subtitle e.g cd:uk
2106 int position = event.m_title.indexOf(":");
2107 if ((position != -1) && ((position + 1) < event.m_title.size()) &&
2108 (event.m_title[position + 1].toUpper() == event.m_title[position + 1]) &&
2109 (event.m_subtitle.isEmpty()))
2110 {
2111 event.m_subtitle = event.m_title.mid(position + 1);
2112 event.m_title = event.m_title.left(position);
2113 }
2114
2115
2116 // Get the actors
2117 static const QRegularExpression nlActors { R"(\sMet:\s.+e\.a\.)" };
2118 static const QRegularExpression nlPersSeparator { R"((, |\sen\s))" };
2119 match = nlActors.match(fullinfo);
2120 if (match.hasMatch())
2121 {
2122 QString tmpActorsString = match.captured(0);
2123 tmpActorsString = tmpActorsString.right(tmpActorsString.length() - 6);
2124 tmpActorsString = tmpActorsString.left(tmpActorsString.length() - 5);
2125 const QStringList actors =
2126 tmpActorsString.split(nlPersSeparator, Qt::SkipEmptyParts);
2127 /* Possible TODO: if EIT inlcude the priority and/or character
2128 * names for the actors, include them in AddPerson call. */
2129 for (const auto & actor : std::as_const(actors))
2130 event.AddPerson(DBPerson::kActor, actor);
2131 fullinfo.remove(match.capturedStart(), match.capturedLength());
2132 }
2133
2134 // Try to find presenter
2135 static const QRegularExpression nlPres { R"(\sPresentatie:\s([^\.]+)\.)" };
2136 match = nlPres.match(fullinfo);
2137 if (match.hasMatch())
2138 {
2139 QString tmpPresString = match.captured(0);
2140 tmpPresString = tmpPresString.right(tmpPresString.length() - 14);
2141 tmpPresString = tmpPresString.left(tmpPresString.length() -1);
2142 const QStringList presenters =
2143 tmpPresString.split(nlPersSeparator, Qt::SkipEmptyParts);
2144 for (const auto & presenter : std::as_const(presenters))
2145 event.AddPerson(DBPerson::kPresenter, presenter);
2146 fullinfo.remove(match.capturedStart(), match.capturedLength());
2147 }
2148
2149 // Try to find year
2150 static const QRegularExpression nlYear1 { R"(\suit\s([1-2][0-9]{3}))" };
2151 static const QRegularExpression nlYear2 { R"((\s\‍([A-Z]{0,3}/?)([1-2][0-9]{3})\))",
2152 QRegularExpression::CaseInsensitiveOption };
2153 match = nlYear1.match(fullinfo);
2154 if (match.hasMatch())
2155 {
2156 bool ok = false;
2157 uint y = match.capturedView(1).toUInt(&ok);
2158 if (ok)
2159 event.m_originalairdate = QDate(y, 1, 1);
2160 }
2161
2162 match = nlYear2.match(fullinfo);
2163 if (match.hasMatch())
2164 {
2165 bool ok = false;
2166 uint y = match.capturedView(2).toUInt(&ok);
2167 if (ok)
2168 event.m_originalairdate = QDate(y, 1, 1);
2169 }
2170
2171 // Try to find director
2172 static const QRegularExpression nlDirector { R"(\svan\s(([A-Z][a-z]+\s)|([A-Z]\.\s)))" };
2173 match = nlDirector.match(fullinfo);
2174 if (match.hasMatch())
2175 event.AddPerson(DBPerson::kDirector, match.captured(1));
2176
2177 // Strip leftovers
2178 static const QRegularExpression nlRub { R"(\s?\‍(\W+\)\s?)" };
2179 fullinfo.remove(nlRub);
2180
2181 // Strip category info from description
2182 static const QRegularExpression nlCat { "^(Amusement|Muziek|Informatief|Nieuws/actualiteiten|Jeugd|Animatie|Sport|Serie/soap|Kunst/Cultuur|Documentaire|Film|Natuur|Erotiek|Comedy|Misdaad|Religieus)\\.\\s" };
2183 fullinfo.remove(nlCat);
2184
2185 // Remove omroep from title
2186 static const QRegularExpression nlOmroep { R"(\s\‍(([A-Z]+/?)+\)$)" };
2187 event.m_title.remove(nlOmroep);
2188
2189 // Put information back in description
2190
2191 event.m_description = fullinfo;
2192}
2193
2195{
2196 // remove category movie from short events
2198 event.m_starttime.secsTo(event.m_endtime) < kMinMovieDuration)
2199 {
2200 /* default taken from ContentDescriptor::GetMythCategory */
2201 event.m_categoryType = ProgramInfo::kCategoryTVShow;
2202 }
2203}
2204
2209{
2210 // Check for "title (R)" in the title
2211 static const QRegularExpression noRerun { "\\(R\\)" };
2212 auto match = noRerun.match(event.m_title);
2213 if (match.hasMatch())
2214 {
2215 event.m_previouslyshown = true;
2216 event.m_title.remove(match.capturedStart(), match.capturedLength());
2217 }
2218 // Check for "subtitle (HD)" in the subtitle
2219 static const QRegularExpression noHD { R"([\‍(\[]HD[\)\]])" };
2220 match = noHD.match(event.m_subtitle);
2221 if (match.hasMatch())
2222 {
2223 event.m_videoProps |= VID_HDTV;
2224 event.m_subtitle.remove(match.capturedStart(), match.capturedLength());
2225 }
2226 // Check for "description (HD)" in the description
2227 match = noHD.match(event.m_description);
2228 if (match.hasMatch())
2229 {
2230 event.m_videoProps |= VID_HDTV;
2231 event.m_description.remove(match.capturedStart(), match.capturedLength());
2232 }
2233}
2234
2239{
2240 // Check for "title (R)" in the title
2241 static const QRegularExpression noRerun { "\\(R\\)" };
2242 auto match = noRerun.match(event.m_title);
2243 if (match.hasMatch())
2244 {
2245 event.m_previouslyshown = true;
2246 event.m_title.remove(match.capturedStart(), match.capturedLength());
2247 }
2248 // Check for "(R)" in the description
2249 match = noRerun.match(event.m_description);
2250 if (match.hasMatch())
2251 {
2252 event.m_previouslyshown = true;
2253 }
2254
2255 // Move colon separated category from program-titles into description
2256 // Have seen "NRK2s historiekveld: Film: bla-bla"
2257 static const QRegularExpression noNRKCategories
2258 { "^(Superstrek[ea]r|Supersomm[ea]r|Superjul|Barne-tv|Fantorangen|Kuraffen|Supermorg[eo]n|Julemorg[eo]n|Sommermorg[eo]n|"
2259 "Kuraffen-TV|Sport i dag|NRKs sportsl.rdag|NRKs sportss.ndag|Dagens dokumentar|"
2260 "NRK2s historiekveld|Detektimen|Nattkino|Filmklassiker|Film|Kortfilm|P.skemorg[eo]n|"
2261 "Radioteatret|Opera|P2-Akademiet|Nyhetsmorg[eo]n i P2 og Alltid Nyheter:): (.+)" };
2262 match = noNRKCategories.match(event.m_title);
2263 if (match.hasMatch() && (match.capturedLength(2) > 1))
2264 {
2265 event.m_title = match.captured(2);
2266 event.m_description = "(" + match.captured(1) + ") " + event.m_description;
2267 }
2268
2269 // Remove season premiere markings
2270 static const QRegularExpression noPremiere { "\\s+-\\s+(Sesongpremiere|Premiere|premiere)!?$" };
2271 match = noPremiere.match(event.m_title);
2272 if (match.hasMatch() && (match.capturedStart() >= 3))
2273 event.m_title.remove(match.capturedStart(), match.capturedLength());
2274
2275 // Try to find colon-delimited subtitle in title, only tested for NRK channels
2276 if (!event.m_title.startsWith("CSI:") &&
2277 !event.m_title.startsWith("CD:") &&
2278 !event.m_title.startsWith("Distriktsnyheter: fra"))
2279 {
2280 static const QRegularExpression noColonSubtitle { "^([^:]+): (.+)" };
2281 match = noColonSubtitle.match(event.m_title);
2282 if (match.hasMatch())
2283 {
2284 if (event.m_subtitle.length() <= 0)
2285 {
2286 event.m_title = match.captured(1);
2287 event.m_subtitle = match.captured(2);
2288 }
2289 else if (event.m_subtitle == match.captured(2))
2290 {
2291 event.m_title = match.captured(1);
2292 }
2293 }
2294 }
2295}
2296
2301{
2302 // Source: YouSee Rules of Operation v1.16
2303 // url: http://yousee.dk/~/media/pdf/CPE/Rules_Operation.ashx
2304 int episode = -1;
2305 int season = -1;
2306
2307 // Title search
2308 // episode and part/part total
2309 static const QRegularExpression dkEpisode { R"(\‍(([0-9]+)\))" };
2310 auto match = dkEpisode.match(event.m_title);
2311 if (match.hasMatch())
2312 {
2313 episode = match.capturedView(1).toInt();
2314 event.m_partnumber = match.capturedView(1).toInt();
2315 event.m_title.remove(match.capturedStart(), match.capturedLength());
2316 }
2317
2318 static const QRegularExpression dkPart { R"(\‍(([0-9]+):([0-9]+)\))" };
2319 match = dkPart.match(event.m_title);
2320 if (match.hasMatch())
2321 {
2322 episode = match.capturedView(1).toInt();
2323 event.m_partnumber = match.capturedView(1).toInt();
2324 event.m_parttotal = match.capturedView(2).toInt();
2325 event.m_title.remove(match.capturedStart(), match.capturedLength());
2326 }
2327
2328 // subtitle delimiters
2329 static const QRegularExpression dkSubtitle1 { "^([^:]+): (.+)" };
2330 match = dkSubtitle1.match(event.m_title);
2331 if (match.hasMatch())
2332 {
2333 event.m_title = match.captured(1);
2334 event.m_subtitle = match.captured(2);
2335 }
2336 else
2337 {
2338 static const QRegularExpression dkSubtitle2 { "^([^:]+) - (.+)" };
2339 match = dkSubtitle2.match(event.m_title);
2340 if (match.hasMatch())
2341 {
2342 event.m_title = match.captured(1);
2343 event.m_subtitle = match.captured(2);
2344 }
2345 }
2346
2347 // Description search
2348 // Season (Sæson [:digit:]+.) => episode = season episode number
2349 // or year (- år [:digit:]+(\\‍)|:) ) => episode = total episode number
2350 static const QRegularExpression dkSeason1 { "Sæson ([0-9]+)\\." };
2351 match = dkSeason1.match(event.m_description);
2352 if (match.hasMatch())
2353 {
2354 season = match.capturedView(1).toInt();
2355 }
2356 else
2357 {
2358 static const QRegularExpression dkSeason2 { "- år ([0-9]+) :" };
2359 match = dkSeason2.match(event.m_description);
2360 if (match.hasMatch())
2361 {
2362 season = match.capturedView(1).toInt();
2363 }
2364 }
2365
2366 if (episode > 0)
2367 event.m_episode = episode;
2368
2369 if (season > 0)
2370 event.m_season = season;
2371
2372 //Feature:
2373 static const QRegularExpression dkFeatures { "Features:(.+)" };
2374 match = dkFeatures.match(event.m_description);
2375 if (match.hasMatch())
2376 {
2377 QString features = match.captured(1);
2378 event.m_description.remove(match.capturedStart(),
2379 match.capturedLength());
2380 // 16:9
2381 static const QRegularExpression dkWidescreen { " 16:9" };
2382 if (features.indexOf(dkWidescreen) != -1)
2383 event.m_videoProps |= VID_WIDESCREEN;
2384 // HDTV
2385 static const QRegularExpression dkHD { " HD" };
2386 if (features.indexOf(dkHD) != -1)
2387 event.m_videoProps |= VID_HDTV;
2388 // Dolby Digital surround
2389 static const QRegularExpression dkDolby { " 5:1" };
2390 if (features.indexOf(dkDolby) != -1)
2391 event.m_audioProps |= AUD_DOLBY;
2392 // surround
2393 static const QRegularExpression dkSurround { R"( \‍(\‍(S\)\))" };
2394 if (features.indexOf(dkSurround) != -1)
2395 event.m_audioProps |= AUD_SURROUND;
2396 // stereo
2397 static const QRegularExpression dkStereo { " S" };
2398 if (features.indexOf(dkStereo) != -1)
2399 event.m_audioProps |= AUD_STEREO;
2400 // (G)
2401 static const QRegularExpression dkReplay { " \\(G\\)" };
2402 if (features.indexOf(dkReplay) != -1)
2403 event.m_previouslyshown = true;
2404 // TTV
2405 static const QRegularExpression dkTxt { " TTV" };
2406 if (features.indexOf(dkTxt) != -1)
2407 event.m_subtitleType |= SUB_NORMAL;
2408 }
2409
2410 // Series and program id
2411 // programid is currently not transmitted
2412 // YouSee doesn't use a default authority but uses the first byte after
2413 // the / to indicate if the seriesid is global unique or unique on the
2414 // service id
2415 if (event.m_seriesId.length() >= 1 && event.m_seriesId[0] == '/')
2416 {
2417 QString newid;
2418 if (event.m_seriesId[1] == '1')
2419 {
2420 newid = QString("%1%2").arg(event.m_chanid).
2421 arg(event.m_seriesId.mid(2,8));
2422 }
2423 else
2424 {
2425 newid = event.m_seriesId.mid(2,8);
2426 }
2427 event.m_seriesId = newid;
2428 }
2429
2430 if (event.m_programId.length() >= 1 && event.m_programId[0] == '/')
2431 event.m_programId[0]='_';
2432
2433 // Add season and episode number to subtitle
2434 if (episode > 0)
2435 {
2436 event.m_subtitle = QString("%1 (%2").arg(event.m_subtitle).arg(episode);
2437 if (event.m_parttotal >0)
2438 event.m_subtitle = QString("%1:%2").arg(event.m_subtitle).
2439 arg(event.m_parttotal);
2440 if (season > 0)
2441 {
2442 event.m_season = season;
2443 event.m_episode = episode;
2444 event.m_syndicatedepisodenumber =
2445 QString("S%1E%2").arg(season).arg(episode);
2446 event.m_subtitle = QString("%1 Sæson %2").arg(event.m_subtitle).
2447 arg(season);
2448 }
2449 event.m_subtitle = QString("%1)").arg(event.m_subtitle);
2450 }
2451
2452 // Find actors and director in description
2453 static const QRegularExpression dkDirector { "(?:Instr.: |Instrukt.r: )(.+)$" };
2454 static const QRegularExpression dkPersonsSeparator { "(, )|(og )" };
2455 QStringList directors {};
2456 match = dkDirector.match(event.m_description);
2457 if (match.hasMatch())
2458 {
2459 QString tmpDirectorsString = match.captured(1);
2460 directors = tmpDirectorsString.split(dkPersonsSeparator, Qt::SkipEmptyParts);
2461 for (const auto & director : std::as_const(directors))
2462 {
2463 tmpDirectorsString = director.split(":").last().trimmed().
2464 remove(kDotAtEnd);
2465 if (tmpDirectorsString != "")
2466 event.AddPerson(DBPerson::kDirector, tmpDirectorsString);
2467 }
2468 //event.m_description.remove(match.capturedStart(), match.capturedLength());
2469 }
2470
2471 static const QRegularExpression dkActors { "(?:Medvirkende: |Medv\\.: )(.+)" };
2472 match = dkActors.match(event.m_description);
2473 if (match.hasMatch())
2474 {
2475 QString tmpActorsString = match.captured(1);
2476 const QStringList actors =
2477 tmpActorsString.split(dkPersonsSeparator, Qt::SkipEmptyParts);
2478 for (const auto & actor : std::as_const(actors))
2479 {
2480 tmpActorsString = actor.split(":").last().trimmed().remove(kDotAtEnd);
2481 if (!tmpActorsString.isEmpty() && !directors.contains(tmpActorsString))
2482 event.AddPerson(DBPerson::kActor, tmpActorsString);
2483 }
2484 //event.m_description.remove(match.capturedStart(), match.capturedLength());
2485 }
2486
2487 //find year
2488 static const QRegularExpression dkYear { " fra ([0-9]{4})[ \\.]" };
2489 match = dkYear.match(event.m_description);
2490 if (match.hasMatch())
2491 {
2492 bool ok = false;
2493 uint y = match.capturedView(1).toUInt(&ok);
2494 if (ok)
2495 event.m_originalairdate = QDate(y, 1, 1);
2496 }
2497}
2498
2503{
2504 LOG(VB_EIT, LOG_INFO, QString("Applying html strip to %1").arg(event.m_title));
2505 static const QRegularExpression html { "</?EM>", QRegularExpression::CaseInsensitiveOption };
2506 event.m_title.remove(html);
2507}
2508
2509// Moves the subtitle field into the description since it's just used
2510// as more description field. All the sort-out will happen in the description
2511// field. Also, sometimes the description is just a repeat of the title. If so,
2512// we remove it.
2514{
2515 if (event.m_title == event.m_description)
2516 {
2517 event.m_description = QString("");
2518 }
2519 if (!event.m_subtitle.isEmpty())
2520 {
2521 if (event.m_subtitle.trimmed().right(1) != ".'" )
2522 event.m_subtitle = event.m_subtitle.trimmed() + ".";
2523 event.m_description = event.m_subtitle.trimmed() + QString(" ") + event.m_description;
2524 event.m_subtitle = QString("");
2525 }
2526}
2527
2529{
2530 // Program ratings
2531 static const QRegularExpression grRating { R"(\[(K|Κ|8|12|16|18)\]\s*)",
2532 QRegularExpression::CaseInsensitiveOption };
2533 auto match = grRating.match(event.m_title);
2534 if (match.hasMatch())
2535 {
2536 EventRating prograting;
2537 prograting.m_system="GR"; prograting.m_rating = match.captured(1);
2538 event.m_ratings.push_back(prograting);
2539 event.m_title.remove(match.capturedStart(), match.capturedLength());
2540 event.m_title = event.m_title.trimmed();
2541 }
2542
2543 //Live show
2544 int position = event.m_title.indexOf("(Ζ)");
2545 if (position != -1)
2546 {
2547 event.m_title = event.m_title.replace("(Ζ)", "");
2548 event.m_description.prepend("Ζωντανή Μετάδοση. ");
2549 }
2550
2551 // Greek not previously Shown
2552 static const QRegularExpression grNotPreviouslyShown {
2553 R"(\W?(?:-\s*)*(?:\b[Α1]['΄η]?\s*(?:τηλεοπτικ[ηή]\s*)?(?:μετ[αά]δοση|προβολ[ηή]))\W?)",
2554 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption };
2555 match = grNotPreviouslyShown.match(event.m_title);
2556 if (match.hasMatch())
2557 {
2558 event.m_previouslyshown = false;
2559 event.m_title.remove(match.capturedStart(), match.capturedLength());
2560 }
2561
2562 // Greek Replay (Ε)
2563 // it might look redundant compared to previous check but at least it helps
2564 // remove the (Ε) From the title.
2565 static const QRegularExpression grReplay { R"(\‍([ΕE]\))" };
2566 match = grReplay.match(event.m_title);
2567 if (match.hasMatch())
2568 {
2569 event.m_previouslyshown = true;
2570 event.m_title.remove(match.capturedStart(), match.capturedLength());
2571 }
2572
2573 // Check for (HD) in the decription
2574 position = event.m_description.indexOf("(HD)");
2575 if (position != -1)
2576 {
2577 event.m_description = event.m_description.replace("(HD)", "");
2578 event.m_videoProps |= VID_HDTV;
2579 }
2580
2581 // Check for (Full HD) in the decription
2582 position = event.m_description.indexOf("(Full HD)");
2583 if (position != -1)
2584 {
2585 event.m_description = event.m_description.replace("(Full HD)", "");
2586 event.m_videoProps |= VID_HDTV;
2587 }
2588
2589 static const QRegularExpression grFixnofullstopActors { R"(\w\s(Παίζουν:|Πρωταγων))" };
2590 match = grFixnofullstopActors.match(event.m_description);
2591 if (match.hasMatch())
2592 event.m_description.insert(match.capturedStart() + 1, ".");
2593
2594 // If they forgot the "." at the end of the sentence before the actors/directors begin, let's insert it.
2595 static const QRegularExpression grFixnofullstopDirectors { R"(\w\s(Σκηνοθ[εέ]))" };
2596 match = grFixnofullstopDirectors.match(event.m_description);
2597 if (match.hasMatch())
2598 event.m_description.insert(match.capturedStart() + 1, ".");
2599
2600 // Find actors and director in description
2601 // I am looking for actors first and then for directors/presenters because
2602 // sometimes punctuation is missing and the "Παίζουν:" label is mistaken
2603 // for a director's/presenter's surname (directors/presenters are shown
2604 // before actors in the description field.). So removing the text after
2605 // adding the actors AND THEN looking for dir/pres helps to clear things up.
2606 static const QRegularExpression grActors { R"((?:[Ππ]α[ιί]ζουν:|[ΜMμ]ε τους:|Πρωταγωνιστο[υύ]ν:|Πρωταγωνιστε[ιί]:?)(?:\s+στο ρόλο(?: του| της)?\s(?:\w+\s[οη]\s))?([-\w\s']+(?:,[-\w\s']+)*)(?:κ\.[αά])?\W?)" };
2607 // cap(1) actors, just names
2608 static const QRegularExpression grPeopleSeparator { R"(([,-]\s+))" };
2609 match = grActors.match(event.m_description);
2610 if (match.hasMatch())
2611 {
2612 QString tmpActorsString = match.captured(1);
2613 const QStringList actors =
2614 tmpActorsString.split(grPeopleSeparator, Qt::SkipEmptyParts);
2615 for (const auto & actor : std::as_const(actors))
2616 {
2617 tmpActorsString = actor.split(":").last().trimmed().remove(kDotAtEnd);
2618 if (tmpActorsString != "")
2619 event.AddPerson(DBPerson::kActor, tmpActorsString);
2620 }
2621 event.m_description.remove(match.capturedStart(), match.capturedLength());
2622 }
2623
2624 // Director
2625 static const QRegularExpression grDirector { R"((?:Σκηνοθεσία: |Σκηνοθέτης: |Σκηνοθέτης - Επιμέλεια: )(\w+\s\w+\s?)(?:\W?))" };
2626 match = grDirector.match(event.m_description);
2627 if (match.hasMatch())
2628 {
2629 QString tmpDirectorsString = match.captured(1);
2630 const QStringList directors =
2631 tmpDirectorsString.split(grPeopleSeparator, Qt::SkipEmptyParts);
2632 for (const auto & director : std::as_const(directors))
2633 {
2634 tmpDirectorsString = director.split(":").last().trimmed().
2635 remove(kDotAtEnd);
2636 if (tmpDirectorsString != "")
2637 {
2638 event.AddPerson(DBPerson::kDirector, tmpDirectorsString);
2639 }
2640 }
2641 event.m_description.remove(match.capturedStart(), match.capturedLength());
2642 }
2643
2644 //Try to find presenter
2645 static const QRegularExpression grPres { R"((?:Παρουσ[ιί]αση:(?:\b)*|Παρουσι[αά]ζ(?:ουν|ει)(?::|\sο|\sη)|Παρουσι[αά]στ(?:[ηή]ς|ρια|ριες|[εέ]ς)(?::|\sο|\sη)|Με τ(?:ον |ην )(?:[\s|:|ο|η])*(?:\b)*)([-\w\s]+(?:,[-\w\s]+)*)\W?)",
2646 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption };
2647 match = grPres.match(event.m_description);
2648 if (match.hasMatch())
2649 {
2650 QString tmpPresentersString = match.captured(1);
2651 const QStringList presenters =
2652 tmpPresentersString.split(grPeopleSeparator, Qt::SkipEmptyParts);
2653 for (const auto & presenter : std::as_const(presenters))
2654 {
2655 tmpPresentersString = presenter.split(":").last().trimmed().
2656 remove(kDotAtEnd);
2657 if (tmpPresentersString != "")
2658 {
2659 event.AddPerson(DBPerson::kPresenter, tmpPresentersString);
2660 }
2661 }
2662 event.m_description.remove(match.capturedStart(), match.capturedLength());
2663 }
2664
2665 //find year e.g Παραγωγής 1966 ή ΝΤΟΚΙΜΑΝΤΕΡ - 1998 Κατάλληλο για όλους
2666 // Used in Private channels (not 'secret', just not owned by Government!)
2667 static const QRegularExpression grYear { R"(\W?(?:\s?παραγωγ[ηή]ς|\s?-|,)\s*([1-2][0-9]{3})(?:-\d{1,4})?)",
2668 QRegularExpression::CaseInsensitiveOption };
2669 match = grYear.match(event.m_description);
2670 if (match.hasMatch())
2671 {
2672 bool ok = false;
2673 uint y = match.capturedView(1).toUInt(&ok);
2674 if (ok)
2675 {
2676 event.m_originalairdate = QDate(y, 1, 1);
2677 event.m_description.remove(match.capturedStart(), match.capturedLength());
2678 }
2679 }
2680 // Remove " ."
2681 event.m_description = event.m_description.replace(" .",".").trimmed();
2682
2683 //find country of origin and remove it from description.
2684 static const QRegularExpression grCountry {
2685 R"((?:\W|\b)(?:(ελλην|τουρκ|αμερικ[αά]ν|γαλλ|αγγλ|βρεττ?αν|γερμαν|ρωσσ?|ιταλ|ελβετ|σουηδ|ισπαν|πορτογαλ|μεξικ[αά]ν|κιν[εέ]ζικ|ιαπων|καναδ|βραζιλι[αά]ν)(ικ[ηή][ςσ])))",
2686 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption };
2687 match = grCountry.match(event.m_description);
2688 if (match.hasMatch())
2689 event.m_description.remove(match.capturedStart(), match.capturedLength());
2690
2691 // Work out the season and episode numbers (if any)
2692 // Matching pattern "Επεισ[όο]διο:?|Επ 3 από 14|3/14" etc
2693 bool series = false;
2694 static const QRegularExpression grSeason {
2695 R"((?:\W-?)*(?:\‍(-\s*)?\b(([Α-Ω|A|B|E|Z|H|I|K|M|N]{1,2})(?:'|΄)?|(\d{1,2})(?:ος|ου|oς|os)?)(?:\s*[ΚκKk][υύ]κλο(?:[σς]|υ))\s?)",
2696 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption };
2697 // cap(2) is the season for ΑΒΓΔ
2698 // cap(3) is the season for 1234
2699 match = grSeason.match(event.m_title);
2700 if (match.hasMatch())
2701 {
2702 if (!match.capturedView(2).isEmpty()) // we found a letter representing a number
2703 {
2704 //sometimes Nat. TV writes numbers as letters, i.e Α=1, Β=2, Γ=3, etc
2705 //must convert them to numbers.
2706 int tmpinteger = match.capturedView(2).toUInt();
2707 if (tmpinteger < 1)
2708 {
2709 if (match.captured(2) == "ΣΤ") // 6, don't ask!
2710 {
2711 event.m_season = 6;
2712 }
2713 else
2714 {
2715 static const QString LettToNumber = "0ΑΒΓΔΕ6ΖΗΘΙΚΛΜΝ";
2716 tmpinteger = LettToNumber.indexOf(match.capturedView(2));
2717 if (tmpinteger != -1)
2718 {
2719 event.m_season = tmpinteger;
2720 }
2721 else
2722 //sometimes they use english letters instead of greek. Compensating:
2723 {
2724 static const QString LettToNumber2 = "0ABΓΔE6ZHΘIKΛMN";
2725 tmpinteger = LettToNumber2.indexOf(match.capturedView(2));
2726 if (tmpinteger != -1)
2727 event.m_season = tmpinteger;
2728 }
2729 }
2730 }
2731 }
2732 else if (!match.capturedView(3).isEmpty()) //number
2733 {
2734 event.m_season = match.capturedView(3).toUInt();
2735 }
2736 series = true;
2737 event.m_title.remove(match.capturedStart(), match.capturedLength());
2738 }
2739
2740 // I have to search separately for season in title and description because it wouldn't work when in both.
2741 match = grSeason.match(event.m_description);
2742 if (match.hasMatch())
2743 {
2744 if (!match.capturedView(2).isEmpty()) // we found a letter representing a number
2745 {
2746 //sometimes Nat. TV writes numbers as letters, i.e Α=1, Β=2, Γ=3, etc
2747 //must convert them to numbers.
2748 int tmpinteger = match.capturedView(2).toUInt();
2749 if (tmpinteger < 1)
2750 {
2751 if (match.captured(2) == "ΣΤ") // 6, don't ask!
2752 {
2753 event.m_season = 6;
2754 }
2755 else
2756 {
2757 static const QString LettToNumber = "0ΑΒΓΔΕ6ΖΗΘΙΚΛΜΝ";
2758 tmpinteger = LettToNumber.indexOf(match.capturedView(2));
2759 if (tmpinteger != -1)
2760 event.m_season = tmpinteger;
2761 }
2762 }
2763 }
2764 else if (!match.capturedView(3).isEmpty()) //number
2765 {
2766 event.m_season = match.capturedView(3).toUInt();
2767 }
2768 series = true;
2769 event.m_description.remove(match.capturedStart(), match.capturedLength());
2770 }
2771
2772
2773 // If Season is in Roman Numerals (I,II,etc)
2774 static const QRegularExpression grSeasonAsRomanNumerals { ",\\s*([MDCLXVIΙΧ]+)$",
2775 QRegularExpression::CaseInsensitiveOption };
2776 match = grSeasonAsRomanNumerals.match(event.m_title);
2777 auto match2 = grSeasonAsRomanNumerals.match(event.m_description);
2778 if (match.hasMatch())
2779 {
2780 if (!match.capturedView(1).isEmpty()) //number
2781 event.m_season = parseRoman(match.captured(1).toUpper());
2782 series = true;
2783 event.m_title.remove(match.capturedStart(), match.capturedLength());
2784 event.m_title = event.m_title.trimmed();
2785 if (event.m_title.right(1) == ",")
2786 event.m_title.chop(1);
2787 }
2788 else if (match2.hasMatch())
2789 {
2790 if (!match2.capturedView(1).isEmpty()) //number
2791 event.m_season = parseRoman(match2.captured(1).toUpper());
2792 series = true;
2793 event.m_description.remove(match2.capturedStart(), match2.capturedLength());
2794 event.m_description = event.m_description.trimmed();
2795 if (event.m_description.right(1) == ",")
2796 event.m_description.chop(1);
2797 }
2798
2799 static const QRegularExpression grlongEp { R"(\b(?:Επ.|επεισ[οό]διο:?)\s*(\d+)\W?)",
2800 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption };
2801 // cap(1) is the Episode No.
2802 match = grlongEp.match(event.m_title);
2803 match2 = grlongEp.match(event.m_description);
2804 if (match.hasMatch() || match2.hasMatch())
2805 {
2806 if (!match.capturedView(1).isEmpty())
2807 {
2808 event.m_episode = match.capturedView(1).toUInt();
2809 series = true;
2810 event.m_title.remove(match.capturedStart(), match.capturedLength());
2811 }
2812 else if (!match2.capturedView(1).isEmpty())
2813 {
2814 event.m_episode = match2.capturedView(1).toUInt();
2815 series = true;
2816 event.m_description.remove(match2.capturedStart(), match2.capturedLength());
2817 }
2818 // Sometimes description omits Season if it's 1. We fix this
2819 if (0 == event.m_season)
2820 event.m_season = 1;
2821 }
2822
2823 // Sometimes, especially on greek national tv, they include comments in the
2824 // title, e.g "connection to ert1", "ert archives".
2825 // Because they obscure the real title, I'll isolate and remove them.
2826
2827 static const QRegularExpression grCommentsinTitle { R"(\‍(([Α-Ωα-ω\s\d-]+)\)(?:\s*$)*)" };
2828 // cap1 = real title
2829 // cap0 = real title in parentheses.
2830 match = grCommentsinTitle.match(event.m_title);
2831 if (match.hasMatch()) // found in title instead
2832 event.m_title.remove(match.capturedStart(), match.capturedLength());
2833
2834 // Sometimes the real (mostly English) title of a movie or series is
2835 // enclosed in parentheses in the event title, subtitle or description.
2836 // Since the subtitle has been moved to the description field by
2837 // EITFixUp::FixGreekSubtitle, I will search for it only in the description.
2838 // It will replace the translated one to get better chances of metadata
2839 // retrieval. The old title will be moved in the description.
2840 static const QRegularExpression grRealTitleInDescription { R"(^\‍(([A-Za-z\s\d-]+)\)\s*)" };
2841 // cap1 = real title
2842 // cap0 = real title in parentheses.
2843 match = grRealTitleInDescription.match(event.m_description);
2844 if (match.hasMatch())
2845 {
2846 event.m_description.remove(0, match.capturedLength());
2847 if (match.captured(0) != event.m_title.trimmed())
2848 {
2849 event.m_description = "(" + event.m_title.trimmed() + "). " + event.m_description;
2850 }
2851 event.m_title = match.captured(1);
2852 // Remove the real title from the description
2853 }
2854 else // search in title
2855 {
2856 static const QRegularExpression grRealTitleInTitle { R"(\‍(([A-Za-z\s\d-]+)\)(?:\s*$)?)" };
2857 // cap1 = real title
2858 // cap0 = real title in parentheses.
2859 match = grRealTitleInTitle.match(event.m_title);
2860 if (match.hasMatch()) // found in title instead
2861 {
2862 event.m_title.remove(match.capturedStart(), match.capturedLength());
2863 QString tmpTranslTitle = event.m_title;
2864 //QString tmpTranslTitle = event.m_title.replace(tmptitle.cap(0),"");
2865 event.m_title = match.captured(1);
2866 event.m_description = "(" + tmpTranslTitle.trimmed() + "). " + event.m_description;
2867 }
2868 }
2869
2870 // Description field: "^Episode: Lion in the cage. (Description follows)"
2871 static const QRegularExpression grEpisodeAsSubtitle { R"(^Επεισ[οό]διο:\s?([\w\s\-,']+)\.\s?)" };
2872 match = grEpisodeAsSubtitle.match(event.m_description);
2873 if (match.hasMatch())
2874 {
2875 event.m_subtitle = match.captured(1).trimmed();
2876 event.m_description.remove(match.capturedStart(), match.capturedLength());
2877 }
2878 static const QRegularExpression grMovie { R"(\bταιν[ιί]α\b)",
2879 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption };
2880 bool isMovie = (event.m_description.indexOf(grMovie) !=-1) ;
2881 if (isMovie)
2882 event.m_categoryType = ProgramInfo::kCategoryMovie;
2883 else if (series)
2884 event.m_categoryType = ProgramInfo::kCategorySeries;
2885 // clear double commas.
2886 event.m_description.replace(",,", ",");
2887
2888// να σβήσω τα κομμάτια που περισσεύουν από την περιγραφή πχ παραγωγής χχχχ
2889}
2890
2892{
2893 struct grCategoryEntry {
2894 QRegularExpression expr;
2895 QString category;
2896 };
2897 static const QRegularExpression grCategFood { "\\W?(?:εκπομπ[ηή]\\W)?(Γαστρονομ[ιί]α[σς]?|μαγειρικ[ηή][σς]?|chef|συνταγ[εέηή]|διατροφ|wine|μ[αά]γειρα[σς]?)\\W?",
2898 QRegularExpression::CaseInsensitiveOption };
2899 static const QRegularExpression grCategDrama { "\\W?(κοινωνικ[ηήό]|δραματικ[ηή]|δρ[αά]μα)\\W(?:(?:εκπομπ[ηή]|σειρ[αά]|ταιν[ιί]α)\\W)?",
2900 QRegularExpression::CaseInsensitiveOption};
2901 static const QRegularExpression grCategComedy { "\\W?(κωμικ[ηήοό]|χιουμοριστικ[ηήοό]|κωμωδ[ιί]α)\\W(?:(?:εκπομπ[ηή]|σειρ[αά]|ταιν[ιί]α)\\W)?",
2902 QRegularExpression::CaseInsensitiveOption};
2903 static const QRegularExpression grCategChildren { "\\W?(παιδικ[ηήοό]|κινο[υύ]μ[εέ]ν(ων|α)\\sσχ[εέ]δ[ιί](ων|α))\\W(?:(?:εκπομπ[ηή]|σειρ[αά]|ταιν[ιί]α)\\W)?",
2904 QRegularExpression::CaseInsensitiveOption};
2905 static const QRegularExpression grCategMystery { "(?:(?:εκπομπ[ηή]|σειρ[αά]|ταιν[ιί]α)\\W)?\\W?(μυστηρ[ιί]ου)\\W?",
2906 QRegularExpression::CaseInsensitiveOption};
2907 static const QRegularExpression grCategFantasy { "(?:(?:εκπομπ[ηή]|σειρ[αά]|ταιν[ιί]α)\\W)?\\W?(φαντασ[ιί]ας)\\W?",
2908 QRegularExpression::CaseInsensitiveOption};
2909 static const QRegularExpression grCategHistory { "\\W?(ιστορικ[ηήοό])\\W?(?:(?:εκπομπ[ηή]|σειρ[αά]|ταιν[ιί]α)\\W)?",
2910 QRegularExpression::CaseInsensitiveOption};
2911 static const QRegularExpression grCategTeleMag { "\\W?(ενημερωτικ[ηή]|ψυχαγωγικ[ηή]|τηλεπεριοδικ[οό]|μαγκαζ[ιί]νο)\\W?(?:(?:εκπομπ[ηή]|σειρ[αά]|ταιν[ιί]α)\\W)?",
2912 QRegularExpression::CaseInsensitiveOption};
2913 static const QRegularExpression grCategTeleShop { "\\W?(οδηγ[οό][σς]?\\sαγορ[ωώ]ν|τηλεπ[ωώ]λ[ηή]σ|τηλεαγορ|τηλεμ[αά]ρκετ|telemarket)\\W?(?:(?:εκπομπ[ηή]|σειρ[αά]|ταιν[ιί]α)\\W)?",
2914 QRegularExpression::CaseInsensitiveOption};
2915 static const QRegularExpression grCategGameShow { "\\W?(τηλεπαιχν[ιί]δι|quiz)\\W?",
2916 QRegularExpression::CaseInsensitiveOption};
2917 static const QRegularExpression grCategDocumentary { "\\W?(ντοκ[ιυ]μαντ[εέ]ρ)\\W?",
2918 QRegularExpression::CaseInsensitiveOption};
2919 static const QRegularExpression grCategBiography { "\\W?(βιογραφ[ιί]α|βιογραφικ[οό][σς]?)\\W?",
2920 QRegularExpression::CaseInsensitiveOption};
2921 static const QRegularExpression grCategNews { "\\W?(δελτ[ιί]ο\\W?|ειδ[ηή]σε(ι[σς]|ων))\\W?",
2922 QRegularExpression::CaseInsensitiveOption};
2923 static const QRegularExpression grCategSports { "\\W?(champion|αθλητικ[αάοόηή]|πρωτ[αά]θλημα|ποδ[οό]σφαιρο(ου)?|κολ[υύ]μβηση|πατιν[αά]ζ|formula|μπ[αά]σκετ|β[οό]λε[ιϊ])\\W?",
2924 QRegularExpression::CaseInsensitiveOption};
2925 static const QRegularExpression grCategMusic { "\\W?(μουσικ[οόηή]|eurovision|τραγο[υύ]δι)\\W?",
2926 QRegularExpression::CaseInsensitiveOption};
2927 static const QRegularExpression grCategReality { "\\W?(ρι[αά]λιτι|reality)\\W?",
2928 QRegularExpression::CaseInsensitiveOption};
2929 static const QRegularExpression grCategReligion { "\\W?(θρησκε[ιί]α|θρησκευτικ|να[οό][σς]?|θε[ιί]α λειτουργ[ιί]α)\\W?",
2930 QRegularExpression::CaseInsensitiveOption};
2931 static const QRegularExpression grCategCulture { "\\W?(τ[εέ]χν(η|ε[σς])|πολιτισμ)\\W?",
2932 QRegularExpression::CaseInsensitiveOption};
2933 static const QRegularExpression grCategNature { "\\W?(φ[υύ]ση|περιβ[αά]λλο|κατασκευ|επιστ[ηή]μ(?!ονικ[ηή]ς φαντασ[ιί]ας))\\W?",
2934 QRegularExpression::CaseInsensitiveOption};
2935 static const QRegularExpression grCategSciFi { "\\W?(επιστ(.|ημονικ[ηή]ς)\\s?φαντασ[ιί]ας)\\W?",
2936 QRegularExpression::CaseInsensitiveOption};
2937 static const QRegularExpression grCategHealth { "\\W?(υγε[ιί]α|υγειιν|ιατρικ|διατροφ)\\W?",
2938 QRegularExpression::CaseInsensitiveOption};
2939 static const QRegularExpression grCategSpecial { "\\W?(αφι[εέ]ρωμα)\\W?",
2940 QRegularExpression::CaseInsensitiveOption};
2941 static const QList<grCategoryEntry> grCategoryDescData = {
2942 { .expr=grCategComedy, .category="Κωμωδία" },
2943 { .expr=grCategTeleMag, .category="Τηλεπεριοδικό" },
2944 { .expr=grCategNature, .category="Επιστήμη/Φύση" },
2945 { .expr=grCategHealth, .category="Υγεία" },
2946 { .expr=grCategReality, .category="Ριάλιτι" },
2947 { .expr=grCategDrama, .category="Κοινωνικό" },
2948 { .expr=grCategChildren, .category="Παιδικό" },
2949 { .expr=grCategSciFi, .category="Επιστ.Φαντασίας" },
2950 { .expr=grCategMystery, .category="Μυστηρίου" },
2951 { .expr=grCategFantasy, .category="Φαντασίας" },
2952 { .expr=grCategHistory, .category="Ιστορικό" },
2953 { .expr=grCategTeleShop, .category="Τηλεπωλήσεις" },
2954 { .expr=grCategFood, .category="Γαστρονομία" },
2955 { .expr=grCategGameShow, .category="Τηλεπαιχνίδι" },
2956 { .expr=grCategBiography, .category="Βιογραφία" },
2957 { .expr=grCategSports, .category="Αθλητικά" },
2958 { .expr=grCategMusic, .category="Μουσική" },
2959 { .expr=grCategDocumentary, .category="Ντοκιμαντέρ" },
2960 { .expr=grCategReligion, .category="Θρησκεία" },
2961 { .expr=grCategCulture, .category="Τέχνες/Πολιτισμός" },
2962 { .expr=grCategSpecial, .category="Αφιέρωμα" },
2963 };
2964 static const QList<grCategoryEntry> grCategoryTitleData = {
2965 { .expr=grCategTeleShop, .category="Τηλεπωλήσεις" },
2966 { .expr=grCategGameShow, .category="Τηλεπαιχνίδι" },
2967 { .expr=grCategMusic, .category="Μουσική" },
2968 { .expr=grCategNews, .category="Ειδήσεις" },
2969 };
2970
2971 // Handle special cases
2972 if ((event.m_description.indexOf(grCategFantasy) != -1)
2973 && (event.m_description.indexOf(grCategMystery) != -1))
2974 {
2975 event.m_category = "Φαντασίας/Μυστηρίου";
2976 return;
2977 }
2978
2979 // Find categories in the description
2980 for (const auto& [expression, category] : grCategoryDescData)
2981 {
2982 if (event.m_description.indexOf(expression) != -1) {
2983 event.m_category = category;
2984 return;
2985 }
2986 }
2987
2988 // Find categories in the title
2989 for (const auto& [expression, category] : grCategoryTitleData)
2990 {
2991 if (event.m_title.indexOf(expression) != -1) {
2992 event.m_category = category;
2993 return;
2994 }
2995 }
2996}
2997
2999{
3000 // TODO handle scraping the category and category_type from localized text in the short/long description
3001 // TODO remove short description (stored as episode title) which is just the beginning of the long description (actual description)
3002
3003 // drop the short description if its copy the start of the long description
3004 if (event.m_description.startsWith (event.m_subtitle))
3005 {
3006 event.m_subtitle = "";
3007 }
3008
3009 // handle cast and crew in items in the DVB Extended Event Descriptor
3010 // remove handled items from the map, so the left overs can be reported
3011 auto i = event.m_items.begin();
3012 while (i != event.m_items.end())
3013 {
3014 /* Possible TODO: if EIT inlcude the priority and/or character
3015 * names for the actors, include them in AddPerson call. */
3016 if ((QString::compare (i.key(), "Role Player") == 0) ||
3017 (QString::compare (i.key(), "Performing Artist") == 0))
3018 {
3019 event.AddPerson (DBPerson::kActor, i.value());
3020 i = event.m_items.erase (i);
3021 }
3022 else if (QString::compare (i.key(), "Director") == 0)
3023 {
3024 event.AddPerson (DBPerson::kDirector, i.value());
3025 i = event.m_items.erase (i);
3026 }
3027 else if (QString::compare (i.key(), "Commentary or Commentator") == 0)
3028 {
3029 event.AddPerson (DBPerson::kCommentator, i.value());
3030 i = event.m_items.erase (i);
3031 }
3032 else if (QString::compare (i.key(), "Presenter") == 0)
3033 {
3034 event.AddPerson (DBPerson::kPresenter, i.value());
3035 i = event.m_items.erase (i);
3036 }
3037 else if (QString::compare (i.key(), "Producer") == 0)
3038 {
3039 event.AddPerson (DBPerson::kProducer, i.value());
3040 i = event.m_items.erase (i);
3041 }
3042 else if (QString::compare (i.key(), "Scriptwriter") == 0)
3043 {
3044 event.AddPerson (DBPerson::kWriter, i.value());
3045 i = event.m_items.erase (i);
3046 }
3047 else
3048 {
3049 ++i;
3050 }
3051 }
3052
3053 // handle star rating in the description
3054 static const QRegularExpression unitymediaImdbrating { R"(\s*IMDb Rating: (\d\.\d)\s?/10$)" };
3055 auto match = unitymediaImdbrating.match(event.m_description);
3056 if (match.hasMatch())
3057 {
3058 float stars = match.captured(1).toFloat();
3059 event.m_stars = stars / 10.0F;
3060 event.m_description.remove(match.capturedStart(0),
3061 match.capturedLength(0));
3062 }
3063}
static QString GetDefaultAuthority(uint chanid)
Returns the DVB default authority for the chanid given.
uint32_t m_chanid
Definition: programdata.h:221
FixupValue m_fixup
Definition: programdata.h:222
QMultiMap< QString, QString > m_items
Definition: programdata.h:223
QString m_title
Definition: programdata.h:148
QString m_seriesId
Definition: programdata.h:165
uint m_totalepisodes
Definition: programdata.h:174
uint16_t m_partnumber
Definition: programdata.h:157
QString m_programId
Definition: programdata.h:166
ProgramInfo::CategoryType m_categoryType
Definition: programdata.h:164
QDateTime m_starttime
Definition: programdata.h:152
QString m_category
Definition: programdata.h:151
uint16_t m_airdate
movie year / production year
Definition: programdata.h:154
uint m_season
Definition: programdata.h:172
QString m_subtitle
Definition: programdata.h:149
uint16_t m_parttotal
Definition: programdata.h:158
uint m_episode
Definition: programdata.h:173
QDateTime m_endtime
Definition: programdata.h:153
QString m_description
Definition: programdata.h:150
@ kCommentator
Definition: programdata.h:40
@ kPresenter
Definition: programdata.h:39
static void FixUK(DBEventEIT &event)
Use this in the United Kingdom to standardize DVB-T guide.
Definition: eitfixup.cpp:649
static void FixAUFreeview(DBEventEIT &event)
Use this to standardize DVB-T guide in Australia.
Definition: eitfixup.cpp:1418
static const uint kSubtitleMaxLen
Definition: eitfixup.h:15
static void FixStripHTML(DBEventEIT &event)
Use this to clean HTML Tags from EIT Data.
Definition: eitfixup.cpp:2502
static void FixNO(DBEventEIT &event)
Use this to clean DVB-S guide in Norway.
Definition: eitfixup.cpp:2208
static void FixAUNine(DBEventEIT &event)
Use this to standardize DVB-T guide in Australia.
Definition: eitfixup.cpp:1343
static void FixGreekSubtitle(DBEventEIT &event)
Definition: eitfixup.cpp:2513
static void FixAUStar(DBEventEIT &event)
Use this to standardize DVB-S guide in Australia.
Definition: eitfixup.cpp:1301
static void SetUKSubtitle(DBEventEIT &event)
Use this in the United Kingdom to standardize DVB-T guide.
Definition: eitfixup.cpp:512
static const uint kDotToTitle
Definition: eitfixup.h:19
static const uint kMaxToTitle
Definition: eitfixup.h:17
static void FixComHem(DBEventEIT &event, bool process_subtitle)
Use this to standardize ComHem DVB-C service in Sweden.
Definition: eitfixup.cpp:1046
static void FixRTL(DBEventEIT &event)
Use this to standardise the RTL group guide in Germany.
Definition: eitfixup.cpp:1615
static void FixMCA(DBEventEIT &event)
Use this to standardise the MultiChoice Africa DVB-S guide.
Definition: eitfixup.cpp:1476
static void FixATV(DBEventEIT &event)
Use this to standardise the ATV/ATV2 guide in Germany.
Definition: eitfixup.cpp:1869
static const int kMinMovieDuration
Definition: eitfixup.h:25
static void FixAUSeven(DBEventEIT &event)
Use this to standardize DVB-T guide in Australia.
Definition: eitfixup.cpp:1376
static void FixDisneyChannel(DBEventEIT &event)
Use this to standardise the Disney Channel guide in Germany.
Definition: eitfixup.cpp:1842
static void FixAUDescription(DBEventEIT &event)
Use this to standardize DVB-T guide in Australia.
Definition: eitfixup.cpp:1317
static void FixDK(DBEventEIT &event)
Use this to clean YouSee's DVB-C guide in Denmark.
Definition: eitfixup.cpp:2300
static const uint kMaxDotToColon
Definition: eitfixup.h:23
static void Fix(DBEventEIT &event)
Definition: eitfixup.cpp:46
static void FixPBS(DBEventEIT &event)
Use this to standardize PBS ATSC guide in the USA.
Definition: eitfixup.cpp:1031
static const uint kMaxQuestionExclamation
Definition: eitfixup.h:21
static void FixPRO7(DBEventEIT &event)
Use this to standardise the PRO7/Sat1 group guide in Germany.
Definition: eitfixup.cpp:1756
static QString AddDVBEITAuthority(uint chanid, const QString &id)
This adds a DVB EIT default authority to series id or program id if one exists in the DB for that cha...
Definition: eitfixup.cpp:202
static void FixCategory(DBEventEIT &event)
Definition: eitfixup.cpp:2194
static void FixGreekCategories(DBEventEIT &event)
Definition: eitfixup.cpp:2891
static int parseRoman(QString roman)
Definition: eitfixup.cpp:30
static void FixFI(DBEventEIT &event)
Use this to clean DVB-T guide in Finland.
Definition: eitfixup.cpp:1879
static void FixPremiere(DBEventEIT &event)
Use this to standardize DVB-C guide in Germany for the providers Kabel Deutschland and Premiere.
Definition: eitfixup.cpp:1931
static void FixNRK_DVBT(DBEventEIT &event)
Use this to clean DVB-T guide in Norway (NRK)
Definition: eitfixup.cpp:2238
static void FixBellExpressVu(DBEventEIT &event)
Use this for the Canadian BellExpressVu to standardize DVB-S guide.
Definition: eitfixup.cpp:229
static void FixGreekEIT(DBEventEIT &event)
Definition: eitfixup.cpp:2528
@ kFixNRK_DVBT
Definition: eitfixup.h:48
@ kFixPremiere
Definition: eitfixup.h:43
@ kFixMCA
Definition: eitfixup.h:40
@ kFixP7S1
Definition: eitfixup.h:55
@ kFixHTML
Definition: eitfixup.h:56
@ kFixPBS
Definition: eitfixup.h:36
@ kFixFI
Definition: eitfixup.h:42
@ kFixGenericDVB
Definition: eitfixup.h:33
@ kFixUK
Definition: eitfixup.h:35
@ kFixGreekEIT
Definition: eitfixup.h:69
@ kFixAUSeven
Definition: eitfixup.h:54
@ kFixSubtitle
Definition: eitfixup.h:38
@ kFixDisneyChannel
Definition: eitfixup.h:59
@ kFixComHem
Definition: eitfixup.h:37
@ kFixAUDescription
Definition: eitfixup.h:52
@ kFixAUNine
Definition: eitfixup.h:53
@ kFixDK
Definition: eitfixup.h:50
@ kFixNL
Definition: eitfixup.h:45
@ kFixGreekSubtitle
Definition: eitfixup.h:68
@ kFixDish
Definition: eitfixup.h:49
@ kFixAUFreeview
Definition: eitfixup.h:51
@ kFixGreekCategories
Definition: eitfixup.h:70
@ kFixATV
Definition: eitfixup.h:58
@ kFixAUStar
Definition: eitfixup.h:39
@ kFixBell
Definition: eitfixup.h:34
@ kFixRTL
Definition: eitfixup.h:41
@ kFixUnitymedia
Definition: eitfixup.h:57
@ kFixNO
Definition: eitfixup.h:47
@ kFixCategory
Definition: eitfixup.h:46
@ kFixHDTV
Definition: eitfixup.h:44
static void FixUnitymedia(DBEventEIT &event)
Definition: eitfixup.cpp:2998
static void FixNL(DBEventEIT &event)
Use this to standardize @Home DVB-C guide in the Netherlands.
Definition: eitfixup.cpp:2021
QString m_system
Definition: programdata.h:78
QString m_rating
Definition: programdata.h:79
unsigned int uint
Definition: compat.h:60
QString dish_theme_type_to_string(uint theme_type)
static const QMap< QChar, quint16 > r2v
Definition: eitfixup.cpp:24
static const QRegularExpression kDotAtEnd
Definition: eitfixup.cpp:22
static const QMap< QString, NLMapResult > categoryTrans
Definition: eitfixup.cpp:1998
static const QMap< QString, DBPerson::Role > deCrewTitle
Definition: eitfixup.cpp:1747
static const QRegularExpression kStereo
Definition: eitfixup.cpp:20
static const QRegularExpression kUKSpaceColonStart
Definition: eitfixup.cpp:21
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
def rating(profile, smoonURL, gate)
Definition: scan.py:36
ProgramInfo::CategoryType type
Definition: eitfixup.cpp:1996
QString name
Definition: eitfixup.cpp:1995