MythTV master
programinfo.cpp
Go to the documentation of this file.
1// -*- Mode: c++ -*-
2
3// POSIX headers
4#include <unistd.h> // for getpid()
5
6// C++ headers
7#include <algorithm>
8#include <mutex>
9
10// Qt headers
11#include <QMap>
12#include <QUrl>
13#include <QFile>
14#include <QFileInfo>
15#include <QDir>
16#include <QTimeZone>
17
18// MythTV headers
19#include "libmythbase/compat.h"
22#include "libmythbase/mythdb.h"
30
31#include "programinfo.h"
32#include "programinfoupdater.h"
33
34#define LOC QString("ProgramInfo(%1): ").arg(GetBasename())
35
36//#define DEBUG_IN_USE
37
43
44static constexpr uint kInvalidDateTime { UINT_MAX };
45static constexpr int64_t kLastUpdateOffset { 61LL * 60 };
46
47#define DEFINE_FLAGS_NAMES
48#include "programtypeflags.h"
49#undef DEFINE_FLAGS_NAMES
50
52 "SELECT r.title, r.subtitle, r.description, "// 0-2
53 " r.season, r.episode, r.category, "// 3-5
54 " r.chanid, c.channum, c.callsign, "// 6-8
55 " c.name, c.outputfilters,r.recgroup, "// 9-11
56 " r.playgroup, r.storagegroup, r.basename, "//12-14
57 " r.hostname, r.recpriority, r.seriesid, "//15-17
58 " r.programid, r.inetref, r.filesize, "//18-20
59 " r.progstart, r.progend, r.stars, "//21-23
60 " r.starttime, r.endtime, p.airdate+0, "//24-26
61 " r.originalairdate, r.lastmodified, r.recordid, "//27-29
62 " c.commmethod, r.commflagged, r.previouslyshown, "//30-32
63 " r.transcoder, r.transcoded, r.deletepending, "//33-35
64 " r.preserve, r.cutlist, r.autoexpire, "//36-38
65 " r.editing, r.bookmark, r.watched, "//39-41
66 " p.audioprop+0, p.videoprop+0, p.subtitletypes+0, "//42-44
67 " r.findid, rec.dupin, rec.dupmethod, "//45-47
68 " p.syndicatedepisodenumber, p.partnumber, p.parttotal, "//48-50
69 " p.season, p.episode, p.totalepisodes, "//51-53
70 " p.category_type, r.recordedid, r.inputname, "//54-56
71 " r.bookmarkupdate, r.lastplay "//57-58
72 "FROM recorded AS r "
73 "LEFT JOIN channel AS c "
74 "ON (r.chanid = c.chanid) "
75 "LEFT JOIN recordedprogram AS p "
76 "ON (r.chanid = p.chanid AND "
77 " r.progstart = p.starttime) "
78 "LEFT JOIN record AS rec "
79 "ON (r.recordid = rec.recordid) ";
80
81static void set_flag(uint32_t &flags, int flag_to_set, bool is_set)
82{
83 flags &= ~flag_to_set;
84 if (is_set)
85 flags |= flag_to_set;
86}
87
88static QString determineURLType(const QString& url)
89{
90 QString result = url;
91
92 if (!url.startsWith("dvd:") && !url.startsWith("bd:"))
93 {
94 if(url.endsWith(".img", Qt::CaseInsensitive) ||
95 url.endsWith(".iso", Qt::CaseInsensitive))
96 {
97 switch (MythCDROM::inspectImage(url))
98 {
100 result = "bd:" + url;
101 break;
102
103 case MythCDROM::kDVD:
104 result = "dvd:" + url;
105 break;
106
108 // Quiet compiler warning.
109 break;
110 }
111 }
112 else
113 {
114 if (QDir(url + "/BDMV").exists())
115 result = "bd:" + url;
116 else if (QDir(url + "/VIDEO_TS").exists())
117 result = "dvd:" + url;
118 }
119 }
120
121 return result;
122}
123
124static const std::array<const QString,ProgramInfo::kNumCatTypes> kCatName
125{ "", "movie", "series", "sports", "tvshow" };
126
128{
129 if ((category_type > ProgramInfo::kCategoryNone) &&
130 (category_type < kCatName.size()))
131 return kCatName[category_type];
132
133 return "";
134}
135
137{
138 for (size_t i = 1; i < kCatName.size(); i++)
139 if (category_type == kCatName[i])
140 return (ProgramInfo::CategoryType) i;
142}
143
148 m_title(other.m_title),
149 m_sortTitle(other.m_sortTitle),
150 m_subtitle(other.m_subtitle),
151 m_sortSubtitle(other.m_sortSubtitle),
152 m_description(other.m_description),
153 m_season(other.m_season),
154 m_episode(other.m_episode),
155 m_totalEpisodes(other.m_totalEpisodes),
156 m_syndicatedEpisode(other.m_syndicatedEpisode),
157 m_category(other.m_category),
158 m_director(other.m_director),
159
160 m_recPriority(other.m_recPriority),
161
162 m_chanId(other.m_chanId),
163 m_chanStr(other.m_chanStr),
164 m_chanSign(other.m_chanSign),
165 m_chanName(other.m_chanName),
166 m_chanPlaybackFilters(other.m_chanPlaybackFilters),
167
168 m_recGroup(other.m_recGroup),
169 m_playGroup(other.m_playGroup),
170
171 m_pathname(other.m_pathname),
172
173 m_hostname(other.m_hostname),
174 m_storageGroup(other.m_storageGroup),
175
176 m_seriesId(other.m_seriesId),
177 m_programId(other.m_programId),
178 m_inetRef(other.m_inetRef),
179 m_catType(other.m_catType),
180
181 m_fileSize(other.m_fileSize),
182
183 m_startTs(other.m_startTs),
184 m_endTs(other.m_endTs),
185 m_recStartTs(other.m_recStartTs),
186 m_recEndTs(other.m_recEndTs),
187
188 m_stars(other.m_stars),
189
190 m_originalAirDate(other.m_originalAirDate),
191 m_lastModified(other.m_lastModified),
192 m_lastInUseTime(MythDate::current().addSecs(-kLastInUseOffset)),
193
194 m_recPriority2(other.m_recPriority2),
195 m_recordId(other.m_recordId),
196 m_parentId(other.m_parentId),
197
198 m_sourceId(other.m_sourceId),
199 m_inputId(other.m_inputId),
200
201 m_findId(other.m_findId),
202 m_programFlags(other.m_programFlags),
203 m_videoProperties(other.m_videoProperties),
204 m_audioProperties(other.m_audioProperties),
205 m_subtitleProperties(other.m_subtitleProperties),
206 m_year(other.m_year),
207 m_partNumber(other.m_partNumber),
208 m_partTotal(other.m_partTotal),
209
210 m_recStatus(other.m_recStatus),
211 m_recType(other.m_recType),
212 m_dupIn(other.m_dupIn),
213 m_dupMethod(other.m_dupMethod),
214
215 m_recordedId(other.m_recordedId),
216 m_inputName(other.m_inputName),
217 m_bookmarkUpdate(other.m_bookmarkUpdate),
218
219 // everything below this line is not serialized
220 m_availableStatus(other.m_availableStatus),
221 m_spread(other.m_spread),
222 m_startCol(other.m_startCol),
223
224 // Private
225 m_positionMapDBReplacement(other.m_positionMapDBReplacement)
226{
228}
229
234{
236
238 query.prepare(
239 "SELECT chanid, starttime "
240 "FROM recorded "
241 "WHERE recordedid = :RECORDEDID");
242 query.bindValue(":RECORDEDID", _recordedid);
243
244 if (query.exec() && query.next())
245 {
246 uint _chanid = query.value(0).toUInt();
247 QDateTime _recstartts = MythDate::as_utc(query.value(1).toDateTime());
248 LoadProgramFromRecorded(_chanid, _recstartts);
249 }
250 else
251 {
252 LOG(VB_GENERAL, LOG_CRIT, LOC +
253 QString("Failed to find recorded entry for %1.")
254 .arg(_recordedid));
256 }
257
259}
260
264ProgramInfo::ProgramInfo(uint _chanid, const QDateTime &_recstartts)
265{
267
268 LoadProgramFromRecorded(_chanid, _recstartts);
270}
271
276 uint _recordedid,
277 QString _title,
278 QString _sortTitle,
279 QString _subtitle,
280 QString _sortSubtitle,
281 QString _description,
282 uint _season,
283 uint _episode,
284 uint _totalepisodes,
285 QString _syndicatedepisode,
286 QString _category,
287
288 uint _chanid,
289 QString _channum,
290 QString _chansign,
291 QString _channame,
292 QString _chanplaybackfilters,
293
294 QString _recgroup,
295 QString _playgroup,
296
297 const QString &_pathname,
298
299 QString _hostname,
300 QString _storagegroup,
301
302 QString _seriesid,
303 QString _programid,
304 QString _inetref,
305 CategoryType _catType,
306
307 int _recpriority,
308
309 uint64_t _filesize,
310
311 QDateTime _startts,
312 QDateTime _endts,
313 QDateTime _recstartts,
314 QDateTime _recendts,
315
316 float _stars,
317
318 uint _year,
319 uint _partnumber,
320 uint _parttotal,
321 QDate _originalAirDate,
322 QDateTime _lastmodified,
323
324 RecStatus::Type _recstatus,
325
326 uint _recordid,
327
328 RecordingDupInType _dupin,
329 RecordingDupMethodType _dupmethod,
330
331 uint _findid,
332
333 uint _programflags,
334 uint _audioproperties,
335 uint _videoproperties,
336 uint _subtitleType,
337 QString _inputname,
338 QDateTime _bookmarkupdate) :
339 m_title(std::move(_title)),
340 m_sortTitle(std::move(_sortTitle)),
341 m_subtitle(std::move(_subtitle)),
342 m_sortSubtitle(std::move(_sortSubtitle)),
343 m_description(std::move(_description)),
344 m_season(_season),
345 m_episode(_episode),
346 m_totalEpisodes(_totalepisodes),
347 m_syndicatedEpisode(std::move(_syndicatedepisode)),
348 m_category(std::move(_category)),
349
350 m_recPriority(_recpriority),
351
352 m_chanId(_chanid),
353 m_chanStr(std::move(_channum)),
354 m_chanSign(std::move(_chansign)),
355 m_chanName(std::move(_channame)),
356 m_chanPlaybackFilters(std::move(_chanplaybackfilters)),
357
358 m_recGroup(std::move(_recgroup)),
359 m_playGroup(std::move(_playgroup)),
360
361 m_pathname(_pathname),
362
363 m_hostname(std::move(_hostname)),
364 m_storageGroup(std::move(_storagegroup)),
365
366 m_seriesId(std::move(_seriesid)),
367 m_programId(std::move(_programid)),
368 m_inetRef(std::move(_inetref)),
369 m_catType(_catType),
370
371 m_fileSize(_filesize),
372
373 m_startTs(std::move(_startts)),
374 m_endTs(std::move(_endts)),
375 m_recStartTs(std::move(_recstartts)),
376 m_recEndTs(std::move(_recendts)),
377
378 m_stars(std::clamp(_stars, 0.0F, 1.0F)),
379
380 m_originalAirDate(_originalAirDate),
381 m_lastModified(std::move(_lastmodified)),
382 m_lastInUseTime(MythDate::current().addSecs(-kLastInUseOffset)),
383
384 m_recordId(_recordid),
385 m_findId(_findid),
386
387 m_programFlags(_programflags),
388 m_videoProperties(_videoproperties),
389 m_audioProperties(_audioproperties),
390 m_subtitleProperties(_subtitleType),
391 m_year(_year),
392 m_partNumber(_partnumber),
393 m_partTotal(_parttotal),
394
395 m_recStatus(_recstatus),
396 m_dupIn(_dupin),
397 m_dupMethod(_dupmethod),
398
399 m_recordedId(_recordedid),
400 m_inputName(std::move(_inputname)),
401 m_bookmarkUpdate(std::move(_bookmarkupdate))
402{
403 if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1895, 12, 28))
404 m_originalAirDate = QDate();
405
406 SetPathname(_pathname);
408}
409
414 QString _title,
415 QString _sortTitle,
416 QString _subtitle,
417 QString _sortSubtitle,
418 QString _description,
419 uint _season,
420 uint _episode,
421 QString _category,
422
423 uint _chanid,
424 QString _channum,
425 QString _chansign,
426 QString _channame,
427
428 QString _seriesid,
429 QString _programid,
430 QString _inetref,
431
432 QDateTime _startts,
433 QDateTime _endts,
434 QDateTime _recstartts,
435 QDateTime _recendts,
436
437 RecStatus::Type _recstatus,
438
439 uint _recordid,
440
441 RecordingType _rectype,
442
443 uint _findid,
444
445 bool duplicate) :
446 m_title(std::move(_title)),
447 m_sortTitle(std::move(_sortTitle)),
448 m_subtitle(std::move(_subtitle)),
449 m_sortSubtitle(std::move(_sortSubtitle)),
450 m_description(std::move(_description)),
451 m_season(_season),
452 m_episode(_episode),
453 m_category(std::move(_category)),
454
455 m_chanId(_chanid),
456 m_chanStr(std::move(_channum)),
457 m_chanSign(std::move(_chansign)),
458 m_chanName(std::move(_channame)),
459
460 m_seriesId(std::move(_seriesid)),
461 m_programId(std::move(_programid)),
462 m_inetRef(std::move(_inetref)),
463
464 m_startTs(std::move(_startts)),
465 m_endTs(std::move(_endts)),
466 m_recStartTs(std::move(_recstartts)),
467 m_recEndTs(std::move(_recendts)),
468
469 m_lastModified(m_startTs),
470 m_lastInUseTime(MythDate::current().addSecs(-kLastInUseOffset)),
471
472 m_recordId(_recordid),
473 m_findId(_findid),
474
475 m_programFlags(duplicate ? FL_DUPLICATE : FL_NONE),
476
477 m_recStatus(_recstatus),
478 m_recType(_rectype),
479 m_dupIn(kDupsUnset),
480 m_dupMethod(kDupCheckUnset)
481{
483}
484
489 QString _title,
490 QString _sortTitle,
491 QString _subtitle,
492 QString _sortSubtitle,
493 QString _description,
494 QString _syndicatedepisode,
495 QString _category,
496
497 uint _chanid,
498 QString _channum,
499 QString _chansign,
500 QString _channame,
501 QString _chanplaybackfilters,
502
503 QDateTime _startts,
504 QDateTime _endts,
505 QDateTime _recstartts,
506 QDateTime _recendts,
507
508 QString _seriesid,
509 QString _programid,
510 const CategoryType _catType,
511
512 float _stars,
513 uint _year,
514 uint _partnumber,
515 uint _parttotal,
516
517 QDate _originalAirDate,
518 RecStatus::Type _recstatus,
519 uint _recordid,
520 RecordingType _rectype,
521 uint _findid,
522
523 bool commfree,
524 bool repeat,
525
526 uint _videoproperties,
527 uint _audioproperties,
528 uint _subtitleType,
529
530 uint _season,
531 uint _episode,
532 uint _totalepisodes,
533
534 const ProgramList &schedList) :
535 m_title(std::move(_title)),
536 m_sortTitle(std::move(_sortTitle)),
537 m_subtitle(std::move(_subtitle)),
538 m_sortSubtitle(std::move(_sortSubtitle)),
539 m_description(std::move(_description)),
540 m_season(_season),
541 m_episode(_episode),
542 m_totalEpisodes(_totalepisodes),
543 m_syndicatedEpisode(std::move(_syndicatedepisode)),
544 m_category(std::move(_category)),
545
546 m_chanId(_chanid),
547 m_chanStr(std::move(_channum)),
548 m_chanSign(std::move(_chansign)),
549 m_chanName(std::move(_channame)),
550 m_chanPlaybackFilters(std::move(_chanplaybackfilters)),
551
552 m_seriesId(std::move(_seriesid)),
553 m_programId(std::move(_programid)),
554 m_catType(_catType),
555
556 m_startTs(std::move(_startts)),
557 m_endTs(std::move(_endts)),
558 m_recStartTs(std::move(_recstartts)),
559 m_recEndTs(std::move(_recendts)),
560
561 m_stars(std::clamp(_stars, 0.0F, 1.0F)),
562
563 m_originalAirDate(_originalAirDate),
564 m_lastModified(m_startTs),
565 m_lastInUseTime(m_startTs.addSecs(-kLastInUseOffset)),
566
567 m_recordId(_recordid),
568 m_findId(_findid),
569
570 m_videoProperties(_videoproperties),
571 m_audioProperties(_audioproperties),
572 m_subtitleProperties(_subtitleType),
573 m_year(_year),
574 m_partNumber(_partnumber),
575 m_partTotal(_parttotal),
576
577 m_recStatus(_recstatus),
578 m_recType(_rectype)
579{
580 m_programFlags |= commfree ? FL_CHANCOMMFREE : FL_NONE;
581 m_programFlags |= repeat ? FL_REPEAT : FL_NONE;
582
583 if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1895, 12, 28))
584 m_originalAirDate = QDate();
585
586 for (auto *it : schedList)
587 {
588 // If this showing is scheduled to be recorded, then we need to copy
589 // some of the information from the scheduler
590 //
591 // This applies even if the showing may be on a different channel
592 // to the one which is actually being recorded e.g. A regional or HD
593 // variant of the same channel
595 continue;
596
597 const ProgramInfo &s = *it;
604 m_dupIn = s.m_dupIn;
606 m_findId = s.m_findId;
610
611 // This is the exact showing (same chanid or callsign)
612 // which will be recorded
613 if (IsSameChannel(s))
614 {
616 break;
617 }
618
625 }
627}
628
633 QString _title,
634 QString _sortTitle,
635 QString _subtitle,
636 QString _sortSubtitle,
637 QString _description,
638 uint _season,
639 uint _episode,
640 uint _totalepisodes,
641 QString _category,
642
643 uint _chanid,
644 QString _channum,
645 QString _chansign,
646 QString _channame,
647 QString _chanplaybackfilters,
648
649 QString _recgroup,
650 QString _playgroup,
651
652 QDateTime _startts,
653 QDateTime _endts,
654 QDateTime _recstartts,
655 QDateTime _recendts,
656
657 QString _seriesid,
658 QString _programid,
659 QString _inetref,
660 QString _inputname) :
661 m_title(std::move(_title)),
662 m_sortTitle(std::move(_sortTitle)),
663 m_subtitle(std::move(_subtitle)),
664 m_sortSubtitle(std::move(_sortSubtitle)),
665 m_description(std::move(_description)),
666 m_season(_season),
667 m_episode(_episode),
668 m_totalEpisodes(_totalepisodes),
669 m_category(std::move(_category)),
670
671 m_chanId(_chanid),
672 m_chanStr(std::move(_channum)),
673 m_chanSign(std::move(_chansign)),
674 m_chanName(std::move(_channame)),
675 m_chanPlaybackFilters(std::move(_chanplaybackfilters)),
676
677 m_recGroup(std::move(_recgroup)),
678 m_playGroup(std::move(_playgroup)),
679
680 m_seriesId(std::move(_seriesid)),
681 m_programId(std::move(_programid)),
682 m_inetRef(std::move(_inetref)),
683
684 m_startTs(std::move(_startts)),
685 m_endTs(std::move(_endts)),
686 m_recStartTs(std::move(_recstartts)),
687 m_recEndTs(std::move(_recendts)),
688
689 m_lastModified(MythDate::current()),
690 m_lastInUseTime(m_lastModified.addSecs(-kLastInUseOffset)),
691
692 m_inputName(std::move(_inputname))
693{
695}
696
700ProgramInfo::ProgramInfo(const QString &_pathname)
701{
703 if (_pathname.isEmpty())
704 {
705 return;
706 }
707
708 uint _chanid = 0;
709 QDateTime _recstartts;
711 QueryKeyFromPathname(_pathname, _chanid, _recstartts) &&
712 LoadProgramFromRecorded(_chanid, _recstartts))
713 {
714 return;
715 }
716
718
719 QDateTime cur = MythDate::current();
720 m_recStartTs = m_startTs = cur.addSecs(-kLastInUseOffset - 1);
721 m_recEndTs = m_endTs = cur.addSecs(-1);
722
723 QString basename = _pathname.section('/', -1);
724 if (_pathname == basename)
725 SetPathname(QDir::currentPath() + '/' + _pathname);
726 else if (_pathname.contains("./") && !_pathname.contains(":"))
727 SetPathname(QFileInfo(_pathname).absoluteFilePath());
728 else
729 SetPathname(_pathname);
731}
732
736ProgramInfo::ProgramInfo(const QString &_pathname,
737 const QString &_plot,
738 const QString &_title,
739 const QString &_sortTitle,
740 const QString &_subtitle,
741 const QString &_sortSubtitle,
742 const QString &_director,
743 int _season, int _episode,
744 const QString &_inetref,
745 std::chrono::minutes length_in_minutes,
746 uint _year,
747 const QString &_programid)
748{
750
751 //NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer)
752 // These have to remain after the call to ::clear().
753 m_title = _title;
754 m_sortTitle = _sortTitle;
755 m_subtitle = _subtitle;
756 m_sortSubtitle = _sortSubtitle;
757 m_description = _plot;
758 m_season = _season;
759 m_episode = _episode;
760 m_director = _director;
761 m_programId = _programid;
762 m_inetRef = _inetref;
763 m_year = _year;
764 //NOLINTEND(cppcoreguidelines-prefer-member-initializer)
765
766 QDateTime cur = MythDate::current();
767 int64_t minutes = length_in_minutes.count();
768 m_recStartTs = cur.addSecs((minutes + 1) * -60);
769 m_recEndTs = m_recStartTs.addSecs(minutes * 60);
770#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
771 m_startTs = QDateTime(QDate(m_year,1,1),QTime(0,0,0), Qt::UTC);
772#else
773 m_startTs = QDateTime(QDate(m_year,1,1),QTime(0,0,0),
774 QTimeZone(QTimeZone::UTC));
775#endif
776 m_endTs = m_startTs.addSecs(minutes * 60);
777
778 QString pn = _pathname;
779 if (!_pathname.startsWith("myth://"))
780 pn = determineURLType(_pathname);
781
782 SetPathname(pn);
784}
785
789ProgramInfo::ProgramInfo(const QString &_title, uint _chanid,
790 const QDateTime &_startts,
791 const QDateTime &_endts)
792{
794
796 query.prepare(
797 "SELECT chanid, channum, callsign, name, outputfilters, commmethod "
798 "FROM channel "
799 "WHERE chanid=:CHANID");
800 query.bindValue(":CHANID", _chanid);
801 if (query.exec() && query.next())
802 {
803 m_chanStr = query.value(1).toString();
804 m_chanSign = query.value(2).toString();
805 m_chanName = query.value(3).toString();
806 m_chanPlaybackFilters = query.value(4).toString();
807 set_flag(m_programFlags, FL_CHANCOMMFREE,
808 query.value(5).toInt() == COMM_DETECT_COMMFREE);
809 }
810
811 m_chanId = _chanid;
812 m_startTs = _startts;
813 m_endTs = _endts;
814
815 m_title = _title;
816 if (m_title.isEmpty())
817 {
818 QString channelFormat =
819 gCoreContext->GetSetting("ChannelFormat", "<num> <sign>");
820
821 m_title = QString("%1 - %2").arg(ChannelText(channelFormat),
823 }
824
826 QString("%1 (%2)").arg(m_title, QObject::tr("Manual Record"));
828}
829
834{
835 if (this == &other)
836 return *this;
837
838 clone(other);
839 return *this;
840}
841
844 bool ignore_non_serialized_data)
845{
846 bool is_same =
847 ((m_chanId != 0U) && m_recStartTs.isValid() && m_startTs.isValid() &&
848 m_chanId == other.m_chanId && m_recStartTs == other.m_recStartTs &&
849 m_startTs == other.m_startTs);
850
851 m_title = other.m_title;
852 m_sortTitle = other.m_sortTitle;
853 m_subtitle = other.m_subtitle;
856 m_season = other.m_season;
857 m_episode = other.m_episode;
860 m_category = other.m_category;
861 m_director = other.m_director;
862
863 m_chanId = other.m_chanId;
864 m_chanStr = other.m_chanStr;
865 m_chanSign = other.m_chanSign;
866 m_chanName = other.m_chanName;
868
869 m_recGroup = other.m_recGroup;
870 m_playGroup = other.m_playGroup;
871
872 if (!ignore_non_serialized_data || !is_same ||
873 (GetBasename() != other.GetBasename()))
874 {
875 m_pathname = other.m_pathname;
876 }
877
878 m_hostname = other.m_hostname;
880
881 m_seriesId = other.m_seriesId;
882 m_programId = other.m_programId;
883 m_inetRef = other.m_inetRef;
884 m_catType = other.m_catType;
885
887
888 m_fileSize = other.m_fileSize;
889
890 m_startTs = other.m_startTs;
891 m_endTs = other.m_endTs;
893 m_recEndTs = other.m_recEndTs;
894
895 m_stars = other.m_stars;
896
897 m_year = other.m_year;
899 m_partTotal = other.m_partTotal;
900
904
905 m_recStatus = other.m_recStatus;
906
908 m_recordId = other.m_recordId;
909 m_parentId = other.m_parentId;
910
911 m_recType = other.m_recType;
912 m_dupIn = other.m_dupIn;
913 m_dupMethod = other.m_dupMethod;
914
916 m_inputName = other.m_inputName;
918
919 m_sourceId = other.m_sourceId;
920 m_inputId = other.m_inputId;
921
922 m_findId = other.m_findId;
927
928 if (!ignore_non_serialized_data)
929 {
930 m_spread = other.m_spread;
931 m_startCol = other.m_startCol;
933
936 }
937}
938
940{
941 m_title.clear();
942 m_sortTitle.clear();
943 m_subtitle.clear();
944 m_sortSubtitle.clear();
945 m_description.clear();
946 m_season = 0;
947 m_episode = 0;
948 m_totalEpisodes = 0;
949 m_syndicatedEpisode.clear();
950 m_category.clear();
951 m_director.clear();
952
953 m_chanId = 0;
954 m_chanStr.clear();
955 m_chanSign.clear();
956 m_chanName.clear();
957 m_chanPlaybackFilters.clear();
958
959 m_recGroup = "Default";
960 m_playGroup = "Default";
961
962 m_pathname.clear();
963
964 m_hostname.clear();
965 m_storageGroup = "Default";
966
967 m_year = 0;
968 m_partNumber = 0;
969 m_partTotal = 0;
970
971 m_seriesId.clear();
972 m_programId.clear();
973 m_inetRef.clear();
975
976 m_recPriority = 0;
977
978 m_fileSize = 0ULL;
979
984
985 m_stars = 0.0F;
986
987 m_originalAirDate = QDate();
990
992
993 m_recPriority2 = 0;
994 m_recordId = 0;
995 m_parentId = 0;
996
1000
1001 m_recordedId = 0;
1002 m_inputName.clear();
1003 m_bookmarkUpdate = QDateTime();
1004
1005 m_sourceId = 0;
1006 m_inputId = 0;
1007
1008 m_findId = 0;
1009
1010 m_programFlags = FL_NONE;
1011 m_videoProperties = VID_UNKNOWN;
1012 m_audioProperties = AUD_UNKNOWN;
1013 m_subtitleProperties = SUB_UNKNOWN;
1014
1015 // everything below this line is not serialized
1016 m_spread = -1;
1017 m_startCol = -1;
1019
1020 // Private
1021 m_inUseForWhat.clear();
1023}
1024
1029bool qstringEqualOrDefault(const QString& a, const QString& b);
1030bool qstringEqualOrDefault(const QString& a, const QString& b)
1031{
1032 if (a == b)
1033 return true;
1034 if (a.isEmpty() and (b == "Default"))
1035 return true;
1036 if ((a == "Default") and b.isEmpty())
1037 return true;
1038 return false;
1039}
1040
1052{
1053 if ((m_title != rhs.m_title) ||
1054 (m_subtitle != rhs.m_subtitle) ||
1055 (m_description != rhs.m_description) ||
1056 (m_season != rhs.m_season) ||
1057 (m_episode != rhs.m_episode) ||
1060 (m_category != rhs.m_category)
1061#if 0
1062 || (m_director != rhs.m_director)
1063#endif
1064 )
1065 return false;
1066
1067 if (m_recPriority != rhs.m_recPriority)
1068 return false;
1069
1070 if ((m_chanId != rhs.m_chanId) ||
1071 (m_chanStr != rhs.m_chanStr) ||
1072 (m_chanSign != rhs.m_chanSign) ||
1073 (m_chanName != rhs.m_chanName) ||
1075 return false;
1076
1079 return false;
1080
1081 if (m_pathname != rhs.m_pathname)
1082 return false;
1083
1084 if ((m_hostname != rhs.m_hostname) ||
1086 return false;
1087
1088 if ((m_seriesId != rhs.m_seriesId) ||
1089 (m_programId != rhs.m_programId) ||
1090 (m_inetRef != rhs.m_inetRef) ||
1091 (m_catType != rhs.m_catType))
1092 return false;
1093
1094 if (m_fileSize != rhs.m_fileSize)
1095 return false;
1096
1097 if ((m_startTs != rhs.m_startTs) ||
1098 (m_endTs != rhs.m_endTs) ||
1099 (m_recStartTs != rhs.m_recStartTs) ||
1100 (m_recEndTs != rhs.m_recEndTs))
1101 return false;
1102
1103 if ((m_stars != rhs.m_stars) ||
1106#if 0
1108#endif
1109 )
1110 return false;
1111
1112 if (m_recPriority2 != rhs.m_recPriority2)
1113 return false;
1114
1115 if ((m_recordId != rhs.m_recordId) ||
1116 (m_parentId != rhs.m_parentId))
1117 return false;
1118
1119 if ((m_sourceId != rhs.m_sourceId) ||
1120 (m_inputId != rhs.m_inputId) ||
1121 (m_findId != rhs.m_findId))
1122 return false;
1123
1124 if ((m_programFlags != rhs.m_programFlags) ||
1128 (m_year != rhs.m_year) ||
1129 (m_partNumber != rhs.m_partNumber) ||
1130 (m_partTotal != rhs.m_partTotal))
1131 return false;
1132
1133 if ((m_recStatus != rhs.m_recStatus) ||
1134 (m_recType != rhs.m_recType) ||
1135 (m_dupIn != rhs.m_dupIn) ||
1136 (m_dupMethod != rhs.m_dupMethod))
1137 return false;
1138
1139 if ((m_recordedId != rhs.m_recordedId) ||
1140 (m_inputName != rhs.m_inputName) ||
1142 return false;
1143
1144 return true;
1145}
1146
1151{
1152 std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
1153
1154 if (m_sortTitle.isEmpty() and not m_title.isEmpty())
1155 m_sortTitle = sh->doTitle(m_title);
1156 if (m_sortSubtitle.isEmpty() and not m_subtitle.isEmpty())
1157 m_sortSubtitle = sh->doTitle(m_subtitle);
1158}
1159
1160void ProgramInfo::SetTitle(const QString &t, const QString &st)
1161{
1162 m_title = t;
1163 m_sortTitle = st;
1165}
1166
1167void ProgramInfo::SetSubtitle(const QString &st, const QString &sst)
1168{
1169 m_subtitle = st;
1170 m_sortSubtitle = sst;
1172}
1173
1176 uint chanid, const QDateTime &recstartts)
1177{
1178 return QString("%1_%2").arg(chanid).arg(recstartts.toString(Qt::ISODate));
1179}
1180
1184bool ProgramInfo::ExtractKey(const QString &uniquekey,
1185 uint &chanid, QDateTime &recstartts)
1186{
1187 QStringList keyParts = uniquekey.split('_');
1188 if (keyParts.size() != 2)
1189 return false;
1190 chanid = keyParts[0].toUInt();
1191 recstartts = MythDate::fromString(keyParts[1]);
1192 return (chanid != 0U) && recstartts.isValid();
1193}
1194
1196 const QString &pathname, uint &chanid, QDateTime &recstartts)
1197{
1198 QString basename = pathname.section('/', -1);
1199 if (basename.isEmpty())
1200 return false;
1201
1202 QStringList lr = basename.split("_");
1203 if (lr.size() == 2)
1204 {
1205 chanid = lr[0].toUInt();
1206 QStringList ts = lr[1].split(".");
1207 if (chanid && !ts.empty())
1208 {
1209 recstartts = MythDate::fromString(ts[0]);
1210 return recstartts.isValid();
1211 }
1212 }
1213
1214 return false;
1215}
1216
1218 const QString &pathname, uint &chanid, QDateTime &recstartts)
1219{
1220 QString basename = pathname.section('/', -1);
1221 if (basename.isEmpty())
1222 return false;
1223
1225 query.prepare(
1226 "SELECT chanid, starttime "
1227 "FROM recorded "
1228 "WHERE basename = :BASENAME");
1229 query.bindValue(":BASENAME", basename);
1230 if (query.exec() && query.next())
1231 {
1232 chanid = query.value(0).toUInt();
1233 recstartts = MythDate::as_utc(query.value(1).toDateTime());
1234 return true;
1235 }
1236
1237 return ExtractKeyFromPathname(pathname, chanid, recstartts);
1238}
1239
1240bool ProgramInfo::QueryRecordedIdFromPathname(const QString &pathname,
1241 uint &recordedid)
1242{
1243 QString basename = pathname.section('/', -1);
1244 if (basename.isEmpty())
1245 return false;
1246
1248 query.prepare(
1249 "SELECT recordedid "
1250 "FROM recorded "
1251 "WHERE basename = :BASENAME");
1252 query.bindValue(":BASENAME", basename);
1253 if (query.exec() && query.next())
1254 {
1255 recordedid = query.value(0).toUInt();
1256 return true;
1257 }
1258
1259 return false;
1260}
1261
1262static inline QString DateTimeToListInt(const QDateTime& x) {
1263 if (x.isValid())
1264 return QString::number(x.toSecsSinceEpoch());
1265 return QString::number(kInvalidDateTime);
1266}
1267
1274void ProgramInfo::ToStringList(QStringList &list) const
1275{
1276 list << m_title; // 0
1277 list << m_subtitle; // 1
1278 list << m_description; // 2
1279 list << QString::number(m_season ); // 3
1280 list << QString::number(m_episode ); // 4
1281 list << QString::number(m_totalEpisodes); // 5
1282 list << m_syndicatedEpisode; // 6
1283 list << m_category; // 7
1284 list << QString::number(m_chanId); // 8
1285 list << m_chanStr; // 9
1286 list << m_chanSign; // 10
1287 list << m_chanName; // 11
1288 list << m_pathname; // 12
1289 list << QString::number(m_fileSize); // 13
1290
1291 list << DateTimeToListInt(m_startTs); // 14
1292 list << DateTimeToListInt(m_endTs); // 15
1293 list << QString::number(m_findId); // 16
1294 list << m_hostname; // 17
1295 list << QString::number(m_sourceId); // 18
1296 list << QString::number(m_inputId); // 19 (m_formerly cardid)
1297 list << QString::number(m_inputId); // 20
1298 list << QString::number(m_recPriority); // 21
1299 list << QString::number(m_recStatus); // 22
1300 list << QString::number(m_recordId); // 23
1301
1302 list << QString::number(m_recType); // 24
1303 list << QString::number(m_dupIn); // 25
1304 list << QString::number(m_dupMethod); // 26
1305 list << DateTimeToListInt(m_recStartTs); // 27
1306 list << DateTimeToListInt(m_recEndTs); // 28
1307 list << QString::number(m_programFlags); // 29
1308 list << (!m_recGroup.isEmpty() ? m_recGroup : "Default"); // 30
1309 list << m_chanPlaybackFilters; // 31
1310 list << m_seriesId; // 32
1311 list << m_programId; // 33
1312 list << m_inetRef; // 34
1313
1314 list << DateTimeToListInt(m_lastModified); // 35
1315 list << QString("%1").arg(m_stars); // 36
1316 list << m_originalAirDate.toString(Qt::ISODate); // 37
1317 list << (!m_playGroup.isEmpty() ? m_playGroup : "Default"); // 38
1318 list << QString::number(m_recPriority2); // 39
1319 list << QString::number(m_parentId); // 40
1320 list << (!m_storageGroup.isEmpty() ? m_storageGroup : "Default"); // 41
1321 list << QString::number(m_audioProperties); // 42
1322 list << QString::number(m_videoProperties); // 43
1323 list << QString::number(m_subtitleProperties); // 44
1324
1325 list << QString::number(m_year); // 45
1326 list << QString::number(m_partNumber); // 46
1327 list << QString::number(m_partTotal); // 47
1328 list << QString::number(m_catType); // 48
1329
1330 list << QString::number(m_recordedId); // 49
1331 list << m_inputName; // 50
1332 list << DateTimeToListInt(m_bookmarkUpdate); // 51
1333/* do not forget to update the NUMPROGRAMLINES defines! */
1334}
1335
1336// QStringList::const_iterator it = list.begin()+offset;
1337
1338// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
1339#define NEXT_STR() do { if (it == listend) \
1340 { \
1341 LOG(VB_GENERAL, LOG_ERR, listerror); \
1342 clear(); \
1343 return false; \
1344 } \
1345 ts = *it++; } while (false)
1346
1347static inline QDateTime DateTimeFromListItem(const QString& str)
1348{
1349 if (str.isEmpty() or (str.toUInt() == kInvalidDateTime))
1350 return {};
1351 return MythDate::fromSecsSinceEpoch(str.toLongLong());
1352}
1353
1354static inline QDate DateFromListItem(const QString& str)
1355{
1356 if (str.isEmpty() || (str == "0000-00-00"))
1357 return {};
1358 return QDate::fromString(str, Qt::ISODate);
1359}
1360
1361
1373bool ProgramInfo::FromStringList(QStringList::const_iterator &it,
1374 const QStringList::const_iterator& listend)
1375{
1376 QString listerror = LOC + "FromStringList, not enough items in list.";
1377 QString ts;
1378
1379 uint origChanid = m_chanId;
1380 QDateTime origRecstartts = m_recStartTs;
1381
1382 NEXT_STR(); m_title = ts; // 0
1383 NEXT_STR(); m_subtitle = ts; // 1
1384 NEXT_STR(); m_description = ts; // 2
1385 NEXT_STR(); m_season = ts.toLongLong(); // 3
1386 NEXT_STR(); m_episode = ts.toLongLong(); // 4
1387 NEXT_STR(); m_totalEpisodes = ts.toLongLong(); // 5
1388 NEXT_STR(); m_syndicatedEpisode = ts; // 6
1389 NEXT_STR(); m_category = ts; // 7
1390 NEXT_STR(); m_chanId = ts.toLongLong(); // 8
1391 NEXT_STR(); m_chanStr = ts; // 9
1392 NEXT_STR(); m_chanSign = ts; // 10
1393 NEXT_STR(); m_chanName = ts; // 11
1394 NEXT_STR(); m_pathname = ts; // 12
1395 NEXT_STR(); m_fileSize = ts.toLongLong(); // 13
1396
1398 NEXT_STR(); m_endTs = DateTimeFromListItem(ts); // 15
1399 NEXT_STR(); m_findId = ts.toLongLong(); // 16
1400 NEXT_STR(); m_hostname = ts; // 17
1401 NEXT_STR(); m_sourceId = ts.toLongLong(); // 18
1402 NEXT_STR(); // 19 (formerly cardid)
1403 NEXT_STR(); m_inputId = ts.toLongLong(); // 20
1404 NEXT_STR(); m_recPriority = ts.toLongLong(); // 21
1405 NEXT_STR(); m_recStatus = (RecStatus::Type)ts.toInt(); // 22
1406 NEXT_STR(); m_recordId = ts.toLongLong(); // 23
1407
1408 NEXT_STR(); m_recType = (RecordingType)ts.toInt(); // 24
1409 NEXT_STR(); m_dupIn = (RecordingDupInType)ts.toInt(); // 25
1410 NEXT_STR(); m_dupMethod = (RecordingDupMethodType)ts.toInt(); // 26
1413 NEXT_STR(); m_programFlags = ts.toLongLong(); // 29
1414 NEXT_STR(); m_recGroup = ts; // 30
1415 NEXT_STR(); m_chanPlaybackFilters = ts; // 31
1416 NEXT_STR(); m_seriesId = ts; // 32
1417 NEXT_STR(); m_programId = ts; // 33
1418 NEXT_STR(); m_inetRef = ts; // 34
1419
1421 NEXT_STR(); (m_stars) = ts.toFloat(); // 36
1423 NEXT_STR(); m_playGroup = ts; // 38
1424 NEXT_STR(); m_recPriority2 = ts.toLongLong(); // 39
1425 NEXT_STR(); m_parentId = ts.toLongLong(); // 40
1426 NEXT_STR(); m_storageGroup = ts; // 41
1427 NEXT_STR(); m_audioProperties = ts.toLongLong(); // 42
1428 NEXT_STR(); m_videoProperties = ts.toLongLong(); // 43
1429 NEXT_STR(); m_subtitleProperties = ts.toLongLong(); // 44
1430
1431 NEXT_STR(); m_year = ts.toLongLong(); // 45
1432 NEXT_STR(); m_partNumber = ts.toLongLong(); // 46
1433 NEXT_STR(); m_partTotal = ts.toLongLong(); // 47
1434 NEXT_STR(); m_catType = (CategoryType)ts.toInt(); // 48
1435
1436 NEXT_STR(); m_recordedId = ts.toLongLong(); // 49
1437 NEXT_STR(); m_inputName = ts; // 50
1439
1440 if (!origChanid || !origRecstartts.isValid() ||
1441 (origChanid != m_chanId) || (origRecstartts != m_recStartTs))
1442 {
1444 m_spread = -1;
1445 m_startCol = -1;
1446 m_inUseForWhat = QString();
1448 }
1449
1451
1452 return true;
1453}
1454
1455template <typename T>
1456QString propsValueToString (const QString& name, QMap<T,QString> propNames,
1457 T props)
1458{
1459 if (props == 0)
1460 return propNames[0];
1461
1462 QStringList result;
1463 for (uint i = 0; i < (sizeof(T)*8) - 1; ++i)
1464 {
1465 uint bit = 1<<i;
1466 if ((props & bit) == 0)
1467 continue;
1468 if (propNames.contains(bit))
1469 {
1470 result += propNames[bit];
1471 continue;
1472 }
1473 // Ignore ProgramTypeMask (recording, video, DVD, etc)
1474 if (bit & FL_TYPEMASK)
1475 continue;
1476 QString tmp = QString("0x%1").arg(bit, sizeof(T)*2,16,QChar('0'));
1477 LOG(VB_GENERAL, LOG_ERR, QString("Unknown name for %1 flag 0x%2.")
1478 .arg(name, tmp));
1479 result += tmp;
1480 }
1481 return result.join('|');
1482}
1483
1484template <typename T>
1485uint propsValueFromString (const QString& name, const QMap<T,QString>& propNames,
1486 const QString& props)
1487{
1488 if (props.isEmpty())
1489 return 0;
1490
1491 uint result = 0;
1492
1493 QStringList names = props.split('|');
1494 for ( const auto& n : std::as_const(names) )
1495 {
1496 uint bit = propNames.key(n, 0);
1497 if (bit == 0)
1498 {
1499 LOG(VB_GENERAL, LOG_ERR, QString("Unknown flag for %1 %2")
1500 .arg(name, n));
1501 }
1502 else
1503 {
1504 result |= bit;
1505 }
1506 }
1507 return result;
1508}
1509
1511{
1512 return propsValueToString("program", ProgramFlagNames, m_programFlags);
1513}
1514
1515
1516QString ProgramInfo::GetRecTypeStatus(bool showrerecord) const
1517{
1518 QString tmp_rec = ::toString(GetRecordingRuleType());
1520 {
1521 QDateTime timeNow = MythDate::current();
1522 if (((m_recEndTs > timeNow) && (m_recStatus <= RecStatus::WillRecord)) ||
1524 {
1525 tmp_rec += QString(" %1%2").arg(m_recPriority > 0 ? "+" : "").arg(m_recPriority);
1526 if (m_recPriority2)
1527 tmp_rec += QString("/%1%2").arg(m_recPriority2 > 0 ? "+" : "").arg(m_recPriority2);
1528 tmp_rec += " ";
1529 }
1530 else
1531 {
1532 tmp_rec += " -- ";
1533 }
1534 if (showrerecord && (GetRecordingStatus() == RecStatus::Recorded) &&
1535 !IsDuplicate())
1536 {
1537 tmp_rec += QObject::tr("Re-Record");
1538 }
1539 else
1540 {
1542 }
1543 }
1544 return tmp_rec;
1545}
1546
1547
1548
1549
1551{
1552 return propsValueToString("subtitle", SubtitlePropsNames,
1554}
1555
1557{
1558 return propsValueToString("video", VideoPropsNames, m_videoProperties);
1559}
1560
1562{
1563 return propsValueToString("audio", AudioPropsNames, m_audioProperties);
1564}
1565
1567{
1568 return propsValueFromString("subtitle", SubtitlePropsNames, names);
1569}
1570
1572{
1573 return propsValueFromString("video", VideoPropsNames, names);
1574}
1575
1577{
1578 return propsValueFromString("audio", AudioPropsNames, names);
1579}
1580
1581void ProgramInfo::ProgramFlagsFromNames(const QString & names)
1582{
1583 m_programFlags = propsValueFromString("program", ProgramFlagNames, names);
1584}
1585
1590 bool showrerecord,
1591 uint star_range,
1592 uint date_format) const
1593{
1594 QLocale locale = gCoreContext->GetQLocale();
1595 // NOTE: Format changes and relevant additions made here should be
1596 // reflected in RecordingRule
1597 QString channelFormat =
1598 gCoreContext->GetSetting("ChannelFormat", "<num> <sign>");
1599 QString longChannelFormat =
1600 gCoreContext->GetSetting("LongChannelFormat", "<num> <name>");
1601
1602 QDateTime timeNow = MythDate::current();
1603
1604 progMap["title"] = m_title;
1605 progMap["subtitle"] = m_subtitle;
1606 progMap["sorttitle"] = m_sortTitle;
1607 progMap["sortsubtitle"] = m_sortSubtitle;
1608
1609 QString tempSubTitle = m_title;
1610 QString tempSortSubtitle = m_sortTitle;
1611 if (!m_subtitle.trimmed().isEmpty())
1612 {
1613 tempSubTitle = QString("%1 - \"%2\"")
1614 .arg(tempSubTitle, m_subtitle);
1615 tempSortSubtitle = QString("%1 - \"%2\"")
1616 .arg(tempSortSubtitle, m_sortSubtitle);
1617 }
1618
1619 progMap["titlesubtitle"] = tempSubTitle;
1620 progMap["sorttitlesubtitle"] = tempSortSubtitle;
1621
1622 progMap["description"] = progMap["description0"]
1623 = progMap["description1"] = m_description;
1624
1625 progMap["syndicatedepisode"] = m_syndicatedEpisode;
1626 progMap["season"] = progMap["episode"] = "";
1627 progMap["totalepisodes"] = "";
1628 progMap["s00e00"] = progMap["00x00"] = "";
1629
1630 if (m_syndicatedEpisode.isEmpty())
1631 progMap["seasonepisode"] = "";
1632 else
1633 progMap["seasonepisode"] = m_syndicatedEpisode.toLower();
1634
1635 if (m_season > 0 || m_episode > 0)
1636 {
1637 progMap["season"] = StringUtil::intToPaddedString(m_season, 1);
1638 progMap["episode"] = StringUtil::intToPaddedString(m_episode, 1);
1639 progMap["totalepisodes"] = StringUtil::intToPaddedString(m_totalEpisodes, 1);
1640 progMap["s00e00"] = QString("s%1e%2")
1643 progMap["00x00"] = QString("%1x%2")
1646
1647 if (progMap["seasonepisode"].isEmpty())
1648 {
1649 progMap["seasonepisode"] = QString("s%1e%2")
1650 .arg(GetSeason()).arg(GetEpisode());
1651 }
1652 }
1653
1654 progMap["category"] = m_category;
1655 progMap["director"] = m_director;
1656
1657 progMap["callsign"] = m_chanSign;
1658 progMap["commfree"] = (m_programFlags & FL_CHANCOMMFREE) ? "1" : "0";
1659 progMap["outputfilters"] = m_chanPlaybackFilters;
1660 if (IsVideo())
1661 {
1662 progMap["starttime"] = "";
1663 progMap["startdate"] = "";
1664 progMap["endtime"] = "";
1665 progMap["enddate"] = "";
1666 progMap["recstarttime"] = "";
1667 progMap["recstartdate"] = "";
1668 progMap["recendtime"] = "";
1669 progMap["recenddate"] = "";
1670
1671 if (m_startTs.date().year() == 1895)
1672 {
1673 progMap["startdate"] = "";
1674 progMap["recstartdate"] = "";
1675 }
1676 else
1677 {
1678 progMap["startdate"] = m_startTs.toLocalTime().toString("yyyy");
1679 progMap["recstartdate"] = m_startTs.toLocalTime().toString("yyyy");
1680 }
1681 }
1682 else // if (IsRecording())
1683 {
1684 using namespace MythDate;
1685 progMap["starttime"] = MythDate::toString(m_startTs, date_format | kTime);
1686 progMap["startdate"] =
1688 progMap["shortstartdate"] = MythDate::toString(m_startTs, date_format | kDateShort);
1689 progMap["endtime"] = MythDate::toString(m_endTs, date_format | kTime);
1690 progMap["enddate"] = MythDate::toString(m_endTs, date_format | kDateFull | kSimplify);
1691 progMap["shortenddate"] = MythDate::toString(m_endTs, date_format | kDateShort);
1692 progMap["recstarttime"] = MythDate::toString(m_recStartTs, date_format | kTime);
1693 progMap["recstartdate"] = MythDate::toString(m_recStartTs, date_format | kDateShort);
1694 progMap["recendtime"] = MythDate::toString(m_recEndTs, date_format | kTime);
1695 progMap["recenddate"] = MythDate::toString(m_recEndTs, date_format | kDateShort);
1696 progMap["startts"] = QString::number(m_startTs.toSecsSinceEpoch());
1697 progMap["endts"] = QString::number(m_endTs.toSecsSinceEpoch());
1698 if (timeNow.toLocalTime().date().year() !=
1699 m_startTs.toLocalTime().date().year())
1700 progMap["startyear"] = m_startTs.toLocalTime().toString("yyyy");
1701 if (timeNow.toLocalTime().date().year() !=
1702 m_endTs.toLocalTime().date().year())
1703 progMap["endyear"] = m_endTs.toLocalTime().toString("yyyy");
1704 }
1705
1706 using namespace MythDate;
1707 progMap["timedate"] =
1708 MythDate::toString(m_recStartTs, date_format | kDateTimeFull | kSimplify) + " - " +
1709 MythDate::toString(m_recEndTs, date_format | kTime);
1710
1711 progMap["shorttimedate"] =
1712 MythDate::toString(m_recStartTs, date_format | kDateTimeShort | kSimplify) + " - " +
1713 MythDate::toString(m_recEndTs, date_format | kTime);
1714
1715 progMap["starttimedate"] =
1717
1718 progMap["shortstarttimedate"] =
1720
1721 progMap["lastmodifiedtime"] = MythDate::toString(m_lastModified, date_format | kTime);
1722 progMap["lastmodifieddate"] =
1724 progMap["lastmodified"] =
1726
1727 if (m_recordedId)
1728 progMap["recordedid"] = QString::number(m_recordedId);
1729
1730 progMap["channum"] = m_chanStr;
1731 progMap["chanid"] = QString::number(m_chanId);
1732 progMap["channame"] = m_chanName;
1733 progMap["channel"] = ChannelText(channelFormat);
1734 progMap["longchannel"] = ChannelText(longChannelFormat);
1735
1736 QString tmpSize = locale.toString(m_fileSize * (1.0 / (1024.0 * 1024.0 * 1024.0)), 'f', 2);
1737 progMap["filesize_str"] = QObject::tr("%1 GB", "GigaBytes").arg(tmpSize);
1738
1739 progMap["filesize"] = locale.toString((quint64)m_fileSize);
1740
1741 int seconds = m_recStartTs.secsTo(m_recEndTs);
1742 int minutes = seconds / 60;
1743
1744 QString min_str = QObject::tr("%n minute(s)","",minutes);
1745
1746 progMap["lenmins"] = min_str;
1747 int hours = minutes / 60;
1748 minutes = minutes % 60;
1749
1750 progMap["lentime"] = min_str;
1751 if (hours > 0 && minutes > 0)
1752 {
1753 min_str = QObject::tr("%n minute(s)","",minutes);
1754 progMap["lentime"] = QString("%1 %2")
1755 .arg(QObject::tr("%n hour(s)","", hours), min_str);
1756 }
1757 else if (hours > 0)
1758 {
1759 progMap["lentime"] = QObject::tr("%n hour(s)","", hours);
1760 }
1761
1762 progMap["recordedpercent"] =
1763 (m_recordedPercent >= 0)
1764 ? QString::number(m_recordedPercent) : QString();
1765 progMap["watchedpercent"] =
1766 ((m_watchedPercent > 0) && !IsWatched())
1767 ? QString::number(m_watchedPercent) : QString();
1768
1769 // This is calling toChar from recordingtypes.cpp, not the QChar
1770 // constructor.
1771 progMap["rectypechar"] = toQChar(GetRecordingRuleType());
1772 progMap["rectype"] = ::toString(GetRecordingRuleType());
1773 progMap["rectypestatus"] = GetRecTypeStatus(showrerecord);
1774
1775 progMap["card"] = RecStatus::toString(GetRecordingStatus(),
1777 progMap["input"] = RecStatus::toString(GetRecordingStatus(), m_inputId);
1778 progMap["inputname"] = m_inputName;
1779 // Don't add bookmarkupdate to progMap, for now.
1780
1781 progMap["recpriority"] = QString::number(m_recPriority);
1782 progMap["recpriority2"] = QString::number(m_recPriority2);
1783 progMap["recordinggroup"] = (m_recGroup == "Default")
1784 ? QObject::tr("Default") : m_recGroup;
1785 progMap["playgroup"] = m_playGroup;
1786
1787 if (m_storageGroup == "Default")
1788 {
1789 progMap["storagegroup"] = QObject::tr("Default");
1790 }
1792 {
1793 // This relies upon the translation established in the
1794 // definition of StorageGroup::kSpecialGroups.
1795 // clazy:exclude-next-line=tr-non-literal
1796 progMap["storagegroup"] = QObject::tr(m_storageGroup.toUtf8().constData());
1797 }
1798 else
1799 {
1800 progMap["storagegroup"] = m_storageGroup;
1801 }
1802
1803 progMap["programflags"] = QString::number(m_programFlags);
1804 progMap["audioproperties"] = QString::number(m_audioProperties);
1805 progMap["videoproperties"] = QString::number(m_videoProperties);
1806 progMap["subtitleType"] = QString::number(m_subtitleProperties);
1807 progMap["programflags_names"] = GetProgramFlagNames();
1808 progMap["audioproperties_names"] = GetAudioPropertyNames();
1809 progMap["videoproperties_names"] = GetVideoPropertyNames();
1810 progMap["subtitleType_names"] = GetSubtitleTypeNames();
1811
1812 progMap["recstatus"] = RecStatus::toString(GetRecordingStatus(),
1814 progMap["recstatuslong"] = RecStatus::toDescription(GetRecordingStatus(),
1817
1818 if (IsRepeat())
1819 {
1820 progMap["repeat"] = QString("(%1) ").arg(QObject::tr("Repeat"));
1821 progMap["longrepeat"] = progMap["repeat"];
1822 if (m_originalAirDate.isValid())
1823 {
1824 progMap["longrepeat"] = QString("(%1 %2) ")
1825 .arg(QObject::tr("Repeat"),
1828 date_format | MythDate::kDateFull | MythDate::kAddYear));
1829 }
1830 }
1831 else
1832 {
1833 progMap["repeat"] = "";
1834 progMap["longrepeat"] = "";
1835 }
1836
1837 progMap["seriesid"] = m_seriesId;
1838 progMap["programid"] = m_programId;
1839 progMap["inetref"] = m_inetRef;
1840 progMap["catType"] = myth_category_type_to_string(m_catType);
1841
1842 progMap["year"] = m_year > 1895 ? QString::number(m_year) : "";
1843
1844 progMap["partnumber"] = m_partNumber ? QString::number(m_partNumber) : "";
1845 progMap["parttotal"] = m_partTotal ? QString::number(m_partTotal) : "";
1846
1847 QString star_str = (m_stars != 0.0F) ?
1848 QObject::tr("%n star(s)", "", GetStars(star_range)) : "";
1849 progMap["stars"] = star_str;
1850 progMap["numstars"] = QString::number(GetStars(star_range));
1851
1852 if (m_stars != 0.0F && m_year)
1853 progMap["yearstars"] = QString("(%1, %2)").arg(m_year).arg(star_str);
1854 else if (m_stars != 0.0F)
1855 progMap["yearstars"] = QString("(%1)").arg(star_str);
1856 else if (m_year)
1857 progMap["yearstars"] = QString("(%1)").arg(m_year);
1858 else
1859 progMap["yearstars"] = "";
1860
1861 if (!m_originalAirDate.isValid() ||
1862 (!m_programId.isEmpty() && m_programId.startsWith("MV")))
1863 {
1864 progMap["originalairdate"] = "";
1865 progMap["shortoriginalairdate"] = "";
1866 }
1867 else
1868 {
1869 progMap["originalairdate"] = MythDate::toString(
1870 m_originalAirDate, date_format | MythDate::kDateFull);
1871 progMap["shortoriginalairdate"] = MythDate::toString(
1873 }
1874
1875 // 'mediatype' for a statetype, so untranslated
1876 // 'mediatypestring' for textarea, so translated
1877 // TODO Move to a dedicated ToState() method?
1878 QString mediaType;
1879 QString mediaTypeString;
1880 switch (GetProgramInfoType())
1881 {
1883 mediaType = "video";
1884 mediaTypeString = QObject::tr("Video");
1885 break;
1887 mediaType = "dvd";
1888 mediaTypeString = QObject::tr("DVD");
1889 break;
1891 mediaType = "httpstream";
1892 mediaTypeString = QObject::tr("HTTP Streaming");
1893 break;
1895 mediaType = "rtspstream";
1896 mediaTypeString = QObject::tr("RTSP Streaming");
1897 break;
1899 mediaType = "bluraydisc";
1900 mediaTypeString = QObject::tr("Blu-ray Disc");
1901 break;
1902 case kProgramInfoTypeRecording : // Fall through
1903 default :
1904 mediaType = "recording";
1905 mediaTypeString = QObject::tr("Recording",
1906 "Recorded file, object not action");
1907 }
1908 progMap["mediatype"] = mediaType;
1909 progMap["mediatypestring"] = mediaTypeString;
1910}
1911
1913std::chrono::seconds ProgramInfo::GetSecondsInRecording(void) const
1914{
1915 auto recsecs = std::chrono::seconds(m_recStartTs.secsTo(m_endTs));
1916 auto duration = std::chrono::seconds(m_startTs.secsTo(m_endTs));
1917 return (recsecs > 0s) ? recsecs : std::max(duration, 0s);
1918}
1919
1922{
1924}
1925
1928{
1929 uint64_t last_frame = 0;
1930 frm_pos_map_t posMap;
1932 if (posMap.empty())
1933 {
1935 if (posMap.empty())
1937 }
1938 if (!posMap.empty())
1939 {
1940 frm_pos_map_t::const_iterator it = posMap.constEnd();
1941 --it;
1942 last_frame = it.key();
1943 }
1944 return last_frame;
1945}
1946
1948{
1949 if (qsizetype idx = m_inputName.indexOf('/'); idx >= 0)
1950 {
1951 return m_inputName.isRightToLeft() ?
1952 m_inputName.left(idx) : m_inputName.right(idx);
1953 }
1954
1955 return m_inputName.isRightToLeft() ?
1956 m_inputName.left(2) : m_inputName.right(2);
1957}
1958
1960{
1961 return
1962 (m_programId.isEmpty() && m_subtitle.isEmpty() &&
1963 m_description.isEmpty()) ||
1964 (!m_programId.isEmpty() && m_programId.endsWith("0000")
1966}
1967
1968QString ProgramInfo::toString(const Verbosity v, const QString& sep, const QString& grp)
1969 const
1970{
1971 QString str;
1972 switch (v)
1973 {
1974 case kLongDescription:
1975 str = LOC + "channame(" + m_chanName + ")\n";
1976 str += " startts(" +
1977 m_startTs.toString() + ") endts(" + m_endTs.toString() + ")\n";
1978 str += " recstartts(" + m_recStartTs.toString() +
1979 ") recendts(" + m_recEndTs.toString() + ")\n";
1980 str += " title(" + m_title + ")";
1981 break;
1982 case kTitleSubtitle:
1983 str = m_title.contains(' ') ?
1984 QString("%1%2%3").arg(grp, m_title, grp) : m_title;
1985 if (!m_subtitle.isEmpty())
1986 {
1987 str += m_subtitle.contains(' ') ?
1988 QString("%1%2%3%4").arg(sep, grp, m_subtitle, grp) :
1989 QString("%1%2").arg(sep, m_subtitle);
1990 }
1991 break;
1992 case kRecordingKey:
1993 str = QString("%1 at %2")
1995 break;
1996 case kSchedulingKey:
1997 str = QString("%1 @ %2")
1999 break;
2000 }
2001
2002 return str;
2003}
2004
2006{
2008 if (test.GetChanID())
2009 {
2010 clone(test, true);
2011 return true;
2012 }
2013 return false;
2014}
2015
2020 const uint _chanid, const QDateTime &_recstartts)
2021{
2022 if (!_chanid || !_recstartts.isValid())
2023 {
2025 return false;
2026 }
2027
2029 query.prepare(
2031 "WHERE r.chanid = :CHANID AND "
2032 " r.starttime = :RECSTARTTS");
2033 query.bindValue(":CHANID", _chanid);
2034 query.bindValue(":RECSTARTTS", _recstartts);
2035
2036 if (!query.exec())
2037 {
2038 MythDB::DBError("LoadProgramFromRecorded", query);
2040 return false;
2041 }
2042
2043 if (!query.next())
2044 {
2046 return false;
2047 }
2048
2049 bool is_reload = (m_chanId == _chanid) && (m_recStartTs == _recstartts);
2050 if (!is_reload)
2051 {
2052 // These items are not initialized below so they need to be cleared
2053 // if we're loading in a different program into this ProgramInfo
2057 m_recPriority2 = 0;
2058 m_parentId = 0;
2059 m_sourceId = 0;
2060 m_inputId = 0;
2061
2062 // everything below this line (in context) is not serialized
2063 m_spread = m_startCol = -1;
2065 m_inUseForWhat.clear();
2067 }
2068
2069 m_title = query.value(0).toString();
2070 m_subtitle = query.value(1).toString();
2071 m_description = query.value(2).toString();
2072 m_season = query.value(3).toUInt();
2073 if (m_season == 0)
2074 m_season = query.value(51).toUInt();
2075 m_episode = query.value(4).toUInt();
2076 if (m_episode == 0)
2077 m_episode = query.value(52).toUInt();
2078 m_totalEpisodes = query.value(53).toUInt();
2079 m_syndicatedEpisode = query.value(48).toString();
2080 m_category = query.value(5).toString();
2081
2082 m_chanId = _chanid;
2083 m_chanStr = QString("#%1").arg(m_chanId);
2086 m_chanPlaybackFilters.clear();
2087 if (!query.value(7).toString().isEmpty())
2088 {
2089 m_chanStr = query.value(7).toString();
2090 m_chanSign = query.value(8).toString();
2091 m_chanName = query.value(9).toString();
2092 m_chanPlaybackFilters = query.value(10).toString();
2093 }
2094
2095 m_recGroup = query.value(11).toString();
2096 m_playGroup = query.value(12).toString();
2097
2098 // We don't want to update the pathname if the basename is
2099 // the same as we may have already expanded pathname from
2100 // a simple basename to a localized path.
2101 QString new_basename = query.value(14).toString();
2102 if ((GetBasename() != new_basename) || !is_reload)
2103 {
2104 if (is_reload)
2105 {
2106 LOG(VB_FILE, LOG_INFO, LOC +
2107 QString("Updated pathname '%1':'%2' -> '%3'")
2108 .arg(m_pathname, GetBasename(), new_basename));
2109 }
2110 SetPathname(new_basename);
2111 }
2112
2113 m_hostname = query.value(15).toString();
2114 m_storageGroup = query.value(13).toString();
2115
2116 m_seriesId = query.value(17).toString();
2117 m_programId = query.value(18).toString();
2118 m_inetRef = query.value(19).toString();
2119 m_catType = string_to_myth_category_type(query.value(54).toString());
2120
2121 m_recPriority = query.value(16).toInt();
2122
2123 m_fileSize = query.value(20).toULongLong();
2124
2125 m_startTs = MythDate::as_utc(query.value(21).toDateTime());
2126 m_endTs = MythDate::as_utc(query.value(22).toDateTime());
2127 m_recStartTs = MythDate::as_utc(query.value(24).toDateTime());
2128 m_recEndTs = MythDate::as_utc(query.value(25).toDateTime());
2129
2130 m_stars = std::clamp((float)query.value(23).toDouble(), 0.0F, 1.0F);
2131
2132 m_year = query.value(26).toUInt();
2133 m_partNumber = query.value(49).toUInt();
2134 m_partTotal = query.value(50).toUInt();
2135
2136 m_originalAirDate = query.value(27).toDate();
2137 m_lastModified = MythDate::as_utc(query.value(28).toDateTime());
2138 //m_lastInUseTime;
2139
2141
2142 //m_recPriority2;
2143
2144 m_recordId = query.value(29).toUInt();
2145 //m_parentId;
2146
2147 //m_sourcid;
2148 //m_inputId;
2149 //m_cardid;
2150 m_findId = query.value(45).toUInt();
2151
2152 //m_recType;
2153 m_dupIn = RecordingDupInType(query.value(46).toInt());
2154 m_dupMethod = RecordingDupMethodType(query.value(47).toInt());
2155
2156 m_recordedId = query.value(55).toUInt();
2157 m_inputName = query.value(56).toString();
2158 m_bookmarkUpdate = MythDate::as_utc(query.value(57).toDateTime());
2159
2160 // ancillary data -- begin
2161 m_programFlags = FL_NONE;
2162 set_flag(m_programFlags, FL_CHANCOMMFREE,
2163 query.value(30).toInt() == COMM_DETECT_COMMFREE);
2164 set_flag(m_programFlags, FL_COMMFLAG,
2165 query.value(31).toInt() == COMM_FLAG_DONE);
2166 set_flag(m_programFlags, FL_COMMPROCESSING ,
2167 query.value(31).toInt() == COMM_FLAG_PROCESSING);
2168 set_flag(m_programFlags, FL_REPEAT, query.value(32).toBool());
2169 set_flag(m_programFlags, FL_TRANSCODED,
2170 query.value(34).toInt() == TRANSCODING_COMPLETE);
2171 set_flag(m_programFlags, FL_DELETEPENDING, query.value(35).toBool());
2172 set_flag(m_programFlags, FL_PRESERVED, query.value(36).toBool());
2173 set_flag(m_programFlags, FL_CUTLIST, query.value(37).toBool());
2174 set_flag(m_programFlags, FL_AUTOEXP, query.value(38).toBool());
2175 set_flag(m_programFlags, FL_REALLYEDITING, query.value(39).toBool());
2176 set_flag(m_programFlags, FL_BOOKMARK, query.value(40).toBool());
2177 set_flag(m_programFlags, FL_WATCHED, query.value(41).toBool());
2178 set_flag(m_programFlags, FL_LASTPLAYPOS, query.value(58).toBool());
2179 set_flag(m_programFlags, FL_EDITING,
2180 ((m_programFlags & FL_REALLYEDITING) != 0U) ||
2181 ((m_programFlags & FL_COMMPROCESSING) != 0U));
2182
2183 m_audioProperties = query.value(42).toUInt();
2184 m_videoProperties = query.value(43).toUInt();
2185 m_subtitleProperties = query.value(44).toUInt();
2186 // ancillary data -- end
2187
2188 if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1895, 12, 28))
2189 m_originalAirDate = QDate();
2190
2191 // Extra stuff which is not serialized and may get lost.
2192 // m_spread
2193 // m_startCol
2194 // m_availableStatus
2195 // m_inUseForWhat
2196 // m_postitionMapDBReplacement
2197
2198 return true;
2199}
2200
2206{
2207 return (m_title == other.m_title &&
2208 m_chanId == other.m_chanId &&
2209 m_startTs == other.m_startTs);
2210}
2211
2217{
2219 return m_recordId == other.m_recordId;
2220
2221 if (m_findId && m_findId == other.m_findId &&
2222 (m_recordId == other.m_recordId || m_recordId == other.m_parentId))
2223 return true;
2224
2226 return false;
2227
2228 if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2229 return false;
2230
2232 {
2233 if (m_programId.endsWith("0000"))
2234 return false;
2235 }
2236
2237 if (!m_programId.isEmpty() && !other.m_programId.isEmpty())
2238 {
2240 {
2241 int index = m_programId.indexOf('/');
2242 int oindex = other.m_programId.indexOf('/');
2243 if (index == oindex && (index < 0 ||
2244 m_programId.left(index) == other.m_programId.left(oindex)))
2245 return m_programId == other.m_programId;
2246 }
2247 else
2248 {
2249 return m_programId == other.m_programId;
2250 }
2251 }
2252
2253 if ((m_dupMethod & kDupCheckSub) &&
2254 ((m_subtitle.isEmpty()) ||
2255 (m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))
2256 return false;
2257
2258 if ((m_dupMethod & kDupCheckDesc) &&
2259 ((m_description.isEmpty()) ||
2260 (m_description.compare(other.m_description, Qt::CaseInsensitive) != 0)))
2261 return false;
2262
2264 ((m_subtitle.isEmpty() &&
2265 ((!other.m_subtitle.isEmpty() &&
2266 m_description.compare(other.m_subtitle, Qt::CaseInsensitive) != 0) ||
2267 (other.m_subtitle.isEmpty() &&
2268 m_description.compare(other.m_description, Qt::CaseInsensitive) != 0))) ||
2269 (!m_subtitle.isEmpty() &&
2270 ((other.m_subtitle.isEmpty() &&
2271 m_subtitle.compare(other.m_description, Qt::CaseInsensitive) != 0) ||
2272 (!other.m_subtitle.isEmpty() &&
2273 m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))))
2274 return false;
2275
2276 return true;
2277}
2278
2286{
2287 if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2288 return false;
2289
2290 if (!m_programId.isEmpty() && !other.m_programId.isEmpty())
2291 {
2293 {
2294 if (m_programId.endsWith("0000"))
2295 return false;
2296 }
2297
2299 {
2300 int index = m_programId.indexOf('/');
2301 int oindex = other.m_programId.indexOf('/');
2302 if (index == oindex && (index < 0 ||
2303 m_programId.left(index) == other.m_programId.left(oindex)))
2304 return m_programId == other.m_programId;
2305 }
2306 else
2307 {
2308 return m_programId == other.m_programId;
2309 }
2310 }
2311
2312 if ((m_dupMethod & kDupCheckSub) &&
2313 ((m_subtitle.isEmpty()) ||
2314 (m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))
2315 return false;
2316
2317 if ((m_dupMethod & kDupCheckDesc) &&
2318 ((m_description.isEmpty()) ||
2319 (m_description.compare(other.m_description, Qt::CaseInsensitive) != 0)))
2320 return false;
2321
2323 ((m_subtitle.isEmpty() &&
2324 ((!other.m_subtitle.isEmpty() &&
2325 m_description.compare(other.m_subtitle, Qt::CaseInsensitive) != 0) ||
2326 (other.m_subtitle.isEmpty() &&
2327 m_description.compare(other.m_description, Qt::CaseInsensitive) != 0))) ||
2328 (!m_subtitle.isEmpty() &&
2329 ((other.m_subtitle.isEmpty() &&
2330 m_subtitle.compare(other.m_description, Qt::CaseInsensitive) != 0) ||
2331 (!other.m_subtitle.isEmpty() &&
2332 m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))))
2333 return false;
2334
2335 return true;
2336}
2337
2344{
2345 if (m_startTs != other.m_startTs)
2346 return false;
2347 if (IsSameChannel(other))
2348 return true;
2349 if (!IsSameProgram(other))
2350 return false;
2351 return true;
2352}
2353
2360{
2361 if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2362 return false;
2363 return m_startTs == other.m_startTs &&
2364 IsSameChannel(other);
2365}
2366
2374{
2375 if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2376 return false;
2377 return IsSameChannel(other) &&
2378 m_startTs < other.m_endTs &&
2379 m_endTs > other.m_startTs;
2380}
2381
2389{
2390 return m_chanId == other.m_chanId ||
2391 (!m_chanSign.isEmpty() &&
2392 m_chanSign.compare(other.m_chanSign, Qt::CaseInsensitive) == 0);
2393}
2394
2396{
2397 QMap<QString, int> authMap;
2398 std::array<QString,3> tables { "program", "recorded", "oldrecorded" };
2400
2401 for (const QString& table : tables)
2402 {
2403 query.prepare(QString(
2404 "SELECT DISTINCT LEFT(programid, LOCATE('/', programid)) "
2405 "FROM %1 WHERE programid <> ''").arg(table));
2406 if (!query.exec())
2407 {
2408 MythDB::DBError("CheckProgramIDAuthorities", query);
2409 }
2410 else
2411 {
2412 while (query.next())
2413 authMap[query.value(0).toString()] = 1;
2414 }
2415 }
2416
2417 int numAuths = authMap.count();
2418 LOG(VB_GENERAL, LOG_INFO,
2419 QString("Found %1 distinct programid authorities").arg(numAuths));
2420
2421 s_usingProgIDAuth = (numAuths > 1);
2422}
2423
2428QString ProgramInfo::CreateRecordBasename(const QString &ext) const
2429{
2431
2432 QString retval = QString("%1_%2.%3")
2433 .arg(QString::number(m_chanId), starts, ext);
2434
2435 return retval;
2436}
2437
2439 uint chanid, const QString &pathname, bool use_remote)
2440{
2441 QString fn_lower = pathname.toLower();
2443 if (chanid)
2444 {
2446 }
2447 else if (fn_lower.startsWith("http:"))
2448 {
2450 }
2451 else if (fn_lower.startsWith("rtsp:"))
2452 {
2454 }
2455 else
2456 {
2457 fn_lower = determineURLType(pathname);
2458
2459 if (fn_lower.startsWith("dvd:"))
2460 {
2462 }
2463 else if (fn_lower.startsWith("bd:"))
2464 {
2466 }
2467 else if (use_remote && fn_lower.startsWith("myth://"))
2468 {
2469 QString tmpFileDVD = pathname + "/VIDEO_TS";
2470 QString tmpFileBD = pathname + "/BDMV";
2471 if (RemoteFile::Exists(tmpFileDVD))
2473 else if (RemoteFile::Exists(tmpFileBD))
2475 }
2476 }
2477 return pit;
2478}
2479
2480void ProgramInfo::SetPathname(const QString &pn)
2481{
2482 m_pathname = pn;
2483
2485 SetProgramInfoType(pit);
2486}
2487
2489{
2491}
2492
2494 AvailableStatusType status, const QString &where)
2495{
2496 if (status != m_availableStatus)
2497 {
2498 LOG(VB_GUI, LOG_INFO,
2499 toString(kTitleSubtitle) + QString(": %1 -> %2 in %3")
2501 ::toString(status),
2502 where));
2503 }
2504 m_availableStatus = status;
2505}
2506
2510bool ProgramInfo::SaveBasename(const QString &basename)
2511{
2513 query.prepare("UPDATE recorded "
2514 "SET basename = :BASENAME "
2515 "WHERE recordedid = :RECORDEDID;");
2516 query.bindValue(":RECORDEDID", m_recordedId);
2517 query.bindValue(":BASENAME", basename);
2518
2519 if (!query.exec())
2520 {
2521 MythDB::DBError("SetRecordBasename", query);
2522 return false;
2523 }
2524
2525 query.prepare("UPDATE recordedfile "
2526 "SET basename = :BASENAME "
2527 "WHERE recordedid = :RECORDEDID;");
2528 query.bindValue(":RECORDEDID", m_recordedId);
2529 query.bindValue(":BASENAME", basename);
2530
2531 if (!query.exec())
2532 {
2533 MythDB::DBError("SetRecordBasename", query);
2534 return false;
2535 }
2536
2537 SetPathname(basename);
2538
2540 return true;
2541}
2542
2551{
2552 QString bn = GetBasename();
2553 if (!bn.isEmpty())
2554 return bn;
2555
2557 query.prepare(
2558 "SELECT basename "
2559 "FROM recordedfile "
2560 "WHERE recordedid = :RECORDEDID;");
2561 query.bindValue(":RECORDEDID", m_recordedId);
2562
2563 if (!query.exec())
2564 {
2565 MythDB::DBError("QueryBasename", query);
2566 }
2567 else if (query.next())
2568 {
2569 return query.value(0).toString();
2570 }
2571 else
2572 {
2573 LOG(VB_GENERAL, LOG_INFO,
2574 QString("QueryBasename found no entry for recording ID %1")
2575 .arg(m_recordedId));
2576 }
2577
2578 return {};
2579}
2580
2589 bool checkMaster, bool forceCheckLocal)
2590{
2591 // return the original path if BD or DVD URI
2592 if (IsVideoBD() || IsVideoDVD())
2593 return GetPathname();
2594
2595 QString basename = QueryBasename();
2596 if (basename.isEmpty())
2597 return "";
2598
2599 bool checklocal = !gCoreContext->GetBoolSetting("AlwaysStreamFiles", false) ||
2600 forceCheckLocal;
2601
2602 if (IsVideo())
2603 {
2604 QString fullpath = GetPathname();
2605 if (!fullpath.startsWith("myth://", Qt::CaseInsensitive) || !checklocal)
2606 return fullpath;
2607
2608 QUrl url = QUrl(fullpath);
2609 QString path = url.path();
2610 QString host = url.toString(QUrl::RemovePath).mid(7);
2611 QStringList list = host.split(":", Qt::SkipEmptyParts);
2612 if (!list.empty())
2613 {
2614 host = list[0];
2615 list = host.split("@", Qt::SkipEmptyParts);
2616 QString group;
2617 if (!list.empty() && list.size() < 3)
2618 {
2619 host = list.size() == 1 ? list[0] : list[1];
2620 group = list.size() == 1 ? QString() : list[0];
2621 StorageGroup sg = StorageGroup(group, host);
2622 QString local = sg.FindFile(path);
2623 if (!local.isEmpty() && sg.FileExists(local))
2624 return local;
2625 }
2626 }
2627 return fullpath;
2628 }
2629
2630 QString tmpURL;
2631 if (checklocal)
2632 {
2633 // Check to see if the file exists locally
2635#if 0
2636 LOG(VB_FILE, LOG_DEBUG, LOC +
2637 QString("GetPlaybackURL: CHECKING SG : %1 : ").arg(tmpURL));
2638#endif
2639 tmpURL = sgroup.FindFile(basename);
2640
2641 if (!tmpURL.isEmpty())
2642 {
2643 LOG(VB_FILE, LOG_INFO, LOC +
2644 QString("GetPlaybackURL: File is local: '%1'") .arg(tmpURL));
2645 return tmpURL;
2646 }
2648 {
2649 LOG(VB_GENERAL, LOG_ERR, LOC +
2650 QString("GetPlaybackURL: '%1' should be local, but it can "
2651 "not be found.").arg(basename));
2652 // Note do not preceed with "/" that will cause existing code
2653 // to look for a local file with this name...
2654 return QString("GetPlaybackURL/UNABLE/TO/FIND/LOCAL/FILE/ON/%1/%2")
2655 .arg(m_hostname, basename);
2656 }
2657 }
2658
2659 // Check to see if we should stream from the master backend
2660 if (checkMaster &&
2661 (gCoreContext->GetBoolSetting("MasterBackendOverride", false)) &&
2662 (RemoteCheckFile(this, false)))
2663 {
2666 basename);
2667
2668 LOG(VB_FILE, LOG_INFO, LOC +
2669 QString("GetPlaybackURL: Found @ '%1'").arg(tmpURL));
2670 return tmpURL;
2671 }
2672
2673 // Fallback to streaming from the backend the recording was created on
2676 basename);
2677
2678 LOG(VB_FILE, LOG_INFO, LOC +
2679 QString("GetPlaybackURL: Using default of: '%1'") .arg(tmpURL));
2680
2681 return tmpURL;
2682}
2683
2687{
2688 uint ret = 0U;
2689 if (m_chanId)
2690 {
2692
2693 query.prepare("SELECT mplexid FROM channel "
2694 "WHERE chanid = :CHANID");
2695 query.bindValue(":CHANID", m_chanId);
2696
2697 if (!query.exec())
2698 MythDB::DBError("QueryMplexID", query);
2699 else if (query.next())
2700 ret = query.value(0).toUInt();
2701
2702 // clear out bogus mplexid's
2703 ret = (32767 == ret) ? 0 : ret;
2704 }
2705
2706 return ret;
2707}
2708
2711void ProgramInfo::SaveBookmark(uint64_t frame)
2712{
2714
2715 bool is_valid = (frame > 0);
2716 if (is_valid)
2717 {
2718 frm_dir_map_t bookmarkmap;
2719 bookmarkmap[frame] = MARK_BOOKMARK;
2720 SaveMarkupMap(bookmarkmap);
2721 }
2722
2723 set_flag(m_programFlags, FL_BOOKMARK, is_valid);
2724
2725 UpdateMarkTimeStamp(is_valid);
2727}
2728
2729void ProgramInfo::UpdateMarkTimeStamp(bool bookmarked) const
2730{
2731 if (IsRecording())
2732 {
2734 query.prepare(
2735 "UPDATE recorded "
2736 "SET bookmarkupdate = CURRENT_TIMESTAMP, "
2737 " bookmark = :BOOKMARKFLAG "
2738 "WHERE recordedid = :RECORDEDID");
2739
2740 query.bindValue(":BOOKMARKFLAG", bookmarked);
2741 query.bindValue(":RECORDEDID", m_recordedId);
2742
2743 if (!query.exec())
2744 MythDB::DBError("bookmark flag update", query);
2745 }
2746}
2747
2749{
2751
2752 bool isValid = frame > 0;
2753 if (isValid)
2754 {
2755 frm_dir_map_t lastPlayPosMap;
2756 lastPlayPosMap[frame] = MARK_UTIL_LASTPLAYPOS;
2757 SaveMarkupMap(lastPlayPosMap, MARK_UTIL_LASTPLAYPOS);
2758 }
2759
2760 set_flag(m_programFlags, FL_LASTPLAYPOS, isValid);
2761
2762 UpdateLastPlayTimeStamp(isValid);
2764}
2765
2766// This function overloads the 'bookmarkupdate' field to force the UI
2767// to update when the last play timestamp is updated. The alternative
2768// is adding another field to the database and to the programinfo
2769// serialization.
2770void ProgramInfo::UpdateLastPlayTimeStamp(bool hasLastPlay) const
2771{
2772 if (IsRecording())
2773 {
2775 query.prepare(
2776 "UPDATE recorded "
2777 "SET bookmarkupdate = CURRENT_TIMESTAMP, "
2778 " lastplay = :LASTPLAYFLAG "
2779 "WHERE recordedid = :RECORDEDID");
2780
2781 query.bindValue(":LASTPLAYFLAG", hasLastPlay);
2782 query.bindValue(":RECORDEDID", m_recordedId);
2783
2784 if (!query.exec())
2785 MythDB::DBError("lastplay flag update", query);
2786 }
2787}
2788
2790{
2791 if (IsRecording())
2793}
2794
2796{
2798}
2799
2801{
2803}
2804
2809{
2811 query.prepare(
2812 "SELECT bookmarkupdate "
2813 "FROM recorded "
2814 "WHERE chanid = :CHANID AND"
2815 " starttime = :STARTTIME");
2816 query.bindValue(":CHANID", m_chanId);
2817 query.bindValue(":STARTTIME", m_recStartTs);
2818
2819 QDateTime ts;
2820
2821 if (!query.exec())
2822 MythDB::DBError("ProgramInfo::GetBookmarkTimeStamp()", query);
2823 else if (query.next())
2824 ts = MythDate::as_utc(query.value(0).toDateTime());
2825
2826 return ts;
2827}
2828
2835uint64_t ProgramInfo::QueryBookmark(void) const
2836{
2837 if (m_programFlags & FL_IGNOREBOOKMARK)
2838 return 0;
2839
2840 frm_dir_map_t bookmarkmap;
2841 QueryMarkupMap(bookmarkmap, MARK_BOOKMARK);
2842
2843 return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2844}
2845
2846uint64_t ProgramInfo::QueryBookmark(uint chanid, const QDateTime &recstartts)
2847{
2848 frm_dir_map_t bookmarkmap;
2850 chanid, recstartts,
2851 bookmarkmap, MARK_BOOKMARK);
2852
2853 return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2854}
2855
2863{
2864 if (m_programFlags & FL_IGNORELASTPLAYPOS)
2865 return 0;
2866
2867 frm_dir_map_t bookmarkmap;
2869
2870 return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2871}
2872
2880{
2881 if (m_programFlags & FL_IGNOREPROGSTART)
2882 return 0;
2883
2884 frm_dir_map_t bookmarkmap;
2885 QueryMarkupMap(bookmarkmap, MARK_UTIL_PROGSTART);
2886
2887 return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2888}
2889
2891{
2892 uint64_t start = QueryLastPlayPos();
2893 if (start > 0)
2894 {
2895 LOG(VB_PLAYBACK, LOG_INFO, QString("Using last position @ %1").arg(start));
2896 return start;
2897 }
2898
2899 start = QueryBookmark();
2900 if (start > 0)
2901 {
2902 LOG(VB_PLAYBACK, LOG_INFO, QString("Using bookmark @ %1").arg(start));
2903 return start;
2904 }
2905
2906 if (HasCutlist())
2907 {
2908 // Disable progstart if the program has a cutlist.
2909 LOG(VB_PLAYBACK, LOG_INFO, "Ignoring progstart as cutlist exists");
2910 return 0;
2911 }
2912
2913 start = QueryProgStart();
2914 if (start > 0)
2915 {
2916 LOG(VB_PLAYBACK, LOG_INFO, QString("Using progstart @ %1").arg(start));
2917 return start;
2918 }
2919
2920 LOG(VB_PLAYBACK, LOG_INFO, "Using file start");
2921 return 0;
2922}
2923
2930 const QString &serialid) const
2931{
2932 QStringList fields = QStringList();
2934
2935 if (!(m_programFlags & FL_IGNOREBOOKMARK))
2936 {
2937 query.prepare(" SELECT dvdstate, title, framenum, audionum, subtitlenum "
2938 " FROM dvdbookmark "
2939 " WHERE serialid = :SERIALID ");
2940 query.bindValue(":SERIALID", serialid);
2941
2942 if (query.exec() && query.next())
2943 {
2944 QString dvdstate = query.value(0).toString();
2945
2946 if (!dvdstate.isEmpty())
2947 {
2948 fields.append(dvdstate);
2949 }
2950 else
2951 {
2952 fields.reserve(5);
2953 // Legacy bookmark
2954 for(int i = 1; i < 5; i++)
2955 fields.append(query.value(i).toString());
2956 }
2957 }
2958 }
2959
2960 return fields;
2961}
2962
2963void ProgramInfo::SaveDVDBookmark(const QStringList &fields)
2964{
2965 QStringList::const_iterator it = fields.begin();
2967
2968 QString serialid = *it;
2969 QString name = *(++it);
2970
2971 if( fields.count() == 3 )
2972 {
2973 // We have a state field, so update/create the bookmark
2974 QString state = *(++it);
2975
2976 query.prepare("INSERT IGNORE INTO dvdbookmark "
2977 " (serialid, name)"
2978 " VALUES ( :SERIALID, :NAME );");
2979 query.bindValue(":SERIALID", serialid);
2980 query.bindValue(":NAME", name);
2981
2982 if (!query.exec())
2983 MythDB::DBError("SetDVDBookmark inserting", query);
2984
2985 query.prepare(" UPDATE dvdbookmark "
2986 " SET dvdstate = :STATE , "
2987 " timestamp = NOW() "
2988 " WHERE serialid = :SERIALID");
2989 query.bindValue(":STATE",state);
2990 query.bindValue(":SERIALID",serialid);
2991 }
2992 else
2993 {
2994 // No state field, delete the bookmark
2995 query.prepare("DELETE FROM dvdbookmark "
2996 "WHERE serialid = :SERIALID");
2997 query.bindValue(":SERIALID",serialid);
2998 }
2999
3000 if (!query.exec())
3001 MythDB::DBError("SetDVDBookmark updating", query);
3002}
3003
3007QStringList ProgramInfo::QueryBDBookmark(const QString &serialid) const
3008{
3009 QStringList fields = QStringList();
3011
3012 if (!(m_programFlags & FL_IGNOREBOOKMARK))
3013 {
3014 query.prepare(" SELECT bdstate FROM bdbookmark "
3015 " WHERE serialid = :SERIALID ");
3016 query.bindValue(":SERIALID", serialid);
3017
3018 if (query.exec() && query.next())
3019 fields.append(query.value(0).toString());
3020 }
3021
3022 return fields;
3023}
3024
3025void ProgramInfo::SaveBDBookmark(const QStringList &fields)
3026{
3027 QStringList::const_iterator it = fields.begin();
3029
3030 QString serialid = *it;
3031 QString name = *(++it);
3032
3033 if( fields.count() == 3 )
3034 {
3035 // We have a state field, so update/create the bookmark
3036 QString state = *(++it);
3037
3038 query.prepare("INSERT IGNORE INTO bdbookmark "
3039 " (serialid, name)"
3040 " VALUES ( :SERIALID, :NAME );");
3041 query.bindValue(":SERIALID", serialid);
3042 query.bindValue(":NAME", name);
3043
3044 if (!query.exec())
3045 MythDB::DBError("SetBDBookmark inserting", query);
3046
3047 query.prepare(" UPDATE bdbookmark "
3048 " SET bdstate = :STATE , "
3049 " timestamp = NOW() "
3050 " WHERE serialid = :SERIALID");
3051 query.bindValue(":STATE",state);
3052 query.bindValue(":SERIALID",serialid);
3053 }
3054 else
3055 {
3056 // No state field, delete the bookmark
3057 query.prepare("DELETE FROM bdbookmark "
3058 "WHERE serialid = :SERIALID");
3059 query.bindValue(":SERIALID",serialid);
3060 }
3061
3062 if (!query.exec())
3063 MythDB::DBError("SetBDBookmark updating", query);
3064}
3065
3072{
3074
3076
3077 query.prepare(" SELECT category_type "
3078 " FROM recordedprogram "
3079 " WHERE chanid = :CHANID "
3080 " AND starttime = :STARTTIME;");
3081
3082 query.bindValue(":CHANID", m_chanId);
3083 query.bindValue(":STARTTIME", m_startTs);
3084
3085 if (query.exec() && query.next())
3086 {
3087 ret = string_to_myth_category_type(query.value(0).toString());
3088 }
3089
3090 return ret;
3091}
3092
3095{
3096 if (IsRecording())
3097 {
3099
3100 query.prepare("UPDATE recorded"
3101 " SET watched = :WATCHEDFLAG"
3102 " WHERE chanid = :CHANID"
3103 " AND starttime = :STARTTIME ;");
3104 query.bindValue(":CHANID", m_chanId);
3105 query.bindValue(":STARTTIME", m_recStartTs);
3106 query.bindValue(":WATCHEDFLAG", watched);
3107
3108 if (!query.exec())
3109 MythDB::DBError("Set watched flag", query);
3110 else
3111 UpdateLastDelete(watched);
3112
3114 }
3115 else if (IsVideoFile())
3116 {
3117 QString url = m_pathname;
3118 if (url.startsWith("myth://"))
3119 {
3120 url = QUrl(url).path();
3121 url.remove(0,1);
3122 }
3123
3125 query.prepare("UPDATE videometadata"
3126 " SET watched = :WATCHEDFLAG"
3127 " WHERE title = :TITLE"
3128 " AND subtitle = :SUBTITLE"
3129 " AND filename = :FILENAME ;");
3130 query.bindValue(":TITLE", m_title);
3131 query.bindValue(":SUBTITLE", m_subtitle);
3132 query.bindValue(":FILENAME", url);
3133 query.bindValue(":WATCHEDFLAG", watched);
3134
3135 if (!query.exec())
3136 MythDB::DBError("Set watched flag", query);
3137 }
3138
3139 set_flag(m_programFlags, FL_WATCHED, watched);
3140}
3141
3147{
3148 bool editing = (m_programFlags & FL_REALLYEDITING) != 0U;
3149
3151
3152 query.prepare("SELECT editing FROM recorded"
3153 " WHERE chanid = :CHANID"
3154 " AND starttime = :STARTTIME ;");
3155 query.bindValue(":CHANID", m_chanId);
3156 query.bindValue(":STARTTIME", m_recStartTs);
3157
3158 if (query.exec() && query.next())
3159 editing = query.value(0).toBool();
3160
3161 /*
3162 set_flag(programflags, FL_REALLYEDITING, editing);
3163 set_flag(programflags, FL_EDITING, ((programflags & FL_REALLYEDITING) ||
3164 (programflags & COMM_FLAG_PROCESSING)));
3165 */
3166 return editing;
3167}
3168
3173{
3175
3176 query.prepare("UPDATE recorded"
3177 " SET editing = :EDIT"
3178 " WHERE chanid = :CHANID"
3179 " AND starttime = :STARTTIME ;");
3180 query.bindValue(":EDIT", edit);
3181 query.bindValue(":CHANID", m_chanId);
3182 query.bindValue(":STARTTIME", m_recStartTs);
3183
3184 if (!query.exec())
3185 MythDB::DBError("Edit status update", query);
3186
3187 set_flag(m_programFlags, FL_REALLYEDITING, edit);
3188 set_flag(m_programFlags, FL_EDITING, (((m_programFlags & FL_REALLYEDITING) != 0U) ||
3190
3192}
3193
3198{
3200
3201 query.prepare("UPDATE recorded"
3202 " SET deletepending = :DELETEFLAG, "
3203 " duplicate = 0 "
3204 " WHERE chanid = :CHANID"
3205 " AND starttime = :STARTTIME ;");
3206 query.bindValue(":CHANID", m_chanId);
3207 query.bindValue(":STARTTIME", m_recStartTs);
3208 query.bindValue(":DELETEFLAG", deleteFlag);
3209
3210 if (!query.exec())
3211 MythDB::DBError("SaveDeletePendingFlag", query);
3212
3213 set_flag(m_programFlags, FL_DELETEPENDING, deleteFlag);
3214
3215 if (!deleteFlag)
3217
3219}
3220
3225bool ProgramInfo::QueryIsInUse(QStringList &byWho) const
3226{
3227 if (!IsRecording())
3228 return false;
3229
3230 QDateTime oneHourAgo = MythDate::current().addSecs(-kLastUpdateOffset);
3232
3233 query.prepare("SELECT hostname, recusage FROM inuseprograms "
3234 " WHERE chanid = :CHANID"
3235 " AND starttime = :STARTTIME "
3236 " AND lastupdatetime > :ONEHOURAGO ;");
3237 query.bindValue(":CHANID", m_chanId);
3238 query.bindValue(":STARTTIME", m_recStartTs);
3239 query.bindValue(":ONEHOURAGO", oneHourAgo);
3240
3241 byWho.clear();
3242 if (query.exec() && query.size() > 0)
3243 {
3244 QString usageStr;
3245 QString recusage;
3246 while (query.next())
3247 {
3248 usageStr = QObject::tr("Unknown");
3249 recusage = query.value(1).toString();
3250
3251 if (recusage == kPlayerInUseID)
3252 usageStr = QObject::tr("Playing");
3253 else if (recusage == kPIPPlayerInUseID)
3254 usageStr = QObject::tr("PIP");
3255 else if (recusage == kPBPPlayerInUseID)
3256 usageStr = QObject::tr("PBP");
3257 else if ((recusage == kRecorderInUseID) ||
3258 (recusage == kImportRecorderInUseID))
3259 usageStr = QObject::tr("Recording");
3260 else if (recusage == kFileTransferInUseID)
3261 usageStr = QObject::tr("File transfer");
3262 else if (recusage == kTruncatingDeleteInUseID)
3263 usageStr = QObject::tr("Delete");
3264 else if (recusage == kFlaggerInUseID)
3265 usageStr = QObject::tr("Commercial Detection");
3266 else if (recusage == kTranscoderInUseID)
3267 usageStr = QObject::tr("Transcoding");
3268 else if (recusage == kPreviewGeneratorInUseID)
3269 usageStr = QObject::tr("Preview Generation");
3270 else if (recusage == kJobQueueInUseID)
3271 usageStr = QObject::tr("User Job");
3272
3273 byWho.push_back(recusage);
3274 byWho.push_back(query.value(0).toString());
3275 byWho.push_back(query.value(0).toString() + " (" + usageStr + ")");
3276 }
3277
3278 return true;
3279 }
3280
3281 return false;
3282}
3283
3288bool ProgramInfo::QueryIsInUse(QString &byWho) const
3289{
3290 QStringList users;
3291 bool inuse = QueryIsInUse(users);
3292 byWho.clear();
3293 for (int i = 0; i+2 < users.size(); i+=3)
3294 byWho += users[i+2] + "\n";
3295 return inuse;
3296}
3297
3298
3305bool ProgramInfo::QueryIsDeleteCandidate(bool one_playback_allowed) const
3306{
3307 if (!IsRecording())
3308 return false;
3309
3310 // gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete", 0) &&
3311 if (GetRecordingGroup() != "Deleted" && GetRecordingGroup() != "LiveTV")
3312 return true;
3313
3314 bool ok = true;
3315 QStringList byWho;
3316 if (QueryIsInUse(byWho) && !byWho.isEmpty())
3317 {
3318 uint play_cnt = 0;
3319 uint ft_cnt = 0;
3320 uint jq_cnt = 0;
3321 for (uint i = 0; (i+2 < (uint)byWho.size()) && ok; i+=3)
3322 {
3323 play_cnt += byWho[i].contains(kPlayerInUseID) ? 1 : 0;
3324 ft_cnt += (byWho[i].contains(kFlaggerInUseID) ||
3325 byWho[i].contains(kTranscoderInUseID)) ? 1 : 0;
3326 jq_cnt += (byWho[i].contains(kJobQueueInUseID)) ? 1 : 0;
3327 ok = ok && (byWho[i].contains(kRecorderInUseID) ||
3328 byWho[i].contains(kFlaggerInUseID) ||
3329 byWho[i].contains(kTranscoderInUseID) ||
3330 byWho[i].contains(kJobQueueInUseID) ||
3331 (one_playback_allowed && (play_cnt <= 1)));
3332 }
3333 ok = ok && (ft_cnt == jq_cnt);
3334 }
3335
3336 return ok;
3337}
3338
3341{
3343
3344 query.prepare("SELECT transcoded FROM recorded"
3345 " WHERE chanid = :CHANID"
3346 " AND starttime = :STARTTIME ;");
3347 query.bindValue(":CHANID", m_chanId);
3348 query.bindValue(":STARTTIME", m_recStartTs);
3349
3350 if (query.exec() && query.next())
3351 return (TranscodingStatus) query.value(0).toUInt();
3353}
3354
3361{
3363
3364 query.prepare(
3365 "UPDATE recorded "
3366 "SET transcoded = :VALUE "
3367 "WHERE chanid = :CHANID AND"
3368 " starttime = :STARTTIME");
3369 query.bindValue(":VALUE", (uint)trans);
3370 query.bindValue(":CHANID", m_chanId);
3371 query.bindValue(":STARTTIME", m_recStartTs);
3372
3373 if (!query.exec())
3374 MythDB::DBError("Transcoded status update", query);
3375
3376 set_flag(m_programFlags, FL_TRANSCODED, TRANSCODING_COMPLETE == trans);
3378}
3379
3384{
3386
3387 query.prepare("UPDATE recorded"
3388 " SET commflagged = :FLAG"
3389 " WHERE chanid = :CHANID"
3390 " AND starttime = :STARTTIME ;");
3391 query.bindValue(":FLAG", (int)flag);
3392 query.bindValue(":CHANID", m_chanId);
3393 query.bindValue(":STARTTIME", m_recStartTs);
3394
3395 if (!query.exec())
3396 MythDB::DBError("Commercial Flagged status update", query);
3397
3398 set_flag(m_programFlags, FL_COMMFLAG, COMM_FLAG_DONE == flag);
3399 set_flag(m_programFlags, FL_COMMPROCESSING, COMM_FLAG_PROCESSING == flag);
3400 set_flag(m_programFlags, FL_EDITING, (((m_programFlags & FL_REALLYEDITING) != 0U) ||
3403}
3404
3405
3409void ProgramInfo::SavePreserve(bool preserveEpisode)
3410{
3412
3413 query.prepare("UPDATE recorded"
3414 " SET preserve = :PRESERVE"
3415 " WHERE chanid = :CHANID"
3416 " AND starttime = :STARTTIME ;");
3417 query.bindValue(":PRESERVE", preserveEpisode);
3418 query.bindValue(":CHANID", m_chanId);
3419 query.bindValue(":STARTTIME", m_recStartTs);
3420
3421 if (!query.exec())
3422 MythDB::DBError("PreserveEpisode update", query);
3423 else
3424 UpdateLastDelete(false);
3425
3426 set_flag(m_programFlags, FL_PRESERVED, preserveEpisode);
3427
3429}
3430
3436void ProgramInfo::SaveAutoExpire(AutoExpireType autoExpire, bool updateDelete)
3437{
3439
3440 query.prepare("UPDATE recorded"
3441 " SET autoexpire = :AUTOEXPIRE"
3442 " WHERE chanid = :CHANID"
3443 " AND starttime = :STARTTIME ;");
3444 query.bindValue(":AUTOEXPIRE", (uint)autoExpire);
3445 query.bindValue(":CHANID", m_chanId);
3446 query.bindValue(":STARTTIME", m_recStartTs);
3447
3448 if (!query.exec())
3449 MythDB::DBError("AutoExpire update", query);
3450 else if (updateDelete)
3451 UpdateLastDelete(true);
3452
3453 set_flag(m_programFlags, FL_AUTOEXP, autoExpire != kDisableAutoExpire);
3454
3456}
3457
3462void ProgramInfo::UpdateLastDelete(bool setTime) const
3463{
3465
3466 if (setTime)
3467 {
3468 QDateTime timeNow = MythDate::current();
3469 auto delay_secs = std::chrono::seconds(m_recStartTs.secsTo(timeNow));
3470 auto delay = duration_cast<std::chrono::hours>(delay_secs);
3471 delay = std::clamp(delay, 1h, 200h);
3472
3473 query.prepare("UPDATE record SET last_delete = :TIME, "
3474 "avg_delay = (avg_delay * 3 + :DELAY) / 4 "
3475 "WHERE recordid = :RECORDID");
3476 query.bindValue(":TIME", timeNow);
3477 query.bindValue(":DELAY", static_cast<qint64>(delay.count()));
3478 }
3479 else
3480 {
3481 query.prepare("UPDATE record SET last_delete = NULL "
3482 "WHERE recordid = :RECORDID");
3483 }
3484 query.bindValue(":RECORDID", m_recordId);
3485
3486 if (!query.exec())
3487 MythDB::DBError("Update last_delete", query);
3488}
3489
3492{
3494
3495 query.prepare("SELECT autoexpire FROM recorded"
3496 " WHERE chanid = :CHANID"
3497 " AND starttime = :STARTTIME ;");
3498 query.bindValue(":CHANID", m_chanId);
3499 query.bindValue(":STARTTIME", m_recStartTs);
3500
3501 if (query.exec() && query.next())
3502 return (AutoExpireType) query.value(0).toInt();
3503
3504 return kDisableAutoExpire;
3505}
3506
3507bool ProgramInfo::QueryCutList(frm_dir_map_t &delMap, bool loadAutoSave) const
3508{
3509 if (loadAutoSave)
3510 {
3511 frm_dir_map_t autosaveMap;
3512 QueryMarkupMap(autosaveMap, MARK_TMP_CUT_START);
3513 QueryMarkupMap(autosaveMap, MARK_TMP_CUT_END, true);
3514 QueryMarkupMap(autosaveMap, MARK_PLACEHOLDER, true);
3515 // Convert the temporary marks into regular marks.
3516 delMap.clear();
3517 // NOLINTNEXTLINE(modernize-loop-convert)
3518 for (auto i = autosaveMap.constBegin(); i != autosaveMap.constEnd(); ++i)
3519 {
3520 uint64_t frame = i.key();
3521 MarkTypes mark = i.value();
3522 if (mark == MARK_TMP_CUT_START)
3523 mark = MARK_CUT_START;
3524 else if (mark == MARK_TMP_CUT_END)
3525 mark = MARK_CUT_END;
3526 delMap[frame] = mark;
3527 }
3528 }
3529 else
3530 {
3532 QueryMarkupMap(delMap, MARK_CUT_END, true);
3533 QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
3534 }
3535
3536 return !delMap.isEmpty();
3537}
3538
3539void ProgramInfo::SaveCutList(frm_dir_map_t &delMap, bool isAutoSave) const
3540{
3541 if (!isAutoSave)
3542 {
3545 }
3549
3550 frm_dir_map_t tmpDelMap;
3551 // NOLINTNEXTLINE(modernize-loop-convert)
3552 for (auto i = delMap.constBegin(); i != delMap.constEnd(); ++i)
3553 {
3554 uint64_t frame = i.key();
3555 MarkTypes mark = i.value();
3556 if (isAutoSave)
3557 {
3558 if (mark == MARK_CUT_START)
3559 mark = MARK_TMP_CUT_START;
3560 else if (mark == MARK_CUT_END)
3561 mark = MARK_TMP_CUT_END;
3562 }
3563 tmpDelMap[frame] = mark;
3564 }
3565 SaveMarkupMap(tmpDelMap);
3566
3567 if (IsRecording())
3568 {
3570
3571 // Flag the existence of a cutlist
3572 query.prepare("UPDATE recorded"
3573 " SET cutlist = :CUTLIST"
3574 " WHERE chanid = :CHANID"
3575 " AND starttime = :STARTTIME ;");
3576
3577 query.bindValue(":CUTLIST", delMap.isEmpty() ? 0 : 1);
3578 query.bindValue(":CHANID", m_chanId);
3579 query.bindValue(":STARTTIME", m_recStartTs);
3580
3581 if (!query.exec())
3582 MythDB::DBError("cutlist flag update", query);
3583 }
3584}
3585
3587{
3590 SaveMarkupMap(frames);
3591}
3592
3594{
3596 QueryMarkupMap(frames, MARK_COMM_END, true);
3597}
3598
3600 MarkTypes type, int64_t min_frame, int64_t max_frame) const
3601{
3603 QString comp;
3604
3605 if (min_frame >= 0)
3606 comp += QString(" AND mark >= %1 ").arg(min_frame);
3607
3608 if (max_frame >= 0)
3609 comp += QString(" AND mark <= %1 ").arg(max_frame);
3610
3611 if (type != MARK_ALL)
3612 comp += QString(" AND type = :TYPE ");
3613
3614 if (IsVideo())
3615 {
3616 query.prepare("DELETE FROM filemarkup"
3617 " WHERE filename = :PATH "
3618 + comp + ";");
3620 }
3621 else if (IsRecording())
3622 {
3623 query.prepare("DELETE FROM recordedmarkup"
3624 " WHERE chanid = :CHANID"
3625 " AND STARTTIME = :STARTTIME"
3626 + comp + ';');
3627 query.bindValue(":CHANID", m_chanId);
3628 query.bindValue(":STARTTIME", m_recStartTs);
3629 }
3630 else
3631 {
3632 return;
3633 }
3634 query.bindValue(":TYPE", type);
3635
3636 if (!query.exec())
3637 MythDB::DBError("ClearMarkupMap deleting", query);
3638}
3639
3641 const frm_dir_map_t &marks, MarkTypes type,
3642 int64_t min_frame, int64_t max_frame) const
3643{
3645 QString videoPath;
3646
3647 if (IsVideo())
3648 {
3650 }
3651 else if (IsRecording())
3652 {
3653 // check to make sure the show still exists before saving markups
3654 query.prepare("SELECT starttime FROM recorded"
3655 " WHERE chanid = :CHANID"
3656 " AND starttime = :STARTTIME ;");
3657 query.bindValue(":CHANID", m_chanId);
3658 query.bindValue(":STARTTIME", m_recStartTs);
3659
3660 if (!query.exec())
3661 MythDB::DBError("SaveMarkupMap checking record table", query);
3662
3663 if (!query.next())
3664 return;
3665 }
3666 else
3667 {
3668 return;
3669 }
3670
3671 frm_dir_map_t::const_iterator it;
3672 for (it = marks.begin(); it != marks.end(); ++it)
3673 {
3674 uint64_t frame = it.key();
3675
3676 if ((min_frame >= 0) && (frame < (uint64_t)min_frame))
3677 continue;
3678
3679 if ((max_frame >= 0) && (frame > (uint64_t)max_frame))
3680 continue;
3681
3682 int mark_type = (type != MARK_ALL) ? type : *it;
3683
3684 if (IsVideo())
3685 {
3686 query.prepare("INSERT INTO filemarkup (filename, mark, type)"
3687 " VALUES ( :PATH , :MARK , :TYPE );");
3688 query.bindValue(":PATH", videoPath);
3689 }
3690 else // if (IsRecording())
3691 {
3692 query.prepare("INSERT INTO recordedmarkup"
3693 " (chanid, starttime, mark, type)"
3694 " VALUES ( :CHANID , :STARTTIME , :MARK , :TYPE );");
3695 query.bindValue(":CHANID", m_chanId);
3696 query.bindValue(":STARTTIME", m_recStartTs);
3697 }
3698 query.bindValue(":MARK", (quint64)frame);
3699 query.bindValue(":TYPE", mark_type);
3700
3701 if (!query.exec())
3702 MythDB::DBError("SaveMarkupMap inserting", query);
3703 }
3704}
3705
3707 frm_dir_map_t &marks, MarkTypes type, bool merge) const
3708{
3709 if (!merge)
3710 marks.clear();
3711
3712 if (IsVideo())
3713 {
3715 marks, type, merge);
3716 }
3717 else if (IsRecording())
3718 {
3719 QueryMarkupMap(m_chanId, m_recStartTs, marks, type, merge);
3720 }
3721}
3722
3724 const QString &video_pathname,
3725 frm_dir_map_t &marks,
3726 MarkTypes type, bool mergeIntoMap)
3727{
3728 if (!mergeIntoMap)
3729 marks.clear();
3730
3732
3733 query.prepare("SELECT mark, type, `offset` "
3734 "FROM filemarkup "
3735 "WHERE filename = :PATH AND "
3736 " type = :TYPE "
3737 "ORDER BY mark");
3738 query.bindValue(":PATH", video_pathname);
3739 query.bindValue(":TYPE", type);
3740
3741 if (!query.exec())
3742 {
3743 MythDB::DBError("QueryMarkupMap", query);
3744 return;
3745 }
3746
3747 while (query.next())
3748 {
3749 // marks[query.value(0).toLongLong()] =
3750 // (MarkTypes) query.value(1).toInt();
3751 int entryType = query.value(1).toInt();
3752 if (entryType == MARK_VIDEO_RATE)
3753 marks[query.value(2).toLongLong()] = (MarkTypes) entryType;
3754 else
3755 marks[query.value(0).toLongLong()] = (MarkTypes) entryType;
3756
3757 }
3758}
3759
3761 uint chanid, const QDateTime &recstartts,
3762 frm_dir_map_t &marks,
3763 MarkTypes type, bool mergeIntoMap)
3764{
3765 if (!mergeIntoMap)
3766 marks.clear();
3767
3769 query.prepare("SELECT mark, type, data "
3770 "FROM recordedmarkup "
3771 "WHERE chanid = :CHANID AND "
3772 " starttime = :STARTTIME AND"
3773 " type = :TYPE "
3774 "ORDER BY mark");
3775 query.bindValue(":CHANID", chanid);
3776 query.bindValue(":STARTTIME", recstartts);
3777 query.bindValue(":TYPE", type);
3778
3779 if (!query.exec())
3780 {
3781 MythDB::DBError("QueryMarkupMap", query);
3782 return;
3783 }
3784
3785 while (query.next())
3786 {
3787 // marks[query.value(0).toULongLong()] =
3788 // (MarkTypes) query.value(1).toInt();
3789 int entryType = query.value(1).toInt();
3790 if (entryType == MARK_VIDEO_RATE)
3791 marks[query.value(2).toULongLong()] = (MarkTypes) entryType;
3792 else
3793 marks[query.value(0).toULongLong()] = (MarkTypes) entryType;
3794 }
3795}
3796
3799{
3800 frm_dir_map_t flagMap;
3801
3802 QueryMarkupMap(flagMap, type);
3803
3804 return flagMap.contains(0);
3805}
3806
3809{
3811 frm_dir_map_t flagMap;
3812 flagMap[0] = type;
3813 SaveMarkupMap(flagMap, type);
3814}
3815
3817 frm_pos_map_t &posMap, MarkTypes type) const
3818{
3820 {
3821 QMutexLocker locker(m_positionMapDBReplacement->lock);
3823
3824 return;
3825 }
3826
3827 posMap.clear();
3829
3830 if (IsVideo())
3831 {
3832 query.prepare("SELECT mark, `offset` FROM filemarkup"
3833 " WHERE filename = :PATH"
3834 " AND type = :TYPE ;");
3836 }
3837 else if (IsRecording())
3838 {
3839 query.prepare("SELECT mark, `offset` FROM recordedseek"
3840 " WHERE chanid = :CHANID"
3841 " AND starttime = :STARTTIME"
3842 " AND type = :TYPE ;");
3843 query.bindValue(":CHANID", m_chanId);
3844 query.bindValue(":STARTTIME", m_recStartTs);
3845 }
3846 else
3847 {
3848 return;
3849 }
3850 query.bindValue(":TYPE", type);
3851
3852 if (!query.exec())
3853 {
3854 MythDB::DBError("QueryPositionMap", query);
3855 return;
3856 }
3857
3858 while (query.next())
3859 posMap[query.value(0).toULongLong()] = query.value(1).toULongLong();
3860}
3861
3863{
3865 {
3866 QMutexLocker locker(m_positionMapDBReplacement->lock);
3868 return;
3869 }
3870
3872
3873 if (IsVideo())
3874 {
3875 query.prepare("DELETE FROM filemarkup"
3876 " WHERE filename = :PATH"
3877 " AND type = :TYPE ;");
3879 }
3880 else if (IsRecording())
3881 {
3882 query.prepare("DELETE FROM recordedseek"
3883 " WHERE chanid = :CHANID"
3884 " AND starttime = :STARTTIME"
3885 " AND type = :TYPE ;");
3886 query.bindValue(":CHANID", m_chanId);
3887 query.bindValue(":STARTTIME", m_recStartTs);
3888 }
3889 else
3890 {
3891 return;
3892 }
3893
3894 query.bindValue(":TYPE", type);
3895
3896 if (!query.exec())
3897 MythDB::DBError("clear position map", query);
3898}
3899
3901 frm_pos_map_t &posMap, MarkTypes type,
3902 int64_t min_frame, int64_t max_frame) const
3903{
3905 {
3906 QMutexLocker locker(m_positionMapDBReplacement->lock);
3907
3908 if ((min_frame >= 0) || (max_frame >= 0))
3909 {
3910 frm_pos_map_t new_map;
3911 // NOLINTNEXTLINE(modernize-loop-convert)
3912 for (auto it = m_positionMapDBReplacement->map[type].begin();
3913 it != m_positionMapDBReplacement->map[type].end();
3914 ++it)
3915 {
3916 uint64_t frame = it.key();
3917 if ((min_frame >= 0) && (frame >= (uint64_t)min_frame))
3918 continue;
3919 if ((min_frame >= 0) && (frame <= (uint64_t)max_frame))
3920 continue;
3921 new_map.insert(it.key(), *it);
3922 }
3924 }
3925 else
3926 {
3928 }
3929
3930 // NOLINTNEXTLINE(modernize-loop-convert)
3931 for (auto it = posMap.cbegin(); it != posMap.cend(); ++it)
3932 {
3933 uint64_t frame = it.key();
3934 if ((min_frame >= 0) && (frame >= (uint64_t)min_frame))
3935 continue;
3936 if ((min_frame >= 0) && (frame <= (uint64_t)max_frame))
3937 continue;
3938
3940 .insert(frame, *it);
3941 }
3942
3943 return;
3944 }
3945
3947 QString comp;
3948
3949 if (min_frame >= 0)
3950 comp += " AND mark >= :MIN_FRAME ";
3951 if (max_frame >= 0)
3952 comp += " AND mark <= :MAX_FRAME ";
3953
3954 QString videoPath;
3955 if (IsVideo())
3956 {
3958
3959 query.prepare("DELETE FROM filemarkup"
3960 " WHERE filename = :PATH"
3961 " AND type = :TYPE"
3962 + comp + ';');
3963 query.bindValue(":PATH", videoPath);
3964 }
3965 else if (IsRecording())
3966 {
3967 query.prepare("DELETE FROM recordedseek"
3968 " WHERE chanid = :CHANID"
3969 " AND starttime = :STARTTIME"
3970 " AND type = :TYPE"
3971 + comp + ';');
3972 query.bindValue(":CHANID", m_chanId);
3973 query.bindValue(":STARTTIME", m_recStartTs);
3974 }
3975 else
3976 {
3977 return;
3978 }
3979
3980 query.bindValue(":TYPE", type);
3981 if (min_frame >= 0)
3982 query.bindValue(":MIN_FRAME", (quint64)min_frame);
3983 if (max_frame >= 0)
3984 query.bindValue(":MAX_FRAME", (quint64)max_frame);
3985
3986 if (!query.exec())
3987 MythDB::DBError("position map clear", query);
3988
3989 if (posMap.isEmpty())
3990 return;
3991
3992 // Use the multi-value insert syntax to reduce database I/O
3993 QStringList q("INSERT INTO ");
3994 QString qfields;
3995 if (IsVideo())
3996 {
3997 q << "filemarkup (filename, type, mark, `offset`)";
3998 qfields = QString("('%1',%2,") .
3999 // ideally, this should be escaped
4000 arg(videoPath) .
4001 arg(type);
4002 }
4003 else // if (IsRecording())
4004 {
4005 q << "recordedseek (chanid, starttime, type, mark, `offset`)";
4006 qfields = QString("(%1,'%2',%3,") .
4007 arg(m_chanId) .
4008 arg(m_recStartTs.toString(Qt::ISODate)) .
4009 arg(type);
4010 }
4011 q << " VALUES ";
4012
4013 bool add_comma = false;
4014 frm_pos_map_t::iterator it;
4015 for (it = posMap.begin(); it != posMap.end(); ++it)
4016 {
4017 uint64_t frame = it.key();
4018
4019 if ((min_frame >= 0) && (frame < (uint64_t)min_frame))
4020 continue;
4021
4022 if ((max_frame >= 0) && (frame > (uint64_t)max_frame))
4023 continue;
4024
4025 uint64_t offset = *it;
4026
4027 if (add_comma)
4028 {
4029 q << ",";
4030 }
4031 else
4032 {
4033 add_comma = true;
4034 }
4035 q << qfields << QString("%1,%2)").arg(frame).arg(offset);
4036 }
4037 query.prepare(q.join(""));
4038 if (!query.exec())
4039 {
4040 MythDB::DBError("position map insert", query);
4041 }
4042}
4043
4045 frm_pos_map_t &posMap, MarkTypes type) const
4046{
4047 if (posMap.isEmpty())
4048 return;
4049
4051 {
4052 QMutexLocker locker(m_positionMapDBReplacement->lock);
4053
4054 for (auto it = posMap.cbegin(); it != posMap.cend(); ++it)
4055 m_positionMapDBReplacement->map[type].insert(it.key(), *it);
4056
4057 return;
4058 }
4059
4060 // Use the multi-value insert syntax to reduce database I/O
4061 QStringList q("INSERT INTO ");
4062 QString qfields;
4063 if (IsVideo())
4064 {
4065 q << "filemarkup (filename, type, mark, `offset`)";
4066 qfields = QString("('%1',%2,") .
4067 // ideally, this should be escaped
4069 arg(type);
4070 }
4071 else if (IsRecording())
4072 {
4073 q << "recordedseek (chanid, starttime, type, mark, `offset`)";
4074 qfields = QString("(%1,'%2',%3,") .
4075 arg(m_chanId) .
4076 arg(m_recStartTs.toString(Qt::ISODate)) .
4077 arg(type);
4078 }
4079 else
4080 {
4081 return;
4082 }
4083 q << " VALUES ";
4084
4085 bool add_comma = false;
4086 frm_pos_map_t::iterator it;
4087 for (it = posMap.begin(); it != posMap.end(); ++it)
4088 {
4089 uint64_t frame = it.key();
4090 uint64_t offset = *it;
4091
4092 if (add_comma)
4093 {
4094 q << ",";
4095 }
4096 else
4097 {
4098 add_comma = true;
4099 }
4100 q << qfields << QString("%1,%2)").arg(frame).arg(offset);
4101 }
4102
4104 query.prepare(q.join(""));
4105 if (!query.exec())
4106 {
4107 MythDB::DBError("delta position map insert", query);
4108 }
4109}
4110
4111static const char *from_filemarkup_offset_asc =
4112 "SELECT mark, `offset` FROM filemarkup"
4113 " WHERE filename = :PATH"
4114 " AND type = :TYPE"
4115 " AND mark >= :QUERY_ARG"
4116 " ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
4118 "SELECT mark, `offset` FROM filemarkup"
4119 " WHERE filename = :PATH"
4120 " AND type = :TYPE"
4121 " AND mark <= :QUERY_ARG"
4122 " ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
4124 "SELECT mark, `offset` FROM recordedseek"
4125 " WHERE chanid = :CHANID"
4126 " AND starttime = :STARTTIME"
4127 " AND type = :TYPE"
4128 " AND mark >= :QUERY_ARG"
4129 " ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
4131 "SELECT mark, `offset` FROM recordedseek"
4132 " WHERE chanid = :CHANID"
4133 " AND starttime = :STARTTIME"
4134 " AND type = :TYPE"
4135 " AND mark <= :QUERY_ARG"
4136 " ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";
4137static const char *from_filemarkup_mark_asc =
4138 "SELECT `offset`,mark FROM filemarkup"
4139 " WHERE filename = :PATH"
4140 " AND type = :TYPE"
4141 " AND `offset` >= :QUERY_ARG"
4142 " ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
4143static const char *from_filemarkup_mark_desc =
4144 "SELECT `offset`,mark FROM filemarkup"
4145 " WHERE filename = :PATH"
4146 " AND type = :TYPE"
4147 " AND `offset` <= :QUERY_ARG"
4148 " ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
4149static const char *from_recordedseek_mark_asc =
4150 "SELECT `offset`,mark FROM recordedseek"
4151 " WHERE chanid = :CHANID"
4152 " AND starttime = :STARTTIME"
4153 " AND type = :TYPE"
4154 " AND `offset` >= :QUERY_ARG"
4155 " ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
4157 "SELECT `offset`,mark FROM recordedseek"
4158 " WHERE chanid = :CHANID"
4159 " AND starttime = :STARTTIME"
4160 " AND type = :TYPE"
4161 " AND `offset` <= :QUERY_ARG"
4162 " ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";
4163
4164bool ProgramInfo::QueryKeyFrameInfo(uint64_t * result,
4165 uint64_t position_or_keyframe,
4166 bool backwards,
4168 const char *from_filemarkup_asc,
4169 const char *from_filemarkup_desc,
4170 const char *from_recordedseek_asc,
4171 const char *from_recordedseek_desc) const
4172{
4174
4175 if (IsVideo())
4176 {
4177 if (backwards)
4178 query.prepare(from_filemarkup_desc);
4179 else
4180 query.prepare(from_filemarkup_asc);
4182 }
4183 else if (IsRecording())
4184 {
4185 if (backwards)
4186 query.prepare(from_recordedseek_desc);
4187 else
4188 query.prepare(from_recordedseek_asc);
4189 query.bindValue(":CHANID", m_chanId);
4190 query.bindValue(":STARTTIME", m_recStartTs);
4191 }
4192 query.bindValue(":TYPE", type);
4193 query.bindValue(":QUERY_ARG", (unsigned long long)position_or_keyframe);
4194
4195 if (!query.exec())
4196 {
4197 MythDB::DBError("QueryKeyFrameInfo", query);
4198 return false;
4199 }
4200
4201 if (query.next())
4202 {
4203 *result = query.value(1).toULongLong();
4204 return true;
4205 }
4206
4207 if (IsVideo())
4208 {
4209 if (backwards)
4210 query.prepare(from_filemarkup_asc);
4211 else
4212 query.prepare(from_filemarkup_desc);
4214 }
4215 else if (IsRecording())
4216 {
4217 if (backwards)
4218 query.prepare(from_recordedseek_asc);
4219 else
4220 query.prepare(from_recordedseek_desc);
4221 query.bindValue(":CHANID", m_chanId);
4222 query.bindValue(":STARTTIME", m_recStartTs);
4223 }
4224 query.bindValue(":TYPE", type);
4225 query.bindValue(":QUERY_ARG", (unsigned long long)position_or_keyframe);
4226
4227 if (!query.exec())
4228 {
4229 MythDB::DBError("QueryKeyFrameInfo", query);
4230 return false;
4231 }
4232
4233 if (query.next())
4234 {
4235 *result = query.value(1).toULongLong();
4236 return true;
4237 }
4238
4239 return false;
4240
4241}
4242
4243bool ProgramInfo::QueryPositionKeyFrame(uint64_t *keyframe, uint64_t position,
4244 bool backwards) const
4245{
4246 return QueryKeyFrameInfo(keyframe, position, backwards, MARK_GOP_BYFRAME,
4251}
4252bool ProgramInfo::QueryKeyFramePosition(uint64_t *position, uint64_t keyframe,
4253 bool backwards) const
4254{
4255 return QueryKeyFrameInfo(position, keyframe, backwards, MARK_GOP_BYFRAME,
4260}
4261bool ProgramInfo::QueryDurationKeyFrame(uint64_t *keyframe, uint64_t duration,
4262 bool backwards) const
4263{
4264 return QueryKeyFrameInfo(keyframe, duration, backwards, MARK_DURATION_MS,
4269}
4270bool ProgramInfo::QueryKeyFrameDuration(uint64_t *duration, uint64_t keyframe,
4271 bool backwards) const
4272{
4273 return QueryKeyFrameInfo(duration, keyframe, backwards, MARK_DURATION_MS,
4278}
4279
4284 uint64_t frame, MarkTypes type, uint customAspect)
4285{
4286 if (!IsRecording())
4287 return;
4288
4290
4291 query.prepare("INSERT INTO recordedmarkup"
4292 " (chanid, starttime, mark, type, data)"
4293 " VALUES"
4294 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4295 query.bindValue(":CHANID", m_chanId);
4296 query.bindValue(":STARTTIME", m_recStartTs);
4297
4298 query.bindValue(":MARK", (quint64)frame);
4299 query.bindValue(":TYPE", type);
4300
4301 if (type == MARK_ASPECT_CUSTOM)
4302 {
4303 query.bindValue(":DATA", customAspect);
4304 }
4305 else
4306 {
4307 // create NULL value
4308#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
4309 query.bindValue(":DATA", QVariant(QVariant::UInt));
4310#else
4311 query.bindValue(":DATA", QVariant(QMetaType(QMetaType::UInt)));
4312#endif
4313 }
4314
4315 if (!query.exec())
4316 MythDB::DBError("aspect ratio change insert", query);
4317}
4318
4322void ProgramInfo::SaveFrameRate(uint64_t frame, uint framerate)
4323{
4324 if (!IsRecording())
4325 return;
4326
4328
4329 query.prepare("INSERT INTO recordedmarkup"
4330 " (chanid, starttime, mark, type, data)"
4331 " VALUES"
4332 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4333 query.bindValue(":CHANID", m_chanId);
4334 query.bindValue(":STARTTIME", m_recStartTs);
4335 query.bindValue(":MARK", (quint64)frame);
4336 query.bindValue(":TYPE", MARK_VIDEO_RATE);
4337 query.bindValue(":DATA", framerate);
4338
4339 if (!query.exec())
4340 MythDB::DBError("Frame rate insert", query);
4341}
4342
4343
4347void ProgramInfo::SaveVideoScanType(uint64_t frame, bool progressive)
4348{
4349 if (!IsRecording())
4350 return;
4351
4353
4354 query.prepare("INSERT INTO recordedmarkup"
4355 " (chanid, starttime, mark, type, data)"
4356 " VALUES"
4357 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4358 query.bindValue(":CHANID", m_chanId);
4359 query.bindValue(":STARTTIME", m_recStartTs);
4360 query.bindValue(":MARK", (quint64)frame);
4361 query.bindValue(":TYPE", MARK_SCAN_PROGRESSIVE);
4362 query.bindValue(":DATA", progressive);
4363
4364 if (!query.exec())
4365 MythDB::DBError("Video scan type insert", query);
4366}
4367
4368
4370 MarkTypes type, uint chanid, const QDateTime &recstartts)
4371{
4373
4374 query.prepare("DELETE FROM recordedmarkup "
4375 " WHERE chanid=:CHANID "
4376 " AND starttime=:STARTTIME "
4377 " AND type=:TYPE");
4378 query.bindValue(":CHANID", chanid);
4379 query.bindValue(":STARTTIME", recstartts);
4380 query.bindValue(":TYPE", type);
4381
4382 if (!query.exec())
4383 MythDB::DBError("delete_markup_datum", query);
4384}
4385
4386// Delete all marks of the given type
4388 MarkTypes type, const QString &videoPath)
4389{
4391
4392 query.prepare("DELETE FROM filemarkup"
4393 " WHERE filename = :PATH "
4394 " AND type = :TYPE ;");
4395 query.bindValue(":PATH", videoPath);
4396 query.bindValue(":TYPE", type);
4397
4398 if (!query.exec())
4399 MythDB::DBError("delete_markup_datum", query);
4400}
4401
4402// Delete the specified mark of the given type
4404 MarkTypes type, uint mark, const QString &videoPath)
4405{
4407
4408 query.prepare("DELETE FROM filemarkup"
4409 " WHERE filename = :PATH "
4410 " AND mark = :MARK "
4411 " AND type = :TYPE ;");
4412 query.bindValue(":PATH", videoPath);
4413 query.bindValue(":MARK", mark);
4414 query.bindValue(":TYPE", type);
4415
4416 if (!query.exec())
4417 MythDB::DBError("delete_markup_datum", query);
4418}
4419
4421 MarkTypes type, uint mark, uint offset, const QString &videoPath)
4422{
4424
4425 query.prepare("INSERT INTO filemarkup"
4426 " (filename, mark, type, `offset`)"
4427 " VALUES"
4428 " ( :PATH , :MARK , :TYPE, :OFFSET );");
4429 query.bindValue(":PATH", videoPath);
4430 query.bindValue(":OFFSET", offset);
4431 query.bindValue(":TYPE", type);
4432 query.bindValue(":MARK", mark);
4433
4434 if (!query.exec())
4435 MythDB::DBError("insert_markup_datum", query);
4436}
4437
4439 MarkTypes type, uint mark, uint data, uint chanid, const QDateTime &recstartts)
4440{
4442
4443 query.prepare("INSERT INTO recordedmarkup"
4444 " (chanid, starttime, mark, type, data)"
4445 " VALUES"
4446 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4447 query.bindValue(":CHANID", chanid);
4448 query.bindValue(":STARTTIME", recstartts);
4449 query.bindValue(":DATA", data);
4450 query.bindValue(":TYPE", type);
4451 query.bindValue(":MARK", mark);
4452
4453 if (!query.exec())
4454 MythDB::DBError("insert_markup_datum", query);
4455}
4456
4457
4459//
4460// The type MARK_DURATION_MS is used for the total duration when mark is 0.
4461// For video files this type is also for duration of each key frame
4462// when the mark is not 0.
4463// For video files the duration is saved in the filemarkup table.
4464void ProgramInfo::SaveTotalDuration(std::chrono::milliseconds duration)
4465{
4466 if (IsVideo())
4467 {
4470 insert_markup_datum(MARK_DURATION_MS, 0, duration.count(), videoPath);
4471 }
4472 else if (IsRecording())
4473 {
4476 }
4477}
4478
4481{
4482 if (IsVideo())
4483 {
4486 insert_markup_datum(MARK_TOTAL_FRAMES, 0, frames, videoPath);
4487 }
4488 else if (IsRecording())
4489 {
4492 }
4493}
4494
4498void ProgramInfo::SaveResolution(uint64_t frame, uint width, uint height)
4499{
4500 if (!IsRecording())
4501 return;
4502
4504
4505 query.prepare("INSERT INTO recordedmarkup"
4506 " (chanid, starttime, mark, type, data)"
4507 " VALUES"
4508 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4509 query.bindValue(":CHANID", m_chanId);
4510 query.bindValue(":STARTTIME", m_recStartTs);
4511 query.bindValue(":MARK", (quint64)frame);
4512 query.bindValue(":TYPE", MARK_VIDEO_WIDTH);
4513 query.bindValue(":DATA", width);
4514
4515 if (!query.exec())
4516 MythDB::DBError("Resolution insert", query);
4517
4518 query.prepare("INSERT INTO recordedmarkup"
4519 " (chanid, starttime, mark, type, data)"
4520 " VALUES"
4521 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4522 query.bindValue(":CHANID", m_chanId);
4523 query.bindValue(":STARTTIME", m_recStartTs);
4524 query.bindValue(":MARK", (quint64)frame);
4525 query.bindValue(":TYPE", MARK_VIDEO_HEIGHT);
4526 query.bindValue(":DATA", height);
4527
4528 if (!query.exec())
4529 MythDB::DBError("Resolution insert", query);
4530}
4531
4533 MarkTypes type, uint chanid, const QDateTime &recstartts)
4534{
4535 QString qstr = QString(
4536 "SELECT recordedmarkup.data "
4537 "FROM recordedmarkup "
4538 "WHERE recordedmarkup.chanid = :CHANID AND "
4539 " recordedmarkup.starttime = :STARTTIME AND "
4540 " recordedmarkup.type = :TYPE "
4541 "GROUP BY recordedmarkup.data "
4542 "ORDER BY SUM( ( SELECT IFNULL(rm.mark, recordedmarkup.mark)"
4543 " FROM recordedmarkup AS rm "
4544 " WHERE rm.chanid = recordedmarkup.chanid AND "
4545 " rm.starttime = recordedmarkup.starttime AND "
4546 " rm.type = recordedmarkup.type AND "
4547 " rm.mark > recordedmarkup.mark "
4548 " ORDER BY rm.mark ASC LIMIT 1 "
4549 " ) - recordedmarkup.mark "
4550 " ) DESC "
4551 "LIMIT 1");
4552
4554 query.prepare(qstr);
4555 query.bindValue(":TYPE", (int)type);
4556 query.bindValue(":CHANID", chanid);
4557 query.bindValue(":STARTTIME", recstartts);
4558
4559 if (!query.exec())
4560 {
4561 MythDB::DBError("load_markup_datum", query);
4562 return 0;
4563 }
4564
4565 return (query.next()) ? query.value(0).toUInt() : 0;
4566}
4567
4569 MarkTypes type, const QString &videoPath)
4570{
4571 QString qstr = QString(
4572 "SELECT filemarkup.`offset` "
4573 "FROM filemarkup "
4574 "WHERE filemarkup.filename = :PATH AND "
4575 " filemarkup.type = :TYPE "
4576 "GROUP BY filemarkup.`offset` "
4577 "ORDER BY SUM( ( SELECT IFNULL(fm.mark, filemarkup.mark)"
4578 " FROM filemarkup AS fm "
4579 " WHERE fm.filename = filemarkup.filename AND "
4580 " fm.type = filemarkup.type AND "
4581 " fm.mark > filemarkup.mark "
4582 " ORDER BY fm.mark ASC LIMIT 1 "
4583 " ) - filemarkup.mark "
4584 " ) DESC "
4585 "LIMIT 1");
4586
4588 query.prepare(qstr);
4589 query.bindValue(":TYPE", (int)type);
4590 query.bindValue(":PATH", videoPath);
4591
4592 if (!query.exec())
4593 {
4594 MythDB::DBError("load_markup_datum", query);
4595 return 0;
4596 }
4597
4598 return (query.next()) ? query.value(0).toUInt() : 0;
4599}
4600
4601
4607{
4609}
4610
4616{
4618}
4619
4625{
4627}
4628
4630{
4632 query.prepare("SELECT recordedmarkup.type "
4633 "FROM recordedmarkup "
4634 "WHERE recordedmarkup.chanid = :CHANID AND "
4635 " recordedmarkup.starttime = :STARTTIME AND "
4636 " recordedmarkup.type >= :ASPECTSTART AND "
4637 " recordedmarkup.type <= :ASPECTEND "
4638 "GROUP BY recordedmarkup.type "
4639 "ORDER BY SUM( ( SELECT IFNULL(rm.mark, ( "
4640 " SELECT MAX(rmmax.mark) "
4641 " FROM recordedmarkup AS rmmax "
4642 " WHERE rmmax.chanid = recordedmarkup.chanid "
4643 " AND rmmax.starttime = recordedmarkup.starttime "
4644 " ) ) "
4645 " FROM recordedmarkup AS rm "
4646 " WHERE rm.chanid = recordedmarkup.chanid AND "
4647 " rm.starttime = recordedmarkup.starttime AND "
4648 " rm.type >= :ASPECTSTART2 AND "
4649 " rm.type <= :ASPECTEND2 AND "
4650 " rm.mark > recordedmarkup.mark "
4651 " ORDER BY rm.mark ASC LIMIT 1 "
4652 " ) - recordedmarkup.mark "
4653 " ) DESC "
4654 "LIMIT 1");
4655 query.bindValue(":CHANID", m_chanId);
4656 query.bindValue(":STARTTIME", m_recStartTs);
4657 query.bindValue(":ASPECTSTART", MARK_ASPECT_4_3); // 11
4658 query.bindValue(":ASPECTEND", MARK_ASPECT_CUSTOM); // 14
4659 query.bindValue(":ASPECTSTART2", MARK_ASPECT_4_3); // 11
4660 query.bindValue(":ASPECTEND2", MARK_ASPECT_CUSTOM); // 14
4661
4662 if (!query.exec())
4663 {
4664 MythDB::DBError("QueryAverageAspectRatio", query);
4665 return MARK_UNSET;
4666 }
4667
4668 if (!query.next())
4669 return MARK_UNSET;
4670
4671 return static_cast<MarkTypes>(query.value(0).toInt());
4672}
4673
4680{
4681 return static_cast<bool>(load_markup_datum(MARK_SCAN_PROGRESSIVE,
4683}
4684
4690std::chrono::milliseconds ProgramInfo::QueryTotalDuration(void) const
4691{
4693 return 0ms;
4694
4695 auto msec = std::chrono::milliseconds(load_markup_datum(MARK_DURATION_MS, m_chanId, m_recStartTs));
4696
4697// Impossible condition, load_markup_datum returns an unsigned int
4698// if (msec < 0ms)
4699// return 0ms;
4700
4701 return msec;
4702}
4703
4708{
4709 if (IsVideo())
4710 {
4712 return frames;
4713 }
4714 if (IsRecording())
4715 {
4717 return frames;
4718 }
4719 return 0;
4720}
4721
4722void ProgramInfo::QueryMarkup(QVector<MarkupEntry> &mapMark,
4723 QVector<MarkupEntry> &mapSeek) const
4724{
4726 // Get the markup
4727 if (IsVideo())
4728 {
4729 query.prepare("SELECT type, mark, `offset` FROM filemarkup"
4730 " WHERE filename = :PATH"
4731 " AND type NOT IN (:KEYFRAME,:DURATION)"
4732 " ORDER BY mark, type;");
4734 query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4735 query.bindValue(":DURATION", MARK_DURATION_MS);
4736 }
4737 else if (IsRecording())
4738 {
4739 query.prepare("SELECT type, mark, data FROM recordedmarkup"
4740 " WHERE chanid = :CHANID"
4741 " AND STARTTIME = :STARTTIME"
4742 " ORDER BY mark, type");
4743 query.bindValue(":CHANID", m_chanId);
4744 query.bindValue(":STARTTIME", m_recStartTs);
4745 }
4746 else
4747 {
4748 return;
4749 }
4750 if (!query.exec())
4751 {
4752 MythDB::DBError("QueryMarkup markup data", query);
4753 return;
4754 }
4755 while (query.next())
4756 {
4757 int type = query.value(0).toInt();
4758 uint64_t frame = query.value(1).toLongLong();
4759 uint64_t data = 0;
4760 bool isDataNull = query.value(2).isNull();
4761 if (!isDataNull)
4762 data = query.value(2).toLongLong();
4763 mapMark.append(MarkupEntry(type, frame, data, isDataNull));
4764 }
4765
4766 // Get the seektable
4767 if (IsVideo())
4768 {
4769 query.prepare("SELECT type, mark, `offset` FROM filemarkup"
4770 " WHERE filename = :PATH"
4771 " AND type IN (:KEYFRAME,:DURATION)"
4772 " ORDER BY mark, type;");
4774 query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4775 query.bindValue(":DURATION", MARK_DURATION_MS);
4776 }
4777 else if (IsRecording())
4778 {
4779 query.prepare("SELECT type, mark, `offset` FROM recordedseek"
4780 " WHERE chanid = :CHANID"
4781 " AND STARTTIME = :STARTTIME"
4782 " ORDER BY mark, type");
4783 query.bindValue(":CHANID", m_chanId);
4784 query.bindValue(":STARTTIME", m_recStartTs);
4785 }
4786 if (!query.exec())
4787 {
4788 MythDB::DBError("QueryMarkup seektable data", query);
4789 return;
4790 }
4791 while (query.next())
4792 {
4793 int type = query.value(0).toInt();
4794 uint64_t frame = query.value(1).toLongLong();
4795 uint64_t data = 0;
4796 bool isDataNull = query.value(2).isNull();
4797 if (!isDataNull)
4798 data = query.value(2).toLongLong();
4799 mapSeek.append(MarkupEntry(type, frame, data, isDataNull));
4800 }
4801}
4802
4803void ProgramInfo::SaveMarkup(const QVector<MarkupEntry> &mapMark,
4804 const QVector<MarkupEntry> &mapSeek) const
4805{
4807 if (IsVideo())
4808 {
4810 if (mapMark.isEmpty())
4811 {
4812 LOG(VB_GENERAL, LOG_INFO,
4813 QString("No mark entries in input, "
4814 "not removing marks from DB"));
4815 }
4816 else
4817 {
4818 query.prepare("DELETE FROM filemarkup"
4819 " WHERE filename = :PATH"
4820 " AND type NOT IN (:KEYFRAME,:DURATION)");
4821 query.bindValue(":PATH", path);
4822 query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4823 query.bindValue(":DURATION", MARK_DURATION_MS);
4824 if (!query.exec())
4825 {
4826 MythDB::DBError("SaveMarkup seektable data", query);
4827 return;
4828 }
4829 for (const auto& entry : std::as_const(mapMark))
4830 {
4831 if (entry.type == MARK_DURATION_MS)
4832 continue;
4833 if (entry.isDataNull)
4834 {
4835 query.prepare("INSERT INTO filemarkup"
4836 " (filename,type,mark)"
4837 " VALUES (:PATH,:TYPE,:MARK)");
4838 }
4839 else
4840 {
4841 query.prepare("INSERT INTO filemarkup"
4842 " (filename,type,mark,`offset`)"
4843 " VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
4844 query.bindValue(":OFFSET", (quint64)entry.data);
4845 }
4846 query.bindValue(":PATH", path);
4847 query.bindValue(":TYPE", entry.type);
4848 query.bindValue(":MARK", (quint64)entry.frame);
4849 if (!query.exec())
4850 {
4851 MythDB::DBError("SaveMarkup seektable data", query);
4852 return;
4853 }
4854 }
4855 }
4856 if (mapSeek.isEmpty())
4857 {
4858 LOG(VB_GENERAL, LOG_INFO,
4859 QString("No seek entries in input, "
4860 "not removing marks from DB"));
4861 }
4862 else
4863 {
4864 query.prepare("DELETE FROM filemarkup"
4865 " WHERE filename = :PATH"
4866 " AND type IN (:KEYFRAME,:DURATION)");
4867 query.bindValue(":PATH", path);
4868 query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4869 query.bindValue(":DURATION", MARK_DURATION_MS);
4870 if (!query.exec())
4871 {
4872 MythDB::DBError("SaveMarkup seektable data", query);
4873 return;
4874 }
4875 for (int i = 0; i < mapSeek.size(); ++i)
4876 {
4877 if (i > 0 && (i % 1000 == 0))
4878 {
4879 LOG(VB_GENERAL, LOG_INFO,
4880 QString("Inserted %1 of %2 records")
4881 .arg(i).arg(mapSeek.size()));
4882 }
4883 const MarkupEntry &entry = mapSeek[i];
4884 query.prepare("INSERT INTO filemarkup"
4885 " (filename,type,mark,`offset`)"
4886 " VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
4887 query.bindValue(":PATH", path);
4888 query.bindValue(":TYPE", entry.type);
4889 query.bindValue(":MARK", (quint64)entry.frame);
4890 query.bindValue(":OFFSET", (quint64)entry.data);
4891 if (!query.exec())
4892 {
4893 MythDB::DBError("SaveMarkup seektable data", query);
4894 return;
4895 }
4896 }
4897 }
4898 }
4899 else if (IsRecording())
4900 {
4901 if (mapMark.isEmpty())
4902 {
4903 LOG(VB_GENERAL, LOG_INFO,
4904 QString("No mark entries in input, "
4905 "not removing marks from DB"));
4906 }
4907 else
4908 {
4909 query.prepare("DELETE FROM recordedmarkup"
4910 " WHERE chanid = :CHANID"
4911 " AND starttime = :STARTTIME");
4912 query.bindValue(":CHANID", m_chanId);
4913 query.bindValue(":STARTTIME", m_recStartTs);
4914 if (!query.exec())
4915 {
4916 MythDB::DBError("SaveMarkup seektable data", query);
4917 return;
4918 }
4919 for (const auto& entry : std::as_const(mapMark))
4920 {
4921 if (entry.isDataNull)
4922 {
4923 query.prepare("INSERT INTO recordedmarkup"
4924 " (chanid,starttime,type,mark)"
4925 " VALUES (:CHANID,:STARTTIME,:TYPE,:MARK)");
4926 }
4927 else
4928 {
4929 query.prepare("INSERT INTO recordedmarkup"
4930 " (chanid,starttime,type,mark,data)"
4931 " VALUES (:CHANID,:STARTTIME,"
4932 " :TYPE,:MARK,:OFFSET)");
4933 query.bindValue(":OFFSET", (quint64)entry.data);
4934 }
4935 query.bindValue(":CHANID", m_chanId);
4936 query.bindValue(":STARTTIME", m_recStartTs);
4937 query.bindValue(":TYPE", entry.type);
4938 query.bindValue(":MARK", (quint64)entry.frame);
4939 if (!query.exec())
4940 {
4941 MythDB::DBError("SaveMarkup seektable data", query);
4942 return;
4943 }
4944 }
4945 }
4946 if (mapSeek.isEmpty())
4947 {
4948 LOG(VB_GENERAL, LOG_INFO,
4949 QString("No seek entries in input, "
4950 "not removing marks from DB"));
4951 }
4952 else
4953 {
4954 query.prepare("DELETE FROM recordedseek"
4955 " WHERE chanid = :CHANID"
4956 " AND starttime = :STARTTIME");
4957 query.bindValue(":CHANID", m_chanId);
4958 query.bindValue(":STARTTIME", m_recStartTs);
4959 if (!query.exec())
4960 {
4961 MythDB::DBError("SaveMarkup seektable data", query);
4962 return;
4963 }
4964 for (int i = 0; i < mapSeek.size(); ++i)
4965 {
4966 if (i > 0 && (i % 1000 == 0))
4967 {
4968 LOG(VB_GENERAL, LOG_INFO,
4969 QString("Inserted %1 of %2 records")
4970 .arg(i).arg(mapSeek.size()));
4971 }
4972 const MarkupEntry &entry = mapSeek[i];
4973 query.prepare("INSERT INTO recordedseek"
4974 " (chanid,starttime,type,mark,`offset`)"
4975 " VALUES (:CHANID,:STARTTIME,"
4976 " :TYPE,:MARK,:OFFSET)");
4977 query.bindValue(":CHANID", m_chanId);
4978 query.bindValue(":STARTTIME", m_recStartTs);
4979 query.bindValue(":TYPE", entry.type);
4980 query.bindValue(":MARK", (quint64)entry.frame);
4981 query.bindValue(":OFFSET", (quint64)entry.data);
4982 if (!query.exec())
4983 {
4984 MythDB::DBError("SaveMarkup seektable data", query);
4985 return;
4986 }
4987 }
4988 }
4989 }
4990}
4991
4992void ProgramInfo::SaveVideoProperties(uint mask, uint video_property_flags)
4993{
4995
4996 LOG(VB_RECORD, LOG_INFO,
4997 QString("SaveVideoProperties(0x%1, 0x%2)")
4998 .arg(mask,2,16,QChar('0')).arg(video_property_flags,2,16,QChar('0')));
4999
5000 query.prepare(
5001 "UPDATE recordedprogram "
5002 "SET videoprop = ((videoprop+0) & :OTHERFLAGS) | :FLAGS "
5003 "WHERE chanid = :CHANID AND starttime = :STARTTIME");
5004
5005 query.bindValue(":OTHERFLAGS", ~mask);
5006 query.bindValue(":FLAGS", video_property_flags);
5007 query.bindValue(":CHANID", m_chanId);
5008 query.bindValue(":STARTTIME", m_startTs);
5009 if (!query.exec())
5010 {
5011 MythDB::DBError("SaveVideoProperties", query);
5012 return;
5013 }
5014
5015 uint videoproperties = m_videoProperties;
5016 videoproperties &= ~mask;
5017 videoproperties |= video_property_flags;
5018 m_videoProperties = videoproperties;
5019
5021}
5022
5033QString ProgramInfo::ChannelText(const QString &format) const
5034{
5035 QString chan(format);
5036 chan.replace("<num>", m_chanStr)
5037 .replace("<sign>", m_chanSign)
5038 .replace("<name>", m_chanName);
5039 return chan;
5040}
5041
5043{
5044#ifdef DEBUG_IN_USE
5045 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("UpdateInUseMark(%1) '%2'")
5046 .arg(force?"force":"no force").arg(m_inUseForWhat));
5047#endif
5048
5049 if (!IsRecording())
5050 return;
5051
5052 if (m_inUseForWhat.isEmpty())
5053 return;
5054
5056 MarkAsInUse(true);
5057}
5058
5060{
5062
5063 query.prepare(
5064 "UPDATE recorded "
5065 "SET season = :SEASON, episode = :EPISODE "
5066 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
5067 "AND recordid = :RECORDID");
5068
5069 query.bindValue(":SEASON", seas);
5070 query.bindValue(":EPISODE", ep);
5071 query.bindValue(":CHANID", m_chanId);
5072 query.bindValue(":STARTTIME", m_recStartTs);
5073 query.bindValue(":RECORDID", m_recordId);
5074 if (!query.exec())
5075 {
5076 MythDB::DBError("SaveSeasonEpisode", query);
5077 return;
5078 }
5079
5081}
5082
5083void ProgramInfo::SaveInetRef(const QString &inet)
5084{
5086
5087 query.prepare(
5088 "UPDATE recorded "
5089 "SET inetref = :INETREF "
5090 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
5091 "AND recordid = :RECORDID");
5092
5093 query.bindValue(":INETREF", inet);
5094 query.bindValue(":CHANID", m_chanId);
5095 query.bindValue(":STARTTIME", m_recStartTs);
5096 query.bindValue(":RECORDID", m_recordId);
5097 query.exec();
5098
5100}
5101
5109{
5110 if (IsLocal() && QFileInfo(m_pathname).isReadable())
5111 return true;
5112
5113 if (!IsMythStream())
5114 m_pathname = GetPlaybackURL(true, false);
5115
5116 if (IsMythStream())
5117 return RemoteCheckFile(this);
5118
5119 if (IsLocal())
5120 return QFileInfo(m_pathname).isReadable();
5121
5122 return false;
5123}
5124
5125QString ProgramInfo::QueryRecordingGroupPassword(const QString &group)
5126{
5127 QString result;
5128
5130 query.prepare("SELECT password FROM recgroups "
5131 "WHERE recgroup = :GROUP");
5132 query.bindValue(":GROUP", group);
5133
5134 if (query.exec() && query.next())
5135 result = query.value(0).toString();
5136
5137 return result;
5138}
5139
5143{
5145 query.prepare("SELECT recgroup FROM recorded "
5146 "WHERE chanid = :CHANID AND "
5147 " starttime = :START");
5148 query.bindValue(":START", m_recStartTs);
5149 query.bindValue(":CHANID", m_chanId);
5150
5151 QString grp = m_recGroup;
5152 if (query.exec() && query.next())
5153 grp = query.value(0).toString();
5154
5155 return grp;
5156}
5157
5159{
5161 query.prepare("SELECT transcoder FROM recorded "
5162 "WHERE chanid = :CHANID AND "
5163 " starttime = :START");
5164 query.bindValue(":CHANID", m_chanId);
5165 query.bindValue(":START", m_recStartTs);
5166
5167 if (query.exec() && query.next())
5168 return query.value(0).toUInt();
5169
5170 return 0;
5171}
5172
5179{
5180 if (!IsLocal())
5181 {
5182 if (!gCoreContext->IsBackend())
5183 return "";
5184
5185 QString path = GetPlaybackURL(false, true);
5186 if (path.startsWith("/"))
5187 {
5188 QFileInfo testFile(path);
5189 return testFile.path();
5190 }
5191
5192 return "";
5193 }
5194
5195 QFileInfo testFile(m_pathname);
5196 if (testFile.exists() || (gCoreContext->GetHostName() == m_hostname))
5197 {
5198 // we may be recording this file and it may not exist yet so we need
5199 // to do some checking to see what is in pathname
5200 if (testFile.exists())
5201 {
5202 if (testFile.isSymLink())
5203 testFile.setFile(getSymlinkTarget(m_pathname));
5204
5205 if (testFile.isFile())
5206 return testFile.path();
5207 if (testFile.isDir())
5208 return testFile.filePath();
5209 }
5210 else
5211 {
5212 testFile.setFile(testFile.absolutePath());
5213 if (testFile.exists())
5214 {
5215 if (testFile.isSymLink())
5216 testFile.setFile(getSymlinkTarget(testFile.path()));
5217
5218 if (testFile.isDir())
5219 return testFile.filePath();
5220 }
5221 }
5222 }
5223
5224 return "";
5225}
5226
5233void ProgramInfo::MarkAsInUse(bool inuse, const QString& usedFor)
5234{
5235 if (!IsRecording())
5236 return;
5237
5238 bool notifyOfChange = false;
5239
5240 if (inuse &&
5241 (m_inUseForWhat.isEmpty() ||
5242 (!usedFor.isEmpty() && usedFor != m_inUseForWhat)))
5243 {
5244 if (!usedFor.isEmpty())
5245 {
5246
5247#ifdef DEBUG_IN_USE
5248 if (!m_inUseForWhat.isEmpty())
5249 {
5250 LOG(VB_GENERAL, LOG_INFO, LOC +
5251 QString("MarkAsInUse(true, '%1'->'%2')")
5252 .arg(m_inUseForWhat).arg(usedFor) +
5253 " -- use has changed");
5254 }
5255 else
5256 {
5257 LOG(VB_GENERAL, LOG_INFO, LOC +
5258 QString("MarkAsInUse(true, ''->'%1')").arg(usedFor));
5259 }
5260#endif // DEBUG_IN_USE
5261
5262 m_inUseForWhat = usedFor;
5263 }
5264 else if (m_inUseForWhat.isEmpty())
5265 {
5266 m_inUseForWhat = QString("%1 [%2]")
5267 .arg(QObject::tr("Unknown")).arg(getpid());
5268 LOG(VB_GENERAL, LOG_WARNING, LOC +
5269 QString("MarkAsInUse(true, ''->'%1')").arg(m_inUseForWhat) +
5270 " -- use was not explicitly set");
5271 }
5272
5273 notifyOfChange = true;
5274 }
5275
5276 if (!inuse && !m_inUseForWhat.isEmpty() && usedFor != m_inUseForWhat)
5277 {
5278 LOG(VB_GENERAL, LOG_WARNING, LOC +
5279 QString("MarkAsInUse(false, '%1'->'%2')")
5280 .arg(m_inUseForWhat, usedFor) +
5281 " -- use has changed since first setting as in use.");
5282 }
5283#ifdef DEBUG_IN_USE
5284 else if (!inuse)
5285 {
5286 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("MarkAsInUse(false, '%1')")
5287 .arg(m_inUseForWhat));
5288 }
5289#endif // DEBUG_IN_USE
5290
5291 if (!inuse && m_inUseForWhat.isEmpty())
5292 m_inUseForWhat = usedFor;
5293
5294 if (!inuse && m_inUseForWhat.isEmpty())
5295 {
5296 LOG(VB_GENERAL, LOG_WARNING, LOC +
5297 "MarkAsInUse requires a key to delete in use mark");
5298 return; // can't delete if we don't have a key
5299 }
5300
5301 if (!inuse)
5302 {
5304 query.prepare(
5305 "DELETE FROM inuseprograms "
5306 "WHERE chanid = :CHANID AND starttime = :STARTTIME AND "
5307 " hostname = :HOSTNAME AND recusage = :RECUSAGE");
5308 query.bindValue(":CHANID", m_chanId);
5309 query.bindValue(":STARTTIME", m_recStartTs);
5310 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5311 query.bindValue(":RECUSAGE", m_inUseForWhat);
5312
5313 if (!query.exec())
5314 MythDB::DBError("MarkAsInUse -- delete", query);
5315
5316 m_inUseForWhat.clear();
5319 return;
5320 }
5321
5322 if (m_pathname == GetBasename())
5323 m_pathname = GetPlaybackURL(false, true);
5324
5325 QString recDir = DiscoverRecordingDirectory();
5326
5327 QDateTime inUseTime = MythDate::current(true);
5328
5330 query.prepare(
5331 "SELECT count(*) "
5332 "FROM inuseprograms "
5333 "WHERE chanid = :CHANID AND starttime = :STARTTIME AND "
5334 " hostname = :HOSTNAME AND recusage = :RECUSAGE");
5335 query.bindValue(":CHANID", m_chanId);
5336 query.bindValue(":STARTTIME", m_recStartTs);
5337 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5338 query.bindValue(":RECUSAGE", m_inUseForWhat);
5339
5340 if (!query.exec())
5341 {
5342 MythDB::DBError("MarkAsInUse -- select", query);
5343 }
5344 else if (!query.next())
5345 {
5346 LOG(VB_GENERAL, LOG_ERR, LOC + "MarkAsInUse -- select query failed");
5347 }
5348 else if (query.value(0).toBool())
5349 {
5350 query.prepare(
5351 "UPDATE inuseprograms "
5352 "SET lastupdatetime = :UPDATETIME "
5353 "WHERE chanid = :CHANID AND starttime = :STARTTIME AND "
5354 " hostname = :HOSTNAME AND recusage = :RECUSAGE");
5355 query.bindValue(":CHANID", m_chanId);
5356 query.bindValue(":STARTTIME", m_recStartTs);
5357 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5358 query.bindValue(":RECUSAGE", m_inUseForWhat);
5359 query.bindValue(":UPDATETIME", inUseTime);
5360
5361 if (!query.exec())
5362 MythDB::DBError("MarkAsInUse -- update failed", query);
5363 else
5364 m_lastInUseTime = inUseTime;
5365 }
5366 else // if (!query.value(0).toUInt())
5367 {
5368 query.prepare(
5369 "INSERT INTO inuseprograms "
5370 " (chanid, starttime, recusage, hostname, "
5371 " lastupdatetime, rechost, recdir) "
5372 "VALUES "
5373 " (:CHANID, :STARTTIME, :RECUSAGE, :HOSTNAME, "
5374 " :UPDATETIME, :RECHOST, :RECDIR)");
5375 query.bindValue(":CHANID", m_chanId);
5376 query.bindValue(":STARTTIME", m_recStartTs);
5377 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5378 query.bindValue(":RECUSAGE", m_inUseForWhat);
5379 query.bindValue(":UPDATETIME", inUseTime);
5380 query.bindValue(":RECHOST",
5381 m_hostname.isEmpty() ? gCoreContext->GetHostName()
5382 : m_hostname);
5383 query.bindValue(":RECDIR", recDir);
5384
5385 if (!query.exec())
5386 MythDB::DBError("MarkAsInUse -- insert failed", query);
5387 else
5388 m_lastInUseTime = inUseTime;
5389 }
5390
5391 if (!notifyOfChange)
5392 return;
5393
5394 // Let others know we changed status
5395 QDateTime oneHourAgo = MythDate::current().addSecs(-kLastUpdateOffset);
5396 query.prepare("SELECT DISTINCT recusage "
5397 "FROM inuseprograms "
5398 "WHERE lastupdatetime >= :ONEHOURAGO AND "
5399 " chanid = :CHANID AND "
5400 " starttime = :STARTTIME");
5401 query.bindValue(":CHANID", m_chanId);
5402 query.bindValue(":STARTTIME", m_recStartTs);
5403 query.bindValue(":ONEHOURAGO", oneHourAgo);
5404 if (!query.exec())
5405 return; // not safe to send update event...
5406
5407 m_programFlags &= ~(FL_INUSEPLAYING | FL_INUSERECORDING | FL_INUSEOTHER);
5408 while (query.next())
5409 {
5410 QString inUseForWhat = query.value(0).toString();
5411 if (inUseForWhat.contains(kPlayerInUseID))
5412 m_programFlags |= FL_INUSEPLAYING;
5413 else if (inUseForWhat == kRecorderInUseID)
5414 m_programFlags |= FL_INUSERECORDING;
5415 else
5416 m_programFlags |= FL_INUSEOTHER;
5417 }
5419}
5420
5426bool ProgramInfo::QueryTuningInfo(QString &channum, QString &input) const
5427{
5428 channum.clear();
5429 input.clear();
5431
5432 query.prepare("SELECT channel.channum, capturecard.inputname "
5433 "FROM channel, capturecard "
5434 "WHERE channel.chanid = :CHANID AND "
5435 " capturecard.sourceid = :SOURCEID AND "
5436 " capturecard.cardid = :INPUTID");
5437 query.bindValue(":CHANID", m_chanId);
5438 query.bindValue(":SOURCEID", m_sourceId);
5439 query.bindValue(":INPUTID", m_inputId);
5440
5441 if (query.exec() && query.next())
5442 {
5443 channum = query.value(0).toString();
5444 input = query.value(1).toString();
5445 return true;
5446 }
5447 MythDB::DBError("GetChannel(ProgInfo...)", query);
5448 return false;
5449}
5450
5451namespace {
5452 // This prevents the compiler and tools from complaining that all
5453 // the strings in the init_tr function are unused.
5454 int s_tr_count {0};
5455};
5456static void init_tr(void)
5457{
5458 QString rec_profile_names =
5459 QObject::tr("Default", "Recording Profile Default") +
5460 QObject::tr("High Quality", "Recording Profile High Quality") +
5461 QObject::tr("Live TV", "Recording Profile Live TV") +
5462 QObject::tr("Low Quality", "Recording Profile Low Quality") +
5463 QObject::tr("Medium Quality", "Recording Profile Medium Quality") +
5464 QObject::tr("MPEG-2", "Recording Profile MPEG-2") +
5465 QObject::tr("RTjpeg/MPEG-4", "Recording Profile RTjpeg/MPEG-4");
5466
5467
5468 QString rec_profile_groups =
5469 QObject::tr("CRC IP Recorders",
5470 "Recording Profile Group Name") +
5471 QObject::tr("FireWire Input",
5472 "Recording Profile Group Name") +
5473 QObject::tr("Freebox Input",
5474 "Recording Profile Group Name") +
5475 QObject::tr("Hardware DVB Encoders",
5476 "Recording Profile Group Name") +
5477 QObject::tr("Hardware HDTV",
5478 "Recording Profile Group Name") +
5479 QObject::tr("Hardware MJPEG Encoders (Matrox G200-TV, Miro DC10, etc)",
5480 "Recording Profile Group Name") +
5481 QObject::tr("HD-PVR Recorders",
5482 "Recording Profile Group Name") +
5483 QObject::tr("HDHomeRun Recorders",
5484 "Recording Profile Group Name") +
5485 QObject::tr("MPEG-2 Encoders (PVR-x50, PVR-500)",
5486 "Recording Profile Group Name") +
5487 QObject::tr("Software Encoders (V4L based)",
5488 "Recording Profile Group Name") +
5489 QObject::tr("Transcoders",
5490 "Recording Profile Group Name") +
5491 QObject::tr("USB MPEG-4 Encoder (Plextor ConvertX, etc)",
5492 "Recording Profile Group Name") +
5493 QObject::tr("V4L2 Encoders",
5494 "Recording Profile Group Name");
5495
5496 QString display_rec_groups =
5497 QObject::tr("All Programs", "Recording Group All Programs") +
5498 QObject::tr("All", "Recording Group All Programs -- short form") +
5499 QObject::tr("Live TV", "Recording Group Live TV") +
5500 QObject::tr("Default", "Recording Group Default") +
5501 QObject::tr("Deleted", "Recording Group Deleted");
5502
5503 QString special_program_groups =
5504 QObject::tr("All Programs - %1",
5505 "Show all programs from a specific recording group");
5506
5507 QString storage_groups =
5508 QObject::tr("Default", "Storage Group Name") +
5509 QObject::tr("Live TV", "Storage Group Name") +
5510 QObject::tr("Thumbnails", "Storage Group Name") +
5511 QObject::tr("DB Backups", "Storage Group Name");
5512
5513 QString play_groups =
5514 QObject::tr("Default", "Playback Group Name");
5515
5516 s_tr_count = rec_profile_names.length() +
5517 rec_profile_groups.length() +
5518 display_rec_groups.length() +
5519 special_program_groups.length() +
5520 storage_groups.length() +
5521 play_groups.length();
5522}
5523
5525{
5526 QMutexLocker locker(&s_staticDataLock);
5527 if (!s_updater)
5529 return 1;
5530}
5531
5533QString ProgramInfo::i18n(const QString &msg)
5534{
5535 static std::once_flag init_done;
5536 std::call_once(init_done, init_tr);
5537
5538 QByteArray msg_arr = msg.toLatin1();
5539 // The following translation should only ever be called for
5540 // strings that were previously initialized by the init_tr
5541 // function. clazy:exclude-next-line=tr-non-literal
5542 QString msg_i18n = QObject::tr(msg_arr.constData());
5543 QByteArray msg_i18n_arr = msg_i18n.toLatin1();
5544 return (msg_arr == msg_i18n_arr) ? msg : msg_i18n;
5545}
5546
5554{
5555 QString pburl = GetPlaybackURL(false, true);
5556 if (pburl.startsWith("myth://"))
5557 {
5558 str.replace(QString("%DIR%"), pburl);
5559 }
5560 else
5561 {
5562 QFileInfo dirInfo(pburl);
5563 str.replace(QString("%DIR%"), dirInfo.path());
5564 }
5565
5566 // N.B. Contents of %MATCH% string variables get parsed into a single
5567 // QStringLists. Quotes in strings will cause lost/truncated output.
5568 // Replace QUOTATION MARK (U+0022)j with MODIFIER LETTER DOUBLE PRIME (U+02BA).
5569
5570 str.replace(QString("%FILE%"), GetBasename());
5571 str.replace(QString("%TITLE%"), m_title.replace("\"", "ʺ"));
5572 str.replace(QString("%SUBTITLE%"), m_subtitle.replace("\"", "ʺ"));
5573 str.replace(QString("%SEASON%"), QString::number(m_season));
5574 str.replace(QString("%EPISODE%"), QString::number(m_episode));
5575 str.replace(QString("%TOTALEPISODES%"), QString::number(m_totalEpisodes));
5576 str.replace(QString("%SYNDICATEDEPISODE%"), m_syndicatedEpisode);
5577 str.replace(QString("%DESCRIPTION%"), m_description.replace("\"", "ʺ"));
5578 str.replace(QString("%HOSTNAME%"), m_hostname);
5579 str.replace(QString("%CATEGORY%"), m_category);
5580 str.replace(QString("%RECGROUP%"), m_recGroup);
5581 str.replace(QString("%PLAYGROUP%"), m_playGroup);
5582 str.replace(QString("%CHANID%"), QString::number(m_chanId));
5583 str.replace(QString("%INETREF%"), m_inetRef);
5584 str.replace(QString("%PARTNUMBER%"), QString::number(m_partNumber));
5585 str.replace(QString("%PARTTOTAL%"), QString::number(m_partTotal));
5586 str.replace(QString("%ORIGINALAIRDATE%"),
5587 m_originalAirDate.toString(Qt::ISODate));
5588 static const std::array<const QString,4> s_timeStr
5589 { "STARTTIME", "ENDTIME", "PROGSTART", "PROGEND", };
5590 const std::array<const QDateTime *,4> time_dtr
5592 for (size_t i = 0; i < s_timeStr.size(); i++)
5593 {
5594 str.replace(QString("%%1%").arg(s_timeStr[i]),
5595 (time_dtr[i]->toLocalTime()).toString("yyyyMMddhhmmss"));
5596 str.replace(QString("%%1ISO%").arg(s_timeStr[i]),
5597 (time_dtr[i]->toLocalTime()).toString(Qt::ISODate));
5598 str.replace(QString("%%1UTC%").arg(s_timeStr[i]),
5599 time_dtr[i]->toString("yyyyMMddhhmmss"));
5600 str.replace(QString("%%1ISOUTC%").arg(s_timeStr[i]),
5601 time_dtr[i]->toString(Qt::ISODate));
5602 }
5603 str.replace(QString("%RECORDEDID%"), QString::number(m_recordedId));
5604}
5605
5606QMap<QString,uint32_t> ProgramInfo::QueryInUseMap(void)
5607{
5608 QMap<QString, uint32_t> inUseMap;
5609 QDateTime oneHourAgo = MythDate::current().addSecs(-kLastUpdateOffset);
5610
5612
5613 query.prepare("SELECT DISTINCT chanid, starttime, recusage "
5614 "FROM inuseprograms WHERE lastupdatetime >= :ONEHOURAGO");
5615 query.bindValue(":ONEHOURAGO", oneHourAgo);
5616
5617 if (!query.exec())
5618 return inUseMap;
5619
5620 while (query.next())
5621 {
5622 QString inUseKey = ProgramInfo::MakeUniqueKey(
5623 query.value(0).toUInt(),
5624 MythDate::as_utc(query.value(1).toDateTime()));
5625
5626 QString inUseForWhat = query.value(2).toString();
5627
5628 if (!inUseMap.contains(inUseKey))
5629 inUseMap[inUseKey] = 0;
5630
5631 if (inUseForWhat.contains(kPlayerInUseID))
5632 inUseMap[inUseKey] |= FL_INUSEPLAYING;
5633 else if (inUseForWhat == kRecorderInUseID)
5634 inUseMap[inUseKey] |= FL_INUSERECORDING;
5635 else
5636 inUseMap[inUseKey] |= FL_INUSEOTHER;
5637 }
5638
5639 return inUseMap;
5640}
5641
5643{
5644 QMap<QString,bool> is_job_running;
5645
5647 query.prepare("SELECT chanid, starttime, status FROM jobqueue "
5648 "WHERE type = :TYPE");
5649 query.bindValue(":TYPE", type);
5650 if (!query.exec())
5651 return is_job_running;
5652
5653 while (query.next())
5654 {
5655 uint chanid = query.value(0).toUInt();
5656 QDateTime recstartts = MythDate::as_utc(query.value(1).toDateTime());
5657 int tmpStatus = query.value(2).toInt();
5658 if ((tmpStatus != /*JOB_UNKNOWN*/ 0x0000) &&
5659 (tmpStatus != /*JOB_QUEUED*/ 0x0001) &&
5660 (!(tmpStatus & /*JOB_DONE*/ 0x0100)))
5661 {
5662 is_job_running[
5663 ProgramInfo::MakeUniqueKey(chanid,recstartts)] = true;
5664 }
5665 }
5666
5667 return is_job_running;
5668}
5669
5671 const QString &tmptable, int recordid)
5672{
5673 QStringList slist;
5674
5676 if (sched && tmptable.isEmpty())
5677 {
5678 sched->GetAllPending(slist);
5679 return slist;
5680 }
5681
5682 if (sched)
5683 {
5684 LOG(VB_GENERAL, LOG_ERR,
5685 "Called from master backend\n\t\t\t"
5686 "with recordid or tmptable, this is not currently supported");
5687 return slist;
5688 }
5689
5690 slist.push_back(
5691 (tmptable.isEmpty()) ?
5692 QString("QUERY_GETALLPENDING") :
5693 QString("QUERY_GETALLPENDING %1 %2").arg(tmptable).arg(recordid));
5694
5695 if (!gCoreContext->SendReceiveStringList(slist) || slist.size() < 2)
5696 {
5697 LOG(VB_GENERAL, LOG_ALERT,
5698 "LoadFromScheduler(): Error querying master.");
5699 slist.clear();
5700 }
5701
5702 return slist;
5703}
5704
5705// NOTE: This may look ugly, and in some ways it is, but it's designed this
5706// way for with two purposes in mind - Consistent behaviour and speed
5707// So if you do make changes then carefully test that it doesn't result
5708// in any regressions in total speed of execution or adversely affect the
5709// results returned for any of it's users.
5710static bool FromProgramQuery(const QString &sql, const MSqlBindings &bindings,
5711 MSqlQuery &query, const uint start,
5712 const uint limit, uint &count,
5713 ProgGroupBy::Type groupBy)
5714{
5715 count = 0;
5716
5717 QString columns = QString(
5718 "program.chanid, program.starttime, program.endtime, " // 0-2
5719 "program.title, program.subtitle, program.description, " // 3-5
5720 "program.category, channel.channum, channel.callsign, " // 6-8
5721 "channel.name, program.previouslyshown, channel.commmethod, " // 9-11
5722 "channel.outputfilters, program.seriesid, program.programid, " // 12-14
5723 "program.airdate, program.stars, program.originalairdate, " // 15-17
5724 "program.category_type, oldrecstatus.recordid, " // 18-19
5725 "oldrecstatus.rectype, oldrecstatus.recstatus, " // 20-21
5726 "oldrecstatus.findid, program.videoprop+0, program.audioprop+0, " // 22-24
5727 "program.subtitletypes+0, program.syndicatedepisodenumber, " // 25-26
5728 "program.partnumber, program.parttotal, " // 27-28
5729 "program.season, program.episode, program.totalepisodes "); // 29-31
5730
5731 QString querystr = QString(
5732 "SELECT %1 FROM ( "
5733 " SELECT %2 "
5734 " FROM program "
5735 " LEFT JOIN channel ON program.chanid = channel.chanid "
5736 " LEFT JOIN oldrecorded AS oldrecstatus ON "
5737 " oldrecstatus.future = 0 AND "
5738 " program.title = oldrecstatus.title AND "
5739 " channel.callsign = oldrecstatus.station AND "
5740 " program.starttime = oldrecstatus.starttime "
5741 ) + sql +
5742 ") groupsq ";
5743
5744 // If a ProgGroupBy option is specified, wrap the query in an outer
5745 // query using row_number() and select only rows with value 1. We
5746 // do this instead of relying on MySQL's liberal support for group
5747 // by on non-aggregated columns because it is deterministic.
5748 if (groupBy != ProgGroupBy::None)
5749 {
5750 columns +=
5751 ", row_number() over ( "
5752 " partition by ";
5753 if (groupBy == ProgGroupBy::ChanNum)
5754 columns += "channel.channum, "
5755 " channel.callsign, ";
5756 else if (groupBy == ProgGroupBy::CallSign)
5757 columns += "channel.callsign, ";
5758 else if (groupBy == ProgGroupBy::ProgramId)
5759 columns += "program.programid, ";
5760 columns +=
5761 " program.title, "
5762 " program.starttime "
5763 " order by channel.recpriority desc, "
5764 " channel.sourceid, "
5765 " channel.channum+0 "
5766 ") grouprn ";
5767 querystr += "WHERE grouprn = 1 ";
5768 }
5769
5770 // If a limit arg was given then append the LIMIT, otherwise set a hard
5771 // limit of 20000.
5772 if (limit > 0)
5773 querystr += QString("LIMIT %1 ").arg(limit);
5774 else if (!querystr.contains(" LIMIT "))
5775 querystr += " LIMIT 20000 "; // For performance reasons we have to have an upper limit
5776
5777 MSqlBindings::const_iterator it;
5778 // Some end users need to know the total number of matching records,
5779 // irrespective of any LIMIT clause
5780 //
5781 // SQL_CALC_FOUND_ROWS is better than COUNT(*), as COUNT(*) won't work
5782 // with any GROUP BY clauses. COUNT is marginally faster but not enough to
5783 // matter
5784 //
5785 // It's considerably faster in my tests to do a separate query which returns
5786 // no data using SQL_CALC_FOUND_ROWS than it is to use SQL_CALC_FOUND_ROWS
5787 // with the full query. Unfortunate but true.
5788 //
5789 // e.g. Fetching all programs for the next 14 days with a LIMIT of 10 - 220ms
5790 // Same query with SQL_CALC_FOUND_ROWS - 1920ms
5791 // Same query but only one column and with SQL_CALC_FOUND_ROWS - 370ms
5792 // Total to fetch both the count and the data = 590ms vs 1920ms
5793 // Therefore two queries is 1.4 seconds faster than one query.
5794 if (start > 0 || limit > 0)
5795 {
5796 QString countStr = querystr
5797 .arg("SQL_CALC_FOUND_ROWS chanid", columns);
5798 query.prepare(countStr);
5799 for (it = bindings.begin(); it != bindings.end(); ++it)
5800 {
5801 if (countStr.contains(it.key()))
5802 query.bindValue(it.key(), it.value());
5803 }
5804
5805 if (!query.exec())
5806 {
5807 MythDB::DBError("LoadFromProgramQuery", query);
5808 return false;
5809 }
5810
5811 if (query.exec("SELECT FOUND_ROWS()") && query.next())
5812 count = query.value(0).toUInt();
5813 }
5814
5815 if (start > 0)
5816 querystr += QString("OFFSET %1 ").arg(start);
5817
5818 querystr = querystr.arg("*", columns);
5819 query.prepare(querystr);
5820 for (it = bindings.begin(); it != bindings.end(); ++it)
5821 {
5822 if (querystr.contains(it.key()))
5823 query.bindValue(it.key(), it.value());
5824 }
5825
5826 if (!query.exec())
5827 {
5828 MythDB::DBError("LoadFromProgramQuery", query);
5829 return false;
5830 }
5831
5832 return true;
5833}
5834
5835bool LoadFromProgram(ProgramList &destination, const QString &where,
5836 const QString &groupBy, const QString &orderBy,
5837 const MSqlBindings &bindings, const ProgramList &schedList)
5838{
5839 uint count = 0;
5840
5841 QString queryStr = "";
5842
5843 if (!where.isEmpty())
5844 queryStr.append(QString("WHERE %1 ").arg(where));
5845
5846 if (!groupBy.isEmpty())
5847 queryStr.append(QString("GROUP BY %1 ").arg(groupBy));
5848
5849 if (!orderBy.isEmpty())
5850 queryStr.append(QString("ORDER BY %1 ").arg(orderBy));
5851
5852 // ------------------------------------------------------------------------
5853
5854 return LoadFromProgram(destination, queryStr, bindings, schedList, 0, 0,
5855 count);
5856}
5857
5859 const QString &sql, const MSqlBindings &bindings,
5860 const ProgramList &schedList, ProgGroupBy::Type groupBy)
5861{
5862 uint count = 0;
5863
5864 QString queryStr = sql;
5865 // ------------------------------------------------------------------------
5866 // FIXME: Remove the following. These all make assumptions about the content
5867 // of the sql passed in, they can end up breaking that query by
5868 // inserting a WHERE clause after a GROUP BY, or a GROUP BY after
5869 // an ORDER BY. These should be part of the sql passed in otherwise
5870 // the caller isn't getting what they asked for and to fix that they
5871 // are forced to include a GROUP BY, ORDER BY or WHERE that they
5872 // do not want
5873 //
5874 // See the new version of this method above. The plan is to convert existing
5875 // users of this method and have them supply all of their own data for the
5876 // queries (no defaults.)
5877
5878 if (!queryStr.contains("WHERE"))
5879 queryStr += " WHERE deleted IS NULL AND visible > 0 ";
5880
5881 if (!queryStr.contains("ORDER BY"))
5882 {
5883 queryStr += " ORDER BY program.starttime, ";
5884 QString chanorder =
5885 gCoreContext->GetSetting("ChannelOrdering", "channum");
5886 if (chanorder != "channum")
5887 queryStr += chanorder + " ";
5888 else // approximation which the DB can handle
5889 queryStr += "atsc_major_chan,atsc_minor_chan,channum,callsign ";
5890 }
5891
5892 // ------------------------------------------------------------------------
5893
5894 return LoadFromProgram(destination, queryStr, bindings, schedList, 0, 0,
5895 count, groupBy);
5896}
5897
5898bool LoadFromProgram( ProgramList &destination,
5899 const QString &sql, const MSqlBindings &bindings,
5900 const ProgramList &schedList,
5901 const uint start, const uint limit, uint &count,
5902 ProgGroupBy::Type groupBy)
5903{
5904 destination.clear();
5905
5906 if (sql.contains(" OFFSET", Qt::CaseInsensitive))
5907 {
5908 LOG(VB_GENERAL, LOG_WARNING, "LoadFromProgram(): SQL contains OFFSET "
5909 "clause, caller should be updated to use "
5910 "start parameter instead");
5911 }
5912
5913 if (sql.contains(" LIMIT", Qt::CaseInsensitive))
5914 {
5915 LOG(VB_GENERAL, LOG_WARNING, "LoadFromProgram(): SQL contains LIMIT "
5916 "clause, caller should be updated to use "
5917 "limit parameter instead");
5918 }
5919
5921 query.setForwardOnly(true);
5922 if (!FromProgramQuery(sql, bindings, query, start, limit, count, groupBy))
5923 return false;
5924
5925 if (count == 0)
5926 count = query.size();
5927
5928 while (query.next())
5929 {
5930 destination.push_back(
5931 new ProgramInfo(
5932 query.value(3).toString(), // title
5933 QString(), // sortTitle
5934 query.value(4).toString(), // subtitle
5935 QString(), // sortSubtitle
5936 query.value(5).toString(), // description
5937 query.value(26).toString(), // syndicatedepisodenumber
5938 query.value(6).toString(), // category
5939
5940 query.value(0).toUInt(), // chanid
5941 query.value(7).toString(), // channum
5942 query.value(8).toString(), // chansign
5943 query.value(9).toString(), // channame
5944 query.value(12).toString(), // chanplaybackfilters
5945
5946 MythDate::as_utc(query.value(1).toDateTime()), // startts
5947 MythDate::as_utc(query.value(2).toDateTime()), // endts
5948 MythDate::as_utc(query.value(1).toDateTime()), // recstartts
5949 MythDate::as_utc(query.value(2).toDateTime()), // recendts
5950
5951 query.value(13).toString(), // seriesid
5952 query.value(14).toString(), // programid
5953 string_to_myth_category_type(query.value(18).toString()), // catType
5954
5955 query.value(16).toFloat(), // stars
5956 query.value(15).toUInt(), // year
5957 query.value(27).toUInt(), // partnumber
5958 query.value(28).toUInt(), // parttotal
5959 query.value(17).toDate(), // originalAirDate
5960 RecStatus::Type(query.value(21).toInt()), // recstatus
5961 query.value(19).toUInt(), // recordid
5962 RecordingType(query.value(20).toInt()), // rectype
5963 query.value(22).toUInt(), // findid
5964
5965 query.value(11).toInt() == COMM_DETECT_COMMFREE, // commfree
5966 query.value(10).toBool(), // repeat
5967 query.value(23).toInt(), // videoprop
5968 query.value(24).toInt(), // audioprop
5969 query.value(25).toInt(), // subtitletypes
5970 query.value(29).toUInt(), // season
5971 query.value(30).toUInt(), // episode
5972 query.value(31).toUInt(), // totalepisodes
5973
5974 schedList));
5975 }
5976
5977 return true;
5978}
5979
5981 const QDateTime& starttime)
5982{
5983 ProgramInfo *progInfo = nullptr;
5984
5985 // Build add'l SQL statement for Program Listing
5986
5987 MSqlBindings bindings;
5988 QString sSQL = "WHERE program.chanid = :ChanId "
5989 "AND program.starttime = :StartTime ";
5990
5991 bindings[":ChanId" ] = chanid;
5992 bindings[":StartTime"] = starttime;
5993
5994 // Get all Pending Scheduled Programs
5995
5996 ProgramList schedList;
5997 bool hasConflicts = false;
5998 LoadFromScheduler(schedList, hasConflicts);
5999
6000 // ----------------------------------------------------------------------
6001
6002 ProgramList progList;
6003
6004 LoadFromProgram( progList, sSQL, bindings, schedList );
6005
6006 if (progList.empty())
6007 return progInfo;
6008
6009 // progList is an Auto-delete deque, the object will be deleted with the
6010 // list, so we need to make a copy
6011 progInfo = new ProgramInfo(*(progList[0]));
6012
6013 return progInfo;
6014}
6015
6017 ProgramList &destination, const QString &sql, const MSqlBindings &bindings)
6018{
6019 uint count = 0;
6020 return LoadFromOldRecorded(destination, sql, bindings, 0, 0, count);
6021}
6022
6023bool LoadFromOldRecorded(ProgramList &destination, const QString &sql,
6024 const MSqlBindings &bindings,
6025 const uint start, const uint limit, uint &count)
6026{
6027 destination.clear();
6028
6030 query.setForwardOnly(true);
6031
6032 QString columns =
6033 "oldrecorded.chanid, starttime, endtime, "
6034 "title, subtitle, description, season, episode, category, seriesid, "
6035 "programid, inetref, channel.channum, channel.callsign, "
6036 "channel.name, findid, rectype, recstatus, recordid, duplicate ";
6037
6038 QString querystr =
6039 "SELECT %1 "
6040 "FROM oldrecorded "
6041 "LEFT JOIN channel ON oldrecorded.chanid = channel.chanid "
6042 "WHERE oldrecorded.future = 0 "
6043 + sql;
6044
6045 bool hasLimit = querystr.contains(" LIMIT ",Qt::CaseInsensitive);
6046
6047 // make sure the most recent rows are retrieved first in case
6048 // there are more than the limit to be set below
6049 if (!hasLimit && !querystr.contains(" ORDER ",Qt::CaseInsensitive))
6050 querystr += " ORDER BY starttime DESC ";
6051
6052 // If a limit arg was given then append the LIMIT, otherwise set a hard
6053 // limit of 20000, which can be overridden by a setting
6054 if (limit > 0)
6055 {
6056 querystr += QString("LIMIT %1 ").arg(limit);
6057 }
6058 else if (!hasLimit)
6059 {
6060 // For performance reasons we have to have an upper limit
6061 int nLimit = gCoreContext->GetNumSetting("PrevRecLimit", 20000);
6062 // For sanity sake at least 100
6063 nLimit = std::max(nLimit, 100);
6064 querystr += QString("LIMIT %1 ").arg(nLimit);
6065 }
6066
6067 MSqlBindings::const_iterator it;
6068 // If count is non-zero then also return total number of matching records,
6069 // irrespective of any LIMIT clause
6070 //
6071 // SQL_CALC_FOUND_ROWS is better than COUNT(*), as COUNT(*) won't work
6072 // with any GROUP BY clauses. COUNT is marginally faster but not enough to
6073 // matter
6074 //
6075 // It's considerably faster in my tests to do a separate query which returns
6076 // no data using SQL_CALC_FOUND_ROWS than it is to use SQL_CALC_FOUND_ROWS
6077 // with the full query. Unfortunate but true.
6078 //
6079 // e.g. Fetching all programs for the next 14 days with a LIMIT of 10 - 220ms
6080 // Same query with SQL_CALC_FOUND_ROWS - 1920ms
6081 // Same query but only one column and with SQL_CALC_FOUND_ROWS - 370ms
6082 // Total to fetch both the count and the data = 590ms vs 1920ms
6083 // Therefore two queries is 1.4 seconds faster than one query.
6084 if (count > 0 && (start > 0 || limit > 0))
6085 {
6086 QString countStr = querystr.arg("SQL_CALC_FOUND_ROWS oldrecorded.chanid");
6087 query.prepare(countStr);
6088 for (it = bindings.begin(); it != bindings.end(); ++it)
6089 {
6090 if (countStr.contains(it.key()))
6091 query.bindValue(it.key(), it.value());
6092 }
6093
6094 if (!query.exec())
6095 {
6096 MythDB::DBError("LoadFromOldRecorded", query);
6097 return false;
6098 }
6099
6100 if (query.exec("SELECT FOUND_ROWS()") && query.next())
6101 count = query.value(0).toUInt();
6102 }
6103
6104 if (start > 0)
6105 querystr += QString("OFFSET %1 ").arg(start);
6106
6107 querystr = querystr.arg(columns);
6108 query.prepare(querystr);
6109 for (it = bindings.begin(); it != bindings.end(); ++it)
6110 {
6111 if (querystr.contains(it.key()))
6112 query.bindValue(it.key(), it.value());
6113 }
6114
6115 if (!query.exec())
6116 {
6117 MythDB::DBError("LoadFromOldRecorded", query);
6118 return false;
6119 }
6120
6121 while (query.next())
6122 {
6123 uint chanid = query.value(0).toUInt();
6124 QString channum = QString("#%1").arg(chanid);
6125 QString chansign = channum;
6126 QString channame = channum;
6127 if (!query.value(12).toString().isEmpty())
6128 {
6129 channum = query.value(12).toString();
6130 chansign = query.value(13).toString();
6131 channame = query.value(14).toString();
6132 }
6133
6134 destination.push_back(new ProgramInfo(
6135 query.value(3).toString(),
6136 QString(),
6137 query.value(4).toString(),
6138 QString(),
6139 query.value(5).toString(),
6140 query.value(6).toUInt(),
6141 query.value(7).toUInt(),
6142 query.value(8).toString(),
6143
6144 chanid, channum, chansign, channame,
6145
6146 query.value(9).toString(), query.value(10).toString(),
6147 query.value(11).toString(),
6148
6149 MythDate::as_utc(query.value(1).toDateTime()),
6150 MythDate::as_utc(query.value(2).toDateTime()),
6151 MythDate::as_utc(query.value(1).toDateTime()),
6152 MythDate::as_utc(query.value(2).toDateTime()),
6153
6154 RecStatus::Type(query.value(17).toInt()),
6155 query.value(18).toUInt(),
6156 RecordingType(query.value(16).toInt()),
6157 query.value(15).toUInt(),
6158
6159 query.value(19).toBool()));
6160 }
6161
6162 return true;
6163}
6164
6184 ProgramList &destination,
6185 bool possiblyInProgressRecordingsOnly,
6186 const QMap<QString,uint32_t> &inUseMap,
6187 const QMap<QString,bool> &isJobRunning,
6188 const QMap<QString, ProgramInfo*> &recMap,
6189 int sort,
6190 const QString &sortBy,
6191 bool ignoreLiveTV,
6192 bool ignoreDeleted)
6193{
6194 destination.clear();
6195
6196 QDateTime rectime = MythDate::current().addSecs(
6197 -gCoreContext->GetNumSetting("RecordOverTime"));
6198
6199 // ----------------------------------------------------------------------
6200
6201 QString thequery = ProgramInfo::kFromRecordedQuery;
6202 if (possiblyInProgressRecordingsOnly || ignoreLiveTV || ignoreDeleted)
6203 {
6204 thequery += "WHERE ";
6205 if (possiblyInProgressRecordingsOnly)
6206 {
6207 thequery += "(r.endtime >= NOW() AND r.starttime <= NOW()) ";
6208 }
6209 if (ignoreLiveTV)
6210 {
6211 thequery += QString("%1 r.recgroup != 'LiveTV' ")
6212 .arg(possiblyInProgressRecordingsOnly ? "AND" : "");
6213 }
6214 if (ignoreDeleted)
6215 {
6216 thequery += QString("%1 r.recgroup != 'Deleted' ")
6217 .arg((possiblyInProgressRecordingsOnly || ignoreLiveTV)
6218 ? "AND" : "");
6219 }
6220 }
6221
6222 if (sortBy.isEmpty())
6223 {
6224 if (sort)
6225 thequery += "ORDER BY r.starttime ";
6226 if (sort < 0)
6227 thequery += "DESC ";
6228 }
6229 else
6230 {
6231 QStringList sortByFields;
6232 sortByFields << "starttime" << "title" << "subtitle" << "season" << "episode" << "category"
6233 << "watched" << "stars" << "originalairdate" << "recgroup" << "storagegroup"
6234 << "channum" << "callsign" << "name" << "filesize" << "duration";
6235 // sanity check the fields are one of the above fields
6236 QString sSortBy;
6237 QStringList fields = sortBy.split(",");
6238 for (const QString& oneField : std::as_const(fields))
6239 {
6240 bool ascending = true;
6241 QString field = oneField.simplified().toLower();
6242
6243 if (field.endsWith("desc"))
6244 {
6245 ascending = false;
6246 field = field.remove("desc");
6247 }
6248
6249 if (field.endsWith("asc"))
6250 {
6251 ascending = true;
6252 field = field.remove("asc");
6253 }
6254
6255 field = field.simplified();
6256
6257 if (field == "channelname")
6258 field = "name";
6259
6260 if (sortByFields.contains(field))
6261 {
6262 QString table;
6263 if (field == "channum" || field == "callsign" || field == "name")
6264 table = "c.";
6265 else
6266 table = "r.";
6267
6268 if (field == "channum")
6269 {
6270 // this is to sort numerically rather than alphabetically
6271 field = "channum*1000-ifnull(regexp_substr(c.channum,'-.*'),0)";
6272 }
6273
6274 else if (field == "duration")
6275 {
6276 field = "timestampdiff(second,r.starttime,r.endtime)";
6277 table = "";
6278 }
6279
6280 else if (field == "season")
6281 {
6282 field = "if(r.season,r.season,p.season)";
6283 table = "";
6284 }
6285
6286 else if (field == "episode")
6287 {
6288 field = "if(r.episode,r.episode,p.episode)";
6289 table = "";
6290 }
6291
6292 else if (field == "title")
6293 {
6294 std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
6295 QString prefixes = sh->getPrefixes();
6296 field = "REGEXP_REPLACE(r.title,'" + prefixes + "','')";
6297 table = "";
6298 }
6299
6300 else if (field == "subtitle")
6301 {
6302 std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
6303 QString prefixes = sh->getPrefixes();
6304 field = "REGEXP_REPLACE(r.subtitle,'" + prefixes + "','')";
6305 table = "";
6306 }
6307
6308 else if (field == "originalairdate")
6309 {
6310 field = "IF(r.originalairdate != '0000-00-00', r.originalairdate, STR_TO_DATE(CONCAT(p.airdate,',01,01'),'%Y,%m,%d'))";
6311 table = "";
6312 }
6313
6314 if (sSortBy.isEmpty())
6315 sSortBy = QString("%1%2 %3").arg(table, field, ascending ? "ASC" : "DESC");
6316 else
6317 sSortBy += QString(",%1%2 %3").arg(table, field, ascending ? "ASC" : "DESC");
6318 }
6319 else
6320 {
6321 LOG(VB_GENERAL, LOG_WARNING, QString("ProgramInfo::LoadFromRecorded() got an unknown sort field '%1' - ignoring").arg(oneField));
6322 }
6323 }
6324
6325 thequery += "ORDER BY " + sSortBy;
6326 }
6327
6329 query.prepare(thequery);
6330
6331 if (!query.exec())
6332 {
6333 MythDB::DBError("ProgramList::FromRecorded", query);
6334 return true;
6335 }
6336
6337 while (query.next())
6338 {
6339 const uint chanid = query.value(6).toUInt();
6340 QString channum = QString("#%1").arg(chanid);
6341 QString chansign = channum;
6342 QString channame = channum;
6343 QString chanfilt;
6344 if (!query.value(7).toString().isEmpty())
6345 {
6346 channum = query.value(7).toString();
6347 chansign = query.value(8).toString();
6348 channame = query.value(9).toString();
6349 chanfilt = query.value(10).toString();
6350 }
6351
6352 QString hostname = query.value(15).toString();
6353 if (hostname.isEmpty())
6355
6357 QDateTime recstartts = MythDate::as_utc(query.value(24).toDateTime());
6358
6359 QString key = ProgramInfo::MakeUniqueKey(chanid, recstartts);
6360 if (MythDate::as_utc(query.value(25).toDateTime()) > rectime &&
6361 recMap.contains(key))
6362 {
6363 recstatus = RecStatus::Recording;
6364 }
6365
6366 bool save_not_commflagged = false;
6367 uint flags = 0;
6368
6369 set_flag(flags, FL_CHANCOMMFREE,
6370 query.value(30).toInt() == COMM_DETECT_COMMFREE);
6371 set_flag(flags, FL_COMMFLAG,
6372 query.value(31).toInt() == COMM_FLAG_DONE);
6373 set_flag(flags, FL_COMMPROCESSING ,
6374 query.value(31).toInt() == COMM_FLAG_PROCESSING);
6375 set_flag(flags, FL_REPEAT, query.value(32).toBool());
6376 set_flag(flags, FL_TRANSCODED,
6377 query.value(34).toInt() == TRANSCODING_COMPLETE);
6378 set_flag(flags, FL_DELETEPENDING, query.value(35).toBool());
6379 set_flag(flags, FL_PRESERVED, query.value(36).toBool());
6380 set_flag(flags, FL_CUTLIST, query.value(37).toBool());
6381 set_flag(flags, FL_AUTOEXP, query.value(38).toBool());
6382 set_flag(flags, FL_REALLYEDITING, query.value(39).toBool());
6383 set_flag(flags, FL_BOOKMARK, query.value(40).toBool());
6384 set_flag(flags, FL_WATCHED, query.value(41).toBool());
6385 set_flag(flags, FL_LASTPLAYPOS, query.value(58).toBool());
6386
6387 if (inUseMap.contains(key))
6388 flags |= inUseMap[key];
6389
6390 if (((flags & FL_COMMPROCESSING) != 0U) &&
6391 (!isJobRunning.contains(key)))
6392 {
6393 flags &= ~FL_COMMPROCESSING;
6394 save_not_commflagged = true;
6395 }
6396
6397 set_flag(flags, FL_EDITING,
6398 ((flags & FL_REALLYEDITING) != 0U) ||
6399 ((flags & COMM_FLAG_PROCESSING) != 0U));
6400
6401 // User/metadata defined season from recorded
6402 uint season = query.value(3).toUInt();
6403 if (season == 0)
6404 season = query.value(51).toUInt(); // Guide defined season from recordedprogram
6405
6406 // User/metadata defined episode from recorded
6407 uint episode = query.value(4).toUInt();
6408 if (episode == 0)
6409 episode = query.value(52).toUInt(); // Guide defined episode from recordedprogram
6410
6411 // Guide defined total episodes from recordedprogram
6412 uint totalepisodes = query.value(53).toUInt();
6413
6414 destination.push_back(
6415 new ProgramInfo(
6416 query.value(55).toUInt(),
6417 query.value(0).toString(),
6418 QString(),
6419 query.value(1).toString(),
6420 QString(),
6421 query.value(2).toString(),
6422 season,
6423 episode,
6424 totalepisodes,
6425 query.value(48).toString(), // syndicatedepisode
6426 query.value(5).toString(), // category
6427
6428 chanid, channum, chansign, channame, chanfilt,
6429
6430 query.value(11).toString(), query.value(12).toString(),
6431
6432 query.value(14).toString(), // pathname
6433
6434 hostname, query.value(13).toString(),
6435
6436 query.value(17).toString(), query.value(18).toString(),
6437 query.value(19).toString(), // inetref
6438 string_to_myth_category_type(query.value(54).toString()), // category_type
6439
6440 query.value(16).toInt(), // recpriority
6441
6442 query.value(20).toULongLong(), // filesize
6443
6444 MythDate::as_utc(query.value(21).toDateTime()), //startts
6445 MythDate::as_utc(query.value(22).toDateTime()), // endts
6446 MythDate::as_utc(query.value(24).toDateTime()), // recstartts
6447 MythDate::as_utc(query.value(25).toDateTime()), // recendts
6448
6449 query.value(23).toFloat(), // stars
6450
6451 query.value(26).toUInt(), // year
6452 query.value(49).toUInt(), // partnumber
6453 query.value(50).toUInt(), // parttotal
6454 query.value(27).toDate(), // originalAirdate
6455 MythDate::as_utc(query.value(28).toDateTime()), // lastmodified
6456
6457 recstatus,
6458
6459 query.value(29).toUInt(), // recordid
6460
6461 RecordingDupInType(query.value(46).toInt()),
6462 RecordingDupMethodType(query.value(47).toInt()),
6463
6464 query.value(45).toUInt(), // findid
6465
6466 flags,
6467 query.value(42).toUInt(), // audioproperties
6468 query.value(43).toUInt(), // videoproperties
6469 query.value(44).toUInt(), // subtitleType
6470 query.value(56).toString(), // inputname
6471 MythDate::as_utc(query.value(57)
6472 .toDateTime()))); // bookmarkupdate
6473
6474 if (save_not_commflagged)
6476 }
6477
6478 return true;
6479}
6480
6481bool GetNextRecordingList(QDateTime &nextRecordingStart,
6482 bool *hasConflicts,
6483 std::vector<ProgramInfo> *list)
6484{
6485 nextRecordingStart = QDateTime();
6486
6487 bool dummy = false;
6488 bool *conflicts = hasConflicts ? hasConflicts : &dummy;
6489
6490 ProgramList progList;
6491 if (!LoadFromScheduler(progList, *conflicts))
6492 return false;
6493
6494 // find the earliest scheduled recording
6495 for (auto *prog : progList)
6496 {
6497 if ((prog->GetRecordingStatus() == RecStatus::WillRecord ||
6498 prog->GetRecordingStatus() == RecStatus::Pending) &&
6499 (nextRecordingStart.isNull() ||
6500 nextRecordingStart > prog->GetRecordingStartTime()))
6501 {
6502 nextRecordingStart = prog->GetRecordingStartTime();
6503 }
6504 }
6505
6506 if (!list)
6507 return true;
6508
6509 // save the details of the earliest recording(s)
6510 for (auto & prog : progList)
6511 {
6512 if ((prog->GetRecordingStatus() == RecStatus::WillRecord ||
6513 prog->GetRecordingStatus() == RecStatus::Pending) &&
6514 (prog->GetRecordingStartTime() == nextRecordingStart))
6515 {
6516 list->push_back(*prog);
6517 }
6518 }
6519
6520 return true;
6521}
6522
6524{
6525}
6526
6528{
6529 delete lock;
6530}
6531
6532// ---------------------------------------------------------------------------
6533// DEPRECATED CODE FOLLOWS
6534// ---------------------------------------------------------------------------
6535
6537{
6538 //LOG(VB_GENERAL, LOG_DEBUG, "FIXME: ProgramInfo::SetFilesize() called instead of RecordingInfo::SetFilesize()");
6539 m_fileSize = sz;
6540}
6541
6542
6546void ProgramInfo::SaveFilesize(uint64_t fsize)
6547{
6548 //LOG(VB_GENERAL, LOG_DEBUG, "FIXME: ProgramInfo::SaveFilesize() called instead of RecordingInfo::SaveFilesize()");
6549 SetFilesize(fsize);
6550
6552 query.prepare(
6553 "UPDATE recorded "
6554 "SET filesize = :FILESIZE "
6555 "WHERE chanid = :CHANID AND "
6556 " starttime = :STARTTIME");
6557 query.bindValue(":FILESIZE", (quint64)fsize);
6558 query.bindValue(":CHANID", m_chanId);
6559 query.bindValue(":STARTTIME", m_recStartTs);
6560
6561 if (!query.exec())
6562 MythDB::DBError("File size update", query);
6563
6565}
6566
6567//uint64_t ProgramInfo::GetFilesize(void) const
6568//{
6569 //LOG(VB_GENERAL, LOG_DEBUG, "FIXME: ProgramInfo::GetFilesize() called instead of RecordingInfo::GetFilesize()");
6570// return m_fileSize;
6571//}
6572
6573// Restore the original query. When a recording finishes, a
6574// check is made on the filesize and it's 0 at times. If
6575// less than 1000, commflag isn't queued. See #12290.
6576
6577uint64_t ProgramInfo::GetFilesize(void) const
6578{
6579
6580 if (m_fileSize)
6581 return m_fileSize;
6582
6583 // Always query in the 0 case because we can't
6584 // tell if the filesize was 'lost'. Other than
6585 // the 0 case, tests showed the DB and RI sizes
6586 // were equal.
6587
6588 uint64_t db_filesize = 0;
6589
6591
6592 query.prepare(
6593 "SELECT filesize "
6594 "FROM recorded "
6595 "WHERE chanid = :CHANID AND "
6596 " starttime = :STARTTIME");
6597 query.bindValue(":CHANID", m_chanId);
6598 query.bindValue(":STARTTIME", m_recStartTs);
6599 if (query.exec() && query.next())
6600 db_filesize = query.value(0).toULongLong();
6601
6602 if (db_filesize)
6603 LOG(VB_RECORD, LOG_INFO, LOC + QString("RI Filesize=0, DB Filesize=%1")
6604 .arg(db_filesize));
6605
6606 return db_filesize;
6607}
6608
6610{
6611 m_recordedPercent = -1;
6613 return;
6614
6615 QDateTime startTime = m_recStartTs;
6616 QDateTime now = MythDate::current();
6617 if (now < startTime)
6618 return;
6619
6620 QDateTime endTime = m_recEndTs;
6621 int current = startTime.secsTo(now);
6622 int duration = startTime.secsTo(endTime);
6623 if (duration < 1)
6624 return;
6625
6626 m_recordedPercent = std::clamp(current * 100 / duration, 0, 100);
6627 LOG(VB_GUI, LOG_DEBUG, QString("%1 recorded percent %2/%3 = %4%")
6628 .arg(m_title).arg(current).arg(duration).arg(m_recordedPercent));
6629}
6630
6632{
6633 if (pos == 0)
6634 {
6635 m_watchedPercent = -1;
6636 return;
6637 }
6638
6639 uint64_t total = 0;
6640 if (IsVideo())
6641 {
6642 total = std::max((int64_t)0, QueryTotalFrames());
6643 }
6644 else if (IsRecording())
6645 {
6646 switch (m_recStatus)
6647 {
6649 total = std::max((int64_t)0, QueryTotalFrames());
6650 break;
6652 {
6653 // Compute expected total frames based on frame rate.
6654 int64_t rate1000 = QueryAverageFrameRate();
6655 int64_t duration = m_recStartTs.secsTo(m_recEndTs);
6656 total = rate1000 * duration / 1000;
6657 }
6658 break;
6659 default:
6660 break;
6661 }
6662 }
6663
6664 if (total == 0)
6665 {
6666 if (IsVideo())
6667 {
6668 LOG(VB_GUI, LOG_DEBUG,
6669 QString("%1 %2 no frame count. Please rebuild seek table for this video.")
6670 .arg(m_pathname, m_title));
6671 }
6672 else if (IsRecording())
6673 {
6674 LOG(VB_GUI, LOG_DEBUG,
6675 QString("%1 %2 no frame count. Please rebuild seek table for this recording.")
6676 .arg(m_recordedId).arg(m_title));
6677 }
6678 m_watchedPercent = 0;
6679 return;
6680 }
6681
6682 m_watchedPercent = std::clamp(100 * pos / total, (uint64_t)0, (uint64_t)100);
6683 LOG(VB_GUI, LOG_DEBUG, QString("%1 %2 watched percent %3/%4 = %5%")
6684 .arg(m_recordedId).arg(m_title)
6685 .arg(pos).arg(total).arg(m_watchedPercent));
6686}
6687
6689{
6692}
6693
6695{
6696 switch (groupBy)
6697 {
6698 case ProgGroupBy::None:
6699 return tr("None");
6701 return tr("Channel Number");
6703 return tr("CallSign");
6705 return tr("ProgramId");
6706 default:
6707 return tr("Unknown");
6708 }
6709}
6710
6711bool RemoteCheckFile(ProgramInfo *pginfo, bool checkSlaves)
6712{
6713 QStringList strlist("QUERY_CHECKFILE");
6714 strlist << QString::number((int)checkSlaves);
6715 pginfo->ToStringList(strlist);
6716
6717 if (!gCoreContext->SendReceiveStringList(strlist) ||
6718 (strlist.size() < 2) || !strlist[0].toInt())
6719 return false;
6720
6721 // Only modify the pathname if the recording file is available locally on
6722 // this host
6723 QString localpath = strlist[1];
6724 QFile checkFile(localpath);
6725 if (checkFile.exists())
6726 pginfo->SetPathname(localpath);
6727
6728 return true;
6729}
6730
6731#include "moc_programinfo.cpp"
void clear(void)
bool empty(void) const
void push_back(T info)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:129
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:839
QVariant value(int i) const
Definition: mythdbcon.h:205
int size(void) const
Definition: mythdbcon.h:215
void setForwardOnly(bool f)
Definition: mythdbcon.h:219
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:620
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:890
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:814
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:552
@ kUnknown
Definition: mythcdrom.h:28
static ImageType inspectImage(const QString &path)
Definition: mythcdrom.cpp:189
bool IsDatabaseIgnored(void) const
/brief Returns true if database is being ignored.
QString GetHostName(void)
QLocale GetQLocale(void)
MythScheduler * GetScheduler(void)
QString GetSetting(const QString &key, const QString &defaultval="")
static int GetMasterServerPort(void)
Returns the Master Backend control port If no master server port has been defined in the database,...
int GetBackendServerPort(void)
Returns the locally defined backend control port.
bool IsBackend(void) const
is this process a backend process
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
int GetNumSetting(const QString &key, int defaultval=0)
QString GetMasterHostName(void)
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
This is an generic interface to a program scheduler.
Definition: mythscheduler.h:18
QMap< MarkTypes, frm_pos_map_t > map
Definition: programinfo.h:1005
static QString toString(ProgGroupBy::Type groupBy)
void insert(uint recordedid, PIAction action, uint64_t filesize=0ULL)
Holds information on recordings and videos.
Definition: programinfo.h:75
float GetStars(void) const
Definition: programinfo.h:454
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:381
void SetAvailableStatus(AvailableStatusType status, const QString &where)
ProgramInfo & operator=(const ProgramInfo &other)
Copies important fields from other ProgramInfo.
static uint SubtitleTypesFromNames(const QString &names)
bool QueryKeyFrameInfo(uint64_t *result, uint64_t position_or_keyframe, bool backwards, MarkTypes type, const char *from_filemarkup_asc, const char *from_filemarkup_desc, const char *from_recordedseek_asc, const char *from_recordedseek_desc) const
uint64_t QueryStartMark(void) const
QString GetShortInputName(void) const
void SaveTranscodeStatus(TranscodingStatus trans)
Set "transcoded" field in "recorded" table to "trans".
void QueryMarkup(QVector< MarkupEntry > &mapMark, QVector< MarkupEntry > &mapSeek) const
bool IsSameProgramWeakCheck(const ProgramInfo &other) const
Checks for duplicate using only title, chanid and startts.
QString GetBasename(void) const
Definition: programinfo.h:352
bool IsSameTitleTimeslotAndChannel(const ProgramInfo &other) const
Checks title, chanid or chansign, start/end times, cardid, inputid for fully inclusive overlap.
QString GetAudioPropertyNames(void) const
static bool ExtractKey(const QString &uniquekey, uint &chanid, QDateTime &recstartts)
Extracts chanid and recstartts from a unique key generated by MakeUniqueKey().
void SavePreserve(bool preserveEpisode)
Set "preserve" field in "recorded" table to "preserveEpisode".
static bool s_usingProgIDAuth
Definition: programinfo.h:869
bool QueryIsInUse(QStringList &byWho) const
Returns true if Program is in use.
PMapDBReplacement * m_positionMapDBReplacement
Definition: programinfo.h:865
uint32_t m_sourceId
Definition: programinfo.h:828
static ProgramInfoUpdater * s_updater
Definition: programinfo.h:868
bool IsVideoFile(void) const
Definition: programinfo.h:353
virtual void clear(void)
VideoPropsType m_videoProperties
Definition: programinfo.h:833
QString m_hostname
Definition: programinfo.h:802
QString m_storageGroup
Definition: programinfo.h:803
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
bool IsVideoDVD(void) const
Definition: programinfo.h:355
QString m_pathname
Definition: programinfo.h:800
uint8_t m_dupIn
Definition: programinfo.h:842
ProgramInfoType GetProgramInfoType(void) const
Definition: programinfo.h:484
bool QueryTuningInfo(QString &channum, QString &input) const
Returns the channel and input needed to record the program.
void ensureSortFields(void)
Ensure that the sortTitle and sortSubtitle fields are set.
QString GetCategoryTypeString(void) const
Returns catType as a string.
bool IsSameTitleStartTimeAndChannel(const ProgramInfo &other) const
Checks title, chanid or callsign and start times for equality.
void SetTitle(const QString &t, const QString &st=nullptr)
static const QString kFromRecordedQuery
Definition: programinfo.h:861
AutoExpireType QueryAutoExpire(void) const
Returns "autoexpire" field from "recorded" table.
static int InitStatics(void)
uint GetEpisode(void) const
Definition: programinfo.h:375
uint64_t QueryProgStart(void) const
Gets any progstart position in database, unless the ignore progstart flag is set.
uint32_t m_programFlags
ProgramFlag.
Definition: programinfo.h:832
void SaveWatched(bool watchedFlag)
Set "watched" field in recorded/videometadata to "watchedFlag".
bool IsVideo(void) const
Definition: programinfo.h:498
virtual void SaveFilesize(uint64_t fsize)
Sets recording file size in database, and sets "filesize" field.
QString m_category
Definition: programinfo.h:786
void CalculateWatchedProgress(uint64_t pos)
void UpdateInUseMark(bool force=false)
int8_t m_watchedPercent
Definition: programinfo.h:852
bool IsDuplicateProgram(const ProgramInfo &other) const
Checks for duplicates according to dupmethod.
QString m_chanPlaybackFilters
Definition: programinfo.h:795
uint32_t m_inputId
Definition: programinfo.h:829
QString m_sortSubtitle
Definition: programinfo.h:780
bool IsSameChannel(const ProgramInfo &other) const
Checks whether channel id or callsign are identical.
uint m_recordedId
Definition: programinfo.h:845
QDateTime m_recEndTs
Definition: programinfo.h:815
QString GetRecordingGroup(void) const
Definition: programinfo.h:428
QString ChannelText(const QString &format) const
Returns channel info using "format".
void SaveAutoExpire(AutoExpireType autoExpire, bool updateDelete=false)
Set "autoexpire" field in "recorded" table to "autoExpire".
QString m_inputName
Definition: programinfo.h:846
QString GetProgramFlagNames(void) const
uint8_t m_availableStatus
Definition: programinfo.h:850
void ProgramFlagsFromNames(const QString &names)
ProgramInfo(void)
Null constructor.
Definition: programinfo.h:88
uint16_t m_partTotal
Definition: programinfo.h:838
uint32_t m_chanId
Definition: programinfo.h:791
void ClearPositionMap(MarkTypes type) const
uint QueryMplexID(void) const
Queries multiplex any recording would be made on, zero if unknown.
bool IsSameProgram(const ProgramInfo &other) const
Checks whether this is the same program as "other", which may or may not be a repeat or on another ch...
uint QueryTranscoderID(void) const
void SaveMarkup(const QVector< MarkupEntry > &mapMark, const QVector< MarkupEntry > &mapSeek) const
uint8_t m_dupMethod
Definition: programinfo.h:843
bool FromStringList(QStringList::const_iterator &it, const QStringList::const_iterator &end)
Uses a QStringList to initialize this ProgramInfo instance.
bool HasCutlist(void) const
Definition: programinfo.h:492
void SendAddedEvent(void) const
Sends event out that the ProgramInfo should be added to lists.
QStringList QueryBDBookmark(const QString &serialid) const
Queries "bdbookmark" table for bookmarking BD serial number.
bool QueryIsDeleteCandidate(bool one_playback_allowed=false) const
Returns true iff this is a recording, it is not in use (except by the recorder), and at most one play...
bool QueryKeyFrameDuration(uint64_t *duration, uint64_t keyframe, bool backwards) const
bool LoadProgramFromRecorded(uint chanid, const QDateTime &recstartts)
Loads ProgramInfo for an existing recording.
void QueryCommBreakList(frm_dir_map_t &frames) const
int64_t QueryTotalFrames(void) const
If present in recording this loads total frames of the main video stream from database's stream marku...
void CalculateRecordedProgress()
uint64_t m_fileSize
Definition: programinfo.h:810
bool IsFileReadable(void)
Attempts to ascertain if the main file for this ProgramInfo is readable.
bool IsRepeat(void) const
Definition: programinfo.h:500
void SaveVideoScanType(uint64_t frame, bool progressive)
Store the Progressive/Interlaced state in the recordedmarkup table.
QDateTime QueryBookmarkTimeStamp(void) const
Queries Latest bookmark timestamp from the database.
void QueryPositionMap(frm_pos_map_t &posMap, MarkTypes type) const
void SaveTotalDuration(std::chrono::milliseconds duration)
Store the Total Duration at frame 0 in the recordedmarkup table.
QString CreateRecordBasename(const QString &ext) const
Returns a filename for a recording based on the recording channel and date.
bool IsRecording(void) const
Definition: programinfo.h:499
static bool QueryKeyFromPathname(const QString &pathname, uint &chanid, QDateTime &recstartts)
static QStringList LoadFromScheduler(const QString &tmptable, int recordid)
CategoryType QueryCategoryType(void) const
Queries recordedprogram to get the category_type of the recording.
void UpdateMarkTimeStamp(bool bookmarked) const
virtual void SetFilesize(uint64_t sz)
QString m_title
Definition: programinfo.h:777
QString DiscoverRecordingDirectory(void)
void UpdateLastDelete(bool setTime) const
Set or unset the record.last_delete field.
void SaveResolution(uint64_t frame, uint width, uint height)
Store the Resolution at frame in the recordedmarkup table.
void SaveFrameRate(uint64_t frame, uint framerate)
Store the Frame Rate at frame in the recordedmarkup table.
bool SaveBasename(const QString &basename)
Sets a recording's basename in the database.
void SaveAspect(uint64_t frame, MarkTypes type, uint customAspect)
Store aspect ratio of a frame in the recordedmark table.
void SendDeletedEvent(void) const
Sends event out that the ProgramInfo should be delete from lists.
QString m_sortTitle
Definition: programinfo.h:778
int32_t m_recPriority2
Definition: programinfo.h:823
uint QueryAverageWidth(void) const
If present in recording this loads average width of the main video stream from database's stream mark...
void SaveVideoProperties(uint mask, uint video_property_flags)
TranscodingStatus QueryTranscodeStatus(void) const
Returns the "transcoded" field in "recorded" table.
uint m_episode
Definition: programinfo.h:783
bool QueryIsEditing(void) const
Queries "recorded" table for its "editing" field and returns true if it is set to true.
static uint AudioPropertiesFromNames(const QString &names)
uint QueryAverageHeight(void) const
If present in recording this loads average height of the main video stream from database's stream mar...
AudioPropsType m_audioProperties
Definition: programinfo.h:834
int8_t m_spread
Definition: programinfo.h:858
void MarkAsInUse(bool inuse, const QString &usedFor="")
Tracks a recording's in use status, to prevent deletion and to allow the storage scheduler to perform...
static void CheckProgramIDAuthorities(void)
static QMap< QString, bool > QueryJobsRunning(int type)
bool IsGeneric(void) const
static QString QueryRecordingGroupPassword(const QString &group)
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:413
uint32_t m_findId
Definition: programinfo.h:830
QString m_description
Definition: programinfo.h:781
bool IsSameProgramAndStartTime(const ProgramInfo &other) const
Match same program, with same starttime (channel may be different)
bool QueryPositionKeyFrame(uint64_t *keyframe, uint64_t position, bool backwards) const
static bool ExtractKeyFromPathname(const QString &pathname, uint &chanid, QDateTime &recstartts)
bool QueryAverageScanProgressive(void) const
If present in recording this loads average video scan type of the main video stream from database's s...
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:399
CategoryType m_catType
Definition: programinfo.h:808
void SavePositionMap(frm_pos_map_t &posMap, MarkTypes type, int64_t min_frame=-1, int64_t max_frame=-1) const
bool IsLocal(void) const
Definition: programinfo.h:359
QString QueryBasename(void) const
Gets the basename, from the DB if necessary.
void SaveLastPlayPos(uint64_t frame)
TODO Move to RecordingInfo.
void SaveInetRef(const QString &inet)
uint64_t QueryLastFrameInPosMap(void) const
Returns last frame in position map or 0.
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
QString m_seriesId
Definition: programinfo.h:805
QString m_syndicatedEpisode
Definition: programinfo.h:785
uint QueryAverageFrameRate(void) const
If present in recording this loads average frame rate of the main video stream from database's stream...
float m_stars
Rating, range [0..1].
Definition: programinfo.h:817
std::chrono::milliseconds QueryTotalDuration(void) const
If present this loads the total duration in milliseconds of the main video stream from recordedmarkup...
int32_t m_recPriority
Definition: programinfo.h:789
bool Reload(void)
void SaveCommBreakList(frm_dir_map_t &frames) const
QString GetSubtitleTypeNames(void) const
ProgramInfoType DiscoverProgramInfoType(void) const
QDateTime m_recStartTs
Definition: programinfo.h:814
QString m_director
Definition: programinfo.h:787
bool IsDuplicate(void) const
Definition: programinfo.h:501
MarkTypes QueryAverageAspectRatio(void) const
void SaveCutList(frm_dir_map_t &delMap, bool isAutoSave=false) const
void SetProgramInfoType(ProgramInfoType t)
Definition: programinfo.h:532
void SaveTotalFrames(int64_t frames)
Store the Total Frames at frame 0 in the recordedmarkup table.
int8_t m_startCol
Definition: programinfo.h:859
QStringList QueryDVDBookmark(const QString &serialid) const
Queries "dvdbookmark" table for bookmarking DVD serial number.
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
Definition: programinfo.h:347
virtual void SubstituteMatches(QString &str)
Subsitute MATCH% type variable names in the given string.
QDateTime m_endTs
Definition: programinfo.h:813
uint m_totalEpisodes
Definition: programinfo.h:784
static QString i18n(const QString &msg)
Translations for play,recording, & storage groups +.
bool IsVideoBD(void) const
Definition: programinfo.h:357
static constexpr int64_t kLastInUseOffset
Definition: programinfo.h:820
SubtitlePropsType m_subtitleProperties
Definition: programinfo.h:835
void SavePositionMapDelta(frm_pos_map_t &posMap, MarkTypes type) const
void ClearMarkupMap(MarkTypes type=MARK_ALL, int64_t min_frame=-1, int64_t max_frame=-1) const
QString m_programId
Definition: programinfo.h:806
QString m_chanStr
Definition: programinfo.h:792
std::chrono::seconds GetSecondsInRecording(void) const
Returns length of program/recording in seconds.
void SaveMarkupFlag(MarkTypes type) const
Clears the specified flag, then if sets it.
static void SaveBDBookmark(const QStringList &fields)
uint16_t m_year
Definition: programinfo.h:836
virtual void ToMap(InfoMap &progMap, bool showrerecord=false, uint star_range=10, uint date_format=0) const
Converts ProgramInfo into QString QHash containing each field in ProgramInfo converted into localized...
QString m_chanSign
Definition: programinfo.h:793
QString GetPathname(void) const
Definition: programinfo.h:351
bool QueryMarkupFlag(MarkTypes type) const
Returns true iff the speficied mark type is set on frame 0.
QDate m_originalAirDate
Definition: programinfo.h:818
bool QueryKeyFramePosition(uint64_t *position, uint64_t keyframe, bool backwards) const
uint32_t m_recordId
Definition: programinfo.h:825
void SaveEditing(bool edit)
Sets "editing" field in "recorded" table to "edit".
QString m_chanName
Definition: programinfo.h:794
uint32_t m_parentId
Definition: programinfo.h:826
QDateTime m_lastModified
Definition: programinfo.h:819
int8_t m_recordedPercent
Definition: programinfo.h:851
void QueryMarkupMap(frm_dir_map_t &marks, MarkTypes type, bool merge=false) const
static uint VideoPropertiesFromNames(const QString &names)
QString GetVideoPropertyNames(void) const
virtual uint64_t GetFilesize(void) const
bool QueryDurationKeyFrame(uint64_t *keyframe, uint64_t duration, bool backwards) const
void UpdateLastPlayTimeStamp(bool lastplay) const
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
QString GetPlaybackURL(bool checkMaster=false, bool forceCheckLocal=false)
Returns filename or URL to be used to play back this recording.
void SaveSeasonEpisode(uint seas, uint ep)
bool IsWatched(void) const
Definition: programinfo.h:495
virtual void clone(const ProgramInfo &other, bool ignore_non_serialized_data=false)
Copies important fields from other ProgramInfo.
static bool QueryRecordedIdFromPathname(const QString &pathname, uint &recordedid)
QString m_subtitle
Definition: programinfo.h:779
QString m_inUseForWhat
Definition: programinfo.h:864
bool operator==(const ProgramInfo &rhs) const
static QMap< QString, uint32_t > QueryInUseMap(void)
RecStatus::Type GetRecordingStatus(void) const
Definition: programinfo.h:459
QString GetRecTypeStatus(bool showrerecord) const
uint64_t QueryLastPlayPos(void) const
Gets any lastplaypos position in database, unless the ignore lastplaypos flag is set.
uint64_t QueryBookmark(void) const
Gets any bookmark position in database, unless the ignore bookmark flag is set.
QDateTime m_bookmarkUpdate
Definition: programinfo.h:847
int8_t m_recStatus
Definition: programinfo.h:840
void SaveMarkupMap(const frm_dir_map_t &marks, MarkTypes type=MARK_ALL, int64_t min_frame=-1, int64_t max_frame=-1) const
QDateTime m_startTs
Definition: programinfo.h:812
void SaveCommFlagged(CommFlagStatus flag)
Set "commflagged" field in "recorded" table to "flag".
void SendUpdateEvent(void) const
Sends event out that the ProgramInfo should be reloaded.
QString m_recGroup
Definition: programinfo.h:797
void SaveBookmark(uint64_t frame)
Clears any existing bookmark in DB and if frame is greater than 0 sets a new bookmark.
uint8_t m_recType
Definition: programinfo.h:841
uint GetSeason(void) const
Definition: programinfo.h:374
void SetSubtitle(const QString &st, const QString &sst=nullptr)
static void SaveDVDBookmark(const QStringList &fields)
QDateTime m_lastInUseTime
Definition: programinfo.h:821
void SaveDeletePendingFlag(bool deleteFlag)
Set "deletepending" field in "recorded" table to "deleteFlag".
bool IsMythStream(void) const
Definition: programinfo.h:364
QString m_inetRef
Definition: programinfo.h:807
void SetPathname(const QString &pn)
RecordingType GetRecordingRuleType(void) const
Definition: programinfo.h:463
uint16_t m_partNumber
Definition: programinfo.h:837
QString m_playGroup
Definition: programinfo.h:798
void CalculateProgress(uint64_t pos)
static QMutex s_staticDataLock
Definition: programinfo.h:867
QString QueryRecordingGroup(void) const
Query recgroup from recorded.
static QString toDescription(Type recstatus, RecordingType rectype, const QDateTime &recstartts)
Converts "recstatus" into a long human readable description.
static QString toString(RecStatus::Type recstatus, uint id)
Converts "recstatus" into a short (unreadable) string.
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:464
bool GetAllPending(RecList &retList, int recRuleId=0) const
Definition: scheduler.cpp:1756
bool FileExists(const QString &filename)
QString FindFile(const QString &filename)
static const QStringList kSpecialGroups
Definition: storagegroup.h:46
static QString GetRelativePathname(const QString &filename)
Returns the relative pathname of a file by comparing the filename against all Storage Group directori...
unsigned int uint
Definition: compat.h:60
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
Definition: mythdbcon.h:101
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QString getSymlinkTarget(const QString &start_file, QStringList *intermediaries, unsigned maxLinks)
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
QDateTime as_utc(const QDateTime &old_dt)
Returns copy of QDateTime with TimeSpec set to UTC.
Definition: mythdate.cpp:28
std::chrono::seconds secsInPast(const QDateTime &past)
Definition: mythdate.cpp:212
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(int64_t seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:81
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kDateTimeFull
Default local time.
Definition: mythdate.h:24
@ kFilename
Default UTC, "yyyyMMddhhmmss".
Definition: mythdate.h:19
@ kSimplify
Do Today/Yesterday/Tomorrow transform.
Definition: mythdate.h:27
@ kDateFull
Default local time.
Definition: mythdate.h:20
@ kDateTimeShort
Default local time.
Definition: mythdate.h:25
@ ISODate
Default UTC.
Definition: mythdate.h:18
@ kTime
Default local time.
Definition: mythdate.h:23
@ kAddYear
Add year to string if not included.
Definition: mythdate.h:26
@ kDateShort
Default local time.
Definition: mythdate.h:21
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
QString intToPaddedString(int n, int width=2)
Creates a zero padded string representation of an integer.
Definition: stringutil.h:32
string hostname
Definition: caa.py:17
bool exists(str path)
Definition: xbmcvfs.py:51
static QDateTime DateTimeFromListItem(const QString &str)
static const char * from_recordedseek_offset_desc
#define LOC
Definition: programinfo.cpp:34
static void insert_markup_datum(MarkTypes type, uint mark, uint offset, const QString &videoPath)
bool LoadFromRecorded(ProgramList &destination, bool possiblyInProgressRecordingsOnly, const QMap< QString, uint32_t > &inUseMap, const QMap< QString, bool > &isJobRunning, const QMap< QString, ProgramInfo * > &recMap, int sort, const QString &sortBy, bool ignoreLiveTV, bool ignoreDeleted)
static QString determineURLType(const QString &url)
Definition: programinfo.cpp:88
static void init_tr(void)
QString myth_category_type_to_string(ProgramInfo::CategoryType category_type)
static const char * from_recordedseek_mark_desc
bool RemoteCheckFile(ProgramInfo *pginfo, bool checkSlaves)
ProgramInfo::CategoryType string_to_myth_category_type(const QString &category_type)
bool GetNextRecordingList(QDateTime &nextRecordingStart, bool *hasConflicts, std::vector< ProgramInfo > *list)
static void set_flag(uint32_t &flags, int flag_to_set, bool is_set)
Definition: programinfo.cpp:81
ProgramInfo * LoadProgramFromProgram(const uint chanid, const QDateTime &starttime)
bool LoadFromOldRecorded(ProgramList &destination, const QString &sql, const MSqlBindings &bindings)
static QString DateTimeToListInt(const QDateTime &x)
static const char * from_filemarkup_mark_desc
static const char * from_filemarkup_offset_desc
bool LoadFromProgram(ProgramList &destination, const QString &where, const QString &groupBy, const QString &orderBy, const MSqlBindings &bindings, const ProgramList &schedList)
static const std::array< const QString, ProgramInfo::kNumCatTypes > kCatName
static const char * from_recordedseek_mark_asc
uint propsValueFromString(const QString &name, const QMap< T, QString > &propNames, const QString &props)
static constexpr int64_t kLastUpdateOffset
Definition: programinfo.cpp:45
bool qstringEqualOrDefault(const QString &a, const QString &b)
#define NEXT_STR()
static constexpr uint kInvalidDateTime
Definition: programinfo.cpp:44
static void delete_markup_datum(MarkTypes type, uint chanid, const QDateTime &recstartts)
static QDate DateFromListItem(const QString &str)
static ProgramInfoType discover_program_info_type(uint chanid, const QString &pathname, bool use_remote)
int pginfo_init_statics()
Definition: programinfo.cpp:38
static const char * from_filemarkup_offset_asc
static uint load_markup_datum(MarkTypes type, uint chanid, const QDateTime &recstartts)
static const char * from_recordedseek_offset_asc
QString propsValueToString(const QString &name, QMap< T, QString > propNames, T props)
int force_init
Definition: programinfo.cpp:41
static bool FromProgramQuery(const QString &sql, const MSqlBindings &bindings, MSqlQuery &query, const uint start, const uint limit, uint &count, ProgGroupBy::Type groupBy)
static const char * from_filemarkup_mark_asc
bool LoadFromScheduler(AutoDeleteDeque< TYPE * > &destination, bool &hasConflicts, const QString &altTable="", int recordid=-1)
Definition: programinfo.h:947
@ kPIUpdateFileSize
@ kPIUpdate
@ kPIAdd
@ kPIDelete
const QString kTranscoderInUseID
const QString kPlayerInUseID
const QString kPreviewGeneratorInUseID
const QString kImportRecorderInUseID
const QString kJobQueueInUseID
const QString kPBPPlayerInUseID
const QString kFlaggerInUseID
const QString kFileTransferInUseID
const QString kRecorderInUseID
const QString kPIPPlayerInUseID
const QString kTruncatingDeleteInUseID
AvailableStatusType
Definition: programtypes.h:175
@ asAvailable
Definition: programtypes.h:176
MarkTypes
Definition: programtypes.h:46
@ MARK_ASPECT_4_3
Definition: programtypes.h:65
@ MARK_CUT_START
Definition: programtypes.h:55
@ MARK_SCAN_PROGRESSIVE
Definition: programtypes.h:69
@ MARK_KEYFRAME
Definition: programtypes.h:61
@ MARK_BOOKMARK
Definition: programtypes.h:56
@ MARK_UTIL_PROGSTART
Definition: programtypes.h:75
@ MARK_TOTAL_FRAMES
Definition: programtypes.h:74
@ MARK_GOP_BYFRAME
Definition: programtypes.h:63
@ MARK_CUT_END
Definition: programtypes.h:54
@ MARK_UNSET
Definition: programtypes.h:49
@ MARK_VIDEO_RATE
Definition: programtypes.h:72
@ MARK_VIDEO_HEIGHT
Definition: programtypes.h:71
@ MARK_TMP_CUT_END
Definition: programtypes.h:50
@ MARK_UTIL_LASTPLAYPOS
Definition: programtypes.h:76
@ MARK_TMP_CUT_START
Definition: programtypes.h:51
@ MARK_PLACEHOLDER
Definition: programtypes.h:53
@ MARK_ALL
Definition: programtypes.h:48
@ MARK_VIDEO_WIDTH
Definition: programtypes.h:70
@ MARK_COMM_END
Definition: programtypes.h:59
@ MARK_COMM_START
Definition: programtypes.h:58
@ MARK_DURATION_MS
Definition: programtypes.h:73
@ MARK_ASPECT_CUSTOM
Definition: programtypes.h:68
@ MARK_GOP_START
Definition: programtypes.h:60
@ COMM_DETECT_COMMFREE
Definition: programtypes.h:128
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:117
ProgramInfoType
Definition: programtypes.h:166
@ kProgramInfoTypeVideoStreamingRTSP
Definition: programtypes.h:171
@ kProgramInfoTypeVideoDVD
Definition: programtypes.h:169
@ kProgramInfoTypeRecording
Definition: programtypes.h:167
@ kProgramInfoTypeVideoStreamingHTML
Definition: programtypes.h:170
@ kProgramInfoTypeVideoFile
Definition: programtypes.h:168
@ kProgramInfoTypeVideoBD
Definition: programtypes.h:172
AutoExpireType
Definition: programtypes.h:192
@ kDisableAutoExpire
Definition: programtypes.h:193
CommFlagStatus
Definition: programtypes.h:119
@ COMM_FLAG_DONE
Definition: programtypes.h:121
@ COMM_FLAG_NOT_FLAGGED
Definition: programtypes.h:120
@ COMM_FLAG_PROCESSING
Definition: programtypes.h:122
QMap< long long, long long > frm_pos_map_t
Frame # -> File offset map.
Definition: programtypes.h:44
TranscodingStatus
Definition: programtypes.h:156
@ TRANSCODING_COMPLETE
Definition: programtypes.h:158
@ TRANSCODING_NOT_TRANSCODED
Definition: programtypes.h:157
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:204
QChar toQChar(RecordingType rectype)
Converts "rectype" into a human readable character.
RecordingDupInType
@ kDupsInAll
@ kDupsUnset
RecordingType
@ kOneRecord
@ kNotRecording
RecordingDupMethodType
@ kDupCheckUnset
@ kDupCheckSub
@ kDupCheckSubThenDesc
@ kDupCheckDesc
@ kDupCheckNone
Scheduler * sched