|
MythTV
0.26-pre
|
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: */
1.7.6.1