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