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