MythTV  0.26-pre
main.cpp
Go to the documentation of this file.
00001 // POSIX headers
00002 #include <unistd.h>
00003 #include <sys/time.h> // for gettimeofday
00004 
00005 // ANSI C headers
00006 #include <cstdlib>
00007 #include <cstdio>
00008 #include <ctime>
00009 #include <cmath>
00010 
00011 // C++ headers
00012 #include <string>
00013 #include <iostream>
00014 #include <fstream>
00015 using namespace std;
00016 
00017 // Qt headers
00018 #include <QCoreApplication>
00019 #include <QString>
00020 #include <QRegExp>
00021 #include <QDir>
00022 #include <QEvent>
00023 
00024 // MythTV headers
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 // Commercial Flagging headers
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     //this is connected to the commdetectors breathe signal so we get a chance
00415     //while its busy to see if the user already told us to stop.
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     // configure commercial detection method
00741     SkipTypes commDetectMethod =
00742             (enum SkipTypes)gCoreContext->GetNumSetting(
00743                                     "CommercialSkipMethod", COMM_DETECT_ALL);
00744 
00745     if (cmdline.toBool("commmethod"))
00746     {
00747         // pull commercial detection method from command line
00748         QString commmethod = cmdline.toString("commmethod");
00749 
00750         // assume definition as integer value
00751         bool ok = true;
00752         commDetectMethod = (SkipTypes) commmethod.toInt(&ok);
00753         if (!ok)
00754         {
00755             // not an integer, attempt comma separated list
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                 // append flag method to list
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         // if not manually specified, and we have a database to access
00790         // pull the commflag type from the channel
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             // if the query fails, return with an error
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                 // if the channel is commercial free, drop to the default instead
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                 // no value set, so use the database default
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         // default to a cheaper method for debugging purposes
00829         commDetectMethod = COMM_DETECT_BLANK;
00830 
00831     // if selection has failed, or intentionally disabled, drop out
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  * is there a purpose to this not fulfilled by --getskiplist?
00842     if (onlyDumpDBCommercialBreakList)
00843     {
00844         frm_dir_map_t commBreakList;
00845         program_info->QueryCommBreakList(commBreakList);
00846 
00847         print_comm_flag_output(program_info, commBreakList,
00848                                0, NULL, outputfilename);
00849 
00850         global_program_info = NULL;
00851         return GENERIC_EXIT_OK;
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                                       /* Disabled due to libav bug 297 */
00887                                       /* kDecodeLowRes | */
00888                                       kDecodeSingleThreaded |
00889                                       kDecodeNoLoopFilter);
00890     /* blank detector needs to be only sample center for this optimization. */
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     // TODO: Add back insertion of job if not in jobqueue
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         // file not found on local filesystem
01016         // assume file is in Video storage group on local backend
01017         // and try again
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     // Update the file size since mythcommflag --rebuild is often used in user
01031     // scripts after transcoding or other size-changing operations
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 //    QString allStart = "19700101000000";
01100 //    QString allEnd   = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
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, /*use gui*/
01131                          false, /*prompt for backend*/
01132                          false, /*bypass auto discovery*/
01133                          cmdline.toBool("skipdb"))) /*ignoreDB*/
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         // operate on a recording in the database
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         // TODO: check for matching jobid
01162         // create temporary id to operate off of if not
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         // build skiplist for video file
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             // perform commercial flagging on file outside the database
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             // pass chanid and starttime
01239             // inefficient, but it lets the other function
01240             // handle sanity checking
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         // run flagging for all recordings with no skiplist
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 //                     "WHERE startime >= :STARTTIME AND endtime <= :ENDTIME "
01260                     "ORDER BY starttime;");
01261         //query.bindValue(":STARTTIME", allStart);
01262         //query.bindValue(":ENDTIME", allEnd);
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                     // recording is already flagged
01278                     if (IsMarked(chanid, starttime))
01279                         continue;
01280 
01281                     // channel is marked as commercial free
01282                     if (query.value(2).toInt() == COMM_DETECT_COMMFREE)
01283                         continue;
01284 
01285                     // recording rule did not enable commflagging
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 /* vim: set expandtab tabstop=4 shiftwidth=4: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends