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