MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
mythtv/programs/mythcommflag/main.cpp
Go to the documentation of this file.
1 // POSIX headers
2 #include <unistd.h>
3 #include <sys/time.h> // for gettimeofday
4 
5 // ANSI C headers
6 #include <cstdlib>
7 #include <cstdio>
8 #include <ctime>
9 #include <cmath>
10 
11 // C++ headers
12 #include <string>
13 #include <iostream>
14 #include <fstream>
15 using namespace std;
16 
17 // Qt headers
18 #include <QCoreApplication>
19 #include <QString>
20 #include <QRegExp>
21 #include <QDir>
22 #include <QEvent>
23 
24 // MythTV headers
25 #include "mythmiscutil.h"
26 #include "mythdate.h"
27 #include "exitcodes.h"
28 #include "mythcontext.h"
29 #include "mythdb.h"
30 #include "mythversion.h"
31 #include "mythcommflagplayer.h"
32 #include "programinfo.h"
33 #include "remoteutil.h"
34 #include "remotefile.h"
35 #include "tvremoteutil.h"
36 #include "jobqueue.h"
37 #include "remoteencoder.h"
38 #include "ringbuffer.h"
39 #include "commandlineparser.h"
40 #include "mythtranslation.h"
41 #include "mythlogging.h"
42 #include "signalhandling.h"
43 
44 // Commercial Flagging headers
45 #include "CommDetectorBase.h"
46 #include "CommDetectorFactory.h"
47 #include "SlotRelayer.h"
48 #include "CustomEventRelayer.h"
49 
50 #define LOC QString("MythCommFlag: ")
51 #define LOC_WARN QString("MythCommFlag, Warning: ")
52 #define LOC_ERR QString("MythCommFlag, Error: ")
53 
54 namespace
55 {
56  void cleanup()
57  {
58  delete gContext;
59  gContext = NULL;
61  }
62 
63  class CleanupGuard
64  {
65  public:
66  typedef void (*CleanupFunc)();
67 
68  public:
69  CleanupGuard(CleanupFunc cleanFunction) :
70  m_cleanFunction(cleanFunction) {}
71 
72  ~CleanupGuard()
73  {
74  m_cleanFunction();
75  }
76 
77  private:
78  CleanupFunc m_cleanFunction;
79  };
80 }
81 
82 int quiet = 0;
83 bool progress = true;
84 bool force = false;
85 
87 
88 bool watchingRecording = false;
92 int recorderNum = -1;
93 
94 int jobID = -1;
95 int lastCmd = -1;
96 
97 static QMap<QString,SkipTypes> *init_skip_types();
98 QMap<QString,SkipTypes> *skipTypes = init_skip_types();
99 
100 static QMap<QString,SkipTypes> *init_skip_types(void)
101 {
102  QMap<QString,SkipTypes> *tmp = new QMap<QString,SkipTypes>;
103  (*tmp)["commfree"] = COMM_DETECT_COMMFREE;
104  (*tmp)["uninit"] = COMM_DETECT_UNINIT;
105  (*tmp)["off"] = COMM_DETECT_OFF;
106  (*tmp)["blank"] = COMM_DETECT_BLANKS;
107  (*tmp)["blanks"] = COMM_DETECT_BLANKS;
108  (*tmp)["scene"] = COMM_DETECT_SCENE;
109  (*tmp)["blankscene"] = COMM_DETECT_BLANK_SCENE;
110  (*tmp)["blank_scene"] = COMM_DETECT_BLANK_SCENE;
111  (*tmp)["logo"] = COMM_DETECT_LOGO;
112  (*tmp)["all"] = COMM_DETECT_ALL;
113  (*tmp)["d2"] = COMM_DETECT_2;
114  (*tmp)["d2_logo"] = COMM_DETECT_2_LOGO;
115  (*tmp)["d2_blank"] = COMM_DETECT_2_BLANK;
116  (*tmp)["d2_scene"] = COMM_DETECT_2_SCENE;
117  (*tmp)["d2_all"] = COMM_DETECT_2_ALL;
118  return tmp;
119 }
120 
121 typedef enum
122 {
125 } OutputMethod;
127 
128 static QMap<QString,OutputMethod> *init_output_types();
129 QMap<QString,OutputMethod> *outputTypes = init_output_types();
130 
131 static QMap<QString,OutputMethod> *init_output_types(void)
132 {
133  QMap<QString,OutputMethod> *tmp = new QMap<QString,OutputMethod>;
134  (*tmp)["essentials"] = kOutputMethodEssentials;
135  (*tmp)["full"] = kOutputMethodFull;
136  return tmp;
137 }
138 
139 static QString get_filename(ProgramInfo *program_info)
140 {
141  QString filename = program_info->GetPathname();
142  if (!QFile::exists(filename))
143  filename = program_info->GetPlaybackURL(true);
144  return filename;
145 }
146 
147 static int QueueCommFlagJob(uint chanid, QDateTime starttime, bool rebuild)
148 {
149  QString startstring = MythDate::toString(starttime, MythDate::kFilename);
150  const ProgramInfo pginfo(chanid, starttime);
151 
152  if (!pginfo.GetChanID())
153  {
154  if (progress)
155  {
156  QString tmp = QString(
157  "Unable to find program info for chanid %1 @ %2")
158  .arg(chanid).arg(startstring);
159  cerr << tmp.toLocal8Bit().constData() << endl;
160  }
161  return GENERIC_EXIT_NO_RECORDING_DATA;
162  }
163 
164  if (cmdline.toBool("dryrun"))
165  {
166  QString tmp = QString("Job have been queued for chanid %1 @ %2")
167  .arg(chanid).arg(startstring);
168  cerr << tmp.toLocal8Bit().constData() << endl;
169  return GENERIC_EXIT_OK;
170  }
171 
172  bool result = JobQueue::QueueJob(JOB_COMMFLAG,
173  pginfo.GetChanID(), pginfo.GetRecordingStartTime(), "", "", "",
174  rebuild ? JOB_REBUILD : 0, JOB_QUEUED, QDateTime());
175 
176  if (result)
177  {
178  if (progress)
179  {
180  QString tmp = QString("Job Queued for chanid %1 @ %2")
181  .arg(chanid).arg(startstring);
182  cerr << tmp.toLocal8Bit().constData() << endl;
183  }
184  return GENERIC_EXIT_OK;
185  }
186  else
187  {
188  if (progress)
189  {
190  QString tmp = QString("Error queueing job for chanid %1 @ %2")
191  .arg(chanid).arg(startstring);
192  cerr << tmp.toLocal8Bit().constData() << endl;
193  }
194  return GENERIC_EXIT_DB_ERROR;
195  }
196 
197  return GENERIC_EXIT_OK;
198 }
199 
200 static int CopySkipListToCutList(uint chanid, QDateTime starttime)
201 {
202  frm_dir_map_t cutlist;
203  frm_dir_map_t::const_iterator it;
204 
205  QString startstring = MythDate::toString(starttime, MythDate::kFilename);
206  const ProgramInfo pginfo(chanid, starttime);
207 
208  if (!pginfo.GetChanID())
209  {
210  LOG(VB_GENERAL, LOG_ERR,
211  QString("No program data exists for channel %1 at %2")
212  .arg(chanid).arg(startstring));
213  return GENERIC_EXIT_NO_RECORDING_DATA;
214  }
215 
216  pginfo.QueryCommBreakList(cutlist);
217  for (it = cutlist.begin(); it != cutlist.end(); ++it)
218  if (*it == MARK_COMM_START)
219  cutlist[it.key()] = MARK_CUT_START;
220  else
221  cutlist[it.key()] = MARK_CUT_END;
222  pginfo.SaveCutList(cutlist);
223 
224  return GENERIC_EXIT_OK;
225 }
226 
227 static int ClearSkipList(uint chanid, QDateTime starttime)
228 {
229  QString startstring = MythDate::toString(starttime, MythDate::kFilename);
230  const ProgramInfo pginfo(chanid, starttime);
231 
232  if (!pginfo.GetChanID())
233  {
234  LOG(VB_GENERAL, LOG_ERR,
235  QString("No program data exists for channel %1 at %2")
236  .arg(chanid).arg(startstring));
237  return GENERIC_EXIT_NO_RECORDING_DATA;
238  }
239 
240  frm_dir_map_t skiplist;
241  pginfo.SaveCommBreakList(skiplist);
242 
243  LOG(VB_GENERAL, LOG_NOTICE, "Commercial skip list cleared");
244 
245  return GENERIC_EXIT_OK;
246 }
247 
248 static int SetCutList(uint chanid, QDateTime starttime, QString newCutList)
249 {
250  frm_dir_map_t cutlist;
251 
252  newCutList.replace(QRegExp(" "), "");
253 
254  QStringList tokens = newCutList.split(",", QString::SkipEmptyParts);
255 
256  for (int i = 0; i < tokens.size(); i++)
257  {
258  QStringList cutpair = tokens[i].split("-", QString::SkipEmptyParts);
259  cutlist[cutpair[0].toInt()] = MARK_CUT_START;
260  cutlist[cutpair[1].toInt()] = MARK_CUT_END;
261  }
262 
263  QString startstring = MythDate::toString(starttime, MythDate::kFilename);
264  const ProgramInfo pginfo(chanid, starttime);
265 
266  if (!pginfo.GetChanID())
267  {
268  LOG(VB_GENERAL, LOG_ERR,
269  QString("No program data exists for channel %1 at %2")
270  .arg(chanid).arg(startstring));
271  return GENERIC_EXIT_NO_RECORDING_DATA;
272  }
273 
274  pginfo.SaveCutList(cutlist);
275 
276  LOG(VB_GENERAL, LOG_NOTICE, QString("Cutlist set to: %1").arg(newCutList));
277 
278  return GENERIC_EXIT_OK;
279 }
280 
281 static int GetMarkupList(QString list, uint chanid, QDateTime starttime)
282 {
283  frm_dir_map_t cutlist;
284  frm_dir_map_t::const_iterator it;
285  QString result;
286 
287  QString startstring = MythDate::toString(starttime, MythDate::kFilename);
288  const ProgramInfo pginfo(chanid, starttime);
289 
290  if (!pginfo.GetChanID())
291  {
292  LOG(VB_GENERAL, LOG_ERR,
293  QString("No program data exists for channel %1 at %2")
294  .arg(chanid).arg(startstring));
295  return GENERIC_EXIT_NO_RECORDING_DATA;
296  }
297 
298  if (list == "cutlist")
299  pginfo.QueryCutList(cutlist);
300  else
301  pginfo.QueryCommBreakList(cutlist);
302 
303  uint64_t lastStart = 0;
304  for (it = cutlist.begin(); it != cutlist.end(); ++it)
305  {
306  if ((*it == MARK_COMM_START) ||
307  (*it == MARK_CUT_START))
308  {
309  if (!result.isEmpty())
310  result += ",";
311  lastStart = it.key();
312  result += QString("%1-").arg(lastStart);
313  }
314  else
315  {
316  if (result.isEmpty())
317  result += "0-";
318  result += QString("%1").arg(it.key());
319  }
320  }
321 
322  if (result.endsWith('-'))
323  {
324  uint64_t lastFrame = pginfo.QueryLastFrameInPosMap() + 60;
325  if (lastFrame > lastStart)
326  result += QString("%1").arg(lastFrame);
327  }
328 
329  if (list == "cutlist")
330  cout << QString("Cutlist: %1\n").arg(result).toLocal8Bit().constData();
331  else
332  {
333  cout << QString("Commercial Skip List: %1\n")
334  .arg(result).toLocal8Bit().constData();
335  }
336 
337  return GENERIC_EXIT_OK;
338 }
339 
341  ostream &output, const frm_dir_map_t &commercialBreakList)
342 {
343  if (progress)
344  output << "----------------------------" << endl;
345 
346  if (commercialBreakList.empty())
347  {
348  if (progress)
349  output << "No breaks" << endl;
350  }
351  else
352  {
353  frm_dir_map_t::const_iterator it = commercialBreakList.begin();
354  for (; it != commercialBreakList.end(); ++it)
355  {
356  output << "framenum: " << it.key() << "\tmarktype: " << *it
357  << endl;
358  }
359  }
360 
361  if (progress)
362  output << "----------------------------" << endl;
363 }
364 
366  const ProgramInfo *program_info,
367  const frm_dir_map_t &commBreakList,
368  uint64_t frame_count,
369  const CommDetectorBase *commDetect,
370  const QString &output_filename)
371 {
372  if (output_filename.isEmpty())
373  return;
374 
375  ostream *out = &cout;
376  if (output_filename != "-")
377  {
378  QByteArray tmp = output_filename.toLocal8Bit();
379  out = new fstream(tmp.constData(), ios::app | ios::out );
380  }
381 
382  if (progress)
383  {
384  QString tmp = "";
385  if (program_info->GetChanID())
386  {
387  tmp = QString("commercialBreakListFor: %1 on %2 @ %3")
388  .arg(program_info->GetTitle())
389  .arg(program_info->GetChanID())
390  .arg(program_info->GetRecordingStartTime(MythDate::ISODate));
391  }
392  else
393  {
394  tmp = QString("commercialBreakListFor: %1")
395  .arg(program_info->GetPathname());
396  }
397 
398  const QByteArray tmp2 = tmp.toLocal8Bit();
399  *out << tmp2.constData() << endl;
400 
401  if (frame_count)
402  *out << "totalframecount: " << frame_count << endl;
403  }
404 
405  if (commDetect)
406  commDetect->PrintFullMap(*out, &commBreakList, progress);
407  else
408  streamOutCommercialBreakList(*out, commBreakList);
409 
410  if (output_filename != "-")
411  delete out;
412 }
413 
414 static void commDetectorBreathe()
415 {
416  //this is connected to the commdetectors breathe signal so we get a chance
417  //while its busy to see if the user already told us to stop.
418  qApp->processEvents();
419 
420  if (jobID != -1)
421  {
422  int curCmd = JobQueue::GetJobCmd(jobID);
423  if (curCmd == lastCmd)
424  return;
425 
426  switch (curCmd)
427  {
428  case JOB_STOP:
429  {
430  commDetector->stop();
431  break;
432  }
433  case JOB_PAUSE:
434  {
435  JobQueue::ChangeJobStatus(jobID, JOB_PAUSED,
436  QCoreApplication::translate("(mythcommflag)",
437  "Paused", "Job status"));
438  commDetector->pause();
439  break;
440  }
441  case JOB_RESUME:
442  {
443  JobQueue::ChangeJobStatus(jobID, JOB_RUNNING,
444  QCoreApplication::translate("(mythcommflag)",
445  "Running", "Job status"));
446  commDetector->resume();
447  break;
448  }
449  }
450  }
451 }
452 
453 static void commDetectorStatusUpdate(const QString& status)
454 {
455  if (jobID != -1)
456  {
457  JobQueue::ChangeJobStatus(jobID, JOB_RUNNING, status);
459  }
460 }
461 
463 {
464  frm_dir_map_t newCommercialMap;
465  commDetector->GetCommercialBreakList(newCommercialMap);
466 
467  frm_dir_map_t::Iterator it = newCommercialMap.begin();
468  QString message = "COMMFLAG_UPDATE ";
469  message += global_program_info->MakeUniqueKey();
470 
471  for (it = newCommercialMap.begin();
472  it != newCommercialMap.end(); ++it)
473  {
474  if (it != newCommercialMap.begin())
475  message += ",";
476  else
477  message += " ";
478  message += QString("%1:%2").arg(it.key())
479  .arg(*it);
480  }
481 
482  LOG(VB_COMMFLAG, LOG_INFO,
483  QString("mythcommflag sending update: %1").arg(message));
484 
485  gCoreContext->SendMessage(message);
486 }
487 
488 static void incomingCustomEvent(QEvent* e)
489 {
490  if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
491  {
492  MythEvent *me = (MythEvent *)e;
493  QString message = me->Message();
494 
495  message = message.simplified();
496  QStringList tokens = message.split(" ", QString::SkipEmptyParts);
497 
498  LOG(VB_COMMFLAG, LOG_INFO,
499  QString("mythcommflag: Received Event: '%1'") .arg(message));
500 
501  if ((watchingRecording) && (tokens.size() >= 3) &&
502  (tokens[0] == "DONE_RECORDING"))
503  {
504  int cardnum = tokens[1].toInt();
505  int filelen = tokens[2].toInt();
506 
507  message = QString("mythcommflag: Received a "
508  "DONE_RECORDING event for card %1. ")
509  .arg(cardnum);
510 
511  if (recorderNum != -1 && cardnum == recorderNum)
512  {
514  watchingRecording = false;
515  message += "Informed CommDetector that recording has finished.";
516  LOG(VB_COMMFLAG, LOG_INFO, message);
517  }
518  }
519 
520  if ((tokens.size() >= 2) && (tokens[0] == "COMMFLAG_REQUEST"))
521  {
522  uint chanid = 0;
523  QDateTime recstartts;
524  ProgramInfo::ExtractKey(tokens[1], chanid, recstartts);
525 
526  message = QString("mythcommflag: Received a "
527  "COMMFLAG_REQUEST event for chanid %1 @ %2. ")
528  .arg(chanid).arg(recstartts.toString(Qt::ISODate));
529 
530  if ((global_program_info->GetChanID() == chanid) &&
531  (global_program_info->GetRecordingStartTime() == recstartts))
532  {
534  message += "Requested CommDetector to generate new break list.";
535  LOG(VB_COMMFLAG, LOG_INFO, message);
536  }
537  }
538  }
539 }
540 
541 static int DoFlagCommercials(
542  ProgramInfo *program_info,
543  bool showPercentage, bool fullSpeed, int jobid,
544  MythCommFlagPlayer* cfp, enum SkipTypes commDetectMethod,
545  const QString &outputfilename, bool useDB)
546 {
547  CommDetectorFactory factory;
548  commDetector = factory.makeCommDetector(
549  commDetectMethod, showPercentage,
550  fullSpeed, cfp,
551  program_info->GetChanID(),
552  program_info->GetScheduledStartTime(),
553  program_info->GetScheduledEndTime(),
554  program_info->GetRecordingStartTime(),
555  program_info->GetRecordingEndTime(), useDB);
556 
557  if (jobid > 0)
558  LOG(VB_COMMFLAG, LOG_INFO,
559  QString("mythcommflag processing JobID %1").arg(jobid));
560 
561  if (useDB)
562  program_info->SaveCommFlagged(COMM_FLAG_PROCESSING);
563 
568  QObject::connect(commDetector, SIGNAL(breathe()),
569  a, SLOT(relay()));
570  QObject::connect(commDetector, SIGNAL(statusUpdate(const QString&)),
571  b, SLOT(relay(const QString&)));
572  QObject::connect(commDetector, SIGNAL(gotNewCommercialBreakList()),
573  c, SLOT(relay()));
574 
575  if (useDB)
576  {
577  LOG(VB_COMMFLAG, LOG_INFO,
578  "mythcommflag sending COMMFLAG_START notification");
579  QString message = "COMMFLAG_START ";
580  message += program_info->MakeUniqueKey();
581  gCoreContext->SendMessage(message);
582  }
583 
584  bool result = commDetector->go();
585  int comms_found = 0;
586 
587  if (result)
588  {
589  cfp->SaveTotalDuration();
590 
591  frm_dir_map_t commBreakList;
592  commDetector->GetCommercialBreakList(commBreakList);
593  comms_found = commBreakList.size() / 2;
594 
595  if (useDB)
596  {
597  program_info->SaveMarkupFlag(MARK_UPDATED_CUT);
598  program_info->SaveCommBreakList(commBreakList);
599  program_info->SaveCommFlagged(COMM_FLAG_DONE);
600  }
601 
603  program_info, commBreakList, cfp->GetTotalFrameCount(),
605  outputfilename);
606  }
607  else
608  {
609  if (useDB)
610  program_info->SaveCommFlagged(COMM_FLAG_NOT_FLAGGED);
611  }
612 
614  commDetector = NULL;
615  sleep(1);
616  tmp->deleteLater();
617 
618  cer->deleteLater();
619  c->deleteLater();
620  b->deleteLater();
621  a->deleteLater();
622 
623  return comms_found;
624 }
625 
626 static qint64 GetFileSize(ProgramInfo *program_info)
627 {
628  QString filename = get_filename(program_info);
629  qint64 size = -1;
630 
631  if (filename.startsWith("myth://"))
632  {
633  RemoteFile remotefile(filename, false, false, 0);
634  size = remotefile.GetFileSize();
635  }
636  else
637  {
638  QFile file(filename);
639  if (file.exists())
640  {
641  size = file.size();
642  }
643  }
644 
645  return size;
646 }
647 
648 static bool DoesFileExist(ProgramInfo *program_info)
649 {
650  QString filename = get_filename(program_info);
651  qint64 size = GetFileSize(program_info);
652 
653  if (size < 0)
654  {
655  LOG(VB_GENERAL, LOG_ERR, QString("Couldn't find file %1, aborting.")
656  .arg(filename));
657  return false;
658  }
659 
660  if (size == 0)
661  {
662  LOG(VB_GENERAL, LOG_ERR, QString("File %1 is zero-byte, aborting.")
663  .arg(filename));
664  return false;
665  }
666 
667  return true;
668 }
669 
670 static void UpdateFileSize(ProgramInfo *program_info)
671 {
672  qint64 size = GetFileSize(program_info);
673 
674  if (size != (qint64)program_info->GetFilesize())
675  program_info->SaveFilesize(size);
676 }
677 
678 static bool IsMarked(uint chanid, QDateTime starttime)
679 {
680  MSqlQuery mark_query(MSqlQuery::InitCon());
681  mark_query.prepare("SELECT commflagged, count(rm.type) "
682  "FROM recorded r "
683  "LEFT JOIN recordedmarkup rm ON "
684  "( r.chanid = rm.chanid AND "
685  "r.starttime = rm.starttime AND "
686  "type in (:MARK_START,:MARK_END)) "
687  "WHERE r.chanid = :CHANID AND "
688  "r.starttime = :STARTTIME "
689  "GROUP BY COMMFLAGGED;");
690  mark_query.bindValue(":MARK_START", MARK_COMM_START);
691  mark_query.bindValue(":MARK_END", MARK_COMM_END);
692  mark_query.bindValue(":CHANID", chanid);
693  mark_query.bindValue(":STARTTIME", starttime);
694 
695  if (mark_query.exec() && mark_query.isActive() &&
696  mark_query.size() > 0)
697  {
698  if (mark_query.next())
699  {
700  int flagStatus = mark_query.value(0).toInt();
701  int marksFound = mark_query.value(1).toInt();
702 
703  QString flagStatusStr = "UNKNOWN";
704  switch (flagStatus) {
706  flagStatusStr = "Not Flagged";
707  break;
708  case COMM_FLAG_DONE:
709  flagStatusStr = QString("Flagged with %1 breaks")
710  .arg(marksFound / 2);
711  break;
713  flagStatusStr = "Flagging";
714  break;
715  case COMM_FLAG_COMMFREE:
716  flagStatusStr = "Commercial Free";
717  break;
718  }
719 
720  LOG(VB_COMMFLAG, LOG_INFO,
721  QString("Status for chanid %1 @ %2 is '%3'")
722  .arg(chanid).arg(starttime.toString(Qt::ISODate))
723  .arg(flagStatusStr));
724 
725  if ((flagStatus == COMM_FLAG_NOT_FLAGGED) && (marksFound == 0))
726  return false;
727  }
728  }
729  return true;
730 }
731 
732 static int FlagCommercials(ProgramInfo *program_info, int jobid,
733  const QString &outputfilename, bool useDB, bool fullSpeed)
734 {
735  global_program_info = program_info;
736 
737  int breaksFound = 0;
738 
739  // configure commercial detection method
740  SkipTypes commDetectMethod =
742  "CommercialSkipMethod", COMM_DETECT_ALL);
743 
744  if (cmdline.toBool("commmethod"))
745  {
746  // pull commercial detection method from command line
747  QString commmethod = cmdline.toString("commmethod");
748 
749  // assume definition as integer value
750  bool ok = true;
751  commDetectMethod = (SkipTypes) commmethod.toInt(&ok);
752  if (!ok)
753  {
754  // not an integer, attempt comma separated list
755  commDetectMethod = COMM_DETECT_UNINIT;
756  QMap<QString, SkipTypes>::const_iterator sit;
757 
758  QStringList list = commmethod.split(",", QString::SkipEmptyParts);
759  QStringList::const_iterator it = list.begin();
760  for (; it != list.end(); ++it)
761  {
762  QString val = (*it).toLower();
763  if (val == "off")
764  {
765  commDetectMethod = COMM_DETECT_OFF;
766  break;
767  }
768 
769  if (!skipTypes->contains(val))
770  {
771  cerr << "Failed to decode --method option '"
772  << val.toLatin1().constData()
773  << "'" << endl;
774  return GENERIC_EXIT_INVALID_CMDLINE;
775  }
776 
777  // append flag method to list
778  commDetectMethod = (SkipTypes) ((int)commDetectMethod
779  || (int)skipTypes->value(val));
780  }
781 
782  }
783  if (commDetectMethod == COMM_DETECT_UNINIT)
784  return GENERIC_EXIT_INVALID_CMDLINE;
785  }
786  else if (!cmdline.toBool("skipdb"))
787  {
788  // if not manually specified, and we have a database to access
789  // pull the commflag type from the channel
790  MSqlQuery query(MSqlQuery::InitCon());
791  query.prepare("SELECT commmethod FROM channel "
792  "WHERE chanid = :CHANID;");
793  query.bindValue(":CHANID", program_info->GetChanID());
794 
795  if (!query.exec())
796  {
797  // if the query fails, return with an error
798  commDetectMethod = COMM_DETECT_UNINIT;
799  MythDB::DBError("FlagCommercials", query);
800  }
801  else if (query.next())
802  {
803  commDetectMethod = (enum SkipTypes)query.value(0).toInt();
804  if (commDetectMethod == COMM_DETECT_COMMFREE)
805  {
806  // if the channel is commercial free, drop to the default instead
807  commDetectMethod =
809  "CommercialSkipMethod", COMM_DETECT_ALL);
810  LOG(VB_COMMFLAG, LOG_INFO,
811  QString("Chanid %1 is marked as being Commercial Free, "
812  "we will use the default commercial detection "
813  "method").arg(program_info->GetChanID()));
814  }
815  else if (commDetectMethod == COMM_DETECT_UNINIT)
816  // no value set, so use the database default
817  commDetectMethod =
819  "CommercialSkipMethod", COMM_DETECT_ALL);
820  LOG(VB_COMMFLAG, LOG_INFO,
821  QString("Using method: %1 from channel %2")
822  .arg(commDetectMethod).arg(program_info->GetChanID()));
823  }
824 
825  }
826  else if (cmdline.toBool("skipdb"))
827  // default to a cheaper method for debugging purposes
828  commDetectMethod = COMM_DETECT_BLANK;
829 
830  // if selection has failed, or intentionally disabled, drop out
831  if (commDetectMethod == COMM_DETECT_UNINIT)
832  return GENERIC_EXIT_NOT_OK;
833  else if (commDetectMethod == COMM_DETECT_OFF)
834  return GENERIC_EXIT_OK;
835 
836  frm_dir_map_t blanks;
837  recorder = NULL;
838 
839 /*
840  * is there a purpose to this not fulfilled by --getskiplist?
841  if (onlyDumpDBCommercialBreakList)
842  {
843  frm_dir_map_t commBreakList;
844  program_info->QueryCommBreakList(commBreakList);
845 
846  print_comm_flag_output(program_info, commBreakList,
847  0, NULL, outputfilename);
848 
849  global_program_info = NULL;
850  return GENERIC_EXIT_OK;
851  }
852 */
853 
854  if (!DoesFileExist(program_info))
855  {
856  LOG(VB_GENERAL, LOG_ERR,
857  "Unable to find file in defined storage paths.");
858  return GENERIC_EXIT_PERMISSIONS_ERROR;
859  }
860 
861  QString filename = get_filename(program_info);
862 
863  RingBuffer *tmprbuf = RingBuffer::Create(filename, false);
864  if (!tmprbuf)
865  {
866  LOG(VB_GENERAL, LOG_ERR,
867  QString("Unable to create RingBuffer for %1").arg(filename));
868  global_program_info = NULL;
869  return GENERIC_EXIT_PERMISSIONS_ERROR;
870  }
871 
872  if (useDB)
873  {
875  {
876  LOG(VB_GENERAL, LOG_ERR, "Unable to open commflag DB connection");
877  delete tmprbuf;
878  global_program_info = NULL;
879  return GENERIC_EXIT_DB_ERROR;
880  }
881  }
882 
884  kVideoIsNull |
885  kDecodeLowRes |
888  /* blank detector needs to be only sample center for this optimization. */
889  if ((COMM_DETECT_BLANKS == commDetectMethod) ||
890  (COMM_DETECT_2_BLANK == commDetectMethod))
891  {
892  flags = (PlayerFlags) (flags | kDecodeFewBlocks);
893  }
894 
895  MythCommFlagPlayer *cfp = new MythCommFlagPlayer(flags);
897  ctx->SetPlayingInfo(program_info);
898  ctx->SetRingBuffer(tmprbuf);
899  ctx->SetPlayer(cfp);
900  cfp->SetPlayerInfo(NULL, NULL, ctx);
901 
902  if (useDB)
903  {
904  if (program_info->GetRecordingEndTime() > MythDate::current())
905  {
907 
908  recorder = RemoteGetExistingRecorder(program_info);
909  if (recorder && (recorder->GetRecorderNumber() != -1))
910  {
912  watchingRecording = true;
913  ctx->SetRecorder(recorder);
914 
915  LOG(VB_COMMFLAG, LOG_INFO,
916  QString("mythcommflag will flag recording "
917  "currently in progress on cardid %1")
918  .arg(recorderNum));
919  }
920  else
921  {
922  recorderNum = -1;
923  watchingRecording = false;
924 
925  LOG(VB_GENERAL, LOG_ERR,
926  "Unable to find active recorder for this "
927  "recording, realtime flagging will not be enabled.");
928  }
930  }
931  }
932 
933  // TODO: Add back insertion of job if not in jobqueue
934 
935  breaksFound = DoFlagCommercials(
936  program_info, progress, fullSpeed, jobid,
937  cfp, commDetectMethod, outputfilename, useDB);
938 
939  if (progress)
940  cerr << breaksFound << "\n";
941 
942  LOG(VB_GENERAL, LOG_NOTICE, QString("Finished, %1 break(s) found.")
943  .arg(breaksFound));
944 
945  delete ctx;
946  global_program_info = NULL;
947 
948  return breaksFound;
949 }
950 
951 static int FlagCommercials( uint chanid, const QDateTime &starttime,
952  int jobid, const QString &outputfilename,
953  bool fullSpeed )
954 {
955  QString startstring = MythDate::toString(starttime, MythDate::kFilename);
956  ProgramInfo pginfo(chanid, starttime);
957 
958  if (!pginfo.GetChanID())
959  {
960  LOG(VB_GENERAL, LOG_ERR,
961  QString("No program data exists for channel %1 at %2")
962  .arg(chanid).arg(startstring));
963  return GENERIC_EXIT_NO_RECORDING_DATA;
964  }
965 
966  if (!force && JobQueue::IsJobRunning(JOB_COMMFLAG, pginfo))
967  {
968  if (progress)
969  {
970  cerr << "IN USE\n";
971  cerr << " "
972  "(the program is already being flagged elsewhere)\n";
973  }
974  LOG(VB_GENERAL, LOG_ERR, "Program is already being flagged elsewhere");
975  return GENERIC_EXIT_IN_USE;
976  }
977 
978 
979  if (progress)
980  {
981  cerr << "MythTV Commercial Flagger, flagging commercials for:" << endl;
982  if (pginfo.GetSubtitle().isEmpty())
983  cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << endl;
984  else
985  cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << " - "
986  << pginfo.GetSubtitle().toLocal8Bit().constData() << endl;
987  }
988 
989  return FlagCommercials(&pginfo, jobid, outputfilename, true, fullSpeed);
990 }
991 
992 static int FlagCommercials(QString filename, int jobid,
993  const QString &outputfilename, bool useDB,
994  bool fullSpeed)
995 {
996 
997  if (progress)
998  {
999  cerr << "MythTV Commercial Flagger, flagging commercials for:" << endl
1000  << " " << filename.toLatin1().constData() << endl;
1001  }
1002 
1003  ProgramInfo pginfo(filename);
1004  return FlagCommercials(&pginfo, jobid, outputfilename, useDB, fullSpeed);
1005 }
1006 
1007 static int RebuildSeekTable(ProgramInfo *pginfo, int jobid)
1008 {
1009  QString filename = get_filename(pginfo);
1010 
1011  if (!DoesFileExist(pginfo))
1012  {
1013  // file not found on local filesystem
1014  // assume file is in Video storage group on local backend
1015  // and try again
1016 
1017  filename = QString("myth://Videos@%1/%2")
1018  .arg(gCoreContext->GetHostName()).arg(filename);
1019  pginfo->SetPathname(filename);
1020  if (!DoesFileExist(pginfo))
1021  {
1022  LOG(VB_GENERAL, LOG_ERR,
1023  "Unable to find file in defined storage paths.");
1024  return GENERIC_EXIT_PERMISSIONS_ERROR;
1025  }
1026  }
1027 
1028  // Update the file size since mythcommflag --rebuild is often used in user
1029  // scripts after transcoding or other size-changing operations
1030  UpdateFileSize(pginfo);
1031 
1032  RingBuffer *tmprbuf = RingBuffer::Create(filename, false);
1033  if (!tmprbuf)
1034  {
1035  LOG(VB_GENERAL, LOG_ERR,
1036  QString("Unable to create RingBuffer for %1").arg(filename));
1037  return GENERIC_EXIT_PERMISSIONS_ERROR;
1038  }
1039 
1043  ctx->SetPlayingInfo(pginfo);
1044  ctx->SetRingBuffer(tmprbuf);
1045  ctx->SetPlayer(cfp);
1046  cfp->SetPlayerInfo(NULL, NULL, ctx);
1047 
1048  if (progress)
1049  {
1050  QString time = QDateTime::currentDateTime().toString(Qt::TextDate);
1051  cerr << "Rebuild started at " << qPrintable(time) << endl;
1052  }
1053 
1054  cfp->RebuildSeekTable(progress);
1055 
1056  if (progress)
1057  {
1058  QString time = QDateTime::currentDateTime().toString(Qt::TextDate);
1059  cerr << "Rebuild completed at " << qPrintable(time) << endl;
1060  }
1061 
1062  delete ctx;
1063 
1064  return GENERIC_EXIT_OK;
1065 }
1066 
1067 static int RebuildSeekTable(QString filename, int jobid)
1068 {
1069  if (progress)
1070  {
1071  cerr << "MythTV Commercial Flagger, building seek table for:" << endl
1072  << " " << filename.toLatin1().constData() << endl;
1073  }
1074  ProgramInfo pginfo(filename);
1075  return RebuildSeekTable(&pginfo, jobid);
1076 }
1077 
1078 static int RebuildSeekTable(uint chanid, QDateTime starttime, int jobid)
1079 {
1080  ProgramInfo pginfo(chanid, starttime);
1081  if (progress)
1082  {
1083  cerr << "MythTV Commercial Flagger, building seek table for:" << endl;
1084  if (pginfo.GetSubtitle().isEmpty())
1085  cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << endl;
1086  else
1087  cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << " - "
1088  << pginfo.GetSubtitle().toLocal8Bit().constData() << endl;
1089  }
1090  return RebuildSeekTable(&pginfo, jobid);
1091 }
1092 
1093 int main(int argc, char *argv[])
1094 {
1095  int result = GENERIC_EXIT_OK;
1096 
1097 // QString allStart = "19700101000000";
1098 // QString allEnd = MythDate::current().toString("yyyyMMddhhmmss");
1099  int jobType = JOB_NONE;
1100 
1101  if (!cmdline.Parse(argc, argv))
1102  {
1103  cmdline.PrintHelp();
1104  return GENERIC_EXIT_INVALID_CMDLINE;
1105  }
1106 
1107  if (cmdline.toBool("showhelp"))
1108  {
1109  cmdline.PrintHelp();
1110  return GENERIC_EXIT_OK;
1111  }
1112 
1113  if (cmdline.toBool("showversion"))
1114  {
1116  return GENERIC_EXIT_OK;
1117  }
1118 
1119  QCoreApplication a(argc, argv);
1120  QCoreApplication::setApplicationName(MYTH_APPNAME_MYTHCOMMFLAG);
1121  int retval = cmdline.ConfigureLogging("general",
1122  !cmdline.toBool("noprogress"));
1123  if (retval != GENERIC_EXIT_OK)
1124  return retval;
1125 
1126  CleanupGuard callCleanup(cleanup);
1127 
1128 #ifndef _WIN32
1129  QList<int> signallist;
1130  signallist << SIGINT << SIGTERM << SIGSEGV << SIGABRT << SIGBUS << SIGFPE
1131  << SIGILL;
1132 #if ! CONFIG_DARWIN
1133  signallist << SIGRTMIN;
1134 #endif
1135  SignalHandler::Init(signallist);
1136  signal(SIGHUP, SIG_IGN);
1137 #endif
1138 
1139  gContext = new MythContext(MYTH_BINARY_VERSION);
1140  if (!gContext->Init( false, /*use gui*/
1141  false, /*prompt for backend*/
1142  false, /*bypass auto discovery*/
1143  cmdline.toBool("skipdb"))) /*ignoreDB*/
1144  {
1145  LOG(VB_GENERAL, LOG_EMERG, "Failed to init MythContext, exiting.");
1146  return GENERIC_EXIT_NO_MYTHCONTEXT;
1147  }
1149 
1150  MythTranslation::load("mythfrontend");
1151 
1152  if (cmdline.toBool("chanid") && cmdline.toBool("starttime"))
1153  {
1154  // operate on a recording in the database
1155  uint chanid = cmdline.toUInt("chanid");
1156  QDateTime starttime = cmdline.toDateTime("starttime");
1157 
1158  if (cmdline.toBool("clearskiplist"))
1159  return ClearSkipList(chanid, starttime);
1160  if (cmdline.toBool("gencutlist"))
1161  return CopySkipListToCutList(chanid, starttime);
1162  if (cmdline.toBool("clearcutlist"))
1163  return SetCutList(chanid, starttime, "");
1164  if (cmdline.toBool("setcutlist"))
1165  return SetCutList(chanid, starttime, cmdline.toString("setcutlist"));
1166  if (cmdline.toBool("getcutlist"))
1167  return GetMarkupList("cutlist", chanid, starttime);
1168  if (cmdline.toBool("getskiplist"))
1169  return GetMarkupList("commflag", chanid, starttime);
1170 
1171  // TODO: check for matching jobid
1172  // create temporary id to operate off of if not
1173 
1174  if (cmdline.toBool("queue"))
1175  QueueCommFlagJob(chanid, starttime, cmdline.toBool("rebuild"));
1176  else if (cmdline.toBool("rebuild"))
1177  result = RebuildSeekTable(chanid, starttime, -1);
1178  else
1179  result = FlagCommercials(chanid, starttime, -1,
1180  cmdline.toString("outputfile"), true);
1181  }
1182  else if (cmdline.toBool("jobid"))
1183  {
1184  jobID = cmdline.toInt("jobid");
1185  uint chanid;
1186  QDateTime starttime;
1187 
1188  if (!JobQueue::GetJobInfoFromID(jobID, jobType, chanid, starttime))
1189  {
1190  cerr << "mythcommflag: ERROR: Unable to find DB info for "
1191  << "JobQueue ID# " << jobID << endl;
1192  return GENERIC_EXIT_NO_RECORDING_DATA;
1193  }
1194  force = true;
1195  int jobQueueCPU = gCoreContext->GetNumSetting("JobQueueCPU", 0);
1196 
1197  if (jobQueueCPU < 2)
1198  {
1199  myth_nice(17);
1200  myth_ioprio((0 == jobQueueCPU) ? 8 : 7);
1201  }
1202 
1203  progress = false;
1204 
1205  int ret = 0;
1206 
1208  RebuildSeekTable(chanid, starttime, jobID);
1209  else
1210  ret = FlagCommercials(chanid, starttime, jobID, "", jobQueueCPU != 0);
1211 
1212  if (ret > GENERIC_EXIT_NOT_OK)
1213  JobQueue::ChangeJobStatus(jobID, JOB_ERRORED,
1214  QCoreApplication::translate("(mythcommflag)",
1215  "Failed with exit status %1",
1216  "Job status").arg(ret));
1217  else
1218  JobQueue::ChangeJobStatus(jobID, JOB_FINISHED,
1219  QCoreApplication::translate("(mythcommflag)",
1220  "%1 commercial break(s)",
1221  "Job status").arg(ret));
1222  }
1223  else if (cmdline.toBool("video"))
1224  {
1225  // build skiplist for video file
1226  return RebuildSeekTable(cmdline.toString("video"), -1);
1227  }
1228  else if (cmdline.toBool("file"))
1229  {
1230  if (cmdline.toBool("skipdb"))
1231  {
1232  if (cmdline.toBool("rebuild"))
1233  {
1234  cerr << "The --rebuild parameter builds the seektable for "
1235  "internal MythTV use only. It cannot be used in "
1236  "combination with --skipdb." << endl;
1237  return GENERIC_EXIT_INVALID_CMDLINE;
1238  }
1239 
1240  if (!cmdline.toBool("outputfile"))
1241  cmdline.SetValue("outputfile", "-");
1242 
1243  // perform commercial flagging on file outside the database
1244  FlagCommercials(cmdline.toString("file"), -1,
1245  cmdline.toString("outputfile"),
1246  !cmdline.toBool("skipdb"),
1247  true);
1248  }
1249  else
1250  {
1251  ProgramInfo pginfo(cmdline.toString("file"));
1252  // pass chanid and starttime
1253  // inefficient, but it lets the other function
1254  // handle sanity checking
1255  if (cmdline.toBool("rebuild"))
1256  result = RebuildSeekTable(pginfo.GetChanID(),
1257  pginfo.GetRecordingStartTime(),
1258  -1);
1259  else
1260  result = FlagCommercials(pginfo.GetChanID(),
1261  pginfo.GetRecordingStartTime(),
1262  -1, cmdline.toString("outputfile"),
1263  true);
1264  }
1265  }
1266  else if (cmdline.toBool("queue"))
1267  {
1268  // run flagging for all recordings with no skiplist
1269  MSqlQuery query(MSqlQuery::InitCon());
1270  query.prepare("SELECT r.chanid, r.starttime, c.commmethod "
1271  "FROM recorded AS r "
1272  "LEFT JOIN channel AS c ON r.chanid=c.chanid "
1273 // "WHERE startime >= :STARTTIME AND endtime <= :ENDTIME "
1274  "ORDER BY starttime;");
1275  //query.bindValue(":STARTTIME", allStart);
1276  //query.bindValue(":ENDTIME", allEnd);
1277 
1278  if (query.exec() && query.isActive() && query.size() > 0)
1279  {
1280  QDateTime starttime;
1281  uint chanid;
1282 
1283  while (query.next())
1284  {
1285  starttime = MythDate::fromString(query.value(1).toString());
1286  chanid = query.value(0).toUInt();
1287 
1288  if (!cmdline.toBool("force") && !cmdline.toBool("rebuild"))
1289  {
1290  // recording is already flagged
1291  if (IsMarked(chanid, starttime))
1292  continue;
1293 
1294  // channel is marked as commercial free
1295  if (query.value(2).toInt() == COMM_DETECT_COMMFREE)
1296  continue;
1297 
1298  // recording rule did not enable commflagging
1299 #if 0
1300  RecordingInfo recinfo(chanid, starttime);
1301  if (!(recinfo.GetAutoRunJobs() & JOB_COMMFLAG))
1302  continue;
1303 #endif
1304  }
1305 
1306  QueueCommFlagJob(chanid, starttime, cmdline.toBool("rebuild"));
1307  }
1308  }
1309 
1310  }
1311  else
1312  {
1313  LOG(VB_GENERAL, LOG_ERR,
1314  "No valid combination of command inputs received.");
1315  cmdline.PrintHelp();
1316  return GENERIC_EXIT_INVALID_CMDLINE;
1317  }
1318 
1319  return result;
1320 }
1321 
1322 
1323 /* vim: set expandtab tabstop=4 shiftwidth=4: */