MythTV master
mythcommflag.cpp
Go to the documentation of this file.
1
2#if defined ANDROID && __ANDROID_API__ < 24
3// ftello and fseeko do not exist in android before api level 24
4#define ftello ftell
5#define fseeko fseek
6#endif
7
8// POSIX headers
9#include <unistd.h>
10
11// C++ headers
12#include <cmath>
13#include <cstdio>
14#include <cstdlib>
15#include <fstream>
16#include <iostream>
17#include <string>
18
19// Qt headers
20#include <QCoreApplication>
21#include <QDir>
22#include <QEvent>
23#include <QString>
24#include <QtGlobal>
25
26// MythTV headers
27#include "libmyth/mythcontext.h"
32#include "libmythbase/mythdb.h"
36#include "libmythbase/mythversion.h"
39#include "libmythtv/jobqueue.h"
44
46
47// Commercial Flagging headers
48#include "CommDetectorBase.h"
49#include "CommDetectorFactory.h"
50#include "CustomEventRelayer.h"
51#include "SlotRelayer.h"
52
53#define LOC QString("MythCommFlag: ")
54#define LOC_WARN QString("MythCommFlag, Warning: ")
55#define LOC_ERR QString("MythCommFlag, Error: ")
56
57// File scope variables
58namespace {
59
60bool progress = true;
61bool force = false;
62
64
65bool watchingRecording = false;
69int recorderNum = -1;
70
71int jobID = -1;
72int lastCmd = -1;
73
74} // namespace
75
76static QMap<QString,SkipType> *init_skip_types();
77QMap<QString,SkipType> *skipTypes = init_skip_types();
78
79static QMap<QString,SkipType> *init_skip_types(void)
80{
81 auto *tmp = new QMap<QString,SkipType>;
82 (*tmp)["commfree"] = COMM_DETECT_COMMFREE;
83 (*tmp)["uninit"] = COMM_DETECT_UNINIT;
84 (*tmp)["off"] = COMM_DETECT_OFF;
85 (*tmp)["blank"] = COMM_DETECT_BLANKS;
86 (*tmp)["blanks"] = COMM_DETECT_BLANKS;
87 (*tmp)["scene"] = COMM_DETECT_SCENE;
88 (*tmp)["blankscene"] = COMM_DETECT_BLANK_SCENE;
89 (*tmp)["blank_scene"] = COMM_DETECT_BLANK_SCENE;
90 (*tmp)["logo"] = COMM_DETECT_LOGO;
91 (*tmp)["all"] = COMM_DETECT_ALL;
92 (*tmp)["d2"] = COMM_DETECT_2;
93 (*tmp)["d2_logo"] = COMM_DETECT_2_LOGO;
94 (*tmp)["d2_blank"] = COMM_DETECT_2_BLANK;
95 (*tmp)["d2_scene"] = COMM_DETECT_2_SCENE;
96 (*tmp)["d2_all"] = COMM_DETECT_2_ALL;
97 return tmp;
98}
99
100enum OutputMethod : std::uint8_t
101{
104};
106
107static QMap<QString,OutputMethod> *init_output_types();
108QMap<QString,OutputMethod> *outputTypes = init_output_types();
109
110static QMap<QString,OutputMethod> *init_output_types(void)
111{
112 auto *tmp = new QMap<QString,OutputMethod>;
113 (*tmp)["essentials"] = kOutputMethodEssentials;
114 (*tmp)["full"] = kOutputMethodFull;
115 return tmp;
116}
117
118static QString get_filename(ProgramInfo *program_info)
119{
120 QString filename = program_info->GetPathname();
122 filename = program_info->GetPlaybackURL(true);
123 return filename;
124}
125
126static int QueueCommFlagJob(uint chanid, const QDateTime& starttime, bool rebuild)
127{
128 QString startstring = MythDate::toString(starttime, MythDate::kFilename);
129 const ProgramInfo pginfo(chanid, starttime);
130
131 if (!pginfo.GetChanID())
132 {
133 if (progress)
134 {
135 QString tmp = QString(
136 "Unable to find program info for chanid %1 @ %2")
137 .arg(chanid).arg(startstring);
138 std::cerr << tmp.toLocal8Bit().constData() << std::endl;
139 }
141 }
142
143 if (cmdline.toBool("dryrun"))
144 {
145 QString tmp = QString("Job have been queued for chanid %1 @ %2")
146 .arg(chanid).arg(startstring);
147 std::cerr << tmp.toLocal8Bit().constData() << std::endl;
148 return GENERIC_EXIT_OK;
149 }
150
151 bool result = JobQueue::QueueJob(JOB_COMMFLAG,
152 pginfo.GetChanID(), pginfo.GetRecordingStartTime(), "", "", "",
153 rebuild ? JOB_REBUILD : 0, JOB_QUEUED, QDateTime());
154
155 if (result)
156 {
157 if (progress)
158 {
159 QString tmp = QString("Job Queued for chanid %1 @ %2")
160 .arg(chanid).arg(startstring);
161 std::cerr << tmp.toLocal8Bit().constData() << std::endl;
162 }
163 return GENERIC_EXIT_OK;
164 }
165
166 if (progress)
167 {
168 QString tmp = QString("Error queueing job for chanid %1 @ %2")
169 .arg(chanid).arg(startstring);
170 std::cerr << tmp.toLocal8Bit().constData() << std::endl;
171 }
173}
174
176 std::ostream &output, const frm_dir_map_t &commercialBreakList)
177{
178 if (progress)
179 output << "----------------------------" << std::endl;
180
181 if (commercialBreakList.empty())
182 {
183 if (progress)
184 output << "No breaks" << std::endl;
185 }
186 else
187 {
188 frm_dir_map_t::const_iterator it = commercialBreakList.begin();
189 for (; it != commercialBreakList.end(); ++it)
190 {
191 output << "framenum: " << it.key() << "\tmarktype: " << *it
192 << std::endl;
193 }
194 }
195
196 if (progress)
197 output << "----------------------------" << std::endl;
198}
199
201 const ProgramInfo *program_info,
202 const frm_dir_map_t &commBreakList,
203 uint64_t frame_count,
204 const CommDetectorBase *commDetect,
205 const QString &output_filename)
206{
207 if (output_filename.isEmpty())
208 return;
209
210 std::ostream *out = &std::cout;
211 if (output_filename != "-")
212 {
213 QByteArray tmp = output_filename.toLocal8Bit();
214 out = new std::fstream(tmp.constData(), std::ios::app | std::ios::out );
215 }
216
217 if (progress)
218 {
219 QString tmp = "";
220 if (program_info->GetChanID())
221 {
222 tmp = QString("commercialBreakListFor: %1 on %2 @ %3")
223 .arg(program_info->GetTitle())
224 .arg(program_info->GetChanID())
225 .arg(program_info->GetRecordingStartTime(MythDate::ISODate));
226 }
227 else
228 {
229 tmp = QString("commercialBreakListFor: %1")
230 .arg(program_info->GetPathname());
231 }
232
233 const QByteArray tmp2 = tmp.toLocal8Bit();
234 *out << tmp2.constData() << std::endl;
235
236 if (frame_count)
237 *out << "totalframecount: " << frame_count << std::endl;
238 }
239
240 if (commDetect)
241 commDetect->PrintFullMap(*out, &commBreakList, progress);
242 else
243 streamOutCommercialBreakList(*out, commBreakList);
244
245 if (out != &std::cout)
246 delete out;
247}
248
250{
251 //this is connected to the commdetectors breathe signal so we get a chance
252 //while its busy to see if the user already told us to stop.
253 qApp->processEvents();
254
255 if (jobID != -1)
256 {
257 int curCmd = JobQueue::GetJobCmd(jobID);
258 if (curCmd == lastCmd)
259 return;
260
261 switch (curCmd)
262 {
263 case JOB_STOP:
264 {
266 break;
267 }
268 case JOB_PAUSE:
269 {
271 QCoreApplication::translate("(mythcommflag)",
272 "Paused", "Job status"));
274 break;
275 }
276 case JOB_RESUME:
277 {
278 JobQueue::ChangeJobStatus(jobID, JOB_RUNNING,
279 QCoreApplication::translate("(mythcommflag)",
280 "Running", "Job status"));
282 break;
283 }
284 }
285 }
286}
287
288static void commDetectorStatusUpdate(const QString& status)
289{
290 if (jobID != -1)
291 {
292 JobQueue::ChangeJobStatus(jobID, JOB_RUNNING, status);
294 }
295}
296
298{
299 frm_dir_map_t newCommercialMap;
300 commDetector->GetCommercialBreakList(newCommercialMap);
301
302 QString message = "COMMFLAG_UPDATE ";
304
305 for (auto it = newCommercialMap.begin();
306 it != newCommercialMap.end(); ++it)
307 {
308 if (it != newCommercialMap.begin())
309 message += ",";
310 else
311 message += " ";
312 message += QString("%1:%2").arg(it.key())
313 .arg(*it);
314 }
315
316 LOG(VB_COMMFLAG, LOG_INFO,
317 QString("mythcommflag sending update: %1").arg(message));
318
319 gCoreContext->SendMessage(message);
320}
321
322static void incomingCustomEvent(QEvent* e)
323{
324 if (e->type() == MythEvent::kMythEventMessage)
325 {
326 auto *me = dynamic_cast<MythEvent *>(e);
327 if (me == nullptr)
328 return;
329
330 QString message = me->Message();
331
332 message = message.simplified();
333 QStringList tokens = message.split(" ", Qt::SkipEmptyParts);
334
335 LOG(VB_COMMFLAG, LOG_INFO,
336 QString("mythcommflag: Received Event: '%1'") .arg(message));
337
338 if ((watchingRecording) && (tokens.size() >= 3) &&
339 (tokens[0] == "DONE_RECORDING"))
340 {
341 int cardnum = tokens[1].toInt();
342 int filelen = tokens[2].toInt();
343
344 message = QString("mythcommflag: Received a "
345 "DONE_RECORDING event for card %1. ")
346 .arg(cardnum);
347
348 if (recorderNum != -1 && cardnum == recorderNum)
349 {
351 watchingRecording = false;
352 message += "Informed CommDetector that recording has finished.";
353 LOG(VB_COMMFLAG, LOG_INFO, message);
354 }
355 }
356
357 if ((tokens.size() >= 2) && (tokens[0] == "COMMFLAG_REQUEST"))
358 {
359 uint chanid = 0;
360 QDateTime recstartts;
361 ProgramInfo::ExtractKey(tokens[1], chanid, recstartts);
362
363 message = QString("mythcommflag: Received a "
364 "COMMFLAG_REQUEST event for chanid %1 @ %2. ")
365 .arg(chanid).arg(recstartts.toString(Qt::ISODate));
366
367 if ((global_program_info->GetChanID() == chanid) &&
369 {
371 message += "Requested CommDetector to generate new break list.";
372 LOG(VB_COMMFLAG, LOG_INFO, message);
373 }
374 }
375 }
376}
377
379 ProgramInfo *program_info,
380 bool showPercentage, bool fullSpeed, int jobid,
381 MythCommFlagPlayer* cfp, SkipType commDetectMethod,
382 const QString &outputfilename, bool useDB)
383{
385 commDetectMethod, showPercentage,
386 fullSpeed, cfp,
387 program_info->GetChanID(),
388 program_info->GetScheduledStartTime(),
389 program_info->GetScheduledEndTime(),
390 program_info->GetRecordingStartTime(),
391 program_info->GetRecordingEndTime(), useDB);
392
393 if (jobid > 0)
394 LOG(VB_COMMFLAG, LOG_INFO,
395 QString("mythcommflag processing JobID %1").arg(jobid));
396
397 if (useDB)
399
401 auto *a = new SlotRelayer(commDetectorBreathe);
404 QObject::connect(commDetector, &CommDetectorBase::breathe,
405 a, qOverload<>(&SlotRelayer::relay));
407 b, qOverload<const QString&>(&SlotRelayer::relay));
409 c, qOverload<>(&SlotRelayer::relay));
410
411 if (useDB)
412 {
413 LOG(VB_COMMFLAG, LOG_INFO,
414 "mythcommflag sending COMMFLAG_START notification");
415 QString message = "COMMFLAG_START ";
416 message += program_info->MakeUniqueKey();
417 gCoreContext->SendMessage(message);
418 }
419
420 bool result = commDetector->go();
421 int comms_found = 0;
422
423 if (result)
424 {
425 cfp->SaveTotalDuration();
426
427 frm_dir_map_t commBreakList;
428 commDetector->GetCommercialBreakList(commBreakList);
429 comms_found = commBreakList.size() / 2;
430
431 if (useDB)
432 {
433 program_info->SaveMarkupFlag(MARK_UPDATED_CUT);
434 program_info->SaveCommBreakList(commBreakList);
435 program_info->SaveCommFlagged(COMM_FLAG_DONE);
436 }
437
439 program_info, commBreakList, cfp->GetTotalFrameCount(),
441 outputfilename);
442 }
443 else
444 {
445 if (useDB)
447 }
448
450 commDetector = nullptr;
451 sleep(1);
452 tmp->deleteLater();
453
454 cer->deleteLater();
455 c->deleteLater();
456 b->deleteLater();
457 a->deleteLater();
458
459 return comms_found;
460}
461
462static qint64 GetFileSize(ProgramInfo *program_info)
463{
464 QString filename = get_filename(program_info);
465 qint64 size = -1;
466
467 if (filename.startsWith("myth://"))
468 {
469 RemoteFile remotefile(filename, false, false, 0s);
470 size = remotefile.GetFileSize();
471 }
472 else
473 {
474 QFile file(filename);
475 if (file.exists())
476 {
477 size = file.size();
478 }
479 }
480
481 return size;
482}
483
484static bool DoesFileExist(ProgramInfo *program_info)
485{
486 QString filename = get_filename(program_info);
487 qint64 size = GetFileSize(program_info);
488
489 if (size < 0)
490 {
491 LOG(VB_GENERAL, LOG_ERR, QString("Couldn't find file %1, aborting.")
492 .arg(filename));
493 return false;
494 }
495
496 if (size == 0)
497 {
498 LOG(VB_GENERAL, LOG_ERR, QString("File %1 is zero-byte, aborting.")
499 .arg(filename));
500 return false;
501 }
502
503 return true;
504}
505
506static void UpdateFileSize(ProgramInfo *program_info)
507{
508 qint64 size = GetFileSize(program_info);
509
510 if (size != (qint64)program_info->GetFilesize())
511 program_info->SaveFilesize(size);
512}
513
514static bool IsMarked(uint chanid, const QDateTime& starttime)
515{
516 MSqlQuery mark_query(MSqlQuery::InitCon());
517 mark_query.prepare("SELECT commflagged, count(rm.type) "
518 "FROM recorded r "
519 "LEFT JOIN recordedmarkup rm ON "
520 "( r.chanid = rm.chanid AND "
521 "r.starttime = rm.starttime AND "
522 "type in (:MARK_START,:MARK_END)) "
523 "WHERE r.chanid = :CHANID AND "
524 "r.starttime = :STARTTIME "
525 "GROUP BY COMMFLAGGED;");
526 mark_query.bindValue(":MARK_START", MARK_COMM_START);
527 mark_query.bindValue(":MARK_END", MARK_COMM_END);
528 mark_query.bindValue(":CHANID", chanid);
529 mark_query.bindValue(":STARTTIME", starttime);
530
531 if (mark_query.exec() && mark_query.isActive() &&
532 mark_query.size() > 0)
533 {
534 if (mark_query.next())
535 {
536 int flagStatus = mark_query.value(0).toInt();
537 int marksFound = mark_query.value(1).toInt();
538
539 QString flagStatusStr = "UNKNOWN";
540 switch (flagStatus) {
542 flagStatusStr = "Not Flagged";
543 break;
544 case COMM_FLAG_DONE:
545 flagStatusStr = QString("Flagged with %1 breaks")
546 .arg(marksFound / 2);
547 break;
549 flagStatusStr = "Flagging";
550 break;
552 flagStatusStr = "Commercial Free";
553 break;
554 }
555
556 LOG(VB_COMMFLAG, LOG_INFO,
557 QString("Status for chanid %1 @ %2 is '%3'")
558 .arg(QString::number(chanid),
559 starttime.toString(Qt::ISODate),
560 flagStatusStr));
561
562 if ((flagStatus == COMM_FLAG_NOT_FLAGGED) && (marksFound == 0))
563 return false;
564 }
565 }
566 return true;
567}
568
569static int FlagCommercials(ProgramInfo *program_info, int jobid,
570 const QString &outputfilename, bool useDB, bool fullSpeed)
571{
572 global_program_info = program_info;
573
574 int breaksFound = 0;
575
576 // configure commercial detection method
577 SkipType commDetectMethod = (SkipType)gCoreContext->GetNumSetting(
578 "CommercialSkipMethod", COMM_DETECT_ALL);
579
580 if (cmdline.toBool("commmethod"))
581 {
582 // pull commercial detection method from command line
583 QString commmethod = cmdline.toString("commmethod");
584
585 // assume definition as integer value
586 bool ok = true;
587 commDetectMethod = (SkipType) commmethod.toInt(&ok);
588 if (!ok)
589 {
590 // not an integer, attempt comma separated list
591 commDetectMethod = COMM_DETECT_UNINIT;
592
593 QStringList list = commmethod.split(",", Qt::SkipEmptyParts);
594 for (const auto & it : std::as_const(list))
595 {
596 QString val = it.toLower();
597 if (val == "off")
598 {
599 commDetectMethod = COMM_DETECT_OFF;
600 break;
601 }
602
603 if (!skipTypes->contains(val))
604 {
605 std::cerr << "Failed to decode --method option '"
606 << val.toLatin1().constData()
607 << "'" << std::endl;
609 }
610
611 if (commDetectMethod == COMM_DETECT_UNINIT) {
612 commDetectMethod = skipTypes->value(val);
613 } else {
614 commDetectMethod = (SkipType) ((int)commDetectMethod
615 | (int)skipTypes->value(val));
616 }
617 }
618
619 }
620 if (commDetectMethod == COMM_DETECT_UNINIT)
622 }
623 else if (useDB)
624 {
625 // if not manually specified, and we have a database to access
626 // pull the commflag type from the channel
628 query.prepare("SELECT commmethod FROM channel "
629 "WHERE chanid = :CHANID;");
630 query.bindValue(":CHANID", program_info->GetChanID());
631
632 if (!query.exec())
633 {
634 // if the query fails, return with an error
635 commDetectMethod = COMM_DETECT_UNINIT;
636 MythDB::DBError("FlagCommercials", query);
637 }
638 else if (query.next())
639 {
640 commDetectMethod = (SkipType)query.value(0).toInt();
641 if (commDetectMethod == COMM_DETECT_COMMFREE)
642 {
643 // if the channel is commercial free, drop to the default instead
644 commDetectMethod = (SkipType)gCoreContext->GetNumSetting(
645 "CommercialSkipMethod", COMM_DETECT_ALL);
646 LOG(VB_COMMFLAG, LOG_INFO,
647 QString("Chanid %1 is marked as being Commercial Free, "
648 "we will use the default commercial detection "
649 "method").arg(program_info->GetChanID()));
650 }
651 else if (commDetectMethod == COMM_DETECT_UNINIT)
652 {
653 // no value set, so use the database default
654 commDetectMethod = (SkipType)gCoreContext->GetNumSetting(
655 "CommercialSkipMethod", COMM_DETECT_ALL);
656 }
657 LOG(VB_COMMFLAG, LOG_INFO,
658 QString("Using method: %1 from channel %2")
659 .arg(commDetectMethod).arg(program_info->GetChanID()));
660 }
661
662 }
663 else if (!useDB)
664 {
665 // default to a cheaper method for debugging purposes
666 commDetectMethod = COMM_DETECT_BLANK;
667 }
668
669 // if selection has failed, or intentionally disabled, drop out
670 if (commDetectMethod == COMM_DETECT_UNINIT)
671 return GENERIC_EXIT_NOT_OK;
672 if (commDetectMethod == COMM_DETECT_OFF)
673 return GENERIC_EXIT_OK;
674
675 recorder = nullptr;
676
677/*
678 * is there a purpose to this not fulfilled by --getskiplist?
679 if (onlyDumpDBCommercialBreakList)
680 {
681 frm_dir_map_t commBreakList;
682 program_info->QueryCommBreakList(commBreakList);
683
684 print_comm_flag_output(program_info, commBreakList,
685 0, nullptr, outputfilename);
686
687 global_program_info = nullptr;
688 return GENERIC_EXIT_OK;
689 }
690*/
691
692 if (!DoesFileExist(program_info))
693 {
694 LOG(VB_GENERAL, LOG_ERR,
695 "Unable to find file in defined storage paths.");
697 }
698
699 QString filename = get_filename(program_info);
700
702 if (!tmprbuf)
703 {
704 LOG(VB_GENERAL, LOG_ERR,
705 QString("Unable to create RingBuffer for %1").arg(filename));
706 global_program_info = nullptr;
708 }
709
710 if (useDB)
711 {
713 {
714 LOG(VB_GENERAL, LOG_ERR, "Unable to open commflag DB connection");
715 delete tmprbuf;
716 global_program_info = nullptr;
718 }
719 }
720
721 auto flags = static_cast<PlayerFlags>(kAudioMuted | kVideoIsNull | kNoITV);
722
723 int flagfast = gCoreContext->GetNumSetting("CommFlagFast", 0);
724 if (flagfast)
725 {
726 // Note: These additional flags replicate the intent of the original
727 // commit that enabled lowres support - but I'm not sure why it requires
728 // single threaded decoding - which surely slows everything down? Though
729 // there is probably no profile to enable multi-threaded decoding anyway.
730 LOG(VB_GENERAL, LOG_INFO, "Enabling experimental flagging speedup (low resolution)");
731 flags = static_cast<PlayerFlags>(flags | kDecodeLowRes | kDecodeSingleThreaded | kDecodeNoLoopFilter);
732 }
733
734 // blank detector needs to be only sample center for this optimization.
735 if (flagfast && ((COMM_DETECT_BLANKS == commDetectMethod) ||
736 (COMM_DETECT_2_BLANK == commDetectMethod)))
737 {
738 flags = static_cast<PlayerFlags>(flags | kDecodeFewBlocks);
739 }
740
741 auto *ctx = new PlayerContext(kFlaggerInUseID);
742 auto *cfp = new MythCommFlagPlayer(ctx, flags);
743 ctx->SetPlayingInfo(program_info);
744 ctx->SetRingBuffer(tmprbuf);
745 ctx->SetPlayer(cfp);
746
747 if (useDB)
748 {
749 if (program_info->GetRecordingEndTime() > MythDate::current())
750 {
752
753 recorder = RemoteGetExistingRecorder(program_info);
754 if (recorder && (recorder->GetRecorderNumber() != -1))
755 {
757 watchingRecording = true;
758 ctx->SetRecorder(recorder);
759
760 LOG(VB_COMMFLAG, LOG_INFO,
761 QString("mythcommflag will flag recording "
762 "currently in progress on cardid %1")
763 .arg(recorderNum));
764 }
765 else
766 {
767 recorderNum = -1;
768 watchingRecording = false;
769
770 LOG(VB_GENERAL, LOG_ERR,
771 "Unable to find active recorder for this "
772 "recording, realtime flagging will not be enabled.");
773 }
774 cfp->SetWatchingRecording(watchingRecording);
775 }
776 }
777
778 // TODO: Add back insertion of job if not in jobqueue
779
780 breaksFound = DoFlagCommercials(
781 program_info, progress, fullSpeed, jobid,
782 cfp, commDetectMethod, outputfilename, useDB);
783
784 if (progress)
785 std::cerr << breaksFound << "\n";
786
787 LOG(VB_GENERAL, LOG_NOTICE, QString("Finished, %1 break(s) found.")
788 .arg(breaksFound));
789
790 delete ctx;
791 global_program_info = nullptr;
792
793 return breaksFound;
794}
795
796static int FlagCommercials( uint chanid, const QDateTime &starttime,
797 int jobid, const QString &outputfilename,
798 bool fullSpeed )
799{
800 QString startstring = MythDate::toString(starttime, MythDate::kFilename);
801 ProgramInfo pginfo(chanid, starttime);
802
803 if (!pginfo.GetChanID())
804 {
805 LOG(VB_GENERAL, LOG_ERR,
806 QString("No program data exists for channel %1 at %2")
807 .arg(chanid).arg(startstring));
809 }
810
812 {
813 if (progress)
814 {
815 std::cerr << "IN USE\n";
816 std::cerr << " "
817 "(the program is already being flagged elsewhere)\n";
818 }
819 LOG(VB_GENERAL, LOG_ERR, "Program is already being flagged elsewhere");
820 return GENERIC_EXIT_IN_USE;
821 }
822
823
824 if (progress)
825 {
826 std::cerr << "MythTV Commercial Flagger, flagging commercials for:" << std::endl;
827 if (pginfo.GetSubtitle().isEmpty())
828 std::cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << std::endl;
829 else
830 std::cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << " - "
831 << pginfo.GetSubtitle().toLocal8Bit().constData() << std::endl;
832 }
833
834 return FlagCommercials(&pginfo, jobid, outputfilename, true, fullSpeed);
835}
836
837static int FlagCommercials(const QString& filename, int jobid,
838 const QString &outputfilename, bool useDB,
839 bool fullSpeed)
840{
841
842 if (progress)
843 {
844 std::cerr << "MythTV Commercial Flagger, flagging commercials for:" << std::endl
845 << " " << filename.toLatin1().constData() << std::endl;
846 }
847
848 ProgramInfo pginfo(filename);
849 return FlagCommercials(&pginfo, jobid, outputfilename, useDB, fullSpeed);
850}
851
852static int RebuildSeekTable(ProgramInfo *pginfo, int jobid, bool writefile = false)
853{
854 QString filename = get_filename(pginfo);
855
856 if (!DoesFileExist(pginfo))
857 {
858 // file not found on local filesystem
859 // assume file is in Video storage group on local backend
860 // and try again
861
862 filename = QString("myth://Videos@%1/%2")
864 pginfo->SetPathname(filename);
865 if (!DoesFileExist(pginfo))
866 {
867 LOG(VB_GENERAL, LOG_ERR,
868 QString("Unable to find file in defined storage "
869 "paths for JobQueue ID# %1.").arg(jobid));
871 }
872 }
873
874 // Update the file size since mythcommflag --rebuild is often used in user
875 // scripts after transcoding or other size-changing operations
876 UpdateFileSize(pginfo);
877
879 if (!tmprbuf)
880 {
881 LOG(VB_GENERAL, LOG_ERR,
882 QString("Unable to create RingBuffer for %1").arg(filename));
884 }
885
886 auto *ctx = new PlayerContext(kFlaggerInUseID);
887 auto *cfp = new MythCommFlagPlayer(ctx, (PlayerFlags)(kAudioMuted | kVideoIsNull |
889
890 ctx->SetPlayingInfo(pginfo);
891 ctx->SetRingBuffer(tmprbuf);
892 ctx->SetPlayer(cfp);
893
894 if (progress)
895 {
896 QString time = QDateTime::currentDateTime().toString(Qt::TextDate);
897 std::cerr << "Rebuild started at " << qPrintable(time) << std::endl;
898 }
899
900 if (writefile)
902 cfp->RebuildSeekTable(progress);
903 if (writefile)
905
906 if (progress)
907 {
908 QString time = QDateTime::currentDateTime().toString(Qt::TextDate);
909 std::cerr << "Rebuild completed at " << qPrintable(time) << std::endl;
910 }
911
912 delete ctx;
913
914 return GENERIC_EXIT_OK;
915}
916
917static int RebuildSeekTable(const QString& filename, int jobid, bool writefile = false)
918{
919 if (progress)
920 {
921 std::cerr << "MythTV Commercial Flagger, building seek table for:" << std::endl
922 << " " << filename.toLatin1().constData() << std::endl;
923 }
924 ProgramInfo pginfo(filename);
925 return RebuildSeekTable(&pginfo, jobid, writefile);
926}
927
928static int RebuildSeekTable(uint chanid, const QDateTime& starttime, int jobid, bool writefile = false)
929{
930 ProgramInfo pginfo(chanid, starttime);
931 if (progress)
932 {
933 std::cerr << "MythTV Commercial Flagger, building seek table for:" << std::endl;
934 if (pginfo.GetSubtitle().isEmpty())
935 std::cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << std::endl;
936 else
937 std::cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << " - "
938 << pginfo.GetSubtitle().toLocal8Bit().constData() << std::endl;
939 }
940 return RebuildSeekTable(&pginfo, jobid, writefile);
941}
942
943int main(int argc, char *argv[])
944{
945 int result = GENERIC_EXIT_OK;
946
947// QString allStart = "19700101000000";
948// QString allEnd = MythDate::current().toString("yyyyMMddhhmmss");
949 int jobType = JOB_NONE;
950
951 if (!cmdline.Parse(argc, argv))
952 {
955 }
956
957 if (cmdline.toBool("showhelp"))
958 {
960 return GENERIC_EXIT_OK;
961 }
962
963 if (cmdline.toBool("showversion"))
964 {
966 return GENERIC_EXIT_OK;
967 }
968
969 progress = !cmdline.toBool("noprogress");
970
971 QCoreApplication a(argc, argv);
972 QCoreApplication::setApplicationName(MYTH_APPNAME_MYTHCOMMFLAG);
973 int retval = cmdline.ConfigureLogging("general", false);
974 if (retval != GENERIC_EXIT_OK)
975 return retval;
976
977 MythContext context {MYTH_BINARY_VERSION};
978 if (!context.Init( false, /*use gui*/
979 false, /*prompt for backend*/
980 false, /*bypass auto discovery*/
981 cmdline.toBool("skipdb"))) /*ignoreDB*/
982 {
983 LOG(VB_GENERAL, LOG_EMERG, "Failed to init MythContext, exiting.");
985 }
987
988 MythTranslation::load("mythfrontend");
989
990 if (cmdline.toBool("outputmethod"))
991 {
992 QString om = cmdline.toString("outputmethod");
993 if (outputTypes->contains(om))
994 outputMethod = outputTypes->value(om);
995 }
996
997 if (cmdline.toBool("chanid") && cmdline.toBool("starttime"))
998 {
999 // operate on a recording in the database
1000 uint chanid = cmdline.toUInt("chanid");
1001 QDateTime starttime = cmdline.toDateTime("starttime");
1002
1003 // TODO: check for matching jobid
1004 // create temporary id to operate off of if not
1005
1006 if (cmdline.toBool("queue"))
1007 QueueCommFlagJob(chanid, starttime, cmdline.toBool("rebuild"));
1008 else if (cmdline.toBool("rebuild"))
1009 result = RebuildSeekTable(chanid, starttime, -1);
1010 else
1011 result = FlagCommercials(chanid, starttime, -1,
1012 cmdline.toString("outputfile"), true);
1013 }
1014 else if (cmdline.toBool("jobid"))
1015 {
1016 jobID = cmdline.toInt("jobid");
1017 uint chanid = 0;
1018 QDateTime starttime;
1019
1020 if (!JobQueue::GetJobInfoFromID(jobID, jobType, chanid, starttime))
1021 {
1022 std::cerr << "mythcommflag: ERROR: Unable to find DB info for "
1023 << "JobQueue ID# " << jobID << std::endl;
1025 }
1026 force = true;
1027 int jobQueueCPU = gCoreContext->GetNumSetting("JobQueueCPU", 0);
1028
1029 if (jobQueueCPU < 2)
1030 {
1031 myth_nice(17);
1032 myth_ioprio((0 == jobQueueCPU) ? 8 : 7);
1033 }
1034
1035 progress = false;
1036
1037 int ret = 0;
1038
1040 RebuildSeekTable(chanid, starttime, jobID);
1041 else
1042 ret = FlagCommercials(chanid, starttime, jobID, "", jobQueueCPU != 0);
1043
1044 if (ret > GENERIC_EXIT_NOT_OK)
1045 {
1046 JobQueue::ChangeJobStatus(jobID, JOB_ERRORED,
1047 QCoreApplication::translate("(mythcommflag)",
1048 "Failed with exit status %1",
1049 "Job status").arg(ret));
1050 }
1051 else
1052 {
1053 JobQueue::ChangeJobStatus(jobID, JOB_FINISHED,
1054 QCoreApplication::translate("(mythcommflag)",
1055 "%n commercial break(s)",
1056 "Job status",
1057 ret));
1058 }
1059 }
1060 else if (cmdline.toBool("video"))
1061 {
1062 // build skiplist for video file
1063 return RebuildSeekTable(cmdline.toString("video"), -1);
1064 }
1065 else if (cmdline.toBool("file"))
1066 {
1067 if (cmdline.toBool("skipdb"))
1068 {
1069 if (cmdline.toBool("rebuild"))
1070 {
1071 std::cerr << "The --rebuild parameter builds the seektable for "
1072 "internal MythTV use only. It cannot be used in "
1073 "combination with --skipdb." << std::endl;
1075 }
1076
1077 if (!cmdline.toBool("outputfile"))
1078 cmdline.SetValue("outputfile", "-");
1079
1080 // perform commercial flagging on file outside the database
1081 FlagCommercials(cmdline.toString("file"), -1,
1082 cmdline.toString("outputfile"),
1083 !cmdline.toBool("skipdb"),
1084 true);
1085 }
1086 else
1087 {
1088 ProgramInfo pginfo(cmdline.toString("file"));
1089 // pass chanid and starttime
1090 // inefficient, but it lets the other function
1091 // handle sanity checking
1092 if (cmdline.toBool("rebuild"))
1093 {
1094 result = RebuildSeekTable(pginfo.GetChanID(),
1095 pginfo.GetRecordingStartTime(),
1096 -1, cmdline.toBool("writefile"));
1097 }
1098 else
1099 {
1100 result = FlagCommercials(pginfo.GetChanID(),
1101 pginfo.GetRecordingStartTime(),
1102 -1, cmdline.toString("outputfile"),
1103 true);
1104 }
1105 }
1106 }
1107 else if (cmdline.toBool("queue"))
1108 {
1109 // run flagging for all recordings with no skiplist
1111 query.prepare("SELECT r.chanid, r.starttime, c.commmethod "
1112 "FROM recorded AS r "
1113 "LEFT JOIN channel AS c ON r.chanid=c.chanid "
1114// "WHERE startime >= :STARTTIME AND endtime <= :ENDTIME "
1115 "ORDER BY starttime;");
1116 //query.bindValue(":STARTTIME", allStart);
1117 //query.bindValue(":ENDTIME", allEnd);
1118
1119 if (query.exec() && query.isActive() && query.size() > 0)
1120 {
1121 QDateTime starttime;
1122
1123 while (query.next())
1124 {
1125 starttime = MythDate::fromString(query.value(1).toString());
1126 uint chanid = query.value(0).toUInt();
1127
1128 if (!cmdline.toBool("force") && !cmdline.toBool("rebuild"))
1129 {
1130 // recording is already flagged
1131 if (IsMarked(chanid, starttime))
1132 continue;
1133
1134 // channel is marked as commercial free
1135 if (query.value(2).toInt() == COMM_DETECT_COMMFREE)
1136 continue;
1137
1138 // recording rule did not enable commflagging
1139#if 0
1140 RecordingInfo recinfo(chanid, starttime);
1141 if (!(recinfo.GetAutoRunJobs() & JOB_COMMFLAG))
1142 continue;
1143#endif
1144 }
1145
1146 QueueCommFlagJob(chanid, starttime, cmdline.toBool("rebuild"));
1147 }
1148 }
1149
1150 }
1151 else
1152 {
1153 LOG(VB_GENERAL, LOG_ERR,
1154 "No valid combination of command inputs received.");
1157 }
1158
1159 return result;
1160}
1161
1162
1163/* vim: set expandtab tabstop=4 shiftwidth=4: */
Abstract base class for all CommDetectors.
virtual void PrintFullMap(std::ostream &out, const frm_dir_map_t *comm_breaks, bool verbose) const =0
virtual void recordingFinished(long long totalFileSize)
virtual void GetCommercialBreakList(frm_dir_map_t &comms)=0
void statusUpdate(const QString &a)
void gotNewCommercialBreakList()
virtual void requestCommBreakMapUpdate(void)
virtual bool go()=0
static CommDetectorBase * makeCommDetector(SkipType commDetectMethod, bool showProgress, bool fullSpeed, MythCommFlagPlayer *player, int chanid, const QDateTime &startedAt, const QDateTime &stopsAt, const QDateTime &recordingStartedAt, const QDateTime &recordingStopsAt, bool useDB)
static bool GetJobInfoFromID(int jobID, int &jobType, uint &chanid, QDateTime &recstartts)
Definition: jobqueue.cpp:665
static enum JobFlags GetJobFlags(int jobID)
Definition: jobqueue.cpp:1507
static enum JobCmds GetJobCmd(int jobID)
Definition: jobqueue.cpp:1465
static bool QueueJob(int jobType, uint chanid, const QDateTime &recstartts, const QString &args="", const QString &comment="", QString host="", int flags=0, int status=JOB_QUEUED, QDateTime schedruntime=QDateTime())
Definition: jobqueue.cpp:508
static bool ChangeJobComment(int jobID, const QString &comment="")
Definition: jobqueue.cpp:1010
static bool IsJobRunning(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1086
static bool ChangeJobStatus(int jobID, int newStatus, const QString &comment="")
Definition: jobqueue.cpp:983
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837
QVariant value(int i) const
Definition: mythdbcon.h:204
int size(void) const
Definition: mythdbcon.h:214
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
Definition: mythdbcon.cpp:876
bool isActive(void) const
Definition: mythdbcon.h:215
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
int toInt(const QString &key) const
Returns stored QVariant as an integer, falling to default if not provided.
virtual bool Parse(int argc, const char *const *argv)
Loop through argv and populate arguments with values.
void ApplySettingsOverride(void)
Apply all overrides to the global context.
int ConfigureLogging(const QString &mask="general", bool progress=false)
Read in logging options and initialize the logging interface.
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
static void PrintVersion(void)
Print application version information.
bool SetValue(const QString &key, const QVariant &value)
Set a new stored value for an existing argument definition, or spawn a new definition store value in.
QDateTime toDateTime(const QString &key) const
Returns stored QVariant as a QDateTime, falling to default if not provided.
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
void PrintHelp(void) const
Print command line option help.
Startup context for MythTV.
Definition: mythcontext.h:20
QString GetHostName(void)
void RegisterFileForWrite(const QString &file, uint64_t size=0LL)
bool ConnectToMasterServer(bool blockingClient=true, bool openEventSocket=true)
void SendMessage(const QString &message)
int GetNumSetting(const QString &key, int defaultval=0)
void UnregisterFileForWrite(const QString &file)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
This class is used as a container for messages.
Definition: mythevent.h:17
const QString & Message() const
Definition: mythevent.h:65
static const Type kMythEventMessage
Definition: mythevent.h:79
static MythMediaBuffer * Create(const QString &Filename, bool Write, bool UseReadAhead=true, std::chrono::milliseconds Timeout=kDefaultOpenTimeout, bool StreamOnly=false)
Creates a RingBuffer instance.
void SaveTotalDuration(void)
uint64_t GetTotalFrameCount(void) const
Definition: mythplayer.h:141
static void load(const QString &module_name)
Load a QTranslator for the user's preferred language.
Holds information on recordings and videos.
Definition: programinfo.h:70
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:375
static bool ExtractKey(const QString &uniquekey, uint &chanid, QDateTime &recstartts)
Extracts chanid and recstartts from a unique key generated by MakeUniqueKey().
virtual void SaveFilesize(uint64_t fsize)
Sets recording file size in database, and sets "filesize" field.
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:400
QString GetTitle(void) const
Definition: programinfo.h:364
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:407
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:393
void SaveCommBreakList(frm_dir_map_t &frames) const
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
Definition: programinfo.h:342
void SaveMarkupFlag(MarkTypes type) const
Clears the specified flag, then if sets it.
QString GetPathname(void) const
Definition: programinfo.h:346
virtual uint64_t GetFilesize(void) const
QString GetPlaybackURL(bool checkMaster=false, bool forceCheckLocal=false)
Returns filename or URL to be used to play back this recording.
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
Definition: programinfo.h:415
void SaveCommFlagged(CommFlagStatus flag)
Set "commflagged" field in "recorded" table to "flag".
QString GetSubtitle(void) const
Definition: programinfo.h:366
void SetPathname(const QString &pn)
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:36
int GetAutoRunJobs(void) const
Returns a bitmap of which jobs are attached to this RecordingInfo.
int GetRecorderNumber(void) const
long long GetFileSize(void) const
GetFileSize: returns the remote file's size at the time it was first opened Will query the server in ...
void relay()
Definition: SlotRelayer.h:26
unsigned int uint
Definition: compat.h:68
@ GENERIC_EXIT_PERMISSIONS_ERROR
File permissions error.
Definition: exitcodes.h:22
@ GENERIC_EXIT_NO_MYTHCONTEXT
No MythContext available.
Definition: exitcodes.h:16
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
@ GENERIC_EXIT_NO_RECORDING_DATA
No program/recording data.
Definition: exitcodes.h:32
@ GENERIC_EXIT_IN_USE
Recording in use, can't flag.
Definition: exitcodes.h:37
@ GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:18
@ GENERIC_EXIT_DB_ERROR
Database error.
Definition: exitcodes.h:20
@ GENERIC_EXIT_NOT_OK
Exited with error.
Definition: exitcodes.h:14
static guint32 * tmp
Definition: goom_core.cpp:26
@ JOB_NONE
Definition: jobqueue.h:75
@ JOB_COMMFLAG
Definition: jobqueue.h:79
@ JOB_STOP
Definition: jobqueue.h:54
@ JOB_RESUME
Definition: jobqueue.h:53
@ JOB_PAUSE
Definition: jobqueue.h:52
@ JOB_REBUILD
Definition: jobqueue.h:63
static constexpr const char * MYTH_APPNAME_MYTHCOMMFLAG
Definition: mythappname.h:9
int main(int argc, char *argv[])
static int RebuildSeekTable(ProgramInfo *pginfo, int jobid, bool writefile=false)
static void UpdateFileSize(ProgramInfo *program_info)
static QString get_filename(ProgramInfo *program_info)
QMap< QString, SkipType > * skipTypes
static int FlagCommercials(ProgramInfo *program_info, int jobid, const QString &outputfilename, bool useDB, bool fullSpeed)
static bool IsMarked(uint chanid, const QDateTime &starttime)
static void incomingCustomEvent(QEvent *e)
static bool DoesFileExist(ProgramInfo *program_info)
static void commDetectorBreathe()
QMap< QString, OutputMethod > * outputTypes
static void commDetectorGotNewCommercialBreakList(void)
static int QueueCommFlagJob(uint chanid, const QDateTime &starttime, bool rebuild)
static QMap< QString, SkipType > * init_skip_types()
OutputMethod
@ kOutputMethodFull
@ kOutputMethodEssentials
static void commDetectorStatusUpdate(const QString &status)
static QMap< QString, OutputMethod > * init_output_types()
static int DoFlagCommercials(ProgramInfo *program_info, bool showPercentage, bool fullSpeed, int jobid, MythCommFlagPlayer *cfp, SkipType commDetectMethod, const QString &outputfilename, bool useDB)
static void streamOutCommercialBreakList(std::ostream &output, const frm_dir_map_t &commercialBreakList)
static void print_comm_flag_output(const ProgramInfo *program_info, const frm_dir_map_t &commBreakList, uint64_t frame_count, const CommDetectorBase *commDetect, const QString &output_filename)
static qint64 GetFileSize(ProgramInfo *program_info)
OutputMethod outputMethod
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
bool myth_nice(int val)
bool myth_ioprio(int)
Allows setting the I/O priority of the current process/thread.
PlayerFlags
Definition: mythplayer.h:64
@ kDecodeLowRes
Definition: mythplayer.h:66
@ kAudioMuted
Definition: mythplayer.h:73
@ kDecodeFewBlocks
Definition: mythplayer.h:68
@ kDecodeNoDecode
Definition: mythplayer.h:70
@ kDecodeNoLoopFilter
Definition: mythplayer.h:69
@ kDecodeSingleThreaded
Definition: mythplayer.h:67
@ kVideoIsNull
Definition: mythplayer.h:72
@ kNoITV
Definition: mythplayer.h:74
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kFilename
Default UTC, "yyyyMMddhhmmss".
Definition: mythdate.h:18
@ ISODate
Default UTC.
Definition: mythdate.h:17
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
MythCommFlagCommandLineParser cmdline
bool exists(str path)
Definition: xbmcvfs.py:51
const QString kFlaggerInUseID
@ MARK_UPDATED_CUT
Definition: programtypes.h:52
@ MARK_COMM_END
Definition: programtypes.h:59
@ MARK_COMM_START
Definition: programtypes.h:58
SkipType
This is used as a bitmask.
Definition: programtypes.h:127
@ COMM_DETECT_COMMFREE
Definition: programtypes.h:128
@ COMM_DETECT_BLANKS
Definition: programtypes.h:132
@ COMM_DETECT_SCENE
Definition: programtypes.h:133
@ COMM_DETECT_2_SCENE
Definition: programtypes.h:142
@ COMM_DETECT_2_LOGO
Definition: programtypes.h:140
@ COMM_DETECT_BLANK_SCENE
Definition: programtypes.h:135
@ COMM_DETECT_BLANK
Definition: programtypes.h:131
@ COMM_DETECT_2_ALL
Definition: programtypes.h:145
@ COMM_DETECT_ALL
Definition: programtypes.h:136
@ COMM_DETECT_2_BLANK
Definition: programtypes.h:141
@ COMM_DETECT_2
Definition: programtypes.h:139
@ COMM_DETECT_UNINIT
Definition: programtypes.h:129
@ COMM_DETECT_LOGO
Definition: programtypes.h:134
@ COMM_DETECT_OFF
Definition: programtypes.h:130
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:117
@ COMM_FLAG_DONE
Definition: programtypes.h:121
@ COMM_FLAG_COMMFREE
Definition: programtypes.h:123
@ COMM_FLAG_NOT_FLAGGED
Definition: programtypes.h:120
@ COMM_FLAG_PROCESSING
Definition: programtypes.h:122
#define output
RemoteEncoder * RemoteGetExistingRecorder(const ProgramInfo *pginfo)