00001
00002 #include <unistd.h>
00003 #include <sys/time.h>
00004
00005
00006 #include <cstdlib>
00007 #include <cstdio>
00008 #include <ctime>
00009 #include <cmath>
00010
00011
00012 #include <string>
00013 #include <iostream>
00014 #include <fstream>
00015 using namespace std;
00016
00017
00018 #include <QCoreApplication>
00019 #include <QString>
00020 #include <QRegExp>
00021 #include <QDir>
00022 #include <QEvent>
00023
00024
00025 #include "mythmiscutil.h"
00026 #include "exitcodes.h"
00027 #include "mythcontext.h"
00028 #include "mythdb.h"
00029 #include "mythversion.h"
00030 #include "mythcommflagplayer.h"
00031 #include "programinfo.h"
00032 #include "remoteutil.h"
00033 #include "remotefile.h"
00034 #include "tvremoteutil.h"
00035 #include "jobqueue.h"
00036 #include "remoteencoder.h"
00037 #include "ringbuffer.h"
00038 #include "commandlineparser.h"
00039 #include "mythtranslation.h"
00040 #include "mythlogging.h"
00041
00042
00043 #include "CommDetectorBase.h"
00044 #include "CommDetectorFactory.h"
00045 #include "SlotRelayer.h"
00046 #include "CustomEventRelayer.h"
00047
00048 #define LOC QString("MythCommFlag: ")
00049 #define LOC_WARN QString("MythCommFlag, Warning: ")
00050 #define LOC_ERR QString("MythCommFlag, Error: ")
00051
00052 namespace
00053 {
00054 void cleanup()
00055 {
00056 delete gContext;
00057 gContext = NULL;
00058
00059 }
00060
00061 class CleanupGuard
00062 {
00063 public:
00064 typedef void (*CleanupFunc)();
00065
00066 public:
00067 CleanupGuard(CleanupFunc cleanFunction) :
00068 m_cleanFunction(cleanFunction) {}
00069
00070 ~CleanupGuard()
00071 {
00072 m_cleanFunction();
00073 }
00074
00075 private:
00076 CleanupFunc m_cleanFunction;
00077 };
00078 }
00079
00080 int quiet = 0;
00081 bool progress = true;
00082 bool force = false;
00083
00084 MythCommFlagCommandLineParser cmdline;
00085
00086 bool watchingRecording = false;
00087 CommDetectorBase* commDetector = NULL;
00088 RemoteEncoder* recorder = NULL;
00089 ProgramInfo *global_program_info = NULL;
00090 int recorderNum = -1;
00091
00092 int jobID = -1;
00093 int lastCmd = -1;
00094
00095 static QMap<QString,SkipTypes> *init_skip_types();
00096 QMap<QString,SkipTypes> *skipTypes = init_skip_types();
00097
00098 static QMap<QString,SkipTypes> *init_skip_types(void)
00099 {
00100 QMap<QString,SkipTypes> *tmp = new QMap<QString,SkipTypes>;
00101 (*tmp)["commfree"] = COMM_DETECT_COMMFREE;
00102 (*tmp)["uninit"] = COMM_DETECT_UNINIT;
00103 (*tmp)["off"] = COMM_DETECT_OFF;
00104 (*tmp)["blank"] = COMM_DETECT_BLANKS;
00105 (*tmp)["blanks"] = COMM_DETECT_BLANKS;
00106 (*tmp)["scene"] = COMM_DETECT_SCENE;
00107 (*tmp)["blankscene"] = COMM_DETECT_BLANK_SCENE;
00108 (*tmp)["blank_scene"] = COMM_DETECT_BLANK_SCENE;
00109 (*tmp)["logo"] = COMM_DETECT_LOGO;
00110 (*tmp)["all"] = COMM_DETECT_ALL;
00111 (*tmp)["d2"] = COMM_DETECT_2;
00112 (*tmp)["d2_logo"] = COMM_DETECT_2_LOGO;
00113 (*tmp)["d2_blank"] = COMM_DETECT_2_BLANK;
00114 (*tmp)["d2_scene"] = COMM_DETECT_2_SCENE;
00115 (*tmp)["d2_all"] = COMM_DETECT_2_ALL;
00116 return tmp;
00117 }
00118
00119 typedef enum
00120 {
00121 kOutputMethodEssentials = 1,
00122 kOutputMethodFull,
00123 } OutputMethod;
00124 OutputMethod outputMethod = kOutputMethodEssentials;
00125
00126 static QMap<QString,OutputMethod> *init_output_types();
00127 QMap<QString,OutputMethod> *outputTypes = init_output_types();
00128
00129 static QMap<QString,OutputMethod> *init_output_types(void)
00130 {
00131 QMap<QString,OutputMethod> *tmp = new QMap<QString,OutputMethod>;
00132 (*tmp)["essentials"] = kOutputMethodEssentials;
00133 (*tmp)["full"] = kOutputMethodFull;
00134 return tmp;
00135 }
00136
00137 static QString get_filename(ProgramInfo *program_info)
00138 {
00139 QString filename = program_info->GetPathname();
00140 if (!QFile::exists(filename))
00141 filename = program_info->GetPlaybackURL(true);
00142 return filename;
00143 }
00144
00145 static int QueueCommFlagJob(uint chanid, QDateTime starttime, bool rebuild)
00146 {
00147 QString startstring = starttime.toString("yyyyMMddhhmmss");
00148 const ProgramInfo pginfo(chanid, starttime);
00149
00150 if (!pginfo.GetChanID())
00151 {
00152 if (progress)
00153 {
00154 QString tmp = QString(
00155 "Unable to find program info for chanid %1 @ %2")
00156 .arg(chanid).arg(startstring);
00157 cerr << tmp.toLocal8Bit().constData() << endl;
00158 }
00159 return GENERIC_EXIT_NO_RECORDING_DATA;
00160 }
00161
00162 if (cmdline.toBool("dryrun"))
00163 {
00164 QString tmp = QString("Job have been queued for chanid %1 @ %2")
00165 .arg(chanid).arg(startstring);
00166 cerr << tmp.toLocal8Bit().constData() << endl;
00167 return GENERIC_EXIT_OK;
00168 }
00169
00170 bool result = JobQueue::QueueJob(JOB_COMMFLAG,
00171 pginfo.GetChanID(), pginfo.GetRecordingStartTime(), "", "", "",
00172 rebuild ? JOB_REBUILD : 0, JOB_QUEUED, QDateTime());
00173
00174 if (result)
00175 {
00176 if (progress)
00177 {
00178 QString tmp = QString("Job Queued for chanid %1 @ %2")
00179 .arg(chanid).arg(startstring);
00180 cerr << tmp.toLocal8Bit().constData() << endl;
00181 }
00182 return GENERIC_EXIT_OK;
00183 }
00184 else
00185 {
00186 if (progress)
00187 {
00188 QString tmp = QString("Error queueing job for chanid %1 @ %2")
00189 .arg(chanid).arg(startstring);
00190 cerr << tmp.toLocal8Bit().constData() << endl;
00191 }
00192 return GENERIC_EXIT_DB_ERROR;
00193 }
00194
00195 return GENERIC_EXIT_OK;
00196 }
00197
00198 static int CopySkipListToCutList(uint chanid, QDateTime starttime)
00199 {
00200 frm_dir_map_t cutlist;
00201 frm_dir_map_t::const_iterator it;
00202
00203 QString startstring = starttime.toString("yyyyMMddhhmmss");
00204 const ProgramInfo pginfo(chanid, starttime);
00205
00206 if (!pginfo.GetChanID())
00207 {
00208 LOG(VB_GENERAL, LOG_ERR,
00209 QString("No program data exists for channel %1 at %2")
00210 .arg(chanid).arg(startstring));
00211 return GENERIC_EXIT_NO_RECORDING_DATA;
00212 }
00213
00214 pginfo.QueryCommBreakList(cutlist);
00215 for (it = cutlist.begin(); it != cutlist.end(); ++it)
00216 if (*it == MARK_COMM_START)
00217 cutlist[it.key()] = MARK_CUT_START;
00218 else
00219 cutlist[it.key()] = MARK_CUT_END;
00220 pginfo.SaveCutList(cutlist);
00221
00222 return GENERIC_EXIT_OK;
00223 }
00224
00225 static int ClearSkipList(uint chanid, QDateTime starttime)
00226 {
00227 QString startstring = starttime.toString("yyyyMMddhhmmss");
00228 const ProgramInfo pginfo(chanid, starttime);
00229
00230 if (!pginfo.GetChanID())
00231 {
00232 LOG(VB_GENERAL, LOG_ERR,
00233 QString("No program data exists for channel %1 at %2")
00234 .arg(chanid).arg(startstring));
00235 return GENERIC_EXIT_NO_RECORDING_DATA;
00236 }
00237
00238 frm_dir_map_t skiplist;
00239 pginfo.SaveCommBreakList(skiplist);
00240
00241 LOG(VB_GENERAL, LOG_NOTICE, "Commercial skip list cleared");
00242
00243 return GENERIC_EXIT_OK;
00244 }
00245
00246 static int SetCutList(uint chanid, QDateTime starttime, QString newCutList)
00247 {
00248 frm_dir_map_t cutlist;
00249
00250 newCutList.replace(QRegExp(" "), "");
00251
00252 QStringList tokens = newCutList.split(",", QString::SkipEmptyParts);
00253
00254 for (int i = 0; i < tokens.size(); i++)
00255 {
00256 QStringList cutpair = tokens[i].split("-", QString::SkipEmptyParts);
00257 cutlist[cutpair[0].toInt()] = MARK_CUT_START;
00258 cutlist[cutpair[1].toInt()] = MARK_CUT_END;
00259 }
00260
00261 QString startstring = starttime.toString("yyyyMMddhhmmss");
00262 const ProgramInfo pginfo(chanid, starttime);
00263
00264 if (!pginfo.GetChanID())
00265 {
00266 LOG(VB_GENERAL, LOG_ERR,
00267 QString("No program data exists for channel %1 at %2")
00268 .arg(chanid).arg(startstring));
00269 return GENERIC_EXIT_NO_RECORDING_DATA;
00270 }
00271
00272 pginfo.SaveCutList(cutlist);
00273
00274 LOG(VB_GENERAL, LOG_NOTICE, QString("Cutlist set to: %1").arg(newCutList));
00275
00276 return GENERIC_EXIT_OK;
00277 }
00278
00279 static int GetMarkupList(QString list, uint chanid, QDateTime starttime)
00280 {
00281 frm_dir_map_t cutlist;
00282 frm_dir_map_t::const_iterator it;
00283 QString result;
00284
00285 QString startstring = starttime.toString("yyyyMMddhhmmss");
00286 const ProgramInfo pginfo(chanid, starttime);
00287
00288 if (!pginfo.GetChanID())
00289 {
00290 LOG(VB_GENERAL, LOG_ERR,
00291 QString("No program data exists for channel %1 at %2")
00292 .arg(chanid).arg(startstring));
00293 return GENERIC_EXIT_NO_RECORDING_DATA;
00294 }
00295
00296 if (list == "cutlist")
00297 pginfo.QueryCutList(cutlist);
00298 else
00299 pginfo.QueryCommBreakList(cutlist);
00300
00301 uint64_t lastStart = 0;
00302 for (it = cutlist.begin(); it != cutlist.end(); ++it)
00303 {
00304 if ((*it == MARK_COMM_START) ||
00305 (*it == MARK_CUT_START))
00306 {
00307 if (!result.isEmpty())
00308 result += ",";
00309 lastStart = it.key();
00310 result += QString("%1-").arg(lastStart);
00311 }
00312 else
00313 {
00314 if (result.isEmpty())
00315 result += "0-";
00316 result += QString("%1").arg(it.key());
00317 }
00318 }
00319
00320 if (result.endsWith('-'))
00321 {
00322 uint64_t lastFrame = pginfo.QueryLastFrameInPosMap() + 60;
00323 if (lastFrame > lastStart)
00324 result += QString("%1").arg(lastFrame);
00325 }
00326
00327 if (list == "cutlist")
00328 cout << QString("Cutlist: %1\n").arg(result).toLocal8Bit().constData();
00329 else
00330 {
00331 cout << QString("Commercial Skip List: %1\n")
00332 .arg(result).toLocal8Bit().constData();
00333 }
00334
00335 return GENERIC_EXIT_OK;
00336 }
00337
00338 static void streamOutCommercialBreakList(
00339 ostream &output, const frm_dir_map_t &commercialBreakList)
00340 {
00341 if (progress)
00342 output << "----------------------------" << endl;
00343
00344 if (commercialBreakList.empty())
00345 {
00346 if (progress)
00347 output << "No breaks" << endl;
00348 }
00349 else
00350 {
00351 frm_dir_map_t::const_iterator it = commercialBreakList.begin();
00352 for (; it != commercialBreakList.end(); ++it)
00353 {
00354 output << "framenum: " << it.key() << "\tmarktype: " << *it
00355 << endl;
00356 }
00357 }
00358
00359 if (progress)
00360 output << "----------------------------" << endl;
00361 }
00362
00363 static void print_comm_flag_output(
00364 const ProgramInfo *program_info,
00365 const frm_dir_map_t &commBreakList,
00366 uint64_t frame_count,
00367 const CommDetectorBase *commDetect,
00368 const QString &output_filename)
00369 {
00370 if (output_filename.isEmpty())
00371 return;
00372
00373 ostream *out = &cout;
00374 if (output_filename != "-")
00375 {
00376 QByteArray tmp = output_filename.toLocal8Bit();
00377 out = new fstream(tmp.constData(), ios::app | ios::out );
00378 }
00379
00380 if (progress)
00381 {
00382 QString tmp = "";
00383 if (program_info->GetChanID())
00384 {
00385 tmp = QString("commercialBreakListFor: %1 on %2 @ %3")
00386 .arg(program_info->GetTitle())
00387 .arg(program_info->GetChanID())
00388 .arg(program_info->GetRecordingStartTime(ISODate));
00389 }
00390 else
00391 {
00392 tmp = QString("commercialBreakListFor: %1")
00393 .arg(program_info->GetPathname());
00394 }
00395
00396 const QByteArray tmp2 = tmp.toLocal8Bit();
00397 *out << tmp2.constData() << endl;
00398
00399 if (frame_count)
00400 *out << "totalframecount: " << frame_count << endl;
00401 }
00402
00403 if (commDetect)
00404 commDetect->PrintFullMap(*out, &commBreakList, progress);
00405 else
00406 streamOutCommercialBreakList(*out, commBreakList);
00407
00408 if (output_filename != "-")
00409 delete out;
00410 }
00411
00412 static void commDetectorBreathe()
00413 {
00414
00415
00416 qApp->processEvents();
00417
00418 if (jobID != -1)
00419 {
00420 int curCmd = JobQueue::GetJobCmd(jobID);
00421 if (curCmd == lastCmd)
00422 return;
00423
00424 switch (curCmd)
00425 {
00426 case JOB_STOP:
00427 {
00428 commDetector->stop();
00429 break;
00430 }
00431 case JOB_PAUSE:
00432 {
00433 JobQueue::ChangeJobStatus(jobID, JOB_PAUSED,
00434 QObject::tr("Paused"));
00435 commDetector->pause();
00436 break;
00437 }
00438 case JOB_RESUME:
00439 {
00440 JobQueue::ChangeJobStatus(jobID, JOB_RUNNING,
00441 QObject::tr("Running"));
00442 commDetector->resume();
00443 break;
00444 }
00445 }
00446 }
00447 }
00448
00449 static void commDetectorStatusUpdate(const QString& status)
00450 {
00451 if (jobID != -1)
00452 {
00453 JobQueue::ChangeJobStatus(jobID, JOB_RUNNING, status);
00454 JobQueue::ChangeJobComment(jobID, status);
00455 }
00456 }
00457
00458 static void commDetectorGotNewCommercialBreakList(void)
00459 {
00460 frm_dir_map_t newCommercialMap;
00461 commDetector->GetCommercialBreakList(newCommercialMap);
00462
00463 frm_dir_map_t::Iterator it = newCommercialMap.begin();
00464 QString message = "COMMFLAG_UPDATE ";
00465 message += global_program_info->MakeUniqueKey();
00466
00467 for (it = newCommercialMap.begin();
00468 it != newCommercialMap.end(); ++it)
00469 {
00470 if (it != newCommercialMap.begin())
00471 message += ",";
00472 else
00473 message += " ";
00474 message += QString("%1:%2").arg(it.key())
00475 .arg(*it);
00476 }
00477
00478 LOG(VB_COMMFLAG, LOG_INFO,
00479 QString("mythcommflag sending update: %1").arg(message));
00480
00481 gCoreContext->SendMessage(message);
00482 }
00483
00484 static void incomingCustomEvent(QEvent* e)
00485 {
00486 if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
00487 {
00488 MythEvent *me = (MythEvent *)e;
00489 QString message = me->Message();
00490
00491 message = message.simplified();
00492 QStringList tokens = message.split(" ", QString::SkipEmptyParts);
00493
00494 LOG(VB_COMMFLAG, LOG_INFO,
00495 QString("mythcommflag: Received Event: '%1'") .arg(message));
00496
00497 if ((watchingRecording) && (tokens.size() >= 3) &&
00498 (tokens[0] == "DONE_RECORDING"))
00499 {
00500 int cardnum = tokens[1].toInt();
00501 int filelen = tokens[2].toInt();
00502
00503 message = QString("mythcommflag: Received a "
00504 "DONE_RECORDING event for card %1. ")
00505 .arg(cardnum);
00506
00507 if (recorderNum != -1 && cardnum == recorderNum)
00508 {
00509 commDetector->recordingFinished(filelen);
00510 watchingRecording = false;
00511 message += "Informed CommDetector that recording has finished.";
00512 LOG(VB_COMMFLAG, LOG_INFO, message);
00513 }
00514 }
00515
00516 if ((tokens.size() >= 2) && (tokens[0] == "COMMFLAG_REQUEST"))
00517 {
00518 uint chanid = 0;
00519 QDateTime recstartts;
00520 ProgramInfo::ExtractKey(tokens[1], chanid, recstartts);
00521
00522 message = QString("mythcommflag: Received a "
00523 "COMMFLAG_REQUEST event for chanid %1 @ %2. ")
00524 .arg(chanid).arg(recstartts.toString());
00525
00526 if ((global_program_info->GetChanID() == chanid) &&
00527 (global_program_info->GetRecordingStartTime() == recstartts))
00528 {
00529 commDetector->requestCommBreakMapUpdate();
00530 message += "Requested CommDetector to generate new break list.";
00531 LOG(VB_COMMFLAG, LOG_INFO, message);
00532 }
00533 }
00534 }
00535 }
00536
00537 static int DoFlagCommercials(
00538 ProgramInfo *program_info,
00539 bool showPercentage, bool fullSpeed, int jobid,
00540 MythCommFlagPlayer* cfp, enum SkipTypes commDetectMethod,
00541 const QString &outputfilename, bool useDB)
00542 {
00543 CommDetectorFactory factory;
00544 commDetector = factory.makeCommDetector(
00545 commDetectMethod, showPercentage,
00546 fullSpeed, cfp,
00547 program_info->GetChanID(),
00548 program_info->GetScheduledStartTime(),
00549 program_info->GetScheduledEndTime(),
00550 program_info->GetRecordingStartTime(),
00551 program_info->GetRecordingEndTime(), useDB);
00552
00553 if (jobid > 0)
00554 LOG(VB_COMMFLAG, LOG_INFO,
00555 QString("mythcommflag processing JobID %1").arg(jobid));
00556
00557 if (useDB)
00558 program_info->SaveCommFlagged(COMM_FLAG_PROCESSING);
00559
00560 CustomEventRelayer *cer = new CustomEventRelayer(incomingCustomEvent);
00561 SlotRelayer *a = new SlotRelayer(commDetectorBreathe);
00562 SlotRelayer *b = new SlotRelayer(commDetectorStatusUpdate);
00563 SlotRelayer *c = new SlotRelayer(commDetectorGotNewCommercialBreakList);
00564 QObject::connect(commDetector, SIGNAL(breathe()),
00565 a, SLOT(relay()));
00566 QObject::connect(commDetector, SIGNAL(statusUpdate(const QString&)),
00567 b, SLOT(relay(const QString&)));
00568 QObject::connect(commDetector, SIGNAL(gotNewCommercialBreakList()),
00569 c, SLOT(relay()));
00570
00571 if (useDB)
00572 {
00573 LOG(VB_COMMFLAG, LOG_INFO,
00574 "mythcommflag sending COMMFLAG_START notification");
00575 QString message = "COMMFLAG_START ";
00576 message += program_info->MakeUniqueKey();
00577 gCoreContext->SendMessage(message);
00578 }
00579
00580 bool result = commDetector->go();
00581 int comms_found = 0;
00582
00583 if (result)
00584 {
00585 cfp->SaveTotalDuration();
00586
00587 frm_dir_map_t commBreakList;
00588 commDetector->GetCommercialBreakList(commBreakList);
00589 comms_found = commBreakList.size() / 2;
00590
00591 if (useDB)
00592 {
00593 program_info->SaveMarkupFlag(MARK_UPDATED_CUT);
00594 program_info->SaveCommBreakList(commBreakList);
00595 program_info->SaveCommFlagged(COMM_FLAG_DONE);
00596 }
00597
00598 print_comm_flag_output(
00599 program_info, commBreakList, cfp->GetTotalFrameCount(),
00600 (outputMethod == kOutputMethodFull) ? commDetector : NULL,
00601 outputfilename);
00602 }
00603 else
00604 {
00605 if (useDB)
00606 program_info->SaveCommFlagged(COMM_FLAG_NOT_FLAGGED);
00607 }
00608
00609 CommDetectorBase *tmp = commDetector;
00610 commDetector = NULL;
00611 sleep(1);
00612 tmp->deleteLater();
00613
00614 cer->deleteLater();
00615 c->deleteLater();
00616 b->deleteLater();
00617 a->deleteLater();
00618
00619 return comms_found;
00620 }
00621
00622 static qint64 GetFileSize(ProgramInfo *program_info)
00623 {
00624 QString filename = get_filename(program_info);
00625 qint64 size = -1;
00626
00627 if (filename.startsWith("myth://"))
00628 {
00629 RemoteFile remotefile(filename, false, false, 0);
00630 struct stat filestat;
00631
00632 if (remotefile.Exists(filename, &filestat))
00633 {
00634 size = filestat.st_size;
00635 }
00636 }
00637 else
00638 {
00639 QFile file(filename);
00640 if (file.exists())
00641 {
00642 size = file.size();
00643 }
00644 }
00645
00646 return size;
00647 }
00648
00649 static bool DoesFileExist(ProgramInfo *program_info)
00650 {
00651 QString filename = get_filename(program_info);
00652 qint64 size = GetFileSize(program_info);
00653
00654 if (size < 0)
00655 {
00656 LOG(VB_GENERAL, LOG_ERR, QString("Couldn't find file %1, aborting.")
00657 .arg(filename));
00658 return false;
00659 }
00660
00661 if (size == 0)
00662 {
00663 LOG(VB_GENERAL, LOG_ERR, QString("File %1 is zero-byte, aborting.")
00664 .arg(filename));
00665 return false;
00666 }
00667
00668 return true;
00669 }
00670
00671 static void UpdateFileSize(ProgramInfo *program_info)
00672 {
00673 qint64 size = GetFileSize(program_info);
00674
00675 if (size != (qint64)program_info->GetFilesize())
00676 program_info->SaveFilesize(size);
00677 }
00678
00679 static bool IsMarked(uint chanid, QDateTime starttime)
00680 {
00681 MSqlQuery mark_query(MSqlQuery::InitCon());
00682 mark_query.prepare("SELECT commflagged, count(rm.type) "
00683 "FROM recorded r "
00684 "LEFT JOIN recordedmarkup rm ON "
00685 "( r.chanid = rm.chanid AND "
00686 "r.starttime = rm.starttime AND "
00687 "type in (:MARK_START,:MARK_END)) "
00688 "WHERE r.chanid = :CHANID AND "
00689 "r.starttime = :STARTTIME "
00690 "GROUP BY COMMFLAGGED;");
00691 mark_query.bindValue(":MARK_START", MARK_COMM_START);
00692 mark_query.bindValue(":MARK_END", MARK_COMM_END);
00693 mark_query.bindValue(":CHANID", chanid);
00694 mark_query.bindValue(":STARTTIME", starttime);
00695
00696 if (mark_query.exec() && mark_query.isActive() &&
00697 mark_query.size() > 0)
00698 {
00699 if (mark_query.next())
00700 {
00701 int flagStatus = mark_query.value(0).toInt();
00702 int marksFound = mark_query.value(1).toInt();
00703
00704 QString flagStatusStr = "UNKNOWN";
00705 switch (flagStatus) {
00706 case COMM_FLAG_NOT_FLAGGED:
00707 flagStatusStr = "Not Flagged";
00708 break;
00709 case COMM_FLAG_DONE:
00710 flagStatusStr = QString("Flagged with %1 breaks")
00711 .arg(marksFound / 2);
00712 break;
00713 case COMM_FLAG_PROCESSING:
00714 flagStatusStr = "Flagging";
00715 break;
00716 case COMM_FLAG_COMMFREE:
00717 flagStatusStr = "Commercial Free";
00718 break;
00719 }
00720
00721 LOG(VB_COMMFLAG, LOG_INFO,
00722 QString("Status for chanid %1 @ %2 is '%3'")
00723 .arg(chanid).arg(starttime.toString(Qt::ISODate))
00724 .arg(flagStatusStr));
00725
00726 if ((flagStatus == COMM_FLAG_NOT_FLAGGED) && (marksFound == 0))
00727 return false;
00728 }
00729 }
00730 return true;
00731 }
00732
00733 static int FlagCommercials(ProgramInfo *program_info, int jobid,
00734 const QString &outputfilename, bool useDB, bool fullSpeed)
00735 {
00736 global_program_info = program_info;
00737
00738 int breaksFound = 0;
00739
00740
00741 SkipTypes commDetectMethod =
00742 (enum SkipTypes)gCoreContext->GetNumSetting(
00743 "CommercialSkipMethod", COMM_DETECT_ALL);
00744
00745 if (cmdline.toBool("commmethod"))
00746 {
00747
00748 QString commmethod = cmdline.toString("commmethod");
00749
00750
00751 bool ok = true;
00752 commDetectMethod = (SkipTypes) commmethod.toInt(&ok);
00753 if (!ok)
00754 {
00755
00756 commDetectMethod = COMM_DETECT_UNINIT;
00757 QMap<QString, SkipTypes>::const_iterator sit;
00758
00759 QStringList list = commmethod.split(",", QString::SkipEmptyParts);
00760 QStringList::const_iterator it = list.begin();
00761 for (; it != list.end(); ++it)
00762 {
00763 QString val = (*it).toLower();
00764 if (val == "off")
00765 {
00766 commDetectMethod = COMM_DETECT_OFF;
00767 break;
00768 }
00769
00770 if (!skipTypes->contains(val))
00771 {
00772 cerr << "Failed to decode --method option '"
00773 << val.toAscii().constData()
00774 << "'" << endl;
00775 return GENERIC_EXIT_INVALID_CMDLINE;
00776 }
00777
00778
00779 commDetectMethod = (SkipTypes) ((int)commDetectMethod
00780 || (int)skipTypes->value(val));
00781 }
00782
00783 }
00784 if (commDetectMethod == COMM_DETECT_UNINIT)
00785 return GENERIC_EXIT_INVALID_CMDLINE;
00786 }
00787 else if (!cmdline.toBool("skipdb"))
00788 {
00789
00790
00791 MSqlQuery query(MSqlQuery::InitCon());
00792 query.prepare("SELECT commmethod FROM channel "
00793 "WHERE chanid = :CHANID;");
00794 query.bindValue(":CHANID", program_info->GetChanID());
00795
00796 if (!query.exec())
00797 {
00798
00799 commDetectMethod = COMM_DETECT_UNINIT;
00800 MythDB::DBError("FlagCommercials", query);
00801 }
00802 else if (query.next())
00803 {
00804 commDetectMethod = (enum SkipTypes)query.value(0).toInt();
00805 if (commDetectMethod == COMM_DETECT_COMMFREE)
00806 {
00807
00808 commDetectMethod =
00809 (enum SkipTypes)gCoreContext->GetNumSetting(
00810 "CommercialSkipMethod", COMM_DETECT_ALL);
00811 LOG(VB_COMMFLAG, LOG_INFO,
00812 QString("Chanid %1 is marked as being Commercial Free, "
00813 "we will use the default commercial detection "
00814 "method").arg(program_info->GetChanID()));
00815 }
00816 else if (commDetectMethod == COMM_DETECT_UNINIT)
00817
00818 commDetectMethod =
00819 (enum SkipTypes)gCoreContext->GetNumSetting(
00820 "CommercialSkipMethod", COMM_DETECT_ALL);
00821 LOG(VB_COMMFLAG, LOG_INFO,
00822 QString("Using method: %1 from channel %2")
00823 .arg(commDetectMethod).arg(program_info->GetChanID()));
00824 }
00825
00826 }
00827 else if (cmdline.toBool("skipdb"))
00828
00829 commDetectMethod = COMM_DETECT_BLANK;
00830
00831
00832 if (commDetectMethod == COMM_DETECT_UNINIT)
00833 return GENERIC_EXIT_NOT_OK;
00834 else if (commDetectMethod == COMM_DETECT_OFF)
00835 return GENERIC_EXIT_OK;
00836
00837 frm_dir_map_t blanks;
00838 recorder = NULL;
00839
00840
00841
00842
00843
00844
00845
00846
00847
00848
00849
00850
00851
00852
00853
00854
00855 if (!DoesFileExist(program_info))
00856 {
00857 LOG(VB_GENERAL, LOG_ERR,
00858 "Unable to find file in defined storage paths.");
00859 return GENERIC_EXIT_PERMISSIONS_ERROR;
00860 }
00861
00862 QString filename = get_filename(program_info);
00863
00864 RingBuffer *tmprbuf = RingBuffer::Create(filename, false);
00865 if (!tmprbuf)
00866 {
00867 LOG(VB_GENERAL, LOG_ERR,
00868 QString("Unable to create RingBuffer for %1").arg(filename));
00869 global_program_info = NULL;
00870 return GENERIC_EXIT_PERMISSIONS_ERROR;
00871 }
00872
00873 if (useDB)
00874 {
00875 if (!MSqlQuery::testDBConnection())
00876 {
00877 LOG(VB_GENERAL, LOG_ERR, "Unable to open commflag DB connection");
00878 delete tmprbuf;
00879 global_program_info = NULL;
00880 return GENERIC_EXIT_DB_ERROR;
00881 }
00882 }
00883
00884 PlayerFlags flags = (PlayerFlags)(kAudioMuted |
00885 kVideoIsNull |
00886
00887
00888 kDecodeSingleThreaded |
00889 kDecodeNoLoopFilter);
00890
00891 if ((COMM_DETECT_BLANKS == commDetectMethod) ||
00892 (COMM_DETECT_2_BLANK == commDetectMethod))
00893 {
00894 flags = (PlayerFlags) (flags | kDecodeFewBlocks);
00895 }
00896
00897 MythCommFlagPlayer *cfp = new MythCommFlagPlayer(flags);
00898 PlayerContext *ctx = new PlayerContext(kFlaggerInUseID);
00899 ctx->SetPlayingInfo(program_info);
00900 ctx->SetRingBuffer(tmprbuf);
00901 ctx->SetPlayer(cfp);
00902 cfp->SetPlayerInfo(NULL, NULL, ctx);
00903
00904 if (useDB)
00905 {
00906 if (program_info->GetRecordingEndTime() > QDateTime::currentDateTime())
00907 {
00908 gCoreContext->ConnectToMasterServer();
00909
00910 recorder = RemoteGetExistingRecorder(program_info);
00911 if (recorder && (recorder->GetRecorderNumber() != -1))
00912 {
00913 recorderNum = recorder->GetRecorderNumber();
00914 watchingRecording = true;
00915 ctx->SetRecorder(recorder);
00916
00917 LOG(VB_COMMFLAG, LOG_INFO,
00918 QString("mythcommflag will flag recording "
00919 "currently in progress on cardid %1")
00920 .arg(recorderNum));
00921 }
00922 else
00923 {
00924 recorderNum = -1;
00925 watchingRecording = false;
00926
00927 LOG(VB_GENERAL, LOG_ERR,
00928 "Unable to find active recorder for this "
00929 "recording, realtime flagging will not be enabled.");
00930 }
00931 cfp->SetWatchingRecording(watchingRecording);
00932 }
00933 }
00934
00935
00936
00937 breaksFound = DoFlagCommercials(
00938 program_info, progress, fullSpeed, jobid,
00939 cfp, commDetectMethod, outputfilename, useDB);
00940
00941 if (progress)
00942 cerr << breaksFound << "\n";
00943
00944 LOG(VB_GENERAL, LOG_NOTICE, QString("Finished, %1 break(s) found.")
00945 .arg(breaksFound));
00946
00947 delete ctx;
00948 global_program_info = NULL;
00949
00950 return breaksFound;
00951 }
00952
00953 static int FlagCommercials( uint chanid, const QDateTime &starttime,
00954 int jobid, const QString &outputfilename,
00955 bool fullSpeed )
00956 {
00957 QString startstring = starttime.toString("yyyyMMddhhmmss");
00958 ProgramInfo pginfo(chanid, starttime);
00959
00960 if (!pginfo.GetChanID())
00961 {
00962 LOG(VB_GENERAL, LOG_ERR,
00963 QString("No program data exists for channel %1 at %2")
00964 .arg(chanid).arg(startstring));
00965 return GENERIC_EXIT_NO_RECORDING_DATA;
00966 }
00967
00968 if (!force && JobQueue::IsJobRunning(JOB_COMMFLAG, pginfo))
00969 {
00970 if (progress)
00971 {
00972 cerr << "IN USE\n";
00973 cerr << " "
00974 "(the program is already being flagged elsewhere)\n";
00975 }
00976 LOG(VB_GENERAL, LOG_ERR, "Program is already being flagged elsewhere");
00977 return GENERIC_EXIT_IN_USE;
00978 }
00979
00980
00981 if (progress)
00982 {
00983 cerr << "MythTV Commercial Flagger, flagging commercials for:" << endl;
00984 if (pginfo.GetSubtitle().isEmpty())
00985 cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << endl;
00986 else
00987 cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << " - "
00988 << pginfo.GetSubtitle().toLocal8Bit().constData() << endl;
00989 }
00990
00991 return FlagCommercials(&pginfo, jobid, outputfilename, true, fullSpeed);
00992 }
00993
00994 static int FlagCommercials(QString filename, int jobid,
00995 const QString &outputfilename, bool useDB,
00996 bool fullSpeed)
00997 {
00998
00999 if (progress)
01000 {
01001 cerr << "MythTV Commercial Flagger, flagging commercials for:" << endl
01002 << " " << filename.toAscii().constData() << endl;
01003 }
01004
01005 ProgramInfo pginfo(filename);
01006 return FlagCommercials(&pginfo, jobid, outputfilename, useDB, fullSpeed);
01007 }
01008
01009 static int RebuildSeekTable(ProgramInfo *pginfo, int jobid)
01010 {
01011 QString filename = get_filename(pginfo);
01012
01013 if (!DoesFileExist(pginfo))
01014 {
01015
01016
01017
01018
01019 filename = QString("myth://Videos@%1/%2")
01020 .arg(gCoreContext->GetHostName()).arg(filename);
01021 pginfo->SetPathname(filename);
01022 if (!DoesFileExist(pginfo))
01023 {
01024 LOG(VB_GENERAL, LOG_ERR,
01025 "Unable to find file in defined storage paths.");
01026 return GENERIC_EXIT_PERMISSIONS_ERROR;
01027 }
01028 }
01029
01030
01031
01032 UpdateFileSize(pginfo);
01033
01034 RingBuffer *tmprbuf = RingBuffer::Create(filename, false);
01035 if (!tmprbuf)
01036 {
01037 LOG(VB_GENERAL, LOG_ERR,
01038 QString("Unable to create RingBuffer for %1").arg(filename));
01039 return GENERIC_EXIT_PERMISSIONS_ERROR;
01040 }
01041
01042 MythCommFlagPlayer *cfp = new MythCommFlagPlayer(
01043 (PlayerFlags)(kAudioMuted | kVideoIsNull | kDecodeNoDecode));
01044 PlayerContext *ctx = new PlayerContext(kFlaggerInUseID);
01045 ctx->SetPlayingInfo(pginfo);
01046 ctx->SetRingBuffer(tmprbuf);
01047 ctx->SetPlayer(cfp);
01048 cfp->SetPlayerInfo(NULL, NULL, ctx);
01049
01050 if (progress)
01051 {
01052 QString time = QDateTime::currentDateTime().toString(Qt::TextDate);
01053 cerr << "Rebuild started at " << qPrintable(time) << endl;
01054 }
01055
01056 cfp->RebuildSeekTable(progress);
01057
01058 if (progress)
01059 {
01060 QString time = QDateTime::currentDateTime().toString(Qt::TextDate);
01061 cerr << "Rebuild completed at " << qPrintable(time) << endl;
01062 }
01063
01064 delete ctx;
01065
01066 return GENERIC_EXIT_OK;
01067 }
01068
01069 static int RebuildSeekTable(QString filename, int jobid)
01070 {
01071 if (progress)
01072 {
01073 cerr << "MythTV Commercial Flagger, building seek table for:" << endl
01074 << " " << filename.toAscii().constData() << endl;
01075 }
01076 ProgramInfo pginfo(filename);
01077 return RebuildSeekTable(&pginfo, jobid);
01078 }
01079
01080 static int RebuildSeekTable(uint chanid, QDateTime starttime, int jobid)
01081 {
01082 ProgramInfo pginfo(chanid, starttime);
01083 if (progress)
01084 {
01085 cerr << "MythTV Commercial Flagger, building seek table for:" << endl;
01086 if (pginfo.GetSubtitle().isEmpty())
01087 cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << endl;
01088 else
01089 cerr << " " << pginfo.GetTitle().toLocal8Bit().constData() << " - "
01090 << pginfo.GetSubtitle().toLocal8Bit().constData() << endl;
01091 }
01092 return RebuildSeekTable(&pginfo, jobid);
01093 }
01094
01095 int main(int argc, char *argv[])
01096 {
01097 int result = GENERIC_EXIT_OK;
01098
01099
01100
01101 int jobType = JOB_NONE;
01102
01103 if (!cmdline.Parse(argc, argv))
01104 {
01105 cmdline.PrintHelp();
01106 return GENERIC_EXIT_INVALID_CMDLINE;
01107 }
01108
01109 if (cmdline.toBool("showhelp"))
01110 {
01111 cmdline.PrintHelp();
01112 return GENERIC_EXIT_OK;
01113 }
01114
01115 if (cmdline.toBool("showversion"))
01116 {
01117 cmdline.PrintVersion();
01118 return GENERIC_EXIT_OK;
01119 }
01120
01121 QCoreApplication a(argc, argv);
01122 QCoreApplication::setApplicationName(MYTH_APPNAME_MYTHCOMMFLAG);
01123 int retval = cmdline.ConfigureLogging("general",
01124 !cmdline.toBool("noprogress"));
01125 if (retval != GENERIC_EXIT_OK)
01126 return retval;
01127
01128 CleanupGuard callCleanup(cleanup);
01129 gContext = new MythContext(MYTH_BINARY_VERSION);
01130 if (!gContext->Init( false,
01131 false,
01132 false,
01133 cmdline.toBool("skipdb")))
01134 {
01135 LOG(VB_GENERAL, LOG_EMERG, "Failed to init MythContext, exiting.");
01136 return GENERIC_EXIT_NO_MYTHCONTEXT;
01137 }
01138 cmdline.ApplySettingsOverride();
01139
01140 MythTranslation::load("mythfrontend");
01141
01142 if (cmdline.toBool("chanid") && cmdline.toBool("starttime"))
01143 {
01144
01145 uint chanid = cmdline.toUInt("chanid");
01146 QDateTime starttime = cmdline.toDateTime("starttime");
01147
01148 if (cmdline.toBool("clearskiplist"))
01149 return ClearSkipList(chanid, starttime);
01150 if (cmdline.toBool("gencutlist"))
01151 return CopySkipListToCutList(chanid, starttime);
01152 if (cmdline.toBool("clearcutlist"))
01153 return SetCutList(chanid, starttime, "");
01154 if (cmdline.toBool("setcutlist"))
01155 return SetCutList(chanid, starttime, cmdline.toString("setcutlist"));
01156 if (cmdline.toBool("getcutlist"))
01157 return GetMarkupList("cutlist", chanid, starttime);
01158 if (cmdline.toBool("getskiplist"))
01159 return GetMarkupList("commflag", chanid, starttime);
01160
01161
01162
01163
01164 if (cmdline.toBool("queue"))
01165 QueueCommFlagJob(chanid, starttime, cmdline.toBool("rebuild"));
01166 else if (cmdline.toBool("rebuild"))
01167 result = RebuildSeekTable(chanid, starttime, -1);
01168 else
01169 result = FlagCommercials(chanid, starttime, -1,
01170 cmdline.toString("outputfile"), true);
01171 }
01172 else if (cmdline.toBool("jobid"))
01173 {
01174 jobID = cmdline.toInt("jobid");
01175 uint chanid;
01176 QDateTime starttime;
01177
01178 if (!JobQueue::GetJobInfoFromID(jobID, jobType, chanid, starttime))
01179 {
01180 cerr << "mythcommflag: ERROR: Unable to find DB info for "
01181 << "JobQueue ID# " << jobID << endl;
01182 return GENERIC_EXIT_NO_RECORDING_DATA;
01183 }
01184 force = true;
01185 int jobQueueCPU = gCoreContext->GetNumSetting("JobQueueCPU", 0);
01186
01187 if (jobQueueCPU < 2)
01188 {
01189 myth_nice(17);
01190 myth_ioprio((0 == jobQueueCPU) ? 8 : 7);
01191 }
01192
01193 progress = false;
01194
01195 int ret = 0;
01196
01197 if (JobQueue::GetJobFlags(jobID) & JOB_REBUILD)
01198 RebuildSeekTable(chanid, starttime, jobID);
01199 else
01200 ret = FlagCommercials(chanid, starttime, jobID, "", jobQueueCPU != 0);
01201
01202 if (ret > GENERIC_EXIT_NOT_OK)
01203 JobQueue::ChangeJobStatus(jobID, JOB_ERRORED,
01204 QString("Failed with exit status %1").arg(ret));
01205 else
01206 JobQueue::ChangeJobStatus(jobID, JOB_FINISHED,
01207 QString("%1 commercial breaks").arg(ret));
01208 }
01209 else if (cmdline.toBool("video"))
01210 {
01211
01212 return RebuildSeekTable(cmdline.toString("video"), -1);
01213 }
01214 else if (cmdline.toBool("file"))
01215 {
01216 if (cmdline.toBool("skipdb"))
01217 {
01218 if (cmdline.toBool("rebuild"))
01219 {
01220 cerr << "The --rebuild parameter builds the seektable for "
01221 "internal MythTV use only. It cannot be used in "
01222 "combination with --skipdb." << endl;
01223 return GENERIC_EXIT_INVALID_CMDLINE;
01224 }
01225
01226 if (!cmdline.toBool("outputfile"))
01227 cmdline.SetValue("outputfile", "-");
01228
01229
01230 FlagCommercials(cmdline.toString("file"), -1,
01231 cmdline.toString("outputfile"),
01232 !cmdline.toBool("skipdb"),
01233 true);
01234 }
01235 else
01236 {
01237 ProgramInfo pginfo(cmdline.toString("file"));
01238
01239
01240
01241 if (cmdline.toBool("rebuild"))
01242 result = RebuildSeekTable(pginfo.GetChanID(),
01243 pginfo.GetRecordingStartTime(),
01244 -1);
01245 else
01246 result = FlagCommercials(pginfo.GetChanID(),
01247 pginfo.GetRecordingStartTime(),
01248 -1, cmdline.toString("outputfile"),
01249 true);
01250 }
01251 }
01252 else if (cmdline.toBool("queue"))
01253 {
01254
01255 MSqlQuery query(MSqlQuery::InitCon());
01256 query.prepare("SELECT r.chanid, r.starttime, c.commmethod "
01257 "FROM recorded AS r "
01258 "LEFT JOIN channel AS c ON r.chanid=c.chanid "
01259
01260 "ORDER BY starttime;");
01261
01262
01263
01264 if (query.exec() && query.isActive() && query.size() > 0)
01265 {
01266 QDateTime starttime;
01267 uint chanid;
01268
01269 while (query.next())
01270 {
01271 starttime = QDateTime::fromString(query.value(1).toString(),
01272 Qt::ISODate);
01273 chanid = query.value(0).toUInt();
01274
01275 if (!cmdline.toBool("force") && !cmdline.toBool("rebuild"))
01276 {
01277
01278 if (IsMarked(chanid, starttime))
01279 continue;
01280
01281
01282 if (query.value(2).toInt() == COMM_DETECT_COMMFREE)
01283 continue;
01284
01285
01286 #if 0
01287 RecordingInfo recinfo(chanid, starttime);
01288 if (!(recinfo.GetAutoRunJobs() & JOB_COMMFLAG))
01289 continue;
01290 #endif
01291 }
01292
01293 QueueCommFlagJob(chanid, starttime, cmdline.toBool("rebuild"));
01294 }
01295 }
01296
01297 }
01298 else
01299 {
01300 LOG(VB_GENERAL, LOG_ERR,
01301 "No valid combination of command inputs received.");
01302 cmdline.PrintHelp();
01303 return GENERIC_EXIT_INVALID_CMDLINE;
01304 }
01305
01306 return result;
01307 }
01308
01309
01310