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