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 // Ignore ProgramTypeMask (recording, video, DVD, etc)
1475 if (bit & FL_TYPEMASK)
1476 continue;
1477 QString tmp = QString("0x%1").arg(bit, sizeof(T)*2,16,QChar('0'));
1478 LOG(VB_GENERAL, LOG_ERR, QString("Unknown name for %1 flag 0x%2.")
1479 .arg(name, tmp));
1480 result += tmp;
1481 }
1482 return result.join('|');
1483}
1484
1485template <typename T>
1486uint propsValueFromString (const QString& name, const QMap<T,QString>& propNames,
1487 const QString& props)
1488{
1489 if (props.isEmpty())
1490 return 0;
1491
1492 uint result = 0;
1493
1494 QStringList names = props.split('|');
1495 for ( const auto& n : std::as_const(names) )
1496 {
1497 uint bit = propNames.key(n, 0);
1498 if (bit == 0)
1499 {
1500 LOG(VB_GENERAL, LOG_ERR, QString("Unknown flag for %1 %2")
1501 .arg(name, n));
1502 }
1503 else
1504 {
1505 result |= bit;
1506 }
1507 }
1508 return result;
1509}
1510
1512{
1513 return propsValueToString("program", ProgramFlagNames, m_programFlags);
1514}
1515
1516
1517QString ProgramInfo::GetRecTypeStatus(bool showrerecord) const
1518{
1519 QString tmp_rec = ::toString(GetRecordingRuleType());
1521 {
1522 QDateTime timeNow = MythDate::current();
1523 if (((m_recEndTs > timeNow) && (m_recStatus <= RecStatus::WillRecord)) ||
1525 {
1526 tmp_rec += QString(" %1%2").arg(m_recPriority > 0 ? "+" : "").arg(m_recPriority);
1527 if (m_recPriority2)
1528 tmp_rec += QString("/%1%2").arg(m_recPriority2 > 0 ? "+" : "").arg(m_recPriority2);
1529 tmp_rec += " ";
1530 }
1531 else
1532 {
1533 tmp_rec += " -- ";
1534 }
1535 if (showrerecord && (GetRecordingStatus() == RecStatus::Recorded) &&
1536 !IsDuplicate())
1537 {
1538 tmp_rec += QObject::tr("Re-Record");
1539 }
1540 else
1541 {
1543 }
1544 }
1545 return tmp_rec;
1546}
1547
1548
1549
1550
1552{
1553 return propsValueToString("subtitle", SubtitlePropsNames,
1555}
1556
1558{
1559 return propsValueToString("video", VideoPropsNames, m_videoProperties);
1560}
1561
1563{
1564 return propsValueToString("audio", AudioPropsNames, m_audioProperties);
1565}
1566
1568{
1569 return propsValueFromString("subtitle", SubtitlePropsNames, names);
1570}
1571
1573{
1574 return propsValueFromString("video", VideoPropsNames, names);
1575}
1576
1578{
1579 return propsValueFromString("audio", AudioPropsNames, names);
1580}
1581
1582void ProgramInfo::ProgramFlagsFromNames(const QString & names)
1583{
1584 m_programFlags = propsValueFromString("program", ProgramFlagNames, names);
1585}
1586
1591 bool showrerecord,
1592 uint star_range,
1593 uint date_format) const
1594{
1595 QLocale locale = gCoreContext->GetQLocale();
1596 // NOTE: Format changes and relevant additions made here should be
1597 // reflected in RecordingRule
1598 QString channelFormat =
1599 gCoreContext->GetSetting("ChannelFormat", "<num> <sign>");
1600 QString longChannelFormat =
1601 gCoreContext->GetSetting("LongChannelFormat", "<num> <name>");
1602
1603 QDateTime timeNow = MythDate::current();
1604
1605 progMap["title"] = m_title;
1606 progMap["subtitle"] = m_subtitle;
1607 progMap["sorttitle"] = m_sortTitle;
1608 progMap["sortsubtitle"] = m_sortSubtitle;
1609
1610 QString tempSubTitle = m_title;
1611 QString tempSortSubtitle = m_sortTitle;
1612 if (!m_subtitle.trimmed().isEmpty())
1613 {
1614 tempSubTitle = QString("%1 - \"%2\"")
1615 .arg(tempSubTitle, m_subtitle);
1616 tempSortSubtitle = QString("%1 - \"%2\"")
1617 .arg(tempSortSubtitle, m_sortSubtitle);
1618 }
1619
1620 progMap["titlesubtitle"] = tempSubTitle;
1621 progMap["sorttitlesubtitle"] = tempSortSubtitle;
1622
1623 progMap["description"] = progMap["description0"] = m_description;
1624
1625 if (m_season > 0 || m_episode > 0)
1626 {
1627 progMap["season"] = StringUtil::intToPaddedString(m_season, 1);
1628 progMap["episode"] = StringUtil::intToPaddedString(m_episode, 1);
1629 progMap["totalepisodes"] = StringUtil::intToPaddedString(m_totalEpisodes, 1);
1630 progMap["s00e00"] = QString("s%1e%2")
1633 progMap["00x00"] = QString("%1x%2")
1636 }
1637 else
1638 {
1639 progMap["season"] = progMap["episode"] = "";
1640 progMap["totalepisodes"] = "";
1641 progMap["s00e00"] = progMap["00x00"] = "";
1642 }
1643 progMap["syndicatedepisode"] = m_syndicatedEpisode;
1644
1645 progMap["category"] = m_category;
1646 progMap["director"] = m_director;
1647
1648 progMap["callsign"] = m_chanSign;
1649 progMap["commfree"] = (m_programFlags & FL_CHANCOMMFREE) ? "1" : "0";
1650 progMap["outputfilters"] = m_chanPlaybackFilters;
1651 if (IsVideo())
1652 {
1653 progMap["starttime"] = "";
1654 progMap["startdate"] = "";
1655 progMap["endtime"] = "";
1656 progMap["enddate"] = "";
1657 progMap["recstarttime"] = "";
1658 progMap["recstartdate"] = "";
1659 progMap["recendtime"] = "";
1660 progMap["recenddate"] = "";
1661
1662 if (m_startTs.date().year() == 1895)
1663 {
1664 progMap["startdate"] = "";
1665 progMap["recstartdate"] = "";
1666 }
1667 else
1668 {
1669 progMap["startdate"] = m_startTs.toLocalTime().toString("yyyy");
1670 progMap["recstartdate"] = m_startTs.toLocalTime().toString("yyyy");
1671 }
1672 }
1673 else // if (IsRecording())
1674 {
1675 using namespace MythDate;
1676 progMap["starttime"] = MythDate::toString(m_startTs, date_format | kTime);
1677 progMap["startdate"] =
1679 progMap["shortstartdate"] = MythDate::toString(m_startTs, date_format | kDateShort);
1680 progMap["endtime"] = MythDate::toString(m_endTs, date_format | kTime);
1681 progMap["enddate"] = MythDate::toString(m_endTs, date_format | kDateFull | kSimplify);
1682 progMap["shortenddate"] = MythDate::toString(m_endTs, date_format | kDateShort);
1683 progMap["recstarttime"] = MythDate::toString(m_recStartTs, date_format | kTime);
1684 progMap["recstartdate"] = MythDate::toString(m_recStartTs, date_format | kDateShort);
1685 progMap["recendtime"] = MythDate::toString(m_recEndTs, date_format | kTime);
1686 progMap["recenddate"] = MythDate::toString(m_recEndTs, date_format | kDateShort);
1687 progMap["startts"] = QString::number(m_startTs.toSecsSinceEpoch());
1688 progMap["endts"] = QString::number(m_endTs.toSecsSinceEpoch());
1689 if (timeNow.toLocalTime().date().year() !=
1690 m_startTs.toLocalTime().date().year())
1691 progMap["startyear"] = m_startTs.toLocalTime().toString("yyyy");
1692 if (timeNow.toLocalTime().date().year() !=
1693 m_endTs.toLocalTime().date().year())
1694 progMap["endyear"] = m_endTs.toLocalTime().toString("yyyy");
1695 }
1696
1697 using namespace MythDate;
1698 progMap["timedate"] =
1699 MythDate::toString(m_recStartTs, date_format | kDateTimeFull | kSimplify) + " - " +
1700 MythDate::toString(m_recEndTs, date_format | kTime);
1701
1702 progMap["shorttimedate"] =
1703 MythDate::toString(m_recStartTs, date_format | kDateTimeShort | kSimplify) + " - " +
1704 MythDate::toString(m_recEndTs, date_format | kTime);
1705
1706 progMap["starttimedate"] =
1708
1709 progMap["shortstarttimedate"] =
1711
1712 progMap["lastmodifiedtime"] = MythDate::toString(m_lastModified, date_format | kTime);
1713 progMap["lastmodifieddate"] =
1715 progMap["lastmodified"] =
1717
1718 if (m_recordedId)
1719 progMap["recordedid"] = QString::number(m_recordedId);
1720
1721 progMap["channum"] = m_chanStr;
1722 progMap["chanid"] = QString::number(m_chanId);
1723 progMap["channame"] = m_chanName;
1724 progMap["channel"] = ChannelText(channelFormat);
1725 progMap["longchannel"] = ChannelText(longChannelFormat);
1726
1727 QString tmpSize = locale.toString(m_fileSize * (1.0 / (1024.0 * 1024.0 * 1024.0)), 'f', 2);
1728 progMap["filesize_str"] = QObject::tr("%1 GB", "GigaBytes").arg(tmpSize);
1729
1730 progMap["filesize"] = locale.toString((quint64)m_fileSize);
1731
1732 int seconds = m_recStartTs.secsTo(m_recEndTs);
1733 int minutes = seconds / 60;
1734
1735 QString min_str = QObject::tr("%n minute(s)","",minutes);
1736
1737 progMap["lenmins"] = min_str;
1738 int hours = minutes / 60;
1739 minutes = minutes % 60;
1740
1741 progMap["lentime"] = min_str;
1742 if (hours > 0 && minutes > 0)
1743 {
1744 min_str = QObject::tr("%n minute(s)","",minutes);
1745 progMap["lentime"] = QString("%1 %2")
1746 .arg(QObject::tr("%n hour(s)","", hours), min_str);
1747 }
1748 else if (hours > 0)
1749 {
1750 progMap["lentime"] = QObject::tr("%n hour(s)","", hours);
1751 }
1752
1753 progMap["recordedpercent"] =
1754 (m_recordedPercent >= 0)
1755 ? QString::number(m_recordedPercent) : QString();
1756 progMap["watchedpercent"] =
1757 ((m_watchedPercent > 0) && !IsWatched())
1758 ? QString::number(m_watchedPercent) : QString();
1759
1760 // This is calling toChar from recordingtypes.cpp, not the QChar
1761 // constructor.
1762 progMap["rectypechar"] = toQChar(GetRecordingRuleType());
1763 progMap["rectype"] = ::toString(GetRecordingRuleType());
1764 progMap["rectypestatus"] = GetRecTypeStatus(showrerecord);
1765
1766 progMap["card"] = RecStatus::toString(GetRecordingStatus(),
1768 progMap["input"] = RecStatus::toString(GetRecordingStatus(), m_inputId);
1769 progMap["inputname"] = m_inputName;
1770 // Don't add bookmarkupdate to progMap, for now.
1771
1772 progMap["recpriority"] = QString::number(m_recPriority);
1773 progMap["recpriority2"] = QString::number(m_recPriority2);
1774 progMap["recordinggroup"] = (m_recGroup == "Default")
1775 ? QObject::tr("Default") : m_recGroup;
1776 progMap["playgroup"] = m_playGroup;
1777
1778 if (m_storageGroup == "Default")
1779 progMap["storagegroup"] = QObject::tr("Default");
1781 {
1782 // This relies upon the translation established in the
1783 // definition of StorageGroup::kSpecialGroups.
1784 // clazy:exclude=tr-non-literal
1785 progMap["storagegroup"] = QObject::tr(m_storageGroup.toUtf8().constData());
1786 }
1787 else
1788 {
1789 progMap["storagegroup"] = m_storageGroup;
1790 }
1791
1792 progMap["programflags"] = QString::number(m_programFlags);
1793 progMap["audioproperties"] = QString::number(m_audioProperties);
1794 progMap["videoproperties"] = QString::number(m_videoProperties);
1795 progMap["subtitleType"] = QString::number(m_subtitleProperties);
1796 progMap["programflags_names"] = GetProgramFlagNames();
1797 progMap["audioproperties_names"] = GetAudioPropertyNames();
1798 progMap["videoproperties_names"] = GetVideoPropertyNames();
1799 progMap["subtitleType_names"] = GetSubtitleTypeNames();
1800
1801 progMap["recstatus"] = RecStatus::toString(GetRecordingStatus(),
1803 progMap["recstatuslong"] = RecStatus::toDescription(GetRecordingStatus(),
1806
1807 if (IsRepeat())
1808 {
1809 progMap["repeat"] = QString("(%1) ").arg(QObject::tr("Repeat"));
1810 progMap["longrepeat"] = progMap["repeat"];
1811 if (m_originalAirDate.isValid())
1812 {
1813 progMap["longrepeat"] = QString("(%1 %2) ")
1814 .arg(QObject::tr("Repeat"),
1817 date_format | MythDate::kDateFull | MythDate::kAddYear));
1818 }
1819 }
1820 else
1821 {
1822 progMap["repeat"] = "";
1823 progMap["longrepeat"] = "";
1824 }
1825
1826 progMap["seriesid"] = m_seriesId;
1827 progMap["programid"] = m_programId;
1828 progMap["inetref"] = m_inetRef;
1829 progMap["catType"] = myth_category_type_to_string(m_catType);
1830
1831 progMap["year"] = m_year > 1895 ? QString::number(m_year) : "";
1832
1833 progMap["partnumber"] = m_partNumber ? QString::number(m_partNumber) : "";
1834 progMap["parttotal"] = m_partTotal ? QString::number(m_partTotal) : "";
1835
1836 QString star_str = (m_stars != 0.0F) ?
1837 QObject::tr("%n star(s)", "", GetStars(star_range)) : "";
1838 progMap["stars"] = star_str;
1839 progMap["numstars"] = QString::number(GetStars(star_range));
1840
1841 if (m_stars != 0.0F && m_year)
1842 progMap["yearstars"] = QString("(%1, %2)").arg(m_year).arg(star_str);
1843 else if (m_stars != 0.0F)
1844 progMap["yearstars"] = QString("(%1)").arg(star_str);
1845 else if (m_year)
1846 progMap["yearstars"] = QString("(%1)").arg(m_year);
1847 else
1848 progMap["yearstars"] = "";
1849
1850 if (!m_originalAirDate.isValid() ||
1851 (!m_programId.isEmpty() && m_programId.startsWith("MV")))
1852 {
1853 progMap["originalairdate"] = "";
1854 progMap["shortoriginalairdate"] = "";
1855 }
1856 else
1857 {
1858 progMap["originalairdate"] = MythDate::toString(
1859 m_originalAirDate, date_format | MythDate::kDateFull);
1860 progMap["shortoriginalairdate"] = MythDate::toString(
1862 }
1863
1864 // 'mediatype' for a statetype, so untranslated
1865 // 'mediatypestring' for textarea, so translated
1866 // TODO Move to a dedicated ToState() method?
1867 QString mediaType;
1868 QString mediaTypeString;
1869 switch (GetProgramInfoType())
1870 {
1872 mediaType = "video";
1873 mediaTypeString = QObject::tr("Video");
1874 break;
1876 mediaType = "dvd";
1877 mediaTypeString = QObject::tr("DVD");
1878 break;
1880 mediaType = "httpstream";
1881 mediaTypeString = QObject::tr("HTTP Streaming");
1882 break;
1884 mediaType = "rtspstream";
1885 mediaTypeString = QObject::tr("RTSP Streaming");
1886 break;
1888 mediaType = "bluraydisc";
1889 mediaTypeString = QObject::tr("Blu-ray Disc");
1890 break;
1891 case kProgramInfoTypeRecording : // Fall through
1892 default :
1893 mediaType = "recording";
1894 mediaTypeString = QObject::tr("Recording",
1895 "Recorded file, object not action");
1896 }
1897 progMap["mediatype"] = mediaType;
1898 progMap["mediatypestring"] = mediaTypeString;
1899}
1900
1902std::chrono::seconds ProgramInfo::GetSecondsInRecording(void) const
1903{
1904 auto recsecs = std::chrono::seconds(m_recStartTs.secsTo(m_endTs));
1905 auto duration = std::chrono::seconds(m_startTs.secsTo(m_endTs));
1906 return (recsecs > 0s) ? recsecs : std::max(duration, 0s);
1907}
1908
1911{
1913}
1914
1917{
1918 uint64_t last_frame = 0;
1919 frm_pos_map_t posMap;
1921 if (posMap.empty())
1922 {
1924 if (posMap.empty())
1926 }
1927 if (!posMap.empty())
1928 {
1929 frm_pos_map_t::const_iterator it = posMap.constEnd();
1930 --it;
1931 last_frame = it.key();
1932 }
1933 return last_frame;
1934}
1935
1937{
1938 if (qsizetype idx = m_inputName.indexOf('/'); idx >= 0)
1939 {
1940 return m_inputName.isRightToLeft() ?
1941 m_inputName.left(idx) : m_inputName.right(idx);
1942 }
1943
1944 return m_inputName.isRightToLeft() ?
1945 m_inputName.left(2) : m_inputName.right(2);
1946}
1947
1949{
1950 return
1951 (m_programId.isEmpty() && m_subtitle.isEmpty() &&
1952 m_description.isEmpty()) ||
1953 (!m_programId.isEmpty() && m_programId.endsWith("0000")
1955}
1956
1957QString ProgramInfo::toString(const Verbosity v, const QString& sep, const QString& grp)
1958 const
1959{
1960 QString str;
1961 switch (v)
1962 {
1963 case kLongDescription:
1964 str = LOC + "channame(" + m_chanName + ")\n";
1965 str += " startts(" +
1966 m_startTs.toString() + ") endts(" + m_endTs.toString() + ")\n";
1967 str += " recstartts(" + m_recStartTs.toString() +
1968 ") recendts(" + m_recEndTs.toString() + ")\n";
1969 str += " title(" + m_title + ")";
1970 break;
1971 case kTitleSubtitle:
1972 str = m_title.contains(' ') ?
1973 QString("%1%2%3").arg(grp, m_title, grp) : m_title;
1974 if (!m_subtitle.isEmpty())
1975 {
1976 str += m_subtitle.contains(' ') ?
1977 QString("%1%2%3%4").arg(sep, grp, m_subtitle, grp) :
1978 QString("%1%2").arg(sep, m_subtitle);
1979 }
1980 break;
1981 case kRecordingKey:
1982 str = QString("%1 at %2")
1984 break;
1985 case kSchedulingKey:
1986 str = QString("%1 @ %2")
1988 break;
1989 }
1990
1991 return str;
1992}
1993
1995{
1997 if (test.GetChanID())
1998 {
1999 clone(test, true);
2000 return true;
2001 }
2002 return false;
2003}
2004
2009 const uint _chanid, const QDateTime &_recstartts)
2010{
2011 if (!_chanid || !_recstartts.isValid())
2012 {
2014 return false;
2015 }
2016
2018 query.prepare(
2020 "WHERE r.chanid = :CHANID AND "
2021 " r.starttime = :RECSTARTTS");
2022 query.bindValue(":CHANID", _chanid);
2023 query.bindValue(":RECSTARTTS", _recstartts);
2024
2025 if (!query.exec())
2026 {
2027 MythDB::DBError("LoadProgramFromRecorded", query);
2029 return false;
2030 }
2031
2032 if (!query.next())
2033 {
2035 return false;
2036 }
2037
2038 bool is_reload = (m_chanId == _chanid) && (m_recStartTs == _recstartts);
2039 if (!is_reload)
2040 {
2041 // These items are not initialized below so they need to be cleared
2042 // if we're loading in a different program into this ProgramInfo
2046 m_recPriority2 = 0;
2047 m_parentId = 0;
2048 m_sourceId = 0;
2049 m_inputId = 0;
2050
2051 // everything below this line (in context) is not serialized
2052 m_spread = m_startCol = -1;
2054 m_inUseForWhat.clear();
2056 }
2057
2058 m_title = query.value(0).toString();
2059 m_subtitle = query.value(1).toString();
2060 m_description = query.value(2).toString();
2061 m_season = query.value(3).toUInt();
2062 if (m_season == 0)
2063 m_season = query.value(51).toUInt();
2064 m_episode = query.value(4).toUInt();
2065 if (m_episode == 0)
2066 m_episode = query.value(52).toUInt();
2067 m_totalEpisodes = query.value(53).toUInt();
2068 m_syndicatedEpisode = query.value(48).toString();
2069 m_category = query.value(5).toString();
2070
2071 m_chanId = _chanid;
2072 m_chanStr = QString("#%1").arg(m_chanId);
2075 m_chanPlaybackFilters.clear();
2076 if (!query.value(7).toString().isEmpty())
2077 {
2078 m_chanStr = query.value(7).toString();
2079 m_chanSign = query.value(8).toString();
2080 m_chanName = query.value(9).toString();
2081 m_chanPlaybackFilters = query.value(10).toString();
2082 }
2083
2084 m_recGroup = query.value(11).toString();
2085 m_playGroup = query.value(12).toString();
2086
2087 // We don't want to update the pathname if the basename is
2088 // the same as we may have already expanded pathname from
2089 // a simple basename to a localized path.
2090 QString new_basename = query.value(14).toString();
2091 if ((GetBasename() != new_basename) || !is_reload)
2092 {
2093 if (is_reload)
2094 {
2095 LOG(VB_FILE, LOG_INFO, LOC +
2096 QString("Updated pathname '%1':'%2' -> '%3'")
2097 .arg(m_pathname, GetBasename(), new_basename));
2098 }
2099 SetPathname(new_basename);
2100 }
2101
2102 m_hostname = query.value(15).toString();
2103 m_storageGroup = query.value(13).toString();
2104
2105 m_seriesId = query.value(17).toString();
2106 m_programId = query.value(18).toString();
2107 m_inetRef = query.value(19).toString();
2108 m_catType = string_to_myth_category_type(query.value(54).toString());
2109
2110 m_recPriority = query.value(16).toInt();
2111
2112 m_fileSize = query.value(20).toULongLong();
2113
2114 m_startTs = MythDate::as_utc(query.value(21).toDateTime());
2115 m_endTs = MythDate::as_utc(query.value(22).toDateTime());
2116 m_recStartTs = MythDate::as_utc(query.value(24).toDateTime());
2117 m_recEndTs = MythDate::as_utc(query.value(25).toDateTime());
2118
2119 m_stars = std::clamp((float)query.value(23).toDouble(), 0.0F, 1.0F);
2120
2121 m_year = query.value(26).toUInt();
2122 m_partNumber = query.value(49).toUInt();
2123 m_partTotal = query.value(50).toUInt();
2124
2125 m_originalAirDate = query.value(27).toDate();
2126 m_lastModified = MythDate::as_utc(query.value(28).toDateTime());
2127 //m_lastInUseTime;
2128
2130
2131 //m_recPriority2;
2132
2133 m_recordId = query.value(29).toUInt();
2134 //m_parentId;
2135
2136 //m_sourcid;
2137 //m_inputId;
2138 //m_cardid;
2139 m_findId = query.value(45).toUInt();
2140
2141 //m_recType;
2142 m_dupIn = RecordingDupInType(query.value(46).toInt());
2143 m_dupMethod = RecordingDupMethodType(query.value(47).toInt());
2144
2145 m_recordedId = query.value(55).toUInt();
2146 m_inputName = query.value(56).toString();
2147 m_bookmarkUpdate = MythDate::as_utc(query.value(57).toDateTime());
2148
2149 // ancillary data -- begin
2150 m_programFlags = FL_NONE;
2151 set_flag(m_programFlags, FL_CHANCOMMFREE,
2152 query.value(30).toInt() == COMM_DETECT_COMMFREE);
2153 set_flag(m_programFlags, FL_COMMFLAG,
2154 query.value(31).toInt() == COMM_FLAG_DONE);
2155 set_flag(m_programFlags, FL_COMMPROCESSING ,
2156 query.value(31).toInt() == COMM_FLAG_PROCESSING);
2157 set_flag(m_programFlags, FL_REPEAT, query.value(32).toBool());
2158 set_flag(m_programFlags, FL_TRANSCODED,
2159 query.value(34).toInt() == TRANSCODING_COMPLETE);
2160 set_flag(m_programFlags, FL_DELETEPENDING, query.value(35).toBool());
2161 set_flag(m_programFlags, FL_PRESERVED, query.value(36).toBool());
2162 set_flag(m_programFlags, FL_CUTLIST, query.value(37).toBool());
2163 set_flag(m_programFlags, FL_AUTOEXP, query.value(38).toBool());
2164 set_flag(m_programFlags, FL_REALLYEDITING, query.value(39).toBool());
2165 set_flag(m_programFlags, FL_BOOKMARK, query.value(40).toBool());
2166 set_flag(m_programFlags, FL_WATCHED, query.value(41).toBool());
2167 set_flag(m_programFlags, FL_LASTPLAYPOS, query.value(58).toBool());
2168 set_flag(m_programFlags, FL_EDITING,
2169 ((m_programFlags & FL_REALLYEDITING) != 0U) ||
2170 ((m_programFlags & FL_COMMPROCESSING) != 0U));
2171
2172 m_audioProperties = query.value(42).toUInt();
2173 m_videoProperties = query.value(43).toUInt();
2174 m_subtitleProperties = query.value(44).toUInt();
2175 // ancillary data -- end
2176
2177 if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1895, 12, 28))
2178 m_originalAirDate = QDate();
2179
2180 // Extra stuff which is not serialized and may get lost.
2181 // m_spread
2182 // m_startCol
2183 // m_availableStatus
2184 // m_inUseForWhat
2185 // m_postitionMapDBReplacement
2186
2187 return true;
2188}
2189
2195{
2196 return (m_title == other.m_title &&
2197 m_chanId == other.m_chanId &&
2198 m_startTs == other.m_startTs);
2199}
2200
2206{
2208 return m_recordId == other.m_recordId;
2209
2210 if (m_findId && m_findId == other.m_findId &&
2211 (m_recordId == other.m_recordId || m_recordId == other.m_parentId))
2212 return true;
2213
2215 return false;
2216
2217 if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2218 return false;
2219
2221 {
2222 if (m_programId.endsWith("0000"))
2223 return false;
2224 }
2225
2226 if (!m_programId.isEmpty() && !other.m_programId.isEmpty())
2227 {
2229 {
2230 int index = m_programId.indexOf('/');
2231 int oindex = other.m_programId.indexOf('/');
2232 if (index == oindex && (index < 0 ||
2233 m_programId.left(index) == other.m_programId.left(oindex)))
2234 return m_programId == other.m_programId;
2235 }
2236 else
2237 {
2238 return m_programId == other.m_programId;
2239 }
2240 }
2241
2242 if ((m_dupMethod & kDupCheckSub) &&
2243 ((m_subtitle.isEmpty()) ||
2244 (m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))
2245 return false;
2246
2247 if ((m_dupMethod & kDupCheckDesc) &&
2248 ((m_description.isEmpty()) ||
2249 (m_description.compare(other.m_description, Qt::CaseInsensitive) != 0)))
2250 return false;
2251
2253 ((m_subtitle.isEmpty() &&
2254 ((!other.m_subtitle.isEmpty() &&
2255 m_description.compare(other.m_subtitle, Qt::CaseInsensitive) != 0) ||
2256 (other.m_subtitle.isEmpty() &&
2257 m_description.compare(other.m_description, Qt::CaseInsensitive) != 0))) ||
2258 (!m_subtitle.isEmpty() &&
2259 ((other.m_subtitle.isEmpty() &&
2260 m_subtitle.compare(other.m_description, Qt::CaseInsensitive) != 0) ||
2261 (!other.m_subtitle.isEmpty() &&
2262 m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))))
2263 return false;
2264
2265 return true;
2266}
2267
2275{
2276 if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2277 return false;
2278
2279 if (!m_programId.isEmpty() && !other.m_programId.isEmpty())
2280 {
2282 {
2283 if (m_programId.endsWith("0000"))
2284 return false;
2285 }
2286
2288 {
2289 int index = m_programId.indexOf('/');
2290 int oindex = other.m_programId.indexOf('/');
2291 if (index == oindex && (index < 0 ||
2292 m_programId.left(index) == other.m_programId.left(oindex)))
2293 return m_programId == other.m_programId;
2294 }
2295 else
2296 {
2297 return m_programId == other.m_programId;
2298 }
2299 }
2300
2301 if ((m_dupMethod & kDupCheckSub) &&
2302 ((m_subtitle.isEmpty()) ||
2303 (m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))
2304 return false;
2305
2306 if ((m_dupMethod & kDupCheckDesc) &&
2307 ((m_description.isEmpty()) ||
2308 (m_description.compare(other.m_description, Qt::CaseInsensitive) != 0)))
2309 return false;
2310
2312 ((m_subtitle.isEmpty() &&
2313 ((!other.m_subtitle.isEmpty() &&
2314 m_description.compare(other.m_subtitle, Qt::CaseInsensitive) != 0) ||
2315 (other.m_subtitle.isEmpty() &&
2316 m_description.compare(other.m_description, Qt::CaseInsensitive) != 0))) ||
2317 (!m_subtitle.isEmpty() &&
2318 ((other.m_subtitle.isEmpty() &&
2319 m_subtitle.compare(other.m_description, Qt::CaseInsensitive) != 0) ||
2320 (!other.m_subtitle.isEmpty() &&
2321 m_subtitle.compare(other.m_subtitle, Qt::CaseInsensitive) != 0)))))
2322 return false;
2323
2324 return true;
2325}
2326
2333{
2334 if (m_startTs != other.m_startTs)
2335 return false;
2336 if (IsSameChannel(other))
2337 return true;
2338 if (!IsSameProgram(other))
2339 return false;
2340 return true;
2341}
2342
2349{
2350 if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2351 return false;
2352 return m_startTs == other.m_startTs &&
2353 IsSameChannel(other);
2354}
2355
2363{
2364 if (m_title.compare(other.m_title, Qt::CaseInsensitive) != 0)
2365 return false;
2366 return IsSameChannel(other) &&
2367 m_startTs < other.m_endTs &&
2368 m_endTs > other.m_startTs;
2369}
2370
2378{
2379 return m_chanId == other.m_chanId ||
2380 (!m_chanSign.isEmpty() &&
2381 m_chanSign.compare(other.m_chanSign, Qt::CaseInsensitive) == 0);
2382}
2383
2385{
2386 QMap<QString, int> authMap;
2387 std::array<QString,3> tables { "program", "recorded", "oldrecorded" };
2389
2390 for (const QString& table : tables)
2391 {
2392 query.prepare(QString(
2393 "SELECT DISTINCT LEFT(programid, LOCATE('/', programid)) "
2394 "FROM %1 WHERE programid <> ''").arg(table));
2395 if (!query.exec())
2396 MythDB::DBError("CheckProgramIDAuthorities", query);
2397 else
2398 {
2399 while (query.next())
2400 authMap[query.value(0).toString()] = 1;
2401 }
2402 }
2403
2404 int numAuths = authMap.count();
2405 LOG(VB_GENERAL, LOG_INFO,
2406 QString("Found %1 distinct programid authorities").arg(numAuths));
2407
2408 s_usingProgIDAuth = (numAuths > 1);
2409}
2410
2415QString ProgramInfo::CreateRecordBasename(const QString &ext) const
2416{
2418
2419 QString retval = QString("%1_%2.%3")
2420 .arg(QString::number(m_chanId), starts, ext);
2421
2422 return retval;
2423}
2424
2426 uint chanid, const QString &pathname, bool use_remote)
2427{
2428 QString fn_lower = pathname.toLower();
2430 if (chanid)
2432 else if (fn_lower.startsWith("http:"))
2434 else if (fn_lower.startsWith("rtsp:"))
2436 else
2437 {
2438 fn_lower = determineURLType(pathname);
2439
2440 if (fn_lower.startsWith("dvd:"))
2441 {
2443 }
2444 else if (fn_lower.startsWith("bd:"))
2445 {
2447 }
2448 else if (use_remote && fn_lower.startsWith("myth://"))
2449 {
2450 QString tmpFileDVD = pathname + "/VIDEO_TS";
2451 QString tmpFileBD = pathname + "/BDMV";
2452 if (RemoteFile::Exists(tmpFileDVD))
2454 else if (RemoteFile::Exists(tmpFileBD))
2456 }
2457 }
2458 return pit;
2459}
2460
2461void ProgramInfo::SetPathname(const QString &pn)
2462{
2463 m_pathname = pn;
2464
2466 SetProgramInfoType(pit);
2467}
2468
2470{
2472}
2473
2475 AvailableStatusType status, const QString &where)
2476{
2477 if (status != m_availableStatus)
2478 {
2479 LOG(VB_GUI, LOG_INFO,
2480 toString(kTitleSubtitle) + QString(": %1 -> %2 in %3")
2482 ::toString(status),
2483 where));
2484 }
2485 m_availableStatus = status;
2486}
2487
2491bool ProgramInfo::SaveBasename(const QString &basename)
2492{
2494 query.prepare("UPDATE recorded "
2495 "SET basename = :BASENAME "
2496 "WHERE recordedid = :RECORDEDID;");
2497 query.bindValue(":RECORDEDID", m_recordedId);
2498 query.bindValue(":BASENAME", basename);
2499
2500 if (!query.exec())
2501 {
2502 MythDB::DBError("SetRecordBasename", query);
2503 return false;
2504 }
2505
2506 query.prepare("UPDATE recordedfile "
2507 "SET basename = :BASENAME "
2508 "WHERE recordedid = :RECORDEDID;");
2509 query.bindValue(":RECORDEDID", m_recordedId);
2510 query.bindValue(":BASENAME", basename);
2511
2512 if (!query.exec())
2513 {
2514 MythDB::DBError("SetRecordBasename", query);
2515 return false;
2516 }
2517
2518 SetPathname(basename);
2519
2521 return true;
2522}
2523
2532{
2533 QString bn = GetBasename();
2534 if (!bn.isEmpty())
2535 return bn;
2536
2538 query.prepare(
2539 "SELECT basename "
2540 "FROM recordedfile "
2541 "WHERE recordedid = :RECORDEDID;");
2542 query.bindValue(":RECORDEDID", m_recordedId);
2543
2544 if (!query.exec())
2545 {
2546 MythDB::DBError("QueryBasename", query);
2547 }
2548 else if (query.next())
2549 {
2550 return query.value(0).toString();
2551 }
2552 else
2553 {
2554 LOG(VB_GENERAL, LOG_INFO,
2555 QString("QueryBasename found no entry for recording ID %1")
2556 .arg(m_recordedId));
2557 }
2558
2559 return {};
2560}
2561
2570 bool checkMaster, bool forceCheckLocal)
2571{
2572 // return the original path if BD or DVD URI
2573 if (IsVideoBD() || IsVideoDVD())
2574 return GetPathname();
2575
2576 QString basename = QueryBasename();
2577 if (basename.isEmpty())
2578 return "";
2579
2580 bool checklocal = !gCoreContext->GetBoolSetting("AlwaysStreamFiles", false) ||
2581 forceCheckLocal;
2582
2583 if (IsVideo())
2584 {
2585 QString fullpath = GetPathname();
2586 if (!fullpath.startsWith("myth://", Qt::CaseInsensitive) || !checklocal)
2587 return fullpath;
2588
2589 QUrl url = QUrl(fullpath);
2590 QString path = url.path();
2591 QString host = url.toString(QUrl::RemovePath).mid(7);
2592 QStringList list = host.split(":", Qt::SkipEmptyParts);
2593 if (!list.empty())
2594 {
2595 host = list[0];
2596 list = host.split("@", Qt::SkipEmptyParts);
2597 QString group;
2598 if (!list.empty() && list.size() < 3)
2599 {
2600 host = list.size() == 1 ? list[0] : list[1];
2601 group = list.size() == 1 ? QString() : list[0];
2602 StorageGroup sg = StorageGroup(group, host);
2603 QString local = sg.FindFile(path);
2604 if (!local.isEmpty() && sg.FileExists(local))
2605 return local;
2606 }
2607 }
2608 return fullpath;
2609 }
2610
2611 QString tmpURL;
2612 if (checklocal)
2613 {
2614 // Check to see if the file exists locally
2616#if 0
2617 LOG(VB_FILE, LOG_DEBUG, LOC +
2618 QString("GetPlaybackURL: CHECKING SG : %1 : ").arg(tmpURL));
2619#endif
2620 tmpURL = sgroup.FindFile(basename);
2621
2622 if (!tmpURL.isEmpty())
2623 {
2624 LOG(VB_FILE, LOG_INFO, LOC +
2625 QString("GetPlaybackURL: File is local: '%1'") .arg(tmpURL));
2626 return tmpURL;
2627 }
2629 {
2630 LOG(VB_GENERAL, LOG_ERR, LOC +
2631 QString("GetPlaybackURL: '%1' should be local, but it can "
2632 "not be found.").arg(basename));
2633 // Note do not preceed with "/" that will cause existing code
2634 // to look for a local file with this name...
2635 return QString("GetPlaybackURL/UNABLE/TO/FIND/LOCAL/FILE/ON/%1/%2")
2636 .arg(m_hostname, basename);
2637 }
2638 }
2639
2640 // Check to see if we should stream from the master backend
2641 if ((checkMaster) &&
2642 (gCoreContext->GetBoolSetting("MasterBackendOverride", false)) &&
2643 (RemoteCheckFile(this, false)))
2644 {
2647 basename);
2648
2649 LOG(VB_FILE, LOG_INFO, LOC +
2650 QString("GetPlaybackURL: Found @ '%1'").arg(tmpURL));
2651 return tmpURL;
2652 }
2653
2654 // Fallback to streaming from the backend the recording was created on
2657 basename);
2658
2659 LOG(VB_FILE, LOG_INFO, LOC +
2660 QString("GetPlaybackURL: Using default of: '%1'") .arg(tmpURL));
2661
2662 return tmpURL;
2663}
2664
2668{
2669 uint ret = 0U;
2670 if (m_chanId)
2671 {
2673
2674 query.prepare("SELECT mplexid FROM channel "
2675 "WHERE chanid = :CHANID");
2676 query.bindValue(":CHANID", m_chanId);
2677
2678 if (!query.exec())
2679 MythDB::DBError("QueryMplexID", query);
2680 else if (query.next())
2681 ret = query.value(0).toUInt();
2682
2683 // clear out bogus mplexid's
2684 ret = (32767 == ret) ? 0 : ret;
2685 }
2686
2687 return ret;
2688}
2689
2692void ProgramInfo::SaveBookmark(uint64_t frame)
2693{
2695
2696 bool is_valid = (frame > 0);
2697 if (is_valid)
2698 {
2699 frm_dir_map_t bookmarkmap;
2700 bookmarkmap[frame] = MARK_BOOKMARK;
2701 SaveMarkupMap(bookmarkmap);
2702 }
2703
2704 set_flag(m_programFlags, FL_BOOKMARK, is_valid);
2705
2706 UpdateMarkTimeStamp(is_valid);
2708}
2709
2710void ProgramInfo::UpdateMarkTimeStamp(bool bookmarked) const
2711{
2712 if (IsRecording())
2713 {
2715 query.prepare(
2716 "UPDATE recorded "
2717 "SET bookmarkupdate = CURRENT_TIMESTAMP, "
2718 " bookmark = :BOOKMARKFLAG "
2719 "WHERE recordedid = :RECORDEDID");
2720
2721 query.bindValue(":BOOKMARKFLAG", bookmarked);
2722 query.bindValue(":RECORDEDID", m_recordedId);
2723
2724 if (!query.exec())
2725 MythDB::DBError("bookmark flag update", query);
2726 }
2727}
2728
2730{
2732
2733 bool isValid = frame > 0;
2734 if (isValid)
2735 {
2736 frm_dir_map_t lastPlayPosMap;
2737 lastPlayPosMap[frame] = MARK_UTIL_LASTPLAYPOS;
2738 SaveMarkupMap(lastPlayPosMap, MARK_UTIL_LASTPLAYPOS);
2739 }
2740
2741 set_flag(m_programFlags, FL_LASTPLAYPOS, isValid);
2742
2743 UpdateLastPlayTimeStamp(isValid);
2745}
2746
2747// This function overloads the 'bookmarkupdate' field to force the UI
2748// to update when the last play timestamp is updated. The alternative
2749// is adding another field to the database and to the programinfo
2750// serialization.
2751void ProgramInfo::UpdateLastPlayTimeStamp(bool hasLastPlay) const
2752{
2753 if (IsRecording())
2754 {
2756 query.prepare(
2757 "UPDATE recorded "
2758 "SET bookmarkupdate = CURRENT_TIMESTAMP, "
2759 " lastplay = :LASTPLAYFLAG "
2760 "WHERE recordedid = :RECORDEDID");
2761
2762 query.bindValue(":LASTPLAYFLAG", hasLastPlay);
2763 query.bindValue(":RECORDEDID", m_recordedId);
2764
2765 if (!query.exec())
2766 MythDB::DBError("lastplay flag update", query);
2767 }
2768}
2769
2771{
2772 if (IsRecording())
2774}
2775
2777{
2779}
2780
2782{
2784}
2785
2790{
2792 query.prepare(
2793 "SELECT bookmarkupdate "
2794 "FROM recorded "
2795 "WHERE chanid = :CHANID AND"
2796 " starttime = :STARTTIME");
2797 query.bindValue(":CHANID", m_chanId);
2798 query.bindValue(":STARTTIME", m_recStartTs);
2799
2800 QDateTime ts;
2801
2802 if (!query.exec())
2803 MythDB::DBError("ProgramInfo::GetBookmarkTimeStamp()", query);
2804 else if (query.next())
2805 ts = MythDate::as_utc(query.value(0).toDateTime());
2806
2807 return ts;
2808}
2809
2816uint64_t ProgramInfo::QueryBookmark(void) const
2817{
2818 if (m_programFlags & FL_IGNOREBOOKMARK)
2819 return 0;
2820
2821 frm_dir_map_t bookmarkmap;
2822 QueryMarkupMap(bookmarkmap, MARK_BOOKMARK);
2823
2824 return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2825}
2826
2827uint64_t ProgramInfo::QueryBookmark(uint chanid, const QDateTime &recstartts)
2828{
2829 frm_dir_map_t bookmarkmap;
2831 chanid, recstartts,
2832 bookmarkmap, MARK_BOOKMARK);
2833
2834 return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2835}
2836
2844{
2845 if (m_programFlags & FL_IGNORELASTPLAYPOS)
2846 return 0;
2847
2848 frm_dir_map_t bookmarkmap;
2850
2851 return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2852}
2853
2861{
2862 if (m_programFlags & FL_IGNOREPROGSTART)
2863 return 0;
2864
2865 frm_dir_map_t bookmarkmap;
2866 QueryMarkupMap(bookmarkmap, MARK_UTIL_PROGSTART);
2867
2868 return (bookmarkmap.isEmpty()) ? 0 : bookmarkmap.begin().key();
2869}
2870
2872{
2873 uint64_t start = QueryLastPlayPos();
2874 if (start > 0)
2875 {
2876 LOG(VB_PLAYBACK, LOG_INFO, QString("Using last position @ %1").arg(start));
2877 return start;
2878 }
2879
2880 start = QueryBookmark();
2881 if (start > 0)
2882 {
2883 LOG(VB_PLAYBACK, LOG_INFO, QString("Using bookmark @ %1").arg(start));
2884 return start;
2885 }
2886
2887 if (HasCutlist())
2888 {
2889 // Disable progstart if the program has a cutlist.
2890 LOG(VB_PLAYBACK, LOG_INFO, "Ignoring progstart as cutlist exists");
2891 return 0;
2892 }
2893
2894 start = QueryProgStart();
2895 if (start > 0)
2896 {
2897 LOG(VB_PLAYBACK, LOG_INFO, QString("Using progstart @ %1").arg(start));
2898 return start;
2899 }
2900
2901 LOG(VB_PLAYBACK, LOG_INFO, "Using file start");
2902 return 0;
2903}
2904
2911 const QString &serialid) const
2912{
2913 QStringList fields = QStringList();
2915
2916 if (!(m_programFlags & FL_IGNOREBOOKMARK))
2917 {
2918 query.prepare(" SELECT dvdstate, title, framenum, audionum, subtitlenum "
2919 " FROM dvdbookmark "
2920 " WHERE serialid = :SERIALID ");
2921 query.bindValue(":SERIALID", serialid);
2922
2923 if (query.exec() && query.next())
2924 {
2925 QString dvdstate = query.value(0).toString();
2926
2927 if (!dvdstate.isEmpty())
2928 {
2929 fields.append(dvdstate);
2930 }
2931 else
2932 {
2933 // Legacy bookmark
2934 for(int i = 1; i < 5; i++)
2935 fields.append(query.value(i).toString());
2936 }
2937 }
2938 }
2939
2940 return fields;
2941}
2942
2943void ProgramInfo::SaveDVDBookmark(const QStringList &fields)
2944{
2945 QStringList::const_iterator it = fields.begin();
2947
2948 QString serialid = *(it);
2949 QString name = *(++it);
2950
2951 if( fields.count() == 3 )
2952 {
2953 // We have a state field, so update/create the bookmark
2954 QString state = *(++it);
2955
2956 query.prepare("INSERT IGNORE INTO dvdbookmark "
2957 " (serialid, name)"
2958 " VALUES ( :SERIALID, :NAME );");
2959 query.bindValue(":SERIALID", serialid);
2960 query.bindValue(":NAME", name);
2961
2962 if (!query.exec())
2963 MythDB::DBError("SetDVDBookmark inserting", query);
2964
2965 query.prepare(" UPDATE dvdbookmark "
2966 " SET dvdstate = :STATE , "
2967 " timestamp = NOW() "
2968 " WHERE serialid = :SERIALID");
2969 query.bindValue(":STATE",state);
2970 query.bindValue(":SERIALID",serialid);
2971 }
2972 else
2973 {
2974 // No state field, delete the bookmark
2975 query.prepare("DELETE FROM dvdbookmark "
2976 "WHERE serialid = :SERIALID");
2977 query.bindValue(":SERIALID",serialid);
2978 }
2979
2980 if (!query.exec())
2981 MythDB::DBError("SetDVDBookmark updating", query);
2982}
2983
2987QStringList ProgramInfo::QueryBDBookmark(const QString &serialid) const
2988{
2989 QStringList fields = QStringList();
2991
2992 if (!(m_programFlags & FL_IGNOREBOOKMARK))
2993 {
2994 query.prepare(" SELECT bdstate FROM bdbookmark "
2995 " WHERE serialid = :SERIALID ");
2996 query.bindValue(":SERIALID", serialid);
2997
2998 if (query.exec() && query.next())
2999 fields.append(query.value(0).toString());
3000 }
3001
3002 return fields;
3003}
3004
3005void ProgramInfo::SaveBDBookmark(const QStringList &fields)
3006{
3007 QStringList::const_iterator it = fields.begin();
3009
3010 QString serialid = *(it);
3011 QString name = *(++it);
3012
3013 if( fields.count() == 3 )
3014 {
3015 // We have a state field, so update/create the bookmark
3016 QString state = *(++it);
3017
3018 query.prepare("INSERT IGNORE INTO bdbookmark "
3019 " (serialid, name)"
3020 " VALUES ( :SERIALID, :NAME );");
3021 query.bindValue(":SERIALID", serialid);
3022 query.bindValue(":NAME", name);
3023
3024 if (!query.exec())
3025 MythDB::DBError("SetBDBookmark inserting", query);
3026
3027 query.prepare(" UPDATE bdbookmark "
3028 " SET bdstate = :STATE , "
3029 " timestamp = NOW() "
3030 " WHERE serialid = :SERIALID");
3031 query.bindValue(":STATE",state);
3032 query.bindValue(":SERIALID",serialid);
3033 }
3034 else
3035 {
3036 // No state field, delete the bookmark
3037 query.prepare("DELETE FROM bdbookmark "
3038 "WHERE serialid = :SERIALID");
3039 query.bindValue(":SERIALID",serialid);
3040 }
3041
3042 if (!query.exec())
3043 MythDB::DBError("SetBDBookmark updating", query);
3044}
3045
3052{
3054
3056
3057 query.prepare(" SELECT category_type "
3058 " FROM recordedprogram "
3059 " WHERE chanid = :CHANID "
3060 " AND starttime = :STARTTIME;");
3061
3062 query.bindValue(":CHANID", m_chanId);
3063 query.bindValue(":STARTTIME", m_startTs);
3064
3065 if (query.exec() && query.next())
3066 {
3067 ret = string_to_myth_category_type(query.value(0).toString());
3068 }
3069
3070 return ret;
3071}
3072
3075{
3076 if (IsRecording())
3077 {
3079
3080 query.prepare("UPDATE recorded"
3081 " SET watched = :WATCHEDFLAG"
3082 " WHERE chanid = :CHANID"
3083 " AND starttime = :STARTTIME ;");
3084 query.bindValue(":CHANID", m_chanId);
3085 query.bindValue(":STARTTIME", m_recStartTs);
3086 query.bindValue(":WATCHEDFLAG", watched);
3087
3088 if (!query.exec())
3089 MythDB::DBError("Set watched flag", query);
3090 else
3091 UpdateLastDelete(watched);
3092
3094 }
3095 else if (IsVideoFile())
3096 {
3097 QString url = m_pathname;
3098 if (url.startsWith("myth://"))
3099 {
3100 url = QUrl(url).path();
3101 url.remove(0,1);
3102 }
3103
3105 query.prepare("UPDATE videometadata"
3106 " SET watched = :WATCHEDFLAG"
3107 " WHERE title = :TITLE"
3108 " AND subtitle = :SUBTITLE"
3109 " AND filename = :FILENAME ;");
3110 query.bindValue(":TITLE", m_title);
3111 query.bindValue(":SUBTITLE", m_subtitle);
3112 query.bindValue(":FILENAME", url);
3113 query.bindValue(":WATCHEDFLAG", watched);
3114
3115 if (!query.exec())
3116 MythDB::DBError("Set watched flag", query);
3117 }
3118
3119 set_flag(m_programFlags, FL_WATCHED, watched);
3120}
3121
3127{
3128 bool editing = (m_programFlags & FL_REALLYEDITING) != 0U;
3129
3131
3132 query.prepare("SELECT editing FROM recorded"
3133 " WHERE chanid = :CHANID"
3134 " AND starttime = :STARTTIME ;");
3135 query.bindValue(":CHANID", m_chanId);
3136 query.bindValue(":STARTTIME", m_recStartTs);
3137
3138 if (query.exec() && query.next())
3139 editing = query.value(0).toBool();
3140
3141 /*
3142 set_flag(programflags, FL_REALLYEDITING, editing);
3143 set_flag(programflags, FL_EDITING, ((programflags & FL_REALLYEDITING) ||
3144 (programflags & COMM_FLAG_PROCESSING)));
3145 */
3146 return editing;
3147}
3148
3153{
3155
3156 query.prepare("UPDATE recorded"
3157 " SET editing = :EDIT"
3158 " WHERE chanid = :CHANID"
3159 " AND starttime = :STARTTIME ;");
3160 query.bindValue(":EDIT", edit);
3161 query.bindValue(":CHANID", m_chanId);
3162 query.bindValue(":STARTTIME", m_recStartTs);
3163
3164 if (!query.exec())
3165 MythDB::DBError("Edit status update", query);
3166
3167 set_flag(m_programFlags, FL_REALLYEDITING, edit);
3168 set_flag(m_programFlags, FL_EDITING, (((m_programFlags & FL_REALLYEDITING) != 0U) ||
3170
3172}
3173
3178{
3180
3181 query.prepare("UPDATE recorded"
3182 " SET deletepending = :DELETEFLAG, "
3183 " duplicate = 0 "
3184 " WHERE chanid = :CHANID"
3185 " AND starttime = :STARTTIME ;");
3186 query.bindValue(":CHANID", m_chanId);
3187 query.bindValue(":STARTTIME", m_recStartTs);
3188 query.bindValue(":DELETEFLAG", deleteFlag);
3189
3190 if (!query.exec())
3191 MythDB::DBError("SaveDeletePendingFlag", query);
3192
3193 set_flag(m_programFlags, FL_DELETEPENDING, deleteFlag);
3194
3195 if (!deleteFlag)
3197
3199}
3200
3205bool ProgramInfo::QueryIsInUse(QStringList &byWho) const
3206{
3207 if (!IsRecording())
3208 return false;
3209
3210 QDateTime oneHourAgo = MythDate::current().addSecs(-kLastUpdateOffset);
3212
3213 query.prepare("SELECT hostname, recusage FROM inuseprograms "
3214 " WHERE chanid = :CHANID"
3215 " AND starttime = :STARTTIME "
3216 " AND lastupdatetime > :ONEHOURAGO ;");
3217 query.bindValue(":CHANID", m_chanId);
3218 query.bindValue(":STARTTIME", m_recStartTs);
3219 query.bindValue(":ONEHOURAGO", oneHourAgo);
3220
3221 byWho.clear();
3222 if (query.exec() && query.size() > 0)
3223 {
3224 QString usageStr;
3225 QString recusage;
3226 while (query.next())
3227 {
3228 usageStr = QObject::tr("Unknown");
3229 recusage = query.value(1).toString();
3230
3231 if (recusage == kPlayerInUseID)
3232 usageStr = QObject::tr("Playing");
3233 else if (recusage == kPIPPlayerInUseID)
3234 usageStr = QObject::tr("PIP");
3235 else if (recusage == kPBPPlayerInUseID)
3236 usageStr = QObject::tr("PBP");
3237 else if ((recusage == kRecorderInUseID) ||
3238 (recusage == kImportRecorderInUseID))
3239 usageStr = QObject::tr("Recording");
3240 else if (recusage == kFileTransferInUseID)
3241 usageStr = QObject::tr("File transfer");
3242 else if (recusage == kTruncatingDeleteInUseID)
3243 usageStr = QObject::tr("Delete");
3244 else if (recusage == kFlaggerInUseID)
3245 usageStr = QObject::tr("Commercial Detection");
3246 else if (recusage == kTranscoderInUseID)
3247 usageStr = QObject::tr("Transcoding");
3248 else if (recusage == kPreviewGeneratorInUseID)
3249 usageStr = QObject::tr("Preview Generation");
3250 else if (recusage == kJobQueueInUseID)
3251 usageStr = QObject::tr("User Job");
3252
3253 byWho.push_back(recusage);
3254 byWho.push_back(query.value(0).toString());
3255 byWho.push_back(query.value(0).toString() + " (" + usageStr + ")");
3256 }
3257
3258 return true;
3259 }
3260
3261 return false;
3262}
3263
3268bool ProgramInfo::QueryIsInUse(QString &byWho) const
3269{
3270 QStringList users;
3271 bool inuse = QueryIsInUse(users);
3272 byWho.clear();
3273 for (int i = 0; i+2 < users.size(); i+=3)
3274 byWho += users[i+2] + "\n";
3275 return inuse;
3276}
3277
3278
3285bool ProgramInfo::QueryIsDeleteCandidate(bool one_playback_allowed) const
3286{
3287 if (!IsRecording())
3288 return false;
3289
3290 // gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete", 0) &&
3291 if (GetRecordingGroup() != "Deleted" && GetRecordingGroup() != "LiveTV")
3292 return true;
3293
3294 bool ok = true;
3295 QStringList byWho;
3296 if (QueryIsInUse(byWho) && !byWho.isEmpty())
3297 {
3298 uint play_cnt = 0;
3299 uint ft_cnt = 0;
3300 uint jq_cnt = 0;
3301 for (uint i = 0; (i+2 < (uint)byWho.size()) && ok; i+=3)
3302 {
3303 play_cnt += byWho[i].contains(kPlayerInUseID) ? 1 : 0;
3304 ft_cnt += (byWho[i].contains(kFlaggerInUseID) ||
3305 byWho[i].contains(kTranscoderInUseID)) ? 1 : 0;
3306 jq_cnt += (byWho[i].contains(kJobQueueInUseID)) ? 1 : 0;
3307 ok = ok && (byWho[i].contains(kRecorderInUseID) ||
3308 byWho[i].contains(kFlaggerInUseID) ||
3309 byWho[i].contains(kTranscoderInUseID) ||
3310 byWho[i].contains(kJobQueueInUseID) ||
3311 (one_playback_allowed && (play_cnt <= 1)));
3312 }
3313 ok = ok && (ft_cnt == jq_cnt);
3314 }
3315
3316 return ok;
3317}
3318
3321{
3323
3324 query.prepare("SELECT transcoded FROM recorded"
3325 " WHERE chanid = :CHANID"
3326 " AND starttime = :STARTTIME ;");
3327 query.bindValue(":CHANID", m_chanId);
3328 query.bindValue(":STARTTIME", m_recStartTs);
3329
3330 if (query.exec() && query.next())
3331 return (TranscodingStatus) query.value(0).toUInt();
3333}
3334
3341{
3343
3344 query.prepare(
3345 "UPDATE recorded "
3346 "SET transcoded = :VALUE "
3347 "WHERE chanid = :CHANID AND"
3348 " starttime = :STARTTIME");
3349 query.bindValue(":VALUE", (uint)trans);
3350 query.bindValue(":CHANID", m_chanId);
3351 query.bindValue(":STARTTIME", m_recStartTs);
3352
3353 if (!query.exec())
3354 MythDB::DBError("Transcoded status update", query);
3355
3356 set_flag(m_programFlags, FL_TRANSCODED, TRANSCODING_COMPLETE == trans);
3358}
3359
3364{
3366
3367 query.prepare("UPDATE recorded"
3368 " SET commflagged = :FLAG"
3369 " WHERE chanid = :CHANID"
3370 " AND starttime = :STARTTIME ;");
3371 query.bindValue(":FLAG", (int)flag);
3372 query.bindValue(":CHANID", m_chanId);
3373 query.bindValue(":STARTTIME", m_recStartTs);
3374
3375 if (!query.exec())
3376 MythDB::DBError("Commercial Flagged status update", query);
3377
3378 set_flag(m_programFlags, FL_COMMFLAG, COMM_FLAG_DONE == flag);
3379 set_flag(m_programFlags, FL_COMMPROCESSING, COMM_FLAG_PROCESSING == flag);
3380 set_flag(m_programFlags, FL_EDITING, (((m_programFlags & FL_REALLYEDITING) != 0U) ||
3383}
3384
3385
3389void ProgramInfo::SavePreserve(bool preserveEpisode)
3390{
3392
3393 query.prepare("UPDATE recorded"
3394 " SET preserve = :PRESERVE"
3395 " WHERE chanid = :CHANID"
3396 " AND starttime = :STARTTIME ;");
3397 query.bindValue(":PRESERVE", preserveEpisode);
3398 query.bindValue(":CHANID", m_chanId);
3399 query.bindValue(":STARTTIME", m_recStartTs);
3400
3401 if (!query.exec())
3402 MythDB::DBError("PreserveEpisode update", query);
3403 else
3404 UpdateLastDelete(false);
3405
3406 set_flag(m_programFlags, FL_PRESERVED, preserveEpisode);
3407
3409}
3410
3416void ProgramInfo::SaveAutoExpire(AutoExpireType autoExpire, bool updateDelete)
3417{
3419
3420 query.prepare("UPDATE recorded"
3421 " SET autoexpire = :AUTOEXPIRE"
3422 " WHERE chanid = :CHANID"
3423 " AND starttime = :STARTTIME ;");
3424 query.bindValue(":AUTOEXPIRE", (uint)autoExpire);
3425 query.bindValue(":CHANID", m_chanId);
3426 query.bindValue(":STARTTIME", m_recStartTs);
3427
3428 if (!query.exec())
3429 MythDB::DBError("AutoExpire update", query);
3430 else if (updateDelete)
3431 UpdateLastDelete(true);
3432
3433 set_flag(m_programFlags, FL_AUTOEXP, autoExpire != kDisableAutoExpire);
3434
3436}
3437
3442void ProgramInfo::UpdateLastDelete(bool setTime) const
3443{
3445
3446 if (setTime)
3447 {
3448 QDateTime timeNow = MythDate::current();
3449 auto delay_secs = std::chrono::seconds(m_recStartTs.secsTo(timeNow));
3450 auto delay = duration_cast<std::chrono::hours>(delay_secs);
3451 delay = std::clamp(delay, 1h, 200h);
3452
3453 query.prepare("UPDATE record SET last_delete = :TIME, "
3454 "avg_delay = (avg_delay * 3 + :DELAY) / 4 "
3455 "WHERE recordid = :RECORDID");
3456 query.bindValue(":TIME", timeNow);
3457 query.bindValue(":DELAY", static_cast<qint64>(delay.count()));
3458 }
3459 else
3460 {
3461 query.prepare("UPDATE record SET last_delete = NULL "
3462 "WHERE recordid = :RECORDID");
3463 }
3464 query.bindValue(":RECORDID", m_recordId);
3465
3466 if (!query.exec())
3467 MythDB::DBError("Update last_delete", query);
3468}
3469
3472{
3474
3475 query.prepare("SELECT autoexpire FROM recorded"
3476 " WHERE chanid = :CHANID"
3477 " AND starttime = :STARTTIME ;");
3478 query.bindValue(":CHANID", m_chanId);
3479 query.bindValue(":STARTTIME", m_recStartTs);
3480
3481 if (query.exec() && query.next())
3482 return (AutoExpireType) query.value(0).toInt();
3483
3484 return kDisableAutoExpire;
3485}
3486
3487bool ProgramInfo::QueryCutList(frm_dir_map_t &delMap, bool loadAutoSave) const
3488{
3489 if (loadAutoSave)
3490 {
3491 frm_dir_map_t autosaveMap;
3492 QueryMarkupMap(autosaveMap, MARK_TMP_CUT_START);
3493 QueryMarkupMap(autosaveMap, MARK_TMP_CUT_END, true);
3494 QueryMarkupMap(autosaveMap, MARK_PLACEHOLDER, true);
3495 // Convert the temporary marks into regular marks.
3496 delMap.clear();
3497 // NOLINTNEXTLINE(modernize-loop-convert)
3498 for (auto i = autosaveMap.constBegin(); i != autosaveMap.constEnd(); ++i)
3499 {
3500 uint64_t frame = i.key();
3501 MarkTypes mark = i.value();
3502 if (mark == MARK_TMP_CUT_START)
3503 mark = MARK_CUT_START;
3504 else if (mark == MARK_TMP_CUT_END)
3505 mark = MARK_CUT_END;
3506 delMap[frame] = mark;
3507 }
3508 }
3509 else
3510 {
3512 QueryMarkupMap(delMap, MARK_CUT_END, true);
3513 QueryMarkupMap(delMap, MARK_PLACEHOLDER, true);
3514 }
3515
3516 return !delMap.isEmpty();
3517}
3518
3519void ProgramInfo::SaveCutList(frm_dir_map_t &delMap, bool isAutoSave) const
3520{
3521 if (!isAutoSave)
3522 {
3525 }
3529
3530 frm_dir_map_t tmpDelMap;
3531 // NOLINTNEXTLINE(modernize-loop-convert)
3532 for (auto i = delMap.constBegin(); i != delMap.constEnd(); ++i)
3533 {
3534 uint64_t frame = i.key();
3535 MarkTypes mark = i.value();
3536 if (isAutoSave)
3537 {
3538 if (mark == MARK_CUT_START)
3539 mark = MARK_TMP_CUT_START;
3540 else if (mark == MARK_CUT_END)
3541 mark = MARK_TMP_CUT_END;
3542 }
3543 tmpDelMap[frame] = mark;
3544 }
3545 SaveMarkupMap(tmpDelMap);
3546
3547 if (IsRecording())
3548 {
3550
3551 // Flag the existence of a cutlist
3552 query.prepare("UPDATE recorded"
3553 " SET cutlist = :CUTLIST"
3554 " WHERE chanid = :CHANID"
3555 " AND starttime = :STARTTIME ;");
3556
3557 query.bindValue(":CUTLIST", delMap.isEmpty() ? 0 : 1);
3558 query.bindValue(":CHANID", m_chanId);
3559 query.bindValue(":STARTTIME", m_recStartTs);
3560
3561 if (!query.exec())
3562 MythDB::DBError("cutlist flag update", query);
3563 }
3564}
3565
3567{
3570 SaveMarkupMap(frames);
3571}
3572
3574{
3576 QueryMarkupMap(frames, MARK_COMM_END, true);
3577}
3578
3580 MarkTypes type, int64_t min_frame, int64_t max_frame) const
3581{
3583 QString comp;
3584
3585 if (min_frame >= 0)
3586 comp += QString(" AND mark >= %1 ").arg(min_frame);
3587
3588 if (max_frame >= 0)
3589 comp += QString(" AND mark <= %1 ").arg(max_frame);
3590
3591 if (type != MARK_ALL)
3592 comp += QString(" AND type = :TYPE ");
3593
3594 if (IsVideo())
3595 {
3596 query.prepare("DELETE FROM filemarkup"
3597 " WHERE filename = :PATH "
3598 + comp + ";");
3600 }
3601 else if (IsRecording())
3602 {
3603 query.prepare("DELETE FROM recordedmarkup"
3604 " WHERE chanid = :CHANID"
3605 " AND STARTTIME = :STARTTIME"
3606 + comp + ';');
3607 query.bindValue(":CHANID", m_chanId);
3608 query.bindValue(":STARTTIME", m_recStartTs);
3609 }
3610 else
3611 {
3612 return;
3613 }
3614 query.bindValue(":TYPE", type);
3615
3616 if (!query.exec())
3617 MythDB::DBError("ClearMarkupMap deleting", query);
3618}
3619
3621 const frm_dir_map_t &marks, MarkTypes type,
3622 int64_t min_frame, int64_t max_frame) const
3623{
3625 QString videoPath;
3626
3627 if (IsVideo())
3628 {
3630 }
3631 else if (IsRecording())
3632 {
3633 // check to make sure the show still exists before saving markups
3634 query.prepare("SELECT starttime FROM recorded"
3635 " WHERE chanid = :CHANID"
3636 " AND starttime = :STARTTIME ;");
3637 query.bindValue(":CHANID", m_chanId);
3638 query.bindValue(":STARTTIME", m_recStartTs);
3639
3640 if (!query.exec())
3641 MythDB::DBError("SaveMarkupMap checking record table", query);
3642
3643 if (!query.next())
3644 return;
3645 }
3646 else
3647 {
3648 return;
3649 }
3650
3651 frm_dir_map_t::const_iterator it;
3652 for (it = marks.begin(); it != marks.end(); ++it)
3653 {
3654 uint64_t frame = it.key();
3655
3656 if ((min_frame >= 0) && (frame < (uint64_t)min_frame))
3657 continue;
3658
3659 if ((max_frame >= 0) && (frame > (uint64_t)max_frame))
3660 continue;
3661
3662 int mark_type = (type != MARK_ALL) ? type : *it;
3663
3664 if (IsVideo())
3665 {
3666 query.prepare("INSERT INTO filemarkup (filename, mark, type)"
3667 " VALUES ( :PATH , :MARK , :TYPE );");
3668 query.bindValue(":PATH", videoPath);
3669 }
3670 else // if (IsRecording())
3671 {
3672 query.prepare("INSERT INTO recordedmarkup"
3673 " (chanid, starttime, mark, type)"
3674 " VALUES ( :CHANID , :STARTTIME , :MARK , :TYPE );");
3675 query.bindValue(":CHANID", m_chanId);
3676 query.bindValue(":STARTTIME", m_recStartTs);
3677 }
3678 query.bindValue(":MARK", (quint64)frame);
3679 query.bindValue(":TYPE", mark_type);
3680
3681 if (!query.exec())
3682 MythDB::DBError("SaveMarkupMap inserting", query);
3683 }
3684}
3685
3687 frm_dir_map_t &marks, MarkTypes type, bool merge) const
3688{
3689 if (!merge)
3690 marks.clear();
3691
3692 if (IsVideo())
3693 {
3695 marks, type, merge);
3696 }
3697 else if (IsRecording())
3698 {
3699 QueryMarkupMap(m_chanId, m_recStartTs, marks, type, merge);
3700 }
3701}
3702
3704 const QString &video_pathname,
3705 frm_dir_map_t &marks,
3706 MarkTypes type, bool mergeIntoMap)
3707{
3708 if (!mergeIntoMap)
3709 marks.clear();
3710
3712
3713 query.prepare("SELECT mark, type, `offset` "
3714 "FROM filemarkup "
3715 "WHERE filename = :PATH AND "
3716 " type = :TYPE "
3717 "ORDER BY mark");
3718 query.bindValue(":PATH", video_pathname);
3719 query.bindValue(":TYPE", type);
3720
3721 if (!query.exec())
3722 {
3723 MythDB::DBError("QueryMarkupMap", query);
3724 return;
3725 }
3726
3727 while (query.next())
3728 {
3729 // marks[query.value(0).toLongLong()] =
3730 // (MarkTypes) query.value(1).toInt();
3731 int entryType = query.value(1).toInt();
3732 if (entryType == MARK_VIDEO_RATE)
3733 marks[query.value(2).toLongLong()] = (MarkTypes) entryType;
3734 else
3735 marks[query.value(0).toLongLong()] = (MarkTypes) entryType;
3736
3737 }
3738}
3739
3741 uint chanid, const QDateTime &recstartts,
3742 frm_dir_map_t &marks,
3743 MarkTypes type, bool mergeIntoMap)
3744{
3745 if (!mergeIntoMap)
3746 marks.clear();
3747
3749 query.prepare("SELECT mark, type, data "
3750 "FROM recordedmarkup "
3751 "WHERE chanid = :CHANID AND "
3752 " starttime = :STARTTIME AND"
3753 " type = :TYPE "
3754 "ORDER BY mark");
3755 query.bindValue(":CHANID", chanid);
3756 query.bindValue(":STARTTIME", recstartts);
3757 query.bindValue(":TYPE", type);
3758
3759 if (!query.exec())
3760 {
3761 MythDB::DBError("QueryMarkupMap", query);
3762 return;
3763 }
3764
3765 while (query.next())
3766 {
3767 // marks[query.value(0).toULongLong()] =
3768 // (MarkTypes) query.value(1).toInt();
3769 int entryType = query.value(1).toInt();
3770 if (entryType == MARK_VIDEO_RATE)
3771 marks[query.value(2).toULongLong()] = (MarkTypes) entryType;
3772 else
3773 marks[query.value(0).toULongLong()] = (MarkTypes) entryType;
3774 }
3775}
3776
3779{
3780 frm_dir_map_t flagMap;
3781
3782 QueryMarkupMap(flagMap, type);
3783
3784 return flagMap.contains(0);
3785}
3786
3789{
3791 frm_dir_map_t flagMap;
3792 flagMap[0] = type;
3793 SaveMarkupMap(flagMap, type);
3794}
3795
3797 frm_pos_map_t &posMap, MarkTypes type) const
3798{
3800 {
3801 QMutexLocker locker(m_positionMapDBReplacement->lock);
3803
3804 return;
3805 }
3806
3807 posMap.clear();
3809
3810 if (IsVideo())
3811 {
3812 query.prepare("SELECT mark, `offset` FROM filemarkup"
3813 " WHERE filename = :PATH"
3814 " AND type = :TYPE ;");
3816 }
3817 else if (IsRecording())
3818 {
3819 query.prepare("SELECT mark, `offset` FROM recordedseek"
3820 " WHERE chanid = :CHANID"
3821 " AND starttime = :STARTTIME"
3822 " AND type = :TYPE ;");
3823 query.bindValue(":CHANID", m_chanId);
3824 query.bindValue(":STARTTIME", m_recStartTs);
3825 }
3826 else
3827 {
3828 return;
3829 }
3830 query.bindValue(":TYPE", type);
3831
3832 if (!query.exec())
3833 {
3834 MythDB::DBError("QueryPositionMap", query);
3835 return;
3836 }
3837
3838 while (query.next())
3839 posMap[query.value(0).toULongLong()] = query.value(1).toULongLong();
3840}
3841
3843{
3845 {
3846 QMutexLocker locker(m_positionMapDBReplacement->lock);
3848 return;
3849 }
3850
3852
3853 if (IsVideo())
3854 {
3855 query.prepare("DELETE FROM filemarkup"
3856 " WHERE filename = :PATH"
3857 " AND type = :TYPE ;");
3859 }
3860 else if (IsRecording())
3861 {
3862 query.prepare("DELETE FROM recordedseek"
3863 " WHERE chanid = :CHANID"
3864 " AND starttime = :STARTTIME"
3865 " AND type = :TYPE ;");
3866 query.bindValue(":CHANID", m_chanId);
3867 query.bindValue(":STARTTIME", m_recStartTs);
3868 }
3869 else
3870 {
3871 return;
3872 }
3873
3874 query.bindValue(":TYPE", type);
3875
3876 if (!query.exec())
3877 MythDB::DBError("clear position map", query);
3878}
3879
3881 frm_pos_map_t &posMap, MarkTypes type,
3882 int64_t min_frame, int64_t max_frame) const
3883{
3885 {
3886 QMutexLocker locker(m_positionMapDBReplacement->lock);
3887
3888 if ((min_frame >= 0) || (max_frame >= 0))
3889 {
3890 frm_pos_map_t new_map;
3891 // NOLINTNEXTLINE(modernize-loop-convert)
3892 for (auto it = m_positionMapDBReplacement->map[type].begin();
3893 it != m_positionMapDBReplacement->map[type].end();
3894 ++it)
3895 {
3896 uint64_t frame = it.key();
3897 if ((min_frame >= 0) && (frame >= (uint64_t)min_frame))
3898 continue;
3899 if ((min_frame >= 0) && (frame <= (uint64_t)max_frame))
3900 continue;
3901 new_map.insert(it.key(), *it);
3902 }
3904 }
3905 else
3906 {
3908 }
3909
3910 // NOLINTNEXTLINE(modernize-loop-convert)
3911 for (auto it = posMap.cbegin(); it != posMap.cend(); ++it)
3912 {
3913 uint64_t frame = it.key();
3914 if ((min_frame >= 0) && (frame >= (uint64_t)min_frame))
3915 continue;
3916 if ((min_frame >= 0) && (frame <= (uint64_t)max_frame))
3917 continue;
3918
3920 .insert(frame, *it);
3921 }
3922
3923 return;
3924 }
3925
3927 QString comp;
3928
3929 if (min_frame >= 0)
3930 comp += " AND mark >= :MIN_FRAME ";
3931 if (max_frame >= 0)
3932 comp += " AND mark <= :MAX_FRAME ";
3933
3934 QString videoPath;
3935 if (IsVideo())
3936 {
3938
3939 query.prepare("DELETE FROM filemarkup"
3940 " WHERE filename = :PATH"
3941 " AND type = :TYPE"
3942 + comp + ';');
3943 query.bindValue(":PATH", videoPath);
3944 }
3945 else if (IsRecording())
3946 {
3947 query.prepare("DELETE FROM recordedseek"
3948 " WHERE chanid = :CHANID"
3949 " AND starttime = :STARTTIME"
3950 " AND type = :TYPE"
3951 + comp + ';');
3952 query.bindValue(":CHANID", m_chanId);
3953 query.bindValue(":STARTTIME", m_recStartTs);
3954 }
3955 else
3956 {
3957 return;
3958 }
3959
3960 query.bindValue(":TYPE", type);
3961 if (min_frame >= 0)
3962 query.bindValue(":MIN_FRAME", (quint64)min_frame);
3963 if (max_frame >= 0)
3964 query.bindValue(":MAX_FRAME", (quint64)max_frame);
3965
3966 if (!query.exec())
3967 MythDB::DBError("position map clear", query);
3968
3969 if (posMap.isEmpty())
3970 return;
3971
3972 // Use the multi-value insert syntax to reduce database I/O
3973 QStringList q("INSERT INTO ");
3974 QString qfields;
3975 if (IsVideo())
3976 {
3977 q << "filemarkup (filename, type, mark, `offset`)";
3978 qfields = QString("('%1',%2,") .
3979 // ideally, this should be escaped
3980 arg(videoPath) .
3981 arg(type);
3982 }
3983 else // if (IsRecording())
3984 {
3985 q << "recordedseek (chanid, starttime, type, mark, `offset`)";
3986 qfields = QString("(%1,'%2',%3,") .
3987 arg(m_chanId) .
3988 arg(m_recStartTs.toString(Qt::ISODate)) .
3989 arg(type);
3990 }
3991 q << " VALUES ";
3992
3993 bool add_comma = false;
3994 frm_pos_map_t::iterator it;
3995 for (it = posMap.begin(); it != posMap.end(); ++it)
3996 {
3997 uint64_t frame = it.key();
3998
3999 if ((min_frame >= 0) && (frame < (uint64_t)min_frame))
4000 continue;
4001
4002 if ((max_frame >= 0) && (frame > (uint64_t)max_frame))
4003 continue;
4004
4005 uint64_t offset = *it;
4006
4007 if (add_comma)
4008 {
4009 q << ",";
4010 }
4011 else
4012 {
4013 add_comma = true;
4014 }
4015 q << qfields << QString("%1,%2)").arg(frame).arg(offset);
4016 }
4017 query.prepare(q.join(""));
4018 if (!query.exec())
4019 {
4020 MythDB::DBError("position map insert", query);
4021 }
4022}
4023
4025 frm_pos_map_t &posMap, MarkTypes type) const
4026{
4027 if (posMap.isEmpty())
4028 return;
4029
4031 {
4032 QMutexLocker locker(m_positionMapDBReplacement->lock);
4033
4034 for (auto it = posMap.cbegin(); it != posMap.cend(); ++it)
4035 m_positionMapDBReplacement->map[type].insert(it.key(), *it);
4036
4037 return;
4038 }
4039
4040 // Use the multi-value insert syntax to reduce database I/O
4041 QStringList q("INSERT INTO ");
4042 QString qfields;
4043 if (IsVideo())
4044 {
4045 q << "filemarkup (filename, type, mark, `offset`)";
4046 qfields = QString("('%1',%2,") .
4047 // ideally, this should be escaped
4049 arg(type);
4050 }
4051 else if (IsRecording())
4052 {
4053 q << "recordedseek (chanid, starttime, type, mark, `offset`)";
4054 qfields = QString("(%1,'%2',%3,") .
4055 arg(m_chanId) .
4056 arg(m_recStartTs.toString(Qt::ISODate)) .
4057 arg(type);
4058 }
4059 else
4060 {
4061 return;
4062 }
4063 q << " VALUES ";
4064
4065 bool add_comma = false;
4066 frm_pos_map_t::iterator it;
4067 for (it = posMap.begin(); it != posMap.end(); ++it)
4068 {
4069 uint64_t frame = it.key();
4070 uint64_t offset = *it;
4071
4072 if (add_comma)
4073 {
4074 q << ",";
4075 }
4076 else
4077 {
4078 add_comma = true;
4079 }
4080 q << qfields << QString("%1,%2)").arg(frame).arg(offset);
4081 }
4082
4084 query.prepare(q.join(""));
4085 if (!query.exec())
4086 {
4087 MythDB::DBError("delta position map insert", query);
4088 }
4089}
4090
4091static const char *from_filemarkup_offset_asc =
4092 "SELECT mark, `offset` FROM filemarkup"
4093 " WHERE filename = :PATH"
4094 " AND type = :TYPE"
4095 " AND mark >= :QUERY_ARG"
4096 " ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
4098 "SELECT mark, `offset` FROM filemarkup"
4099 " WHERE filename = :PATH"
4100 " AND type = :TYPE"
4101 " AND mark <= :QUERY_ARG"
4102 " ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
4104 "SELECT mark, `offset` FROM recordedseek"
4105 " WHERE chanid = :CHANID"
4106 " AND starttime = :STARTTIME"
4107 " AND type = :TYPE"
4108 " AND mark >= :QUERY_ARG"
4109 " ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
4111 "SELECT mark, `offset` FROM recordedseek"
4112 " WHERE chanid = :CHANID"
4113 " AND starttime = :STARTTIME"
4114 " AND type = :TYPE"
4115 " AND mark <= :QUERY_ARG"
4116 " ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";
4117static const char *from_filemarkup_mark_asc =
4118 "SELECT `offset`,mark FROM filemarkup"
4119 " WHERE filename = :PATH"
4120 " AND type = :TYPE"
4121 " AND `offset` >= :QUERY_ARG"
4122 " ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
4123static const char *from_filemarkup_mark_desc =
4124 "SELECT `offset`,mark FROM filemarkup"
4125 " WHERE filename = :PATH"
4126 " AND type = :TYPE"
4127 " AND `offset` <= :QUERY_ARG"
4128 " ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
4129static const char *from_recordedseek_mark_asc =
4130 "SELECT `offset`,mark FROM recordedseek"
4131 " WHERE chanid = :CHANID"
4132 " AND starttime = :STARTTIME"
4133 " AND type = :TYPE"
4134 " AND `offset` >= :QUERY_ARG"
4135 " ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
4137 "SELECT `offset`,mark FROM recordedseek"
4138 " WHERE chanid = :CHANID"
4139 " AND starttime = :STARTTIME"
4140 " AND type = :TYPE"
4141 " AND `offset` <= :QUERY_ARG"
4142 " ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";
4143
4144bool ProgramInfo::QueryKeyFrameInfo(uint64_t * result,
4145 uint64_t position_or_keyframe,
4146 bool backwards,
4148 const char *from_filemarkup_asc,
4149 const char *from_filemarkup_desc,
4150 const char *from_recordedseek_asc,
4151 const char *from_recordedseek_desc) const
4152{
4154
4155 if (IsVideo())
4156 {
4157 if (backwards)
4158 query.prepare(from_filemarkup_desc);
4159 else
4160 query.prepare(from_filemarkup_asc);
4162 }
4163 else if (IsRecording())
4164 {
4165 if (backwards)
4166 query.prepare(from_recordedseek_desc);
4167 else
4168 query.prepare(from_recordedseek_asc);
4169 query.bindValue(":CHANID", m_chanId);
4170 query.bindValue(":STARTTIME", m_recStartTs);
4171 }
4172 query.bindValue(":TYPE", type);
4173 query.bindValue(":QUERY_ARG", (unsigned long long)position_or_keyframe);
4174
4175 if (!query.exec())
4176 {
4177 MythDB::DBError("QueryKeyFrameInfo", query);
4178 return false;
4179 }
4180
4181 if (query.next())
4182 {
4183 *result = query.value(1).toULongLong();
4184 return true;
4185 }
4186
4187 if (IsVideo())
4188 {
4189 if (backwards)
4190 query.prepare(from_filemarkup_asc);
4191 else
4192 query.prepare(from_filemarkup_desc);
4194 }
4195 else if (IsRecording())
4196 {
4197 if (backwards)
4198 query.prepare(from_recordedseek_asc);
4199 else
4200 query.prepare(from_recordedseek_desc);
4201 query.bindValue(":CHANID", m_chanId);
4202 query.bindValue(":STARTTIME", m_recStartTs);
4203 }
4204 query.bindValue(":TYPE", type);
4205 query.bindValue(":QUERY_ARG", (unsigned long long)position_or_keyframe);
4206
4207 if (!query.exec())
4208 {
4209 MythDB::DBError("QueryKeyFrameInfo", query);
4210 return false;
4211 }
4212
4213 if (query.next())
4214 {
4215 *result = query.value(1).toULongLong();
4216 return true;
4217 }
4218
4219 return false;
4220
4221}
4222
4223bool ProgramInfo::QueryPositionKeyFrame(uint64_t *keyframe, uint64_t position,
4224 bool backwards) const
4225{
4226 return QueryKeyFrameInfo(keyframe, position, backwards, MARK_GOP_BYFRAME,
4231}
4232bool ProgramInfo::QueryKeyFramePosition(uint64_t *position, uint64_t keyframe,
4233 bool backwards) const
4234{
4235 return QueryKeyFrameInfo(position, keyframe, backwards, MARK_GOP_BYFRAME,
4240}
4241bool ProgramInfo::QueryDurationKeyFrame(uint64_t *keyframe, uint64_t duration,
4242 bool backwards) const
4243{
4244 return QueryKeyFrameInfo(keyframe, duration, backwards, MARK_DURATION_MS,
4249}
4250bool ProgramInfo::QueryKeyFrameDuration(uint64_t *duration, uint64_t keyframe,
4251 bool backwards) const
4252{
4253 return QueryKeyFrameInfo(duration, keyframe, backwards, MARK_DURATION_MS,
4258}
4259
4264 uint64_t frame, MarkTypes type, uint customAspect)
4265{
4266 if (!IsRecording())
4267 return;
4268
4270
4271 query.prepare("INSERT INTO recordedmarkup"
4272 " (chanid, starttime, mark, type, data)"
4273 " VALUES"
4274 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4275 query.bindValue(":CHANID", m_chanId);
4276 query.bindValue(":STARTTIME", m_recStartTs);
4277
4278 query.bindValue(":MARK", (quint64)frame);
4279 query.bindValue(":TYPE", type);
4280
4281 if (type == MARK_ASPECT_CUSTOM)
4282 query.bindValue(":DATA", customAspect);
4283 else
4284 {
4285 // create NULL value
4286#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
4287 query.bindValue(":DATA", QVariant(QVariant::UInt));
4288#else
4289 query.bindValue(":DATA", QVariant(QMetaType(QMetaType::UInt)));
4290#endif
4291 }
4292
4293 if (!query.exec())
4294 MythDB::DBError("aspect ratio change insert", query);
4295}
4296
4300void ProgramInfo::SaveFrameRate(uint64_t frame, uint framerate)
4301{
4302 if (!IsRecording())
4303 return;
4304
4306
4307 query.prepare("INSERT INTO recordedmarkup"
4308 " (chanid, starttime, mark, type, data)"
4309 " VALUES"
4310 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4311 query.bindValue(":CHANID", m_chanId);
4312 query.bindValue(":STARTTIME", m_recStartTs);
4313 query.bindValue(":MARK", (quint64)frame);
4314 query.bindValue(":TYPE", MARK_VIDEO_RATE);
4315 query.bindValue(":DATA", framerate);
4316
4317 if (!query.exec())
4318 MythDB::DBError("Frame rate insert", query);
4319}
4320
4321
4325void ProgramInfo::SaveVideoScanType(uint64_t frame, bool progressive)
4326{
4327 if (!IsRecording())
4328 return;
4329
4331
4332 query.prepare("INSERT INTO recordedmarkup"
4333 " (chanid, starttime, mark, type, data)"
4334 " VALUES"
4335 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4336 query.bindValue(":CHANID", m_chanId);
4337 query.bindValue(":STARTTIME", m_recStartTs);
4338 query.bindValue(":MARK", (quint64)frame);
4339 query.bindValue(":TYPE", MARK_SCAN_PROGRESSIVE);
4340 query.bindValue(":DATA", progressive);
4341
4342 if (!query.exec())
4343 MythDB::DBError("Video scan type insert", query);
4344}
4345
4346
4348 MarkTypes type, uint chanid, const QDateTime &recstartts)
4349{
4351
4352 query.prepare("DELETE FROM recordedmarkup "
4353 " WHERE chanid=:CHANID "
4354 " AND starttime=:STARTTIME "
4355 " AND type=:TYPE");
4356 query.bindValue(":CHANID", chanid);
4357 query.bindValue(":STARTTIME", recstartts);
4358 query.bindValue(":TYPE", type);
4359
4360 if (!query.exec())
4361 MythDB::DBError("delete_markup_datum", query);
4362}
4363
4364// Delete all marks of the given type
4366 MarkTypes type, const QString &videoPath)
4367{
4369
4370 query.prepare("DELETE FROM filemarkup"
4371 " WHERE filename = :PATH "
4372 " AND type = :TYPE ;");
4373 query.bindValue(":PATH", videoPath);
4374 query.bindValue(":TYPE", type);
4375
4376 if (!query.exec())
4377 MythDB::DBError("delete_markup_datum", query);
4378}
4379
4380// Delete the specified mark of the given type
4382 MarkTypes type, uint mark, const QString &videoPath)
4383{
4385
4386 query.prepare("DELETE FROM filemarkup"
4387 " WHERE filename = :PATH "
4388 " AND mark = :MARK "
4389 " AND type = :TYPE ;");
4390 query.bindValue(":PATH", videoPath);
4391 query.bindValue(":MARK", mark);
4392 query.bindValue(":TYPE", type);
4393
4394 if (!query.exec())
4395 MythDB::DBError("delete_markup_datum", query);
4396}
4397
4399 MarkTypes type, uint mark, uint offset, const QString &videoPath)
4400{
4402
4403 query.prepare("INSERT INTO filemarkup"
4404 " (filename, mark, type, `offset`)"
4405 " VALUES"
4406 " ( :PATH , :MARK , :TYPE, :OFFSET );");
4407 query.bindValue(":PATH", videoPath);
4408 query.bindValue(":OFFSET", offset);
4409 query.bindValue(":TYPE", type);
4410 query.bindValue(":MARK", mark);
4411
4412 if (!query.exec())
4413 MythDB::DBError("insert_markup_datum", query);
4414}
4415
4417 MarkTypes type, uint mark, uint data, uint chanid, const QDateTime &recstartts)
4418{
4420
4421 query.prepare("INSERT INTO recordedmarkup"
4422 " (chanid, starttime, mark, type, data)"
4423 " VALUES"
4424 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4425 query.bindValue(":CHANID", chanid);
4426 query.bindValue(":STARTTIME", recstartts);
4427 query.bindValue(":DATA", data);
4428 query.bindValue(":TYPE", type);
4429 query.bindValue(":MARK", mark);
4430
4431 if (!query.exec())
4432 MythDB::DBError("insert_markup_datum", query);
4433}
4434
4435
4437//
4438// The type MARK_DURATION_MS is used for the total duration when mark is 0.
4439// For video files this type is also for duration of each key frame
4440// when the mark is not 0.
4441// For video files the duration is saved in the filemarkup table.
4442void ProgramInfo::SaveTotalDuration(std::chrono::milliseconds duration)
4443{
4444 if (IsVideo())
4445 {
4448 insert_markup_datum(MARK_DURATION_MS, 0, duration.count(), videoPath);
4449 }
4450 else if (IsRecording())
4451 {
4454 }
4455}
4456
4459{
4460 if (IsVideo())
4461 {
4464 insert_markup_datum(MARK_TOTAL_FRAMES, 0, frames, videoPath);
4465 }
4466 else if (IsRecording())
4467 {
4470 }
4471}
4472
4476void ProgramInfo::SaveResolution(uint64_t frame, uint width, uint height)
4477{
4478 if (!IsRecording())
4479 return;
4480
4482
4483 query.prepare("INSERT INTO recordedmarkup"
4484 " (chanid, starttime, mark, type, data)"
4485 " VALUES"
4486 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4487 query.bindValue(":CHANID", m_chanId);
4488 query.bindValue(":STARTTIME", m_recStartTs);
4489 query.bindValue(":MARK", (quint64)frame);
4490 query.bindValue(":TYPE", MARK_VIDEO_WIDTH);
4491 query.bindValue(":DATA", width);
4492
4493 if (!query.exec())
4494 MythDB::DBError("Resolution insert", query);
4495
4496 query.prepare("INSERT INTO recordedmarkup"
4497 " (chanid, starttime, mark, type, data)"
4498 " VALUES"
4499 " ( :CHANID, :STARTTIME, :MARK, :TYPE, :DATA);");
4500 query.bindValue(":CHANID", m_chanId);
4501 query.bindValue(":STARTTIME", m_recStartTs);
4502 query.bindValue(":MARK", (quint64)frame);
4503 query.bindValue(":TYPE", MARK_VIDEO_HEIGHT);
4504 query.bindValue(":DATA", height);
4505
4506 if (!query.exec())
4507 MythDB::DBError("Resolution insert", query);
4508}
4509
4511 MarkTypes type, uint chanid, const QDateTime &recstartts)
4512{
4513 QString qstr = QString(
4514 "SELECT recordedmarkup.data "
4515 "FROM recordedmarkup "
4516 "WHERE recordedmarkup.chanid = :CHANID AND "
4517 " recordedmarkup.starttime = :STARTTIME AND "
4518 " recordedmarkup.type = :TYPE "
4519 "GROUP BY recordedmarkup.data "
4520 "ORDER BY SUM( ( SELECT IFNULL(rm.mark, recordedmarkup.mark)"
4521 " FROM recordedmarkup AS rm "
4522 " WHERE rm.chanid = recordedmarkup.chanid AND "
4523 " rm.starttime = recordedmarkup.starttime AND "
4524 " rm.type = recordedmarkup.type AND "
4525 " rm.mark > recordedmarkup.mark "
4526 " ORDER BY rm.mark ASC LIMIT 1 "
4527 " ) - recordedmarkup.mark "
4528 " ) DESC "
4529 "LIMIT 1");
4530
4532 query.prepare(qstr);
4533 query.bindValue(":TYPE", (int)type);
4534 query.bindValue(":CHANID", chanid);
4535 query.bindValue(":STARTTIME", recstartts);
4536
4537 if (!query.exec())
4538 {
4539 MythDB::DBError("load_markup_datum", query);
4540 return 0;
4541 }
4542
4543 return (query.next()) ? query.value(0).toUInt() : 0;
4544}
4545
4547 MarkTypes type, const QString &videoPath)
4548{
4549 QString qstr = QString(
4550 "SELECT filemarkup.`offset` "
4551 "FROM filemarkup "
4552 "WHERE filemarkup.filename = :PATH AND "
4553 " filemarkup.type = :TYPE "
4554 "GROUP BY filemarkup.`offset` "
4555 "ORDER BY SUM( ( SELECT IFNULL(fm.mark, filemarkup.mark)"
4556 " FROM filemarkup AS fm "
4557 " WHERE fm.filename = filemarkup.filename AND "
4558 " fm.type = filemarkup.type AND "
4559 " fm.mark > filemarkup.mark "
4560 " ORDER BY fm.mark ASC LIMIT 1 "
4561 " ) - filemarkup.mark "
4562 " ) DESC "
4563 "LIMIT 1");
4564
4566 query.prepare(qstr);
4567 query.bindValue(":TYPE", (int)type);
4568 query.bindValue(":PATH", videoPath);
4569
4570 if (!query.exec())
4571 {
4572 MythDB::DBError("load_markup_datum", query);
4573 return 0;
4574 }
4575
4576 return (query.next()) ? query.value(0).toUInt() : 0;
4577}
4578
4579
4585{
4587}
4588
4594{
4596}
4597
4603{
4605}
4606
4608{
4610 query.prepare("SELECT recordedmarkup.type "
4611 "FROM recordedmarkup "
4612 "WHERE recordedmarkup.chanid = :CHANID AND "
4613 " recordedmarkup.starttime = :STARTTIME AND "
4614 " recordedmarkup.type >= :ASPECTSTART AND "
4615 " recordedmarkup.type <= :ASPECTEND "
4616 "GROUP BY recordedmarkup.type "
4617 "ORDER BY SUM( ( SELECT IFNULL(rm.mark, ( "
4618 " SELECT MAX(rmmax.mark) "
4619 " FROM recordedmarkup AS rmmax "
4620 " WHERE rmmax.chanid = recordedmarkup.chanid "
4621 " AND rmmax.starttime = recordedmarkup.starttime "
4622 " ) ) "
4623 " FROM recordedmarkup AS rm "
4624 " WHERE rm.chanid = recordedmarkup.chanid AND "
4625 " rm.starttime = recordedmarkup.starttime AND "
4626 " rm.type >= :ASPECTSTART2 AND "
4627 " rm.type <= :ASPECTEND2 AND "
4628 " rm.mark > recordedmarkup.mark "
4629 " ORDER BY rm.mark ASC LIMIT 1 "
4630 " ) - recordedmarkup.mark "
4631 " ) DESC "
4632 "LIMIT 1");
4633 query.bindValue(":CHANID", m_chanId);
4634 query.bindValue(":STARTTIME", m_recStartTs);
4635 query.bindValue(":ASPECTSTART", MARK_ASPECT_4_3); // 11
4636 query.bindValue(":ASPECTEND", MARK_ASPECT_CUSTOM); // 14
4637 query.bindValue(":ASPECTSTART2", MARK_ASPECT_4_3); // 11
4638 query.bindValue(":ASPECTEND2", MARK_ASPECT_CUSTOM); // 14
4639
4640 if (!query.exec())
4641 {
4642 MythDB::DBError("QueryAverageAspectRatio", query);
4643 return MARK_UNSET;
4644 }
4645
4646 if (!query.next())
4647 return MARK_UNSET;
4648
4649 return static_cast<MarkTypes>(query.value(0).toInt());
4650}
4651
4658{
4659 return static_cast<bool>(load_markup_datum(MARK_SCAN_PROGRESSIVE,
4661}
4662
4668std::chrono::milliseconds ProgramInfo::QueryTotalDuration(void) const
4669{
4671 return 0ms;
4672
4673 auto msec = std::chrono::milliseconds(load_markup_datum(MARK_DURATION_MS, m_chanId, m_recStartTs));
4674
4675// Impossible condition, load_markup_datum returns an unsigned int
4676// if (msec < 0ms)
4677// return 0ms;
4678
4679 return msec;
4680}
4681
4686{
4687 if (IsVideo())
4688 {
4690 return frames;
4691 }
4692 if (IsRecording())
4693 {
4695 return frames;
4696 }
4697 return 0;
4698}
4699
4700void ProgramInfo::QueryMarkup(QVector<MarkupEntry> &mapMark,
4701 QVector<MarkupEntry> &mapSeek) const
4702{
4704 // Get the markup
4705 if (IsVideo())
4706 {
4707 query.prepare("SELECT type, mark, `offset` FROM filemarkup"
4708 " WHERE filename = :PATH"
4709 " AND type NOT IN (:KEYFRAME,:DURATION)"
4710 " ORDER BY mark, type;");
4712 query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4713 query.bindValue(":DURATION", MARK_DURATION_MS);
4714 }
4715 else if (IsRecording())
4716 {
4717 query.prepare("SELECT type, mark, data FROM recordedmarkup"
4718 " WHERE chanid = :CHANID"
4719 " AND STARTTIME = :STARTTIME"
4720 " ORDER BY mark, type");
4721 query.bindValue(":CHANID", m_chanId);
4722 query.bindValue(":STARTTIME", m_recStartTs);
4723 }
4724 else
4725 {
4726 return;
4727 }
4728 if (!query.exec())
4729 {
4730 MythDB::DBError("QueryMarkup markup data", query);
4731 return;
4732 }
4733 while (query.next())
4734 {
4735 int type = query.value(0).toInt();
4736 uint64_t frame = query.value(1).toLongLong();
4737 uint64_t data = 0;
4738 bool isDataNull = query.value(2).isNull();
4739 if (!isDataNull)
4740 data = query.value(2).toLongLong();
4741 mapMark.append(MarkupEntry(type, frame, data, isDataNull));
4742 }
4743
4744 // Get the seektable
4745 if (IsVideo())
4746 {
4747 query.prepare("SELECT type, mark, `offset` FROM filemarkup"
4748 " WHERE filename = :PATH"
4749 " AND type IN (:KEYFRAME,:DURATION)"
4750 " ORDER BY mark, type;");
4752 query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4753 query.bindValue(":DURATION", MARK_DURATION_MS);
4754 }
4755 else if (IsRecording())
4756 {
4757 query.prepare("SELECT type, mark, `offset` FROM recordedseek"
4758 " WHERE chanid = :CHANID"
4759 " AND STARTTIME = :STARTTIME"
4760 " ORDER BY mark, type");
4761 query.bindValue(":CHANID", m_chanId);
4762 query.bindValue(":STARTTIME", m_recStartTs);
4763 }
4764 if (!query.exec())
4765 {
4766 MythDB::DBError("QueryMarkup seektable data", query);
4767 return;
4768 }
4769 while (query.next())
4770 {
4771 int type = query.value(0).toInt();
4772 uint64_t frame = query.value(1).toLongLong();
4773 uint64_t data = 0;
4774 bool isDataNull = query.value(2).isNull();
4775 if (!isDataNull)
4776 data = query.value(2).toLongLong();
4777 mapSeek.append(MarkupEntry(type, frame, data, isDataNull));
4778 }
4779}
4780
4781void ProgramInfo::SaveMarkup(const QVector<MarkupEntry> &mapMark,
4782 const QVector<MarkupEntry> &mapSeek) const
4783{
4785 if (IsVideo())
4786 {
4788 if (mapMark.isEmpty())
4789 {
4790 LOG(VB_GENERAL, LOG_INFO,
4791 QString("No mark entries in input, "
4792 "not removing marks from DB"));
4793 }
4794 else
4795 {
4796 query.prepare("DELETE FROM filemarkup"
4797 " WHERE filename = :PATH"
4798 " AND type NOT IN (:KEYFRAME,:DURATION)");
4799 query.bindValue(":PATH", path);
4800 query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4801 query.bindValue(":DURATION", MARK_DURATION_MS);
4802 if (!query.exec())
4803 {
4804 MythDB::DBError("SaveMarkup seektable data", query);
4805 return;
4806 }
4807 for (const auto& entry : std::as_const(mapMark))
4808 {
4809 if (entry.type == MARK_DURATION_MS)
4810 continue;
4811 if (entry.isDataNull)
4812 {
4813 query.prepare("INSERT INTO filemarkup"
4814 " (filename,type,mark)"
4815 " VALUES (:PATH,:TYPE,:MARK)");
4816 }
4817 else
4818 {
4819 query.prepare("INSERT INTO filemarkup"
4820 " (filename,type,mark,`offset`)"
4821 " VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
4822 query.bindValue(":OFFSET", (quint64)entry.data);
4823 }
4824 query.bindValue(":PATH", path);
4825 query.bindValue(":TYPE", entry.type);
4826 query.bindValue(":MARK", (quint64)entry.frame);
4827 if (!query.exec())
4828 {
4829 MythDB::DBError("SaveMarkup seektable data", query);
4830 return;
4831 }
4832 }
4833 }
4834 if (mapSeek.isEmpty())
4835 {
4836 LOG(VB_GENERAL, LOG_INFO,
4837 QString("No seek entries in input, "
4838 "not removing marks from DB"));
4839 }
4840 else
4841 {
4842 query.prepare("DELETE FROM filemarkup"
4843 " WHERE filename = :PATH"
4844 " AND type IN (:KEYFRAME,:DURATION)");
4845 query.bindValue(":PATH", path);
4846 query.bindValue(":KEYFRAME", MARK_GOP_BYFRAME);
4847 query.bindValue(":DURATION", MARK_DURATION_MS);
4848 if (!query.exec())
4849 {
4850 MythDB::DBError("SaveMarkup seektable data", query);
4851 return;
4852 }
4853 for (int i = 0; i < mapSeek.size(); ++i)
4854 {
4855 if (i > 0 && (i % 1000 == 0))
4856 {
4857 LOG(VB_GENERAL, LOG_INFO,
4858 QString("Inserted %1 of %2 records")
4859 .arg(i).arg(mapSeek.size()));
4860 }
4861 const MarkupEntry &entry = mapSeek[i];
4862 query.prepare("INSERT INTO filemarkup"
4863 " (filename,type,mark,`offset`)"
4864 " VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
4865 query.bindValue(":PATH", path);
4866 query.bindValue(":TYPE", entry.type);
4867 query.bindValue(":MARK", (quint64)entry.frame);
4868 query.bindValue(":OFFSET", (quint64)entry.data);
4869 if (!query.exec())
4870 {
4871 MythDB::DBError("SaveMarkup seektable data", query);
4872 return;
4873 }
4874 }
4875 }
4876 }
4877 else if (IsRecording())
4878 {
4879 if (mapMark.isEmpty())
4880 {
4881 LOG(VB_GENERAL, LOG_INFO,
4882 QString("No mark entries in input, "
4883 "not removing marks from DB"));
4884 }
4885 else
4886 {
4887 query.prepare("DELETE FROM recordedmarkup"
4888 " WHERE chanid = :CHANID"
4889 " AND starttime = :STARTTIME");
4890 query.bindValue(":CHANID", m_chanId);
4891 query.bindValue(":STARTTIME", m_recStartTs);
4892 if (!query.exec())
4893 {
4894 MythDB::DBError("SaveMarkup seektable data", query);
4895 return;
4896 }
4897 for (const auto& entry : std::as_const(mapMark))
4898 {
4899 if (entry.isDataNull)
4900 {
4901 query.prepare("INSERT INTO recordedmarkup"
4902 " (chanid,starttime,type,mark)"
4903 " VALUES (:CHANID,:STARTTIME,:TYPE,:MARK)");
4904 }
4905 else
4906 {
4907 query.prepare("INSERT INTO recordedmarkup"
4908 " (chanid,starttime,type,mark,data)"
4909 " VALUES (:CHANID,:STARTTIME,"
4910 " :TYPE,:MARK,:OFFSET)");
4911 query.bindValue(":OFFSET", (quint64)entry.data);
4912 }
4913 query.bindValue(":CHANID", m_chanId);
4914 query.bindValue(":STARTTIME", m_recStartTs);
4915 query.bindValue(":TYPE", entry.type);
4916 query.bindValue(":MARK", (quint64)entry.frame);
4917 if (!query.exec())
4918 {
4919 MythDB::DBError("SaveMarkup seektable data", query);
4920 return;
4921 }
4922 }
4923 }
4924 if (mapSeek.isEmpty())
4925 {
4926 LOG(VB_GENERAL, LOG_INFO,
4927 QString("No seek entries in input, "
4928 "not removing marks from DB"));
4929 }
4930 else
4931 {
4932 query.prepare("DELETE FROM recordedseek"
4933 " WHERE chanid = :CHANID"
4934 " AND starttime = :STARTTIME");
4935 query.bindValue(":CHANID", m_chanId);
4936 query.bindValue(":STARTTIME", m_recStartTs);
4937 if (!query.exec())
4938 {
4939 MythDB::DBError("SaveMarkup seektable data", query);
4940 return;
4941 }
4942 for (int i = 0; i < mapSeek.size(); ++i)
4943 {
4944 if (i > 0 && (i % 1000 == 0))
4945 {
4946 LOG(VB_GENERAL, LOG_INFO,
4947 QString("Inserted %1 of %2 records")
4948 .arg(i).arg(mapSeek.size()));
4949 }
4950 const MarkupEntry &entry = mapSeek[i];
4951 query.prepare("INSERT INTO recordedseek"
4952 " (chanid,starttime,type,mark,`offset`)"
4953 " VALUES (:CHANID,:STARTTIME,"
4954 " :TYPE,:MARK,:OFFSET)");
4955 query.bindValue(":CHANID", m_chanId);
4956 query.bindValue(":STARTTIME", m_recStartTs);
4957 query.bindValue(":TYPE", entry.type);
4958 query.bindValue(":MARK", (quint64)entry.frame);
4959 query.bindValue(":OFFSET", (quint64)entry.data);
4960 if (!query.exec())
4961 {
4962 MythDB::DBError("SaveMarkup seektable data", query);
4963 return;
4964 }
4965 }
4966 }
4967 }
4968}
4969
4970void ProgramInfo::SaveVideoProperties(uint mask, uint video_property_flags)
4971{
4973
4974 LOG(VB_RECORD, LOG_INFO,
4975 QString("SaveVideoProperties(0x%1, 0x%2)")
4976 .arg(mask,2,16,QChar('0')).arg(video_property_flags,2,16,QChar('0')));
4977
4978 query.prepare(
4979 "UPDATE recordedprogram "
4980 "SET videoprop = ((videoprop+0) & :OTHERFLAGS) | :FLAGS "
4981 "WHERE chanid = :CHANID AND starttime = :STARTTIME");
4982
4983 query.bindValue(":OTHERFLAGS", ~mask);
4984 query.bindValue(":FLAGS", video_property_flags);
4985 query.bindValue(":CHANID", m_chanId);
4986 query.bindValue(":STARTTIME", m_startTs);
4987 if (!query.exec())
4988 {
4989 MythDB::DBError("SaveVideoProperties", query);
4990 return;
4991 }
4992
4993 uint videoproperties = m_videoProperties;
4994 videoproperties &= ~mask;
4995 videoproperties |= video_property_flags;
4996 m_videoProperties = videoproperties;
4997
4999}
5000
5011QString ProgramInfo::ChannelText(const QString &format) const
5012{
5013 QString chan(format);
5014 chan.replace("<num>", m_chanStr)
5015 .replace("<sign>", m_chanSign)
5016 .replace("<name>", m_chanName);
5017 return chan;
5018}
5019
5021{
5022#ifdef DEBUG_IN_USE
5023 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("UpdateInUseMark(%1) '%2'")
5024 .arg(force?"force":"no force").arg(m_inUseForWhat));
5025#endif
5026
5027 if (!IsRecording())
5028 return;
5029
5030 if (m_inUseForWhat.isEmpty())
5031 return;
5032
5034 MarkAsInUse(true);
5035}
5036
5038{
5040
5041 query.prepare(
5042 "UPDATE recorded "
5043 "SET season = :SEASON, episode = :EPISODE "
5044 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
5045 "AND recordid = :RECORDID");
5046
5047 query.bindValue(":SEASON", seas);
5048 query.bindValue(":EPISODE", ep);
5049 query.bindValue(":CHANID", m_chanId);
5050 query.bindValue(":STARTTIME", m_recStartTs);
5051 query.bindValue(":RECORDID", m_recordId);
5052 if (!query.exec())
5053 {
5054 MythDB::DBError("SaveSeasonEpisode", query);
5055 return;
5056 }
5057
5059}
5060
5061void ProgramInfo::SaveInetRef(const QString &inet)
5062{
5064
5065 query.prepare(
5066 "UPDATE recorded "
5067 "SET inetref = :INETREF "
5068 "WHERE chanid = :CHANID AND starttime = :STARTTIME "
5069 "AND recordid = :RECORDID");
5070
5071 query.bindValue(":INETREF", inet);
5072 query.bindValue(":CHANID", m_chanId);
5073 query.bindValue(":STARTTIME", m_recStartTs);
5074 query.bindValue(":RECORDID", m_recordId);
5075 query.exec();
5076
5078}
5079
5087{
5088 if (IsLocal() && QFileInfo(m_pathname).isReadable())
5089 return true;
5090
5091 if (!IsMythStream())
5092 m_pathname = GetPlaybackURL(true, false);
5093
5094 if (IsMythStream())
5095 return RemoteCheckFile(this);
5096
5097 if (IsLocal())
5098 return QFileInfo(m_pathname).isReadable();
5099
5100 return false;
5101}
5102
5103QString ProgramInfo::QueryRecordingGroupPassword(const QString &group)
5104{
5105 QString result;
5106
5108 query.prepare("SELECT password FROM recgroups "
5109 "WHERE recgroup = :GROUP");
5110 query.bindValue(":GROUP", group);
5111
5112 if (query.exec() && query.next())
5113 result = query.value(0).toString();
5114
5115 return(result);
5116}
5117
5121{
5123 query.prepare("SELECT recgroup FROM recorded "
5124 "WHERE chanid = :CHANID AND "
5125 " starttime = :START");
5126 query.bindValue(":START", m_recStartTs);
5127 query.bindValue(":CHANID", m_chanId);
5128
5129 QString grp = m_recGroup;
5130 if (query.exec() && query.next())
5131 grp = query.value(0).toString();
5132
5133 return grp;
5134}
5135
5137{
5139 query.prepare("SELECT transcoder FROM recorded "
5140 "WHERE chanid = :CHANID AND "
5141 " starttime = :START");
5142 query.bindValue(":CHANID", m_chanId);
5143 query.bindValue(":START", m_recStartTs);
5144
5145 if (query.exec() && query.next())
5146 return query.value(0).toUInt();
5147
5148 return 0;
5149}
5150
5157{
5158 if (!IsLocal())
5159 {
5160 if (!gCoreContext->IsBackend())
5161 return "";
5162
5163 QString path = GetPlaybackURL(false, true);
5164 if (path.startsWith("/"))
5165 {
5166 QFileInfo testFile(path);
5167 return testFile.path();
5168 }
5169
5170 return "";
5171 }
5172
5173 QFileInfo testFile(m_pathname);
5174 if (testFile.exists() || (gCoreContext->GetHostName() == m_hostname))
5175 {
5176 // we may be recording this file and it may not exist yet so we need
5177 // to do some checking to see what is in pathname
5178 if (testFile.exists())
5179 {
5180 if (testFile.isSymLink())
5181 testFile.setFile(getSymlinkTarget(m_pathname));
5182
5183 if (testFile.isFile())
5184 return testFile.path();
5185 if (testFile.isDir())
5186 return testFile.filePath();
5187 }
5188 else
5189 {
5190 testFile.setFile(testFile.absolutePath());
5191 if (testFile.exists())
5192 {
5193 if (testFile.isSymLink())
5194 testFile.setFile(getSymlinkTarget(testFile.path()));
5195
5196 if (testFile.isDir())
5197 return testFile.filePath();
5198 }
5199 }
5200 }
5201
5202 return "";
5203}
5204
5211void ProgramInfo::MarkAsInUse(bool inuse, const QString& usedFor)
5212{
5213 if (!IsRecording())
5214 return;
5215
5216 bool notifyOfChange = false;
5217
5218 if (inuse &&
5219 (m_inUseForWhat.isEmpty() ||
5220 (!usedFor.isEmpty() && usedFor != m_inUseForWhat)))
5221 {
5222 if (!usedFor.isEmpty())
5223 {
5224
5225#ifdef DEBUG_IN_USE
5226 if (!m_inUseForWhat.isEmpty())
5227 {
5228 LOG(VB_GENERAL, LOG_INFO, LOC +
5229 QString("MarkAsInUse(true, '%1'->'%2')")
5230 .arg(m_inUseForWhat).arg(usedFor) +
5231 " -- use has changed");
5232 }
5233 else
5234 {
5235 LOG(VB_GENERAL, LOG_INFO, LOC +
5236 QString("MarkAsInUse(true, ''->'%1')").arg(usedFor));
5237 }
5238#endif // DEBUG_IN_USE
5239
5240 m_inUseForWhat = usedFor;
5241 }
5242 else if (m_inUseForWhat.isEmpty())
5243 {
5244 m_inUseForWhat = QString("%1 [%2]")
5245 .arg(QObject::tr("Unknown")).arg(getpid());
5246 LOG(VB_GENERAL, LOG_WARNING, LOC +
5247 QString("MarkAsInUse(true, ''->'%1')").arg(m_inUseForWhat) +
5248 " -- use was not explicitly set");
5249 }
5250
5251 notifyOfChange = true;
5252 }
5253
5254 if (!inuse && !m_inUseForWhat.isEmpty() && usedFor != m_inUseForWhat)
5255 {
5256 LOG(VB_GENERAL, LOG_WARNING, LOC +
5257 QString("MarkAsInUse(false, '%1'->'%2')")
5258 .arg(m_inUseForWhat, usedFor) +
5259 " -- use has changed since first setting as in use.");
5260 }
5261#ifdef DEBUG_IN_USE
5262 else if (!inuse)
5263 {
5264 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("MarkAsInUse(false, '%1')")
5265 .arg(m_inUseForWhat));
5266 }
5267#endif // DEBUG_IN_USE
5268
5269 if (!inuse && m_inUseForWhat.isEmpty())
5270 m_inUseForWhat = usedFor;
5271
5272 if (!inuse && m_inUseForWhat.isEmpty())
5273 {
5274 LOG(VB_GENERAL, LOG_WARNING, LOC +
5275 "MarkAsInUse requires a key to delete in use mark");
5276 return; // can't delete if we don't have a key
5277 }
5278
5279 if (!inuse)
5280 {
5282 query.prepare(
5283 "DELETE FROM inuseprograms "
5284 "WHERE chanid = :CHANID AND starttime = :STARTTIME AND "
5285 " hostname = :HOSTNAME AND recusage = :RECUSAGE");
5286 query.bindValue(":CHANID", m_chanId);
5287 query.bindValue(":STARTTIME", m_recStartTs);
5288 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5289 query.bindValue(":RECUSAGE", m_inUseForWhat);
5290
5291 if (!query.exec())
5292 MythDB::DBError("MarkAsInUse -- delete", query);
5293
5294 m_inUseForWhat.clear();
5297 return;
5298 }
5299
5300 if (m_pathname == GetBasename())
5301 m_pathname = GetPlaybackURL(false, true);
5302
5303 QString recDir = DiscoverRecordingDirectory();
5304
5305 QDateTime inUseTime = MythDate::current(true);
5306
5308 query.prepare(
5309 "SELECT count(*) "
5310 "FROM inuseprograms "
5311 "WHERE chanid = :CHANID AND starttime = :STARTTIME AND "
5312 " hostname = :HOSTNAME AND recusage = :RECUSAGE");
5313 query.bindValue(":CHANID", m_chanId);
5314 query.bindValue(":STARTTIME", m_recStartTs);
5315 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5316 query.bindValue(":RECUSAGE", m_inUseForWhat);
5317
5318 if (!query.exec())
5319 {
5320 MythDB::DBError("MarkAsInUse -- select", query);
5321 }
5322 else if (!query.next())
5323 {
5324 LOG(VB_GENERAL, LOG_ERR, LOC + "MarkAsInUse -- select query failed");
5325 }
5326 else if (query.value(0).toBool())
5327 {
5328 query.prepare(
5329 "UPDATE inuseprograms "
5330 "SET lastupdatetime = :UPDATETIME "
5331 "WHERE chanid = :CHANID AND starttime = :STARTTIME AND "
5332 " hostname = :HOSTNAME AND recusage = :RECUSAGE");
5333 query.bindValue(":CHANID", m_chanId);
5334 query.bindValue(":STARTTIME", m_recStartTs);
5335 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5336 query.bindValue(":RECUSAGE", m_inUseForWhat);
5337 query.bindValue(":UPDATETIME", inUseTime);
5338
5339 if (!query.exec())
5340 MythDB::DBError("MarkAsInUse -- update failed", query);
5341 else
5342 m_lastInUseTime = inUseTime;
5343 }
5344 else // if (!query.value(0).toUInt())
5345 {
5346 query.prepare(
5347 "INSERT INTO inuseprograms "
5348 " (chanid, starttime, recusage, hostname, "
5349 " lastupdatetime, rechost, recdir) "
5350 "VALUES "
5351 " (:CHANID, :STARTTIME, :RECUSAGE, :HOSTNAME, "
5352 " :UPDATETIME, :RECHOST, :RECDIR)");
5353 query.bindValue(":CHANID", m_chanId);
5354 query.bindValue(":STARTTIME", m_recStartTs);
5355 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5356 query.bindValue(":RECUSAGE", m_inUseForWhat);
5357 query.bindValue(":UPDATETIME", inUseTime);
5358 query.bindValue(":RECHOST",
5359 m_hostname.isEmpty() ? gCoreContext->GetHostName()
5360 : m_hostname);
5361 query.bindValue(":RECDIR", recDir);
5362
5363 if (!query.exec())
5364 MythDB::DBError("MarkAsInUse -- insert failed", query);
5365 else
5366 m_lastInUseTime = inUseTime;
5367 }
5368
5369 if (!notifyOfChange)
5370 return;
5371
5372 // Let others know we changed status
5373 QDateTime oneHourAgo = MythDate::current().addSecs(-kLastUpdateOffset);
5374 query.prepare("SELECT DISTINCT recusage "
5375 "FROM inuseprograms "
5376 "WHERE lastupdatetime >= :ONEHOURAGO AND "
5377 " chanid = :CHANID AND "
5378 " starttime = :STARTTIME");
5379 query.bindValue(":CHANID", m_chanId);
5380 query.bindValue(":STARTTIME", m_recStartTs);
5381 query.bindValue(":ONEHOURAGO", oneHourAgo);
5382 if (!query.exec())
5383 return; // not safe to send update event...
5384
5385 m_programFlags &= ~(FL_INUSEPLAYING | FL_INUSERECORDING | FL_INUSEOTHER);
5386 while (query.next())
5387 {
5388 QString inUseForWhat = query.value(0).toString();
5389 if (inUseForWhat.contains(kPlayerInUseID))
5390 m_programFlags |= FL_INUSEPLAYING;
5391 else if (inUseForWhat == kRecorderInUseID)
5392 m_programFlags |= FL_INUSERECORDING;
5393 else
5394 m_programFlags |= FL_INUSEOTHER;
5395 }
5397}
5398
5404bool ProgramInfo::QueryTuningInfo(QString &channum, QString &input) const
5405{
5406 channum.clear();
5407 input.clear();
5409
5410 query.prepare("SELECT channel.channum, capturecard.inputname "
5411 "FROM channel, capturecard "
5412 "WHERE channel.chanid = :CHANID AND "
5413 " capturecard.sourceid = :SOURCEID AND "
5414 " capturecard.cardid = :INPUTID");
5415 query.bindValue(":CHANID", m_chanId);
5416 query.bindValue(":SOURCEID", m_sourceId);
5417 query.bindValue(":INPUTID", m_inputId);
5418
5419 if (query.exec() && query.next())
5420 {
5421 channum = query.value(0).toString();
5422 input = query.value(1).toString();
5423 return true;
5424 }
5425 MythDB::DBError("GetChannel(ProgInfo...)", query);
5426 return false;
5427}
5428
5429static int init_tr(void)
5430{
5431 static bool s_done = false;
5432 static QMutex s_initTrLock;
5433 QMutexLocker locker(&s_initTrLock);
5434 if (s_done)
5435 return 1;
5436
5437 QString rec_profile_names =
5438 QObject::tr("Default", "Recording Profile Default") +
5439 QObject::tr("High Quality", "Recording Profile High Quality") +
5440 QObject::tr("Live TV", "Recording Profile Live TV") +
5441 QObject::tr("Low Quality", "Recording Profile Low Quality") +
5442 QObject::tr("Medium Quality", "Recording Profile Medium Quality") +
5443 QObject::tr("MPEG-2", "Recording Profile MPEG-2") +
5444 QObject::tr("RTjpeg/MPEG-4", "Recording Profile RTjpeg/MPEG-4");
5445
5446
5447 QString rec_profile_groups =
5448 QObject::tr("CRC IP Recorders",
5449 "Recording Profile Group Name") +
5450 QObject::tr("FireWire Input",
5451 "Recording Profile Group Name") +
5452 QObject::tr("Freebox Input",
5453 "Recording Profile Group Name") +
5454 QObject::tr("Hardware DVB Encoders",
5455 "Recording Profile Group Name") +
5456 QObject::tr("Hardware HDTV",
5457 "Recording Profile Group Name") +
5458 QObject::tr("Hardware MJPEG Encoders (Matrox G200-TV, Miro DC10, etc)",
5459 "Recording Profile Group Name") +
5460 QObject::tr("HD-PVR Recorders",
5461 "Recording Profile Group Name") +
5462 QObject::tr("HDHomeRun Recorders",
5463 "Recording Profile Group Name") +
5464 QObject::tr("MPEG-2 Encoders (PVR-x50, PVR-500)",
5465 "Recording Profile Group Name") +
5466 QObject::tr("Software Encoders (V4L based)",
5467 "Recording Profile Group Name") +
5468 QObject::tr("Transcoders",
5469 "Recording Profile Group Name") +
5470 QObject::tr("USB MPEG-4 Encoder (Plextor ConvertX, etc)",
5471 "Recording Profile Group Name") +
5472 QObject::tr("V4L2 Encoders",
5473 "Recording Profile Group Name");
5474
5475 QString display_rec_groups =
5476 QObject::tr("All Programs", "Recording Group All Programs") +
5477 QObject::tr("All", "Recording Group All Programs -- short form") +
5478 QObject::tr("Live TV", "Recording Group Live TV") +
5479 QObject::tr("Default", "Recording Group Default") +
5480 QObject::tr("Deleted", "Recording Group Deleted");
5481
5482 QString special_program_groups =
5483 QObject::tr("All Programs - %1",
5484 "Show all programs from a specific recording group");
5485
5486 QString storage_groups =
5487 QObject::tr("Default", "Storage Group Name") +
5488 QObject::tr("Live TV", "Storage Group Name") +
5489 QObject::tr("Thumbnails", "Storage Group Name") +
5490 QObject::tr("DB Backups", "Storage Group Name");
5491
5492 QString play_groups =
5493 QObject::tr("Default", "Playback Group Name");
5494
5495 s_done = true;
5496 return (rec_profile_names.length() +
5497 rec_profile_groups.length() +
5498 display_rec_groups.length() +
5499 special_program_groups.length() +
5500 storage_groups.length() +
5501 play_groups.length());
5502}
5503
5505{
5506 QMutexLocker locker(&s_staticDataLock);
5507 if (!s_updater)
5509 return 1;
5510}
5511
5513QString ProgramInfo::i18n(const QString &msg)
5514{
5515 init_tr();
5516 QByteArray msg_arr = msg.toLatin1();
5517 QString msg_i18n = QObject::tr(msg_arr.constData());
5518 QByteArray msg_i18n_arr = msg_i18n.toLatin1();
5519 return (msg_arr == msg_i18n_arr) ? msg : msg_i18n;
5520}
5521
5529{
5530 QString pburl = GetPlaybackURL(false, true);
5531 if (pburl.startsWith("myth://"))
5532 {
5533 str.replace(QString("%DIR%"), pburl);
5534 }
5535 else
5536 {
5537 QFileInfo dirInfo(pburl);
5538 str.replace(QString("%DIR%"), dirInfo.path());
5539 }
5540
5541 // N.B. Contents of %MATCH% string variables get parsed into a single
5542 // QStringLists. Quotes in strings will cause lost/truncated output.
5543 // Replace QUOTATION MARK (U+0022)j with MODIFIER LETTER DOUBLE PRIME (U+02BA).
5544
5545 str.replace(QString("%FILE%"), GetBasename());
5546 str.replace(QString("%TITLE%"), m_title.replace("\"", "ʺ"));
5547 str.replace(QString("%SUBTITLE%"), m_subtitle.replace("\"", "ʺ"));
5548 str.replace(QString("%SEASON%"), QString::number(m_season));
5549 str.replace(QString("%EPISODE%"), QString::number(m_episode));
5550 str.replace(QString("%TOTALEPISODES%"), QString::number(m_totalEpisodes));
5551 str.replace(QString("%SYNDICATEDEPISODE%"), m_syndicatedEpisode);
5552 str.replace(QString("%DESCRIPTION%"), m_description.replace("\"", "ʺ"));
5553 str.replace(QString("%HOSTNAME%"), m_hostname);
5554 str.replace(QString("%CATEGORY%"), m_category);
5555 str.replace(QString("%RECGROUP%"), m_recGroup);
5556 str.replace(QString("%PLAYGROUP%"), m_playGroup);
5557 str.replace(QString("%CHANID%"), QString::number(m_chanId));
5558 str.replace(QString("%INETREF%"), m_inetRef);
5559 str.replace(QString("%PARTNUMBER%"), QString::number(m_partNumber));
5560 str.replace(QString("%PARTTOTAL%"), QString::number(m_partTotal));
5561 str.replace(QString("%ORIGINALAIRDATE%"),
5562 m_originalAirDate.toString(Qt::ISODate));
5563 static const std::array<const QString,4> s_timeStr
5564 { "STARTTIME", "ENDTIME", "PROGSTART", "PROGEND", };
5565 const std::array<const QDateTime *,4> time_dtr
5567 for (size_t i = 0; i < s_timeStr.size(); i++)
5568 {
5569 str.replace(QString("%%1%").arg(s_timeStr[i]),
5570 (time_dtr[i]->toLocalTime()).toString("yyyyMMddhhmmss"));
5571 str.replace(QString("%%1ISO%").arg(s_timeStr[i]),
5572 (time_dtr[i]->toLocalTime()).toString(Qt::ISODate));
5573 str.replace(QString("%%1UTC%").arg(s_timeStr[i]),
5574 time_dtr[i]->toString("yyyyMMddhhmmss"));
5575 str.replace(QString("%%1ISOUTC%").arg(s_timeStr[i]),
5576 time_dtr[i]->toString(Qt::ISODate));
5577 }
5578 str.replace(QString("%RECORDEDID%"), QString::number(m_recordedId));
5579}
5580
5581QMap<QString,uint32_t> ProgramInfo::QueryInUseMap(void)
5582{
5583 QMap<QString, uint32_t> inUseMap;
5584 QDateTime oneHourAgo = MythDate::current().addSecs(-kLastUpdateOffset);
5585
5587
5588 query.prepare("SELECT DISTINCT chanid, starttime, recusage "
5589 "FROM inuseprograms WHERE lastupdatetime >= :ONEHOURAGO");
5590 query.bindValue(":ONEHOURAGO", oneHourAgo);
5591
5592 if (!query.exec())
5593 return inUseMap;
5594
5595 while (query.next())
5596 {
5597 QString inUseKey = ProgramInfo::MakeUniqueKey(
5598 query.value(0).toUInt(),
5599 MythDate::as_utc(query.value(1).toDateTime()));
5600
5601 QString inUseForWhat = query.value(2).toString();
5602
5603 if (!inUseMap.contains(inUseKey))
5604 inUseMap[inUseKey] = 0;
5605
5606 if (inUseForWhat.contains(kPlayerInUseID))
5607 inUseMap[inUseKey] |= FL_INUSEPLAYING;
5608 else if (inUseForWhat == kRecorderInUseID)
5609 inUseMap[inUseKey] |= FL_INUSERECORDING;
5610 else
5611 inUseMap[inUseKey] |= FL_INUSEOTHER;
5612 }
5613
5614 return inUseMap;
5615}
5616
5618{
5619 QMap<QString,bool> is_job_running;
5620
5622 query.prepare("SELECT chanid, starttime, status FROM jobqueue "
5623 "WHERE type = :TYPE");
5624 query.bindValue(":TYPE", type);
5625 if (!query.exec())
5626 return is_job_running;
5627
5628 while (query.next())
5629 {
5630 uint chanid = query.value(0).toUInt();
5631 QDateTime recstartts = MythDate::as_utc(query.value(1).toDateTime());
5632 int tmpStatus = query.value(2).toInt();
5633 if ((tmpStatus != /*JOB_UNKNOWN*/ 0x0000) &&
5634 (tmpStatus != /*JOB_QUEUED*/ 0x0001) &&
5635 (!(tmpStatus & /*JOB_DONE*/ 0x0100)))
5636 {
5637 is_job_running[
5638 ProgramInfo::MakeUniqueKey(chanid,recstartts)] = true;
5639 }
5640 }
5641
5642 return is_job_running;
5643}
5644
5646 const QString &tmptable, int recordid)
5647{
5648 QStringList slist;
5649
5651 if (sched && tmptable.isEmpty())
5652 {
5653 sched->GetAllPending(slist);
5654 return slist;
5655 }
5656
5657 if (sched)
5658 {
5659 LOG(VB_GENERAL, LOG_ERR,
5660 "Called from master backend\n\t\t\t"
5661 "with recordid or tmptable, this is not currently supported");
5662 return slist;
5663 }
5664
5665 slist.push_back(
5666 (tmptable.isEmpty()) ?
5667 QString("QUERY_GETALLPENDING") :
5668 QString("QUERY_GETALLPENDING %1 %2").arg(tmptable).arg(recordid));
5669
5670 if (!gCoreContext->SendReceiveStringList(slist) || slist.size() < 2)
5671 {
5672 LOG(VB_GENERAL, LOG_ALERT,
5673 "LoadFromScheduler(): Error querying master.");
5674 slist.clear();
5675 }
5676
5677 return slist;
5678}
5679
5680// NOTE: This may look ugly, and in some ways it is, but it's designed this
5681// way for with two purposes in mind - Consistent behaviour and speed
5682// So if you do make changes then carefully test that it doesn't result
5683// in any regressions in total speed of execution or adversely affect the
5684// results returned for any of it's users.
5685static bool FromProgramQuery(const QString &sql, const MSqlBindings &bindings,
5686 MSqlQuery &query, const uint start,
5687 const uint limit, uint &count,
5688 ProgGroupBy::Type groupBy)
5689{
5690 count = 0;
5691
5692 QString columns = QString(
5693 "program.chanid, program.starttime, program.endtime, " // 0-2
5694 "program.title, program.subtitle, program.description, " // 3-5
5695 "program.category, channel.channum, channel.callsign, " // 6-8
5696 "channel.name, program.previouslyshown, channel.commmethod, " // 9-11
5697 "channel.outputfilters, program.seriesid, program.programid, " // 12-14
5698 "program.airdate, program.stars, program.originalairdate, " // 15-17
5699 "program.category_type, oldrecstatus.recordid, " // 18-19
5700 "oldrecstatus.rectype, oldrecstatus.recstatus, " // 20-21
5701 "oldrecstatus.findid, program.videoprop+0, program.audioprop+0, " // 22-24
5702 "program.subtitletypes+0, program.syndicatedepisodenumber, " // 25-26
5703 "program.partnumber, program.parttotal, " // 27-28
5704 "program.season, program.episode, program.totalepisodes "); // 29-31
5705
5706 QString querystr = QString(
5707 "SELECT %1 FROM ( "
5708 " SELECT %2 "
5709 " FROM program "
5710 " LEFT JOIN channel ON program.chanid = channel.chanid "
5711 " LEFT JOIN oldrecorded AS oldrecstatus ON "
5712 " oldrecstatus.future = 0 AND "
5713 " program.title = oldrecstatus.title AND "
5714 " channel.callsign = oldrecstatus.station AND "
5715 " program.starttime = oldrecstatus.starttime "
5716 ) + sql +
5717 ") groupsq ";
5718
5719 // If a ProgGroupBy option is specified, wrap the query in an outer
5720 // query using row_number() and select only rows with value 1. We
5721 // do this instead of relying on MySQL's liberal support for group
5722 // by on non-aggregated columns because it is deterministic.
5723 if (groupBy != ProgGroupBy::None)
5724 {
5725 columns +=
5726 ", row_number() over ( "
5727 " partition by ";
5728 if (groupBy == ProgGroupBy::ChanNum)
5729 columns += "channel.channum, "
5730 " channel.callsign, ";
5731 else if (groupBy == ProgGroupBy::CallSign)
5732 columns += "channel.callsign, ";
5733 else if (groupBy == ProgGroupBy::ProgramId)
5734 columns += "program.programid, ";
5735 columns +=
5736 " program.title, "
5737 " program.starttime "
5738 " order by channel.recpriority desc, "
5739 " channel.sourceid, "
5740 " channel.channum+0 "
5741 ") grouprn ";
5742 querystr += "WHERE grouprn = 1 ";
5743 }
5744
5745 // If a limit arg was given then append the LIMIT, otherwise set a hard
5746 // limit of 20000.
5747 if (limit > 0)
5748 querystr += QString("LIMIT %1 ").arg(limit);
5749 else if (!querystr.contains(" LIMIT "))
5750 querystr += " LIMIT 20000 "; // For performance reasons we have to have an upper limit
5751
5752 MSqlBindings::const_iterator it;
5753 // Some end users need to know the total number of matching records,
5754 // irrespective of any LIMIT clause
5755 //
5756 // SQL_CALC_FOUND_ROWS is better than COUNT(*), as COUNT(*) won't work
5757 // with any GROUP BY clauses. COUNT is marginally faster but not enough to
5758 // matter
5759 //
5760 // It's considerably faster in my tests to do a separate query which returns
5761 // no data using SQL_CALC_FOUND_ROWS than it is to use SQL_CALC_FOUND_ROWS
5762 // with the full query. Unfortunate but true.
5763 //
5764 // e.g. Fetching all programs for the next 14 days with a LIMIT of 10 - 220ms
5765 // Same query with SQL_CALC_FOUND_ROWS - 1920ms
5766 // Same query but only one column and with SQL_CALC_FOUND_ROWS - 370ms
5767 // Total to fetch both the count and the data = 590ms vs 1920ms
5768 // Therefore two queries is 1.4 seconds faster than one query.
5769 if (start > 0 || limit > 0)
5770 {
5771 QString countStr = querystr
5772 .arg("SQL_CALC_FOUND_ROWS chanid", columns);
5773 query.prepare(countStr);
5774 for (it = bindings.begin(); it != bindings.end(); ++it)
5775 {
5776 if (countStr.contains(it.key()))
5777 query.bindValue(it.key(), it.value());
5778 }
5779
5780 if (!query.exec())
5781 {
5782 MythDB::DBError("LoadFromProgramQuery", query);
5783 return false;
5784 }
5785
5786 if (query.exec("SELECT FOUND_ROWS()") && query.next())
5787 count = query.value(0).toUInt();
5788 }
5789
5790 if (start > 0)
5791 querystr += QString("OFFSET %1 ").arg(start);
5792
5793 querystr = querystr.arg("*", columns);
5794 query.prepare(querystr);
5795 for (it = bindings.begin(); it != bindings.end(); ++it)
5796 {
5797 if (querystr.contains(it.key()))
5798 query.bindValue(it.key(), it.value());
5799 }
5800
5801 if (!query.exec())
5802 {
5803 MythDB::DBError("LoadFromProgramQuery", query);
5804 return false;
5805 }
5806
5807 return true;
5808}
5809
5810bool LoadFromProgram(ProgramList &destination, const QString &where,
5811 const QString &groupBy, const QString &orderBy,
5812 const MSqlBindings &bindings, const ProgramList &schedList)
5813{
5814 uint count = 0;
5815
5816 QString queryStr = "";
5817
5818 if (!where.isEmpty())
5819 queryStr.append(QString("WHERE %1 ").arg(where));
5820
5821 if (!groupBy.isEmpty())
5822 queryStr.append(QString("GROUP BY %1 ").arg(groupBy));
5823
5824 if (!orderBy.isEmpty())
5825 queryStr.append(QString("ORDER BY %1 ").arg(orderBy));
5826
5827 // ------------------------------------------------------------------------
5828
5829 return LoadFromProgram(destination, queryStr, bindings, schedList, 0, 0,
5830 count);
5831}
5832
5834 const QString &sql, const MSqlBindings &bindings,
5835 const ProgramList &schedList, ProgGroupBy::Type groupBy)
5836{
5837 uint count = 0;
5838
5839 QString queryStr = sql;
5840 // ------------------------------------------------------------------------
5841 // FIXME: Remove the following. These all make assumptions about the content
5842 // of the sql passed in, they can end up breaking that query by
5843 // inserting a WHERE clause after a GROUP BY, or a GROUP BY after
5844 // an ORDER BY. These should be part of the sql passed in otherwise
5845 // the caller isn't getting what they asked for and to fix that they
5846 // are forced to include a GROUP BY, ORDER BY or WHERE that they
5847 // do not want
5848 //
5849 // See the new version of this method above. The plan is to convert existing
5850 // users of this method and have them supply all of their own data for the
5851 // queries (no defaults.)
5852
5853 if (!queryStr.contains("WHERE"))
5854 queryStr += " WHERE deleted IS NULL AND visible > 0 ";
5855
5856 if (!queryStr.contains("ORDER BY"))
5857 {
5858 queryStr += " ORDER BY program.starttime, ";
5859 QString chanorder =
5860 gCoreContext->GetSetting("ChannelOrdering", "channum");
5861 if (chanorder != "channum")
5862 queryStr += chanorder + " ";
5863 else // approximation which the DB can handle
5864 queryStr += "atsc_major_chan,atsc_minor_chan,channum,callsign ";
5865 }
5866
5867 // ------------------------------------------------------------------------
5868
5869 return LoadFromProgram(destination, queryStr, bindings, schedList, 0, 0,
5870 count, groupBy);
5871}
5872
5873bool LoadFromProgram( ProgramList &destination,
5874 const QString &sql, const MSqlBindings &bindings,
5875 const ProgramList &schedList,
5876 const uint start, const uint limit, uint &count,
5877 ProgGroupBy::Type groupBy)
5878{
5879 destination.clear();
5880
5881 if (sql.contains(" OFFSET", Qt::CaseInsensitive))
5882 {
5883 LOG(VB_GENERAL, LOG_WARNING, "LoadFromProgram(): SQL contains OFFSET "
5884 "clause, caller should be updated to use "
5885 "start parameter instead");
5886 }
5887
5888 if (sql.contains(" LIMIT", Qt::CaseInsensitive))
5889 {
5890 LOG(VB_GENERAL, LOG_WARNING, "LoadFromProgram(): SQL contains LIMIT "
5891 "clause, caller should be updated to use "
5892 "limit parameter instead");
5893 }
5894
5896 query.setForwardOnly(true);
5897 if (!FromProgramQuery(sql, bindings, query, start, limit, count, groupBy))
5898 return false;
5899
5900 if (count == 0)
5901 count = query.size();
5902
5903 while (query.next())
5904 {
5905 destination.push_back(
5906 new ProgramInfo(
5907 query.value(3).toString(), // title
5908 QString(), // sortTitle
5909 query.value(4).toString(), // subtitle
5910 QString(), // sortSubtitle
5911 query.value(5).toString(), // description
5912 query.value(26).toString(), // syndicatedepisodenumber
5913 query.value(6).toString(), // category
5914
5915 query.value(0).toUInt(), // chanid
5916 query.value(7).toString(), // channum
5917 query.value(8).toString(), // chansign
5918 query.value(9).toString(), // channame
5919 query.value(12).toString(), // chanplaybackfilters
5920
5921 MythDate::as_utc(query.value(1).toDateTime()), // startts
5922 MythDate::as_utc(query.value(2).toDateTime()), // endts
5923 MythDate::as_utc(query.value(1).toDateTime()), // recstartts
5924 MythDate::as_utc(query.value(2).toDateTime()), // recendts
5925
5926 query.value(13).toString(), // seriesid
5927 query.value(14).toString(), // programid
5928 string_to_myth_category_type(query.value(18).toString()), // catType
5929
5930 query.value(16).toFloat(), // stars
5931 query.value(15).toUInt(), // year
5932 query.value(27).toUInt(), // partnumber
5933 query.value(28).toUInt(), // parttotal
5934 query.value(17).toDate(), // originalAirDate
5935 RecStatus::Type(query.value(21).toInt()), // recstatus
5936 query.value(19).toUInt(), // recordid
5937 RecordingType(query.value(20).toInt()), // rectype
5938 query.value(22).toUInt(), // findid
5939
5940 query.value(11).toInt() == COMM_DETECT_COMMFREE, // commfree
5941 query.value(10).toBool(), // repeat
5942 query.value(23).toInt(), // videoprop
5943 query.value(24).toInt(), // audioprop
5944 query.value(25).toInt(), // subtitletypes
5945 query.value(29).toUInt(), // season
5946 query.value(30).toUInt(), // episode
5947 query.value(31).toUInt(), // totalepisodes
5948
5949 schedList));
5950 }
5951
5952 return true;
5953}
5954
5956 const QDateTime& starttime)
5957{
5958 ProgramInfo *progInfo = nullptr;
5959
5960 // Build add'l SQL statement for Program Listing
5961
5962 MSqlBindings bindings;
5963 QString sSQL = "WHERE program.chanid = :ChanId "
5964 "AND program.starttime = :StartTime ";
5965
5966 bindings[":ChanId" ] = chanid;
5967 bindings[":StartTime"] = starttime;
5968
5969 // Get all Pending Scheduled Programs
5970
5971 ProgramList schedList;
5972 bool hasConflicts = false;
5973 LoadFromScheduler(schedList, hasConflicts);
5974
5975 // ----------------------------------------------------------------------
5976
5977 ProgramList progList;
5978
5979 LoadFromProgram( progList, sSQL, bindings, schedList );
5980
5981 if (progList.empty())
5982 return progInfo;
5983
5984 // progList is an Auto-delete deque, the object will be deleted with the
5985 // list, so we need to make a copy
5986 progInfo = new ProgramInfo(*(progList[0]));
5987
5988 return progInfo;
5989}
5990
5992 ProgramList &destination, const QString &sql, const MSqlBindings &bindings)
5993{
5994 uint count = 0;
5995 return LoadFromOldRecorded(destination, sql, bindings, 0, 0, count);
5996}
5997
5998bool LoadFromOldRecorded(ProgramList &destination, const QString &sql,
5999 const MSqlBindings &bindings,
6000 const uint start, const uint limit, uint &count)
6001{
6002 destination.clear();
6003
6005 query.setForwardOnly(true);
6006
6007 QString columns =
6008 "oldrecorded.chanid, starttime, endtime, "
6009 "title, subtitle, description, season, episode, category, seriesid, "
6010 "programid, inetref, channel.channum, channel.callsign, "
6011 "channel.name, findid, rectype, recstatus, recordid, duplicate ";
6012
6013 QString querystr =
6014 "SELECT %1 "
6015 "FROM oldrecorded "
6016 "LEFT JOIN channel ON oldrecorded.chanid = channel.chanid "
6017 "WHERE oldrecorded.future = 0 "
6018 + sql;
6019
6020 bool hasLimit = querystr.contains(" LIMIT ",Qt::CaseInsensitive);
6021
6022 // make sure the most recent rows are retrieved first in case
6023 // there are more than the limit to be set below
6024 if (!hasLimit && !querystr.contains(" ORDER ",Qt::CaseInsensitive))
6025 querystr += " ORDER BY starttime DESC ";
6026
6027 // If a limit arg was given then append the LIMIT, otherwise set a hard
6028 // limit of 20000, which can be overridden by a setting
6029 if (limit > 0)
6030 querystr += QString("LIMIT %1 ").arg(limit);
6031 else if (!hasLimit)
6032 {
6033 // For performance reasons we have to have an upper limit
6034 int nLimit = gCoreContext->GetNumSetting("PrevRecLimit", 20000);
6035 // For sanity sake at least 100
6036 nLimit = std::max(nLimit, 100);
6037 querystr += QString("LIMIT %1 ").arg(nLimit);
6038 }
6039
6040 MSqlBindings::const_iterator it;
6041 // If count is non-zero then also return total number of matching records,
6042 // irrespective of any LIMIT clause
6043 //
6044 // SQL_CALC_FOUND_ROWS is better than COUNT(*), as COUNT(*) won't work
6045 // with any GROUP BY clauses. COUNT is marginally faster but not enough to
6046 // matter
6047 //
6048 // It's considerably faster in my tests to do a separate query which returns
6049 // no data using SQL_CALC_FOUND_ROWS than it is to use SQL_CALC_FOUND_ROWS
6050 // with the full query. Unfortunate but true.
6051 //
6052 // e.g. Fetching all programs for the next 14 days with a LIMIT of 10 - 220ms
6053 // Same query with SQL_CALC_FOUND_ROWS - 1920ms
6054 // Same query but only one column and with SQL_CALC_FOUND_ROWS - 370ms
6055 // Total to fetch both the count and the data = 590ms vs 1920ms
6056 // Therefore two queries is 1.4 seconds faster than one query.
6057 if (count > 0 && (start > 0 || limit > 0))
6058 {
6059 QString countStr = querystr.arg("SQL_CALC_FOUND_ROWS oldrecorded.chanid");
6060 query.prepare(countStr);
6061 for (it = bindings.begin(); it != bindings.end(); ++it)
6062 {
6063 if (countStr.contains(it.key()))
6064 query.bindValue(it.key(), it.value());
6065 }
6066
6067 if (!query.exec())
6068 {
6069 MythDB::DBError("LoadFromOldRecorded", query);
6070 return false;
6071 }
6072
6073 if (query.exec("SELECT FOUND_ROWS()") && query.next())
6074 count = query.value(0).toUInt();
6075 }
6076
6077 if (start > 0)
6078 querystr += QString("OFFSET %1 ").arg(start);
6079
6080 querystr = querystr.arg(columns);
6081 query.prepare(querystr);
6082 for (it = bindings.begin(); it != bindings.end(); ++it)
6083 {
6084 if (querystr.contains(it.key()))
6085 query.bindValue(it.key(), it.value());
6086 }
6087
6088 if (!query.exec())
6089 {
6090 MythDB::DBError("LoadFromOldRecorded", query);
6091 return false;
6092 }
6093
6094 while (query.next())
6095 {
6096 uint chanid = query.value(0).toUInt();
6097 QString channum = QString("#%1").arg(chanid);
6098 QString chansign = channum;
6099 QString channame = channum;
6100 if (!query.value(12).toString().isEmpty())
6101 {
6102 channum = query.value(12).toString();
6103 chansign = query.value(13).toString();
6104 channame = query.value(14).toString();
6105 }
6106
6107 destination.push_back(new ProgramInfo(
6108 query.value(3).toString(),
6109 QString(),
6110 query.value(4).toString(),
6111 QString(),
6112 query.value(5).toString(),
6113 query.value(6).toUInt(),
6114 query.value(7).toUInt(),
6115 query.value(8).toString(),
6116
6117 chanid, channum, chansign, channame,
6118
6119 query.value(9).toString(), query.value(10).toString(),
6120 query.value(11).toString(),
6121
6122 MythDate::as_utc(query.value(1).toDateTime()),
6123 MythDate::as_utc(query.value(2).toDateTime()),
6124 MythDate::as_utc(query.value(1).toDateTime()),
6125 MythDate::as_utc(query.value(2).toDateTime()),
6126
6127 RecStatus::Type(query.value(17).toInt()),
6128 query.value(18).toUInt(),
6129 RecordingType(query.value(16).toInt()),
6130 query.value(15).toUInt(),
6131
6132 query.value(19).toBool()));
6133 }
6134
6135 return true;
6136}
6137
6157 ProgramList &destination,
6158 bool possiblyInProgressRecordingsOnly,
6159 const QMap<QString,uint32_t> &inUseMap,
6160 const QMap<QString,bool> &isJobRunning,
6161 const QMap<QString, ProgramInfo*> &recMap,
6162 int sort,
6163 const QString &sortBy,
6164 bool ignoreLiveTV,
6165 bool ignoreDeleted)
6166{
6167 destination.clear();
6168
6169 QDateTime rectime = MythDate::current().addSecs(
6170 -gCoreContext->GetNumSetting("RecordOverTime"));
6171
6172 // ----------------------------------------------------------------------
6173
6174 QString thequery = ProgramInfo::kFromRecordedQuery;
6175 if (possiblyInProgressRecordingsOnly || ignoreLiveTV || ignoreDeleted)
6176 {
6177 thequery += "WHERE ";
6178 if (possiblyInProgressRecordingsOnly)
6179 {
6180 thequery += "(r.endtime >= NOW() AND r.starttime <= NOW()) ";
6181 }
6182 if (ignoreLiveTV)
6183 {
6184 thequery += QString("%1 r.recgroup != 'LiveTV' ")
6185 .arg(possiblyInProgressRecordingsOnly ? "AND" : "");
6186 }
6187 if (ignoreDeleted)
6188 {
6189 thequery += QString("%1 r.recgroup != 'Deleted' ")
6190 .arg((possiblyInProgressRecordingsOnly || ignoreLiveTV)
6191 ? "AND" : "");
6192 }
6193 }
6194
6195 if (sortBy.isEmpty())
6196 {
6197 if (sort)
6198 thequery += "ORDER BY r.starttime ";
6199 if (sort < 0)
6200 thequery += "DESC ";
6201 }
6202 else
6203 {
6204 QStringList sortByFields;
6205 sortByFields << "starttime" << "title" << "subtitle" << "season" << "episode" << "category"
6206 << "watched" << "stars" << "originalairdate" << "recgroup" << "storagegroup"
6207 << "channum" << "callsign" << "name" << "filesize" << "duration";
6208 // sanity check the fields are one of the above fields
6209 QString sSortBy;
6210 QStringList fields = sortBy.split(",");
6211 for (const QString& oneField : std::as_const(fields))
6212 {
6213 bool ascending = true;
6214 QString field = oneField.simplified().toLower();
6215
6216 if (field.endsWith("desc"))
6217 {
6218 ascending = false;
6219 field = field.remove("desc");
6220 }
6221
6222 if (field.endsWith("asc"))
6223 {
6224 ascending = true;
6225 field = field.remove("asc");
6226 }
6227
6228 field = field.simplified();
6229
6230 if (field == "channelname")
6231 field = "name";
6232
6233 if (sortByFields.contains(field))
6234 {
6235 QString table;
6236 if (field == "channum" || field == "callsign" || field == "name")
6237 table = "c.";
6238 else
6239 table = "r.";
6240
6241 if (field == "channum")
6242 {
6243 // this is to sort numerically rather than alphabetically
6244 field = "channum*1000-ifnull(regexp_substr(c.channum,'-.*'),0)";
6245 }
6246
6247 else if (field == "duration")
6248 {
6249 field = "timestampdiff(second,r.starttime,r.endtime)";
6250 table = "";
6251 }
6252
6253 else if (field == "season")
6254 {
6255 field = "if(r.season,r.season,p.season)";
6256 table = "";
6257 }
6258
6259 else if (field == "episode")
6260 {
6261 field = "if(r.episode,r.episode,p.episode)";
6262 table = "";
6263 }
6264
6265 else if (field == "title")
6266 {
6267 std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
6268 QString prefixes = sh->getPrefixes();
6269 field = "REGEXP_REPLACE(r.title,'" + prefixes + "','')";
6270 table = "";
6271 }
6272
6273 else if (field == "subtitle")
6274 {
6275 std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
6276 QString prefixes = sh->getPrefixes();
6277 field = "REGEXP_REPLACE(r.subtitle,'" + prefixes + "','')";
6278 table = "";
6279 }
6280
6281 else if (field == "originalairdate")
6282 {
6283 field = "IF(r.originalairdate != '0000-00-00', r.originalairdate, STR_TO_DATE(CONCAT(p.airdate,',01,01'),'%Y,%m,%d'))";
6284 table = "";
6285 }
6286
6287 if (sSortBy.isEmpty())
6288 sSortBy = QString("%1%2 %3").arg(table, field, ascending ? "ASC" : "DESC");
6289 else
6290 sSortBy += QString(",%1%2 %3").arg(table, field, ascending ? "ASC" : "DESC");
6291 }
6292 else
6293 {
6294 LOG(VB_GENERAL, LOG_WARNING, QString("ProgramInfo::LoadFromRecorded() got an unknown sort field '%1' - ignoring").arg(oneField));
6295 }
6296 }
6297
6298 thequery += "ORDER BY " + sSortBy;
6299 }
6300
6302 query.prepare(thequery);
6303
6304 if (!query.exec())
6305 {
6306 MythDB::DBError("ProgramList::FromRecorded", query);
6307 return true;
6308 }
6309
6310 while (query.next())
6311 {
6312 const uint chanid = query.value(6).toUInt();
6313 QString channum = QString("#%1").arg(chanid);
6314 QString chansign = channum;
6315 QString channame = channum;
6316 QString chanfilt;
6317 if (!query.value(7).toString().isEmpty())
6318 {
6319 channum = query.value(7).toString();
6320 chansign = query.value(8).toString();
6321 channame = query.value(9).toString();
6322 chanfilt = query.value(10).toString();
6323 }
6324
6325 QString hostname = query.value(15).toString();
6326 if (hostname.isEmpty())
6328
6330 QDateTime recstartts = MythDate::as_utc(query.value(24).toDateTime());
6331
6332 QString key = ProgramInfo::MakeUniqueKey(chanid, recstartts);
6333 if (MythDate::as_utc(query.value(25).toDateTime()) > rectime &&
6334 recMap.contains(key))
6335 {
6336 recstatus = RecStatus::Recording;
6337 }
6338
6339 bool save_not_commflagged = false;
6340 uint flags = 0;
6341
6342 set_flag(flags, FL_CHANCOMMFREE,
6343 query.value(30).toInt() == COMM_DETECT_COMMFREE);
6344 set_flag(flags, FL_COMMFLAG,
6345 query.value(31).toInt() == COMM_FLAG_DONE);
6346 set_flag(flags, FL_COMMPROCESSING ,
6347 query.value(31).toInt() == COMM_FLAG_PROCESSING);
6348 set_flag(flags, FL_REPEAT, query.value(32).toBool());
6349 set_flag(flags, FL_TRANSCODED,
6350 query.value(34).toInt() == TRANSCODING_COMPLETE);
6351 set_flag(flags, FL_DELETEPENDING, query.value(35).toBool());
6352 set_flag(flags, FL_PRESERVED, query.value(36).toBool());
6353 set_flag(flags, FL_CUTLIST, query.value(37).toBool());
6354 set_flag(flags, FL_AUTOEXP, query.value(38).toBool());
6355 set_flag(flags, FL_REALLYEDITING, query.value(39).toBool());
6356 set_flag(flags, FL_BOOKMARK, query.value(40).toBool());
6357 set_flag(flags, FL_WATCHED, query.value(41).toBool());
6358 set_flag(flags, FL_LASTPLAYPOS, query.value(58).toBool());
6359
6360 if (inUseMap.contains(key))
6361 flags |= inUseMap[key];
6362
6363 if (((flags & FL_COMMPROCESSING) != 0U) &&
6364 (!isJobRunning.contains(key)))
6365 {
6366 flags &= ~FL_COMMPROCESSING;
6367 save_not_commflagged = true;
6368 }
6369
6370 set_flag(flags, FL_EDITING,
6371 ((flags & FL_REALLYEDITING) != 0U) ||
6372 ((flags & COMM_FLAG_PROCESSING) != 0U));
6373
6374 // User/metadata defined season from recorded
6375 uint season = query.value(3).toUInt();
6376 if (season == 0)
6377 season = query.value(51).toUInt(); // Guide defined season from recordedprogram
6378
6379 // User/metadata defined episode from recorded
6380 uint episode = query.value(4).toUInt();
6381 if (episode == 0)
6382 episode = query.value(52).toUInt(); // Guide defined episode from recordedprogram
6383
6384 // Guide defined total episodes from recordedprogram
6385 uint totalepisodes = query.value(53).toUInt();
6386
6387 destination.push_back(
6388 new ProgramInfo(
6389 query.value(55).toUInt(),
6390 query.value(0).toString(),
6391 QString(),
6392 query.value(1).toString(),
6393 QString(),
6394 query.value(2).toString(),
6395 season,
6396 episode,
6397 totalepisodes,
6398 query.value(48).toString(), // syndicatedepisode
6399 query.value(5).toString(), // category
6400
6401 chanid, channum, chansign, channame, chanfilt,
6402
6403 query.value(11).toString(), query.value(12).toString(),
6404
6405 query.value(14).toString(), // pathname
6406
6407 hostname, query.value(13).toString(),
6408
6409 query.value(17).toString(), query.value(18).toString(),
6410 query.value(19).toString(), // inetref
6411 string_to_myth_category_type(query.value(54).toString()), // category_type
6412
6413 query.value(16).toInt(), // recpriority
6414
6415 query.value(20).toULongLong(), // filesize
6416
6417 MythDate::as_utc(query.value(21).toDateTime()), //startts
6418 MythDate::as_utc(query.value(22).toDateTime()), // endts
6419 MythDate::as_utc(query.value(24).toDateTime()), // recstartts
6420 MythDate::as_utc(query.value(25).toDateTime()), // recendts
6421
6422 query.value(23).toFloat(), // stars
6423
6424 query.value(26).toUInt(), // year
6425 query.value(49).toUInt(), // partnumber
6426 query.value(50).toUInt(), // parttotal
6427 query.value(27).toDate(), // originalAirdate
6428 MythDate::as_utc(query.value(28).toDateTime()), // lastmodified
6429
6430 recstatus,
6431
6432 query.value(29).toUInt(), // recordid
6433
6434 RecordingDupInType(query.value(46).toInt()),
6435 RecordingDupMethodType(query.value(47).toInt()),
6436
6437 query.value(45).toUInt(), // findid
6438
6439 flags,
6440 query.value(42).toUInt(), // audioproperties
6441 query.value(43).toUInt(), // videoproperties
6442 query.value(44).toUInt(), // subtitleType
6443 query.value(56).toString(), // inputname
6444 MythDate::as_utc(query.value(57)
6445 .toDateTime()))); // bookmarkupdate
6446
6447 if (save_not_commflagged)
6449 }
6450
6451 return true;
6452}
6453
6454bool GetNextRecordingList(QDateTime &nextRecordingStart,
6455 bool *hasConflicts,
6456 std::vector<ProgramInfo> *list)
6457{
6458 nextRecordingStart = QDateTime();
6459
6460 bool dummy = false;
6461 bool *conflicts = (hasConflicts) ? hasConflicts : &dummy;
6462
6463 ProgramList progList;
6464 if (!LoadFromScheduler(progList, *conflicts))
6465 return false;
6466
6467 // find the earliest scheduled recording
6468 for (auto *prog : progList)
6469 {
6470 if ((prog->GetRecordingStatus() == RecStatus::WillRecord ||
6471 prog->GetRecordingStatus() == RecStatus::Pending) &&
6472 (nextRecordingStart.isNull() ||
6473 nextRecordingStart > prog->GetRecordingStartTime()))
6474 {
6475 nextRecordingStart = prog->GetRecordingStartTime();
6476 }
6477 }
6478
6479 if (!list)
6480 return true;
6481
6482 // save the details of the earliest recording(s)
6483 for (auto & prog : progList)
6484 {
6485 if ((prog->GetRecordingStatus() == RecStatus::WillRecord ||
6486 prog->GetRecordingStatus() == RecStatus::Pending) &&
6487 (prog->GetRecordingStartTime() == nextRecordingStart))
6488 {
6489 list->push_back(*prog);
6490 }
6491 }
6492
6493 return true;
6494}
6495
6497{
6498}
6499
6501{
6502 delete lock;
6503}
6504
6505// ---------------------------------------------------------------------------
6506// DEPRECATED CODE FOLLOWS
6507// ---------------------------------------------------------------------------
6508
6510{
6511 //LOG(VB_GENERAL, LOG_DEBUG, "FIXME: ProgramInfo::SetFilesize() called instead of RecordingInfo::SetFilesize()");
6512 m_fileSize = sz;
6513}
6514
6515
6519void ProgramInfo::SaveFilesize(uint64_t fsize)
6520{
6521 //LOG(VB_GENERAL, LOG_DEBUG, "FIXME: ProgramInfo::SaveFilesize() called instead of RecordingInfo::SaveFilesize()");
6522 SetFilesize(fsize);
6523
6525 query.prepare(
6526 "UPDATE recorded "
6527 "SET filesize = :FILESIZE "
6528 "WHERE chanid = :CHANID AND "
6529 " starttime = :STARTTIME");
6530 query.bindValue(":FILESIZE", (quint64)fsize);
6531 query.bindValue(":CHANID", m_chanId);
6532 query.bindValue(":STARTTIME", m_recStartTs);
6533
6534 if (!query.exec())
6535 MythDB::DBError("File size update", query);
6536
6538}
6539
6540//uint64_t ProgramInfo::GetFilesize(void) const
6541//{
6542 //LOG(VB_GENERAL, LOG_DEBUG, "FIXME: ProgramInfo::GetFilesize() called instead of RecordingInfo::GetFilesize()");
6543// return m_fileSize;
6544//}
6545
6546// Restore the original query. When a recording finishes, a
6547// check is made on the filesize and it's 0 at times. If
6548// less than 1000, commflag isn't queued. See #12290.
6549
6550uint64_t ProgramInfo::GetFilesize(void) const
6551{
6552
6553 if (m_fileSize)
6554 return m_fileSize;
6555
6556 // Always query in the 0 case because we can't
6557 // tell if the filesize was 'lost'. Other than
6558 // the 0 case, tests showed the DB and RI sizes
6559 // were equal.
6560
6561 uint64_t db_filesize = 0;
6562
6564
6565 query.prepare(
6566 "SELECT filesize "
6567 "FROM recorded "
6568 "WHERE chanid = :CHANID AND "
6569 " starttime = :STARTTIME");
6570 query.bindValue(":CHANID", m_chanId);
6571 query.bindValue(":STARTTIME", m_recStartTs);
6572 if (query.exec() && query.next())
6573 db_filesize = query.value(0).toULongLong();
6574
6575 if (db_filesize)
6576 LOG(VB_RECORD, LOG_INFO, LOC + QString("RI Filesize=0, DB Filesize=%1")
6577 .arg(db_filesize));
6578
6579 return db_filesize;
6580}
6581
6583{
6584 m_recordedPercent = -1;
6586 return;
6587
6588 QDateTime startTime = m_recStartTs;
6589 QDateTime now = MythDate::current();
6590 if (now < startTime)
6591 return;
6592
6593 QDateTime endTime = m_recEndTs;
6594 int current = startTime.secsTo(now);
6595 int duration = startTime.secsTo(endTime);
6596 if (duration < 1)
6597 return;
6598
6599 m_recordedPercent = std::clamp(current * 100 / duration, 0, 100);
6600 LOG(VB_GUI, LOG_DEBUG, QString("%1 recorded percent %2/%3 = %4%")
6601 .arg(m_title).arg(current).arg(duration).arg(m_recordedPercent));
6602}
6603
6605{
6606 if (pos == 0)
6607 {
6608 m_watchedPercent = -1;
6609 return;
6610 }
6611
6612 uint64_t total = 0;
6613 if (IsVideo())
6614 {
6615 total = std::max((int64_t)0, QueryTotalFrames());
6616 }
6617 else if (IsRecording())
6618 {
6619 switch (m_recStatus)
6620 {
6622 total = std::max((int64_t)0, QueryTotalFrames());
6623 break;
6625 {
6626 // Compute expected total frames based on frame rate.
6627 int64_t rate1000 = QueryAverageFrameRate();
6628 int64_t duration = m_recStartTs.secsTo(m_recEndTs);
6629 total = rate1000 * duration / 1000;
6630 }
6631 break;
6632 default:
6633 break;
6634 }
6635 }
6636
6637 if (total == 0)
6638 {
6639 if (IsVideo())
6640 {
6641 LOG(VB_GUI, LOG_DEBUG,
6642 QString("%1 %2 no frame count. Please rebuild seek table for this video.")
6643 .arg(m_pathname, m_title));
6644 }
6645 else if (IsRecording())
6646 {
6647 LOG(VB_GUI, LOG_DEBUG,
6648 QString("%1 %2 no frame count. Please rebuild seek table for this recording.")
6649 .arg(m_recordedId).arg(m_title));
6650 }
6651 m_watchedPercent = 0;
6652 return;
6653 }
6654
6655 m_watchedPercent = std::clamp(100 * pos / total, (uint64_t)0, (uint64_t)100);
6656 LOG(VB_GUI, LOG_DEBUG, QString("%1 %2 watched percent %3/%4 = %5%")
6657 .arg(m_recordedId).arg(m_title)
6658 .arg(pos).arg(total).arg(m_watchedPercent));
6659}
6660
6662{
6665}
6666
6668{
6669 switch (groupBy)
6670 {
6671 case ProgGroupBy::None:
6672 return tr("None");
6674 return tr("Channel Number");
6676 return tr("CallSign");
6678 return tr("ProgramId");
6679 default:
6680 return tr("Unknown");
6681 }
6682}
6683
6684bool RemoteCheckFile(ProgramInfo *pginfo, bool checkSlaves)
6685{
6686 QStringList strlist("QUERY_CHECKFILE");
6687 strlist << QString::number((int)checkSlaves);
6688 pginfo->ToStringList(strlist);
6689
6690 if (!gCoreContext->SendReceiveStringList(strlist) ||
6691 (strlist.size() < 2) || !strlist[0].toInt())
6692 return false;
6693
6694 // Only modify the pathname if the recording file is available locally on
6695 // this host
6696 QString localpath = strlist[1];
6697 QFile checkFile(localpath);
6698 if (checkFile.exists())
6699 pginfo->SetPathname(localpath);
6700
6701 return true;
6702}
6703
6704/* 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:1747
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 uint32_t * tmp
Definition: goom_core.cpp:28
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