MythTV  master
mythtv/programs/mythtranscode/main.cpp
Go to the documentation of this file.
1 // C++ headers
2 #include <cerrno>
3 #include <fcntl.h> // for open flags
4 #include <fstream>
5 #include <iostream>
6 using namespace std;
7 
8 // Qt headers
9 #include <QCoreApplication>
10 #include <QDir>
11 #include <utility>
12 
13 // MythTV headers
14 #include "mythmiscutil.h"
15 #include "exitcodes.h"
16 #include "programinfo.h"
17 #include "jobqueue.h"
18 #include "mythcontext.h"
19 #include "mythdb.h"
20 #include "mythversion.h"
21 #include "mythdate.h"
22 #include "transcode.h"
23 #include "mpeg2fix.h"
24 #include "remotefile.h"
25 #include "mythtranslation.h"
26 #include "loggingserver.h"
27 #include "mythlogging.h"
28 #include "commandlineparser.h"
29 #include "recordinginfo.h"
30 #include "signalhandling.h"
31 #include "HLS/httplivestream.h"
32 #include "cleanupguard.h"
33 
34 static void CompleteJob(int jobID, ProgramInfo *pginfo, bool useCutlist,
35  frm_dir_map_t *deleteMap, int &exitCode,
36  int resultCode, bool forceDelete);
37 
38 static int glbl_jobID = -1;
39 static QString recorderOptions = "";
40 
41 static void UpdatePositionMap(frm_pos_map_t &posMap, frm_pos_map_t &durMap, const QString& mapfile,
42  ProgramInfo *pginfo)
43 {
44  if (pginfo && mapfile.isEmpty())
45  {
48  pginfo->SavePositionMap(posMap, MARK_GOP_BYFRAME);
49  pginfo->SavePositionMap(durMap, MARK_DURATION_MS);
50  }
51  else if (!mapfile.isEmpty())
52  {
53  MarkTypes keyType = MARK_GOP_BYFRAME;
54  FILE *mapfh = fopen(mapfile.toLocal8Bit().constData(), "w");
55  if (!mapfh)
56  {
57  LOG(VB_GENERAL, LOG_ERR, QString("Could not open map file '%1'")
58  .arg(mapfile) + ENO);
59  return;
60  }
61  frm_pos_map_t::const_iterator it;
62  fprintf (mapfh, "Type: %d\n", keyType);
63  for (it = posMap.begin(); it != posMap.end(); ++it)
64  {
65  QString str = QString("%1 %2\n").arg(it.key()).arg(*it);
66  fprintf(mapfh, "%s", qPrintable(str));
67  }
68  fclose(mapfh);
69  }
70 }
71 
72 static int BuildKeyframeIndex(MPEG2fixup *m2f, QString &infile,
73  frm_pos_map_t &posMap, frm_pos_map_t &durMap, int jobID)
74 {
75  if (!m2f)
76  return 0;
77 
78  if (jobID < 0 || JobQueue::GetJobCmd(jobID) != JOB_STOP)
79  {
80  if (jobID >= 0)
82  QObject::tr("Generating Keyframe Index"));
83  int err = m2f->BuildKeyframeIndex(infile, posMap, durMap);
84  if (err)
85  return err;
86  if (jobID >= 0)
88  QObject::tr("Transcode Completed"));
89  }
90  return 0;
91 }
92 
93 static void UpdateJobQueue(float percent_done)
94 {
96  QString("%1% ").arg(percent_done, 0, 'f', 1) +
97  QObject::tr("Completed"));
98 }
99 
100 static int CheckJobQueue()
101 {
103  {
104  LOG(VB_GENERAL, LOG_NOTICE, "Transcoding stopped by JobQueue");
105  return 1;
106  }
107  return 0;
108 }
109 
110 static int QueueTranscodeJob(ProgramInfo *pginfo, const QString& profile,
111  QString hostname, bool usecutlist)
112 {
113  RecordingInfo recinfo(*pginfo);
114  if (!profile.isEmpty())
116 
118  pginfo->GetRecordingStartTime(),
119  std::move(hostname), "", "",
120  usecutlist ? JOB_USE_CUTLIST : 0))
121  {
122  LOG(VB_GENERAL, LOG_NOTICE,
123  QString("Queued transcode job for chanid %1 @ %2")
124  .arg(pginfo->GetChanID())
126  return GENERIC_EXIT_OK;
127  }
128 
129  LOG(VB_GENERAL, LOG_ERR, QString("Error queuing job for chanid %1 @ %2")
130  .arg(pginfo->GetChanID())
132  return GENERIC_EXIT_DB_ERROR;
133 }
134 
135 namespace
136 {
137  void cleanup()
138  {
139  delete gContext;
140  gContext = nullptr;
142  }
143 }
144 
145 int main(int argc, char *argv[])
146 {
147  uint chanid = 0;
148  QDateTime starttime;
149  QString infile, outfile;
150  QString profilename = QString("autodetect");
151  QString fifodir = nullptr;
152  int jobID = -1;
153  int jobType = JOB_NONE;
154  int otype = REPLEX_MPEG2;
155  bool useCutlist = false, keyframesonly = false;
156  bool build_index = false, fifosync = false;
157  bool mpeg2 = false;
158  bool fifo_info = false;
159  bool cleanCut = false;
160  QMap<QString, QString> settingsOverride;
161  frm_dir_map_t deleteMap;
162  frm_pos_map_t posMap;
163  frm_pos_map_t durMap;
164  int AudioTrackNo = -1;
165 
166  bool found_starttime = false;
167  bool found_chanid = false;
168  bool found_infile = false;
169  int update_index = 1;
170  bool isVideo = false;
171  bool passthru = false;
172 
174  if (!cmdline.Parse(argc, argv))
175  {
176  cmdline.PrintHelp();
178  }
179 
180  if (cmdline.toBool("showhelp"))
181  {
182  cmdline.PrintHelp();
183  return GENERIC_EXIT_OK;
184  }
185 
186  if (cmdline.toBool("showversion"))
187  {
189  return GENERIC_EXIT_OK;
190  }
191 
192  QCoreApplication a(argc, argv);
193  QCoreApplication::setApplicationName(MYTH_APPNAME_MYTHTRANSCODE);
194 
195  if (cmdline.toBool("outputfile"))
196  {
197  outfile = cmdline.toString("outputfile");
198  update_index = 0;
199  }
200 
201  bool showprogress = cmdline.toBool("showprogress");
202 
203  int retval;
204  QString mask("general");
205  bool quiet = (outfile == "-") || showprogress;
206  if ((retval = cmdline.ConfigureLogging(mask, quiet)) != GENERIC_EXIT_OK)
207  return retval;
208 
209  if (cmdline.toBool("starttime"))
210  {
211  starttime = cmdline.toDateTime("starttime");
212  found_starttime = true;
213  }
214  if (cmdline.toBool("chanid"))
215  {
216  chanid = cmdline.toUInt("chanid");
217  found_chanid = true;
218  }
219  if (cmdline.toBool("jobid"))
220  jobID = cmdline.toInt("jobid");
221  if (cmdline.toBool("inputfile"))
222  {
223  infile = cmdline.toString("inputfile");
224  found_infile = true;
225  }
226  if (cmdline.toBool("video"))
227  isVideo = true;
228  if (cmdline.toBool("profile"))
229  profilename = cmdline.toString("profile");
230 
231  if (cmdline.toBool("usecutlist"))
232  {
233  useCutlist = true;
234  if (!cmdline.toString("usecutlist").isEmpty())
235  {
236  if (!cmdline.toBool("inputfile") && !cmdline.toBool("hls"))
237  {
238  LOG(VB_GENERAL, LOG_CRIT, "External cutlists are only allowed "
239  "when using the --infile option.");
241  }
242 
243  uint64_t last = 0, start, end;
244  QStringList cutlist = cmdline.toStringList("usecutlist", " ");
245  QStringList::iterator it;
246  for (it = cutlist.begin(); it != cutlist.end(); ++it)
247  {
248  QStringList startend =
249  (*it).split("-", QString::SkipEmptyParts);
250  if (startend.size() == 2)
251  {
252  start = startend.first().toULongLong();
253  end = startend.last().toULongLong();
254 
255  if (cmdline.toBool("inversecut"))
256  {
257  LOG(VB_GENERAL, LOG_DEBUG,
258  QString("Cutting section %1-%2.")
259  .arg(last).arg(start));
260  deleteMap[start] = MARK_CUT_END;
261  deleteMap[end] = MARK_CUT_START;
262  last = end;
263  }
264  else
265  {
266  LOG(VB_GENERAL, LOG_DEBUG,
267  QString("Cutting section %1-%2.")
268  .arg(start).arg(end));
269  deleteMap[start] = MARK_CUT_START;
270  deleteMap[end] = MARK_CUT_END;
271  }
272  }
273  }
274 
275  if (cmdline.toBool("inversecut"))
276  {
277  if (deleteMap.contains(0) && (deleteMap[0] == MARK_CUT_END))
278  deleteMap.remove(0);
279  else
280  deleteMap[0] = MARK_CUT_START;
281  deleteMap[999999999] = MARK_CUT_END;
282  LOG(VB_GENERAL, LOG_DEBUG,
283  QString("Cutting section %1-999999999.")
284  .arg(last));
285  }
286 
287  // sanitize cutlist
288  if (deleteMap.count() >= 2)
289  {
290  frm_dir_map_t::iterator cur = deleteMap.begin(), prev;
291  prev = cur++;
292  while (cur != deleteMap.end())
293  {
294  if (prev.value() == cur.value())
295  {
296  // two of the same type next to each other
297  QString err("Cut %1points found at %3 and %4, with no "
298  "%2 point in between.");
299  if (prev.value() == MARK_CUT_END)
300  err = err.arg("end").arg("start");
301  else
302  err = err.arg("start").arg("end");
303  LOG(VB_GENERAL, LOG_CRIT, "Invalid cutlist defined!");
304  LOG(VB_GENERAL, LOG_CRIT, err.arg(prev.key())
305  .arg(cur.key()));
307  }
308  if ( (prev.value() == MARK_CUT_START) &&
309  ((cur.key() - prev.key()) < 2) )
310  {
311  LOG(VB_GENERAL, LOG_WARNING, QString("Discarding "
312  "insufficiently long cut: %1-%2")
313  .arg(prev.key()).arg(cur.key()));
314  prev = deleteMap.erase(prev);
315  cur = deleteMap.erase(cur);
316 
317  if (cur == deleteMap.end())
318  continue;
319  }
320  prev = cur++;
321  }
322  }
323  }
324  else if (cmdline.toBool("inversecut"))
325  {
326  cerr << "Cutlist inversion requires an external cutlist be" << endl
327  << "provided using the --honorcutlist option." << endl;
329  }
330  }
331 
332  if (cmdline.toBool("cleancut"))
333  cleanCut = true;
334 
335  if (cmdline.toBool("allkeys"))
336  keyframesonly = true;
337  if (cmdline.toBool("reindex"))
338  build_index = true;
339  if (cmdline.toBool("fifodir"))
340  fifodir = cmdline.toString("fifodir");
341  if (cmdline.toBool("fifoinfo"))
342  fifo_info = true;
343  if (cmdline.toBool("fifosync"))
344  fifosync = true;
345  if (cmdline.toBool("recopt"))
346  recorderOptions = cmdline.toString("recopt");
347  if (cmdline.toBool("mpeg2"))
348  mpeg2 = true;
349  if (cmdline.toBool("ostream"))
350  {
351  if (cmdline.toString("ostream") == "dvd")
352  otype = REPLEX_DVD;
353  else if (cmdline.toString("ostream") == "ps")
354  otype = REPLEX_MPEG2;
355  else if (cmdline.toString("ostream") == "ts")
356  otype = REPLEX_TS_SD;
357  else
358  {
359  cerr << "Invalid 'ostream' type: "
360  << cmdline.toString("ostream").toLocal8Bit().constData()
361  << endl;
363  }
364  }
365  if (cmdline.toBool("audiotrack"))
366  AudioTrackNo = cmdline.toInt("audiotrack");
367  if (cmdline.toBool("passthru"))
368  passthru = true;
369  // Set if we want to delete the original file once conversion succeeded.
370  bool deleteOriginal = cmdline.toBool("delete");
371 
372  CleanupGuard callCleanup(cleanup);
373 
374 #ifndef _WIN32
375  QList<int> signallist;
376  signallist << SIGINT << SIGTERM << SIGSEGV << SIGABRT << SIGBUS << SIGFPE
377  << SIGILL;
378 #if ! CONFIG_DARWIN
379  signallist << SIGRTMIN;
380 #endif
381  SignalHandler::Init(signallist);
382  SignalHandler::SetHandler(SIGHUP, logSigHup);
383 #endif
384 
385  // Load the context
387  if (!gContext->Init(false))
388  {
389  LOG(VB_GENERAL, LOG_ERR, "Failed to init MythContext, exiting.");
391  }
392 
393  MythTranslation::load("mythfrontend");
394 
396 
397  if (jobID != -1)
398  {
399  if (JobQueue::GetJobInfoFromID(jobID, jobType, chanid, starttime))
400  {
401  found_starttime = true;
402  found_chanid = true;
403  }
404  else
405  {
406  cerr << "mythtranscode: ERROR: Unable to find DB info for "
407  << "JobQueue ID# " << jobID << endl;
409  }
410  }
411 
412  if (((!found_infile && !(found_chanid && found_starttime)) ||
413  (found_infile && (found_chanid || found_starttime))) &&
414  (!cmdline.toBool("hls")))
415  {
416  cerr << "Must specify -i OR -c AND -s options!" << endl;
418  }
419  if (isVideo && !found_infile && !cmdline.toBool("hls"))
420  {
421  cerr << "Must specify --infile to use --video" << endl;
423  }
424  if (jobID >= 0 && (found_infile || build_index))
425  {
426  cerr << "Can't specify -j with --buildindex, --video or --infile"
427  << endl;
429  }
430  if ((jobID >= 0) && build_index)
431  {
432  cerr << "Can't specify both -j and --buildindex" << endl;
434  }
435  if (keyframesonly && !fifodir.isEmpty())
436  {
437  cerr << "Cannot specify both --fifodir and --allkeys" << endl;
439  }
440  if (fifosync && fifodir.isEmpty())
441  {
442  cerr << "Must specify --fifodir to use --fifosync" << endl;
444  }
445  if (fifo_info && !fifodir.isEmpty())
446  {
447  cerr << "Cannot specify both --fifodir and --fifoinfo" << endl;
449  }
450  if (cleanCut && fifodir.isEmpty() && !fifo_info)
451  {
452  cerr << "Clean cutting works only in fifodir mode" << endl;
454  }
455  if (cleanCut && !useCutlist)
456  {
457  cerr << "--cleancut is pointless without --honorcutlist" << endl;
459  }
460 
461  if (fifo_info)
462  {
463  // Setup a dummy fifodir path, so that the "fifodir" code path
464  // is taken. The path wont actually be used.
465  fifodir = "DummyFifoPath";
466  }
467 
469  {
470  LOG(VB_GENERAL, LOG_ERR, "couldn't open db");
471  return GENERIC_EXIT_DB_ERROR;
472  }
473 
474  ProgramInfo *pginfo = nullptr;
475  if (cmdline.toBool("hls"))
476  {
477  if (cmdline.toBool("hlsstreamid"))
478  {
479  HTTPLiveStream hls(cmdline.toInt("hlsstreamid"));
480  pginfo = new ProgramInfo(hls.GetSourceFile());
481  }
482  if (pginfo == nullptr)
483  pginfo = new ProgramInfo();
484  }
485  else if (isVideo)
486  {
487  // We want the absolute file path for the filemarkup table
488  QFileInfo inf(infile);
489  infile = inf.absoluteFilePath();
490  pginfo = new ProgramInfo(infile);
491  }
492  else if (!found_infile)
493  {
494  pginfo = new ProgramInfo(chanid, starttime);
495 
496  if (!pginfo->GetChanID())
497  {
498  LOG(VB_GENERAL, LOG_ERR,
499  QString("Couldn't find recording for chanid %1 @ %2")
500  .arg(chanid).arg(starttime.toString(Qt::ISODate)));
501  delete pginfo;
503  }
504 
505  infile = pginfo->GetPlaybackURL(false, true);
506  }
507  else
508  {
509  pginfo = new ProgramInfo(infile);
510  if (!pginfo->GetChanID())
511  {
512  LOG(VB_GENERAL, LOG_ERR,
513  QString("Couldn't find a recording for filename '%1'")
514  .arg(infile));
515  delete pginfo;
517  }
518  }
519 
520  if (!pginfo)
521  {
522  LOG(VB_GENERAL, LOG_ERR, "No program info found!");
524  }
525 
526  if (cmdline.toBool("queue"))
527  {
528  QString hostname = cmdline.toString("queue");
529  return QueueTranscodeJob(pginfo, profilename, hostname, useCutlist);
530  }
531 
532  if (infile.startsWith("myth://") && (outfile.isEmpty() || outfile != "-") &&
533  fifodir.isEmpty() && !cmdline.toBool("hls") && !cmdline.toBool("avf"))
534  {
535  LOG(VB_GENERAL, LOG_ERR,
536  QString("Attempted to transcode %1. Mythtranscode is currently "
537  "unable to transcode remote files.") .arg(infile));
538  delete pginfo;
540  }
541 
542  if (outfile.isEmpty() && !build_index && fifodir.isEmpty())
543  outfile = infile + ".tmp";
544 
545  if (jobID >= 0)
546  JobQueue::ChangeJobStatus(jobID, JOB_RUNNING);
547 
548  Transcode *transcode = new Transcode(pginfo);
549 
550  if (!build_index)
551  {
552  if (cmdline.toBool("hlsstreamid"))
553  LOG(VB_GENERAL, LOG_NOTICE,
554  QString("Transcoding HTTP Live Stream ID %1")
555  .arg(cmdline.toInt("hlsstreamid")));
556  else if (fifodir.isEmpty())
557  LOG(VB_GENERAL, LOG_NOTICE, QString("Transcoding from %1 to %2")
558  .arg(infile).arg(outfile));
559  else
560  LOG(VB_GENERAL, LOG_NOTICE, QString("Transcoding from %1 to FIFO")
561  .arg(infile));
562  }
563 
564  if (cmdline.toBool("avf"))
565  {
566  transcode->SetAVFMode();
567 
568  if (cmdline.toBool("container"))
569  transcode->SetCMDContainer(cmdline.toString("container"));
570  if (cmdline.toBool("acodec"))
571  transcode->SetCMDAudioCodec(cmdline.toString("acodec"));
572  if (cmdline.toBool("vcodec"))
573  transcode->SetCMDVideoCodec(cmdline.toString("vcodec"));
574  }
575  else if (cmdline.toBool("hls"))
576  {
577  transcode->SetHLSMode();
578 
579  if (cmdline.toBool("hlsstreamid"))
580  transcode->SetHLSStreamID(cmdline.toInt("hlsstreamid"));
581  if (cmdline.toBool("maxsegments"))
582  transcode->SetHLSMaxSegments(cmdline.toInt("maxsegments"));
583  if (cmdline.toBool("noaudioonly"))
584  transcode->DisableAudioOnlyHLS();
585  }
586 
587  if (cmdline.toBool("avf") || cmdline.toBool("hls"))
588  {
589  if (cmdline.toBool("width"))
590  transcode->SetCMDWidth(cmdline.toInt("width"));
591  if (cmdline.toBool("height"))
592  transcode->SetCMDHeight(cmdline.toInt("height"));
593  if (cmdline.toBool("bitrate"))
594  transcode->SetCMDBitrate(cmdline.toInt("bitrate") * 1000);
595  if (cmdline.toBool("audiobitrate"))
596  transcode->SetCMDAudioBitrate(cmdline.toInt("audiobitrate") * 1000);
597  }
598 
599  if (showprogress)
600  transcode->ShowProgress(true);
601  if (!recorderOptions.isEmpty())
603  int result = 0;
604  if ((!mpeg2 && !build_index) || cmdline.toBool("hls"))
605  {
606  result = transcode->TranscodeFile(infile, outfile,
607  profilename, useCutlist,
608  (fifosync || keyframesonly), jobID,
609  fifodir, fifo_info, cleanCut, deleteMap,
610  AudioTrackNo, passthru);
611 
612  if ((result == REENCODE_OK) && (jobID >= 0))
613  {
614  JobQueue::ChangeJobArgs(jobID, "RENAME_TO_NUV");
615  RecordingInfo recInfo(pginfo->GetRecordingID());
616  RecordingFile *recFile = recInfo.GetRecordingFile();
617  recFile->m_containerFormat = formatNUV;
618  recFile->Save();
619  }
620  }
621 
622  if (fifo_info)
623  {
624  delete transcode;
625  return GENERIC_EXIT_OK;
626  }
627 
628  int exitcode = GENERIC_EXIT_OK;
629  if ((result == REENCODE_MPEG2TRANS) || mpeg2 || build_index)
630  {
631  void (*update_func)(float) = nullptr;
632  int (*check_func)() = nullptr;
633  if (useCutlist)
634  {
635  LOG(VB_GENERAL, LOG_INFO, "Honoring the cutlist while transcoding");
636  if (deleteMap.isEmpty())
637  pginfo->QueryCutList(deleteMap);
638  }
639  if (jobID >= 0)
640  {
641  glbl_jobID = jobID;
642  update_func = &UpdateJobQueue;
643  check_func = &CheckJobQueue;
644  }
645 
646  MPEG2fixup *m2f = new MPEG2fixup(infile, outfile,
647  &deleteMap, nullptr, false, false, 20,
648  showprogress, otype, update_func,
649  check_func);
650 
651  if (cmdline.toBool("allaudio"))
652  {
653  m2f->SetAllAudio(true);
654  }
655 
656  if (build_index)
657  {
658  int err = BuildKeyframeIndex(m2f, infile, posMap, durMap, jobID);
659  if (err)
660  {
661  delete m2f;
662  m2f = nullptr;
663  return err;
664  }
665  if (update_index)
666  UpdatePositionMap(posMap, durMap, nullptr, pginfo);
667  else
668  UpdatePositionMap(posMap, durMap, outfile + QString(".map"), pginfo);
669  }
670  else
671  {
672  result = m2f->Start();
673  if (result == REENCODE_OK)
674  {
675  result = BuildKeyframeIndex(m2f, outfile, posMap, durMap, jobID);
676  if (result == REENCODE_OK)
677  {
678  if (update_index)
679  UpdatePositionMap(posMap, durMap, nullptr, pginfo);
680  else
681  UpdatePositionMap(posMap, durMap, outfile + QString(".map"),
682  pginfo);
683  }
684  RecordingInfo recInfo(*pginfo);
685  RecordingFile *recFile = recInfo.GetRecordingFile();
686  if (otype == REPLEX_DVD || otype == REPLEX_MPEG2 ||
687  otype == REPLEX_HDTV)
688  {
690  JobQueue::ChangeJobArgs(jobID, "RENAME_TO_MPG");
691  }
692  else
693  {
695  }
696  recFile->Save();
697  }
698  }
699  delete m2f;
700  m2f = nullptr;
701  }
702 
703  if (result == REENCODE_OK)
704  {
705  if (jobID >= 0)
706  JobQueue::ChangeJobStatus(jobID, JOB_STOPPING);
707  LOG(VB_GENERAL, LOG_NOTICE, QString("%1 %2 done")
708  .arg(build_index ? "Building Index for" : "Transcoding")
709  .arg(infile));
710  }
711  else if (result == REENCODE_CUTLIST_CHANGE)
712  {
713  if (jobID >= 0)
714  JobQueue::ChangeJobStatus(jobID, JOB_RETRY);
715  LOG(VB_GENERAL, LOG_NOTICE,
716  QString("Transcoding %1 aborted because of cutlist update")
717  .arg(infile));
718  exitcode = GENERIC_EXIT_RESTART;
719  }
720  else if (result == REENCODE_STOPPED)
721  {
722  if (jobID >= 0)
723  JobQueue::ChangeJobStatus(jobID, JOB_ABORTING);
724  LOG(VB_GENERAL, LOG_NOTICE,
725  QString("Transcoding %1 stopped because of stop command")
726  .arg(infile));
727  exitcode = GENERIC_EXIT_KILLED;
728  }
729  else
730  {
731  if (jobID >= 0)
732  JobQueue::ChangeJobStatus(jobID, JOB_ERRORING);
733  LOG(VB_GENERAL, LOG_ERR, QString("Transcoding %1 failed").arg(infile));
734  exitcode = result;
735  }
736 
737  if (deleteOriginal || jobID >= 0)
738  CompleteJob(jobID, pginfo, useCutlist, &deleteMap, exitcode, result, deleteOriginal);
739 
740  transcode->deleteLater();
741 
742  return exitcode;
743 }
744 
745 static int transUnlink(const QString& filename, ProgramInfo *pginfo)
746 {
747  QString hostname = pginfo->GetHostname();
748 
749  if (!pginfo->GetStorageGroup().isEmpty() &&
750  !hostname.isEmpty())
751  {
753  QString basename = filename.section('/', -1);
754  QString uri = gCoreContext->GenMythURL(hostname, port, basename,
755  pginfo->GetStorageGroup());
756 
757  LOG(VB_GENERAL, LOG_NOTICE, QString("Requesting delete for file '%1'.")
758  .arg(uri));
759  bool ok = RemoteFile::DeleteFile(uri);
760  if (ok)
761  return 0;
762  }
763 
764  LOG(VB_GENERAL, LOG_NOTICE, QString("Deleting file '%1'.").arg(filename));
765  return unlink(filename.toLocal8Bit().constData());
766 }
767 
768 static uint64_t ComputeNewBookmark(uint64_t oldBookmark,
769  frm_dir_map_t *deleteMap)
770 {
771  if (deleteMap == nullptr)
772  return oldBookmark;
773 
774  uint64_t subtraction = 0;
775  uint64_t startOfCutRegion = 0;
776  frm_dir_map_t delMap = *deleteMap;
777  bool withinCut = false;
778  bool firstMark = true;
779  while (!delMap.empty() && delMap.begin().key() <= oldBookmark)
780  {
781  uint64_t key = delMap.begin().key();
782  MarkTypes mark = delMap.begin().value();
783 
784  if (mark == MARK_CUT_START && !withinCut)
785  {
786  withinCut = true;
787  startOfCutRegion = key;
788  }
789  else if (mark == MARK_CUT_END && firstMark)
790  {
791  subtraction += key;
792  }
793  else if (mark == MARK_CUT_END && withinCut)
794  {
795  withinCut = false;
796  subtraction += (key - startOfCutRegion);
797  }
798  delMap.remove(key);
799  firstMark = false;
800  }
801  if (withinCut)
802  subtraction += (oldBookmark - startOfCutRegion);
803  return oldBookmark - subtraction;
804 }
805 
806 static uint64_t ReloadBookmark(ProgramInfo *pginfo)
807 {
808  MSqlQuery query(MSqlQuery::InitCon());
809  uint64_t currentBookmark = 0;
810  query.prepare("SELECT DISTINCT mark FROM recordedmarkup "
811  "WHERE chanid = :CHANID "
812  "AND starttime = :STARTIME "
813  "AND type = :MARKTYPE ;");
814  query.bindValue(":CHANID", pginfo->GetChanID());
815  query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
816  query.bindValue(":MARKTYPE", MARK_BOOKMARK);
817  if (query.exec() && query.next())
818  {
819  currentBookmark = query.value(0).toLongLong();
820  }
821  return currentBookmark;
822 }
823 
824 static void WaitToDelete(ProgramInfo *pginfo)
825 {
826  LOG(VB_GENERAL, LOG_NOTICE,
827  "Transcode: delete old file: waiting while program is in use.");
828 
829  bool inUse = true;
830  MSqlQuery query(MSqlQuery::InitCon());
831  while (inUse)
832  {
833  query.prepare("SELECT count(*) FROM inuseprograms "
834  "WHERE chanid = :CHANID "
835  "AND starttime = :STARTTIME "
836  "AND recusage = 'player' ;");
837  query.bindValue(":CHANID", pginfo->GetChanID());
838  query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
839  if (!query.exec() || !query.next())
840  {
841  LOG(VB_GENERAL, LOG_ERR,
842  "Transcode: delete old file: in-use query failed;");
843  inUse = false;
844  }
845  else
846  {
847  inUse = (query.value(0).toUInt() != 0);
848  }
849 
850  if (inUse)
851  {
852  const unsigned kSecondsToWait = 10;
853  LOG(VB_GENERAL, LOG_NOTICE,
854  QString("Transcode: program in use, rechecking in %1 seconds.")
855  .arg(kSecondsToWait));
856  sleep(kSecondsToWait);
857  }
858  }
859  LOG(VB_GENERAL, LOG_NOTICE, "Transcode: program is no longer in use.");
860 }
861 
862 static void CompleteJob(int jobID, ProgramInfo *pginfo, bool useCutlist,
863  frm_dir_map_t *deleteMap, int &exitCode, int resultCode, bool forceDelete)
864 {
865  int status = JOB_UNKNOWN;
866  if (jobID >= 0)
867  status = JobQueue::GetJobStatus(jobID);
868 
869  if (!pginfo)
870  {
871  if (jobID >= 0)
872  JobQueue::ChangeJobStatus(jobID, JOB_ERRORED,
873  QObject::tr("Job errored, unable to find Program Info for job"));
874  LOG(VB_GENERAL, LOG_CRIT, "MythTranscode: Cleanup errored, unable to find Program Info");
875  return;
876  }
877 
878  const QString filename = pginfo->GetPlaybackURL(false, true);
879  const QByteArray fname = filename.toLocal8Bit();
880 
881  if (resultCode == REENCODE_OK)
882  {
883  WaitToDelete(pginfo);
884 
885  // Transcoding may take several minutes. Reload the bookmark
886  // in case it changed, then save its translated value back.
887  uint64_t previousBookmark =
888  ComputeNewBookmark(ReloadBookmark(pginfo), deleteMap);
889  pginfo->SaveBookmark(previousBookmark);
890 
891  QString jobArgs;
892  if (jobID >= 0)
893  jobArgs = JobQueue::GetJobArgs(jobID);
894 
895  const QString tmpfile = filename + ".tmp";
896  const QByteArray atmpfile = tmpfile.toLocal8Bit();
897 
898  // To save the original file...
899  const QString oldfile = filename + ".old";
900  const QByteArray aoldfile = oldfile.toLocal8Bit();
901 
902  QFileInfo st(tmpfile);
903  qint64 newSize = 0;
904  if (st.exists())
905  newSize = st.size();
906 
907  QString cnf = filename;
908  if (jobID >= 0)
909  {
910  if (filename.endsWith(".mpg") && jobArgs == "RENAME_TO_NUV")
911  {
912  QString newbase = pginfo->QueryBasename();
913  cnf.replace(".mpg", ".nuv");
914  newbase.replace(".mpg", ".nuv");
915  pginfo->SaveBasename(newbase);
916  }
917  else if (filename.endsWith(".ts") &&
918  (jobArgs == "RENAME_TO_MPG"))
919  {
920  QString newbase = pginfo->QueryBasename();
921  // MPEG-TS to MPEG-PS
922  cnf.replace(".ts", ".mpg");
923  newbase.replace(".ts", ".mpg");
924  pginfo->SaveBasename(newbase);
925  }
926  }
927 
928  const QString newfile = cnf;
929  const QByteArray anewfile = newfile.toLocal8Bit();
930 
931  if (rename(fname.constData(), aoldfile.constData()) == -1)
932  {
933  LOG(VB_GENERAL, LOG_ERR,
934  QString("mythtranscode: Error Renaming '%1' to '%2'")
935  .arg(filename).arg(oldfile) + ENO);
936  }
937 
938  if (rename(atmpfile.constData(), anewfile.constData()) == -1)
939  {
940  LOG(VB_GENERAL, LOG_ERR,
941  QString("mythtranscode: Error Renaming '%1' to '%2'")
942  .arg(tmpfile).arg(newfile) + ENO);
943  }
944 
945  if (!gCoreContext->GetBoolSetting("SaveTranscoding", false) || forceDelete)
946  {
947  int err;
948  bool followLinks =
949  gCoreContext->GetBoolSetting("DeletesFollowLinks", false);
950 
951  LOG(VB_FILE, LOG_INFO,
952  QString("mythtranscode: About to unlink/delete file: %1")
953  .arg(oldfile));
954 
955  QFileInfo finfo(oldfile);
956  if (followLinks && finfo.isSymLink())
957  {
958  QString link = getSymlinkTarget(oldfile);
959  QByteArray alink = link.toLocal8Bit();
960  err = transUnlink(alink.constData(), pginfo);
961  if (err)
962  {
963  LOG(VB_GENERAL, LOG_ERR,
964  QString("mythtranscode: Error deleting '%1' "
965  "pointed to by '%2'")
966  .arg(alink.constData())
967  .arg(aoldfile.constData()) + ENO);
968  }
969 
970  err = unlink(aoldfile.constData());
971  if (err)
972  {
973  LOG(VB_GENERAL, LOG_ERR,
974  QString("mythtranscode: Error deleting '%1', "
975  "a link pointing to '%2'")
976  .arg(aoldfile.constData())
977  .arg(alink.constData()) + ENO);
978  }
979  }
980  else
981  {
982  err = transUnlink(aoldfile.constData(), pginfo);
983  if (err)
984  LOG(VB_GENERAL, LOG_ERR,
985  QString("mythtranscode: Error deleting '%1': ")
986  .arg(oldfile) + ENO);
987  }
988  }
989 
990  // Rename or delete all preview thumbnails.
991  //
992  // TODO: This cleanup should be moved to RecordingInfo, and triggered
993  // when SaveBasename() is called
994  QFileInfo fInfo(filename);
995  QStringList nameFilters;
996  nameFilters.push_back(fInfo.fileName() + "*.png");
997  nameFilters.push_back(fInfo.fileName() + "*.jpg");
998 
999  QDir dir (fInfo.path());
1000  QFileInfoList previewFiles = dir.entryInfoList(nameFilters);
1001 
1002  for (int nIdx = 0; nIdx < previewFiles.size(); nIdx++)
1003  {
1004  const QFileInfo& previewFile = previewFiles.at(nIdx);
1005  QString oldFileName = previewFile.absoluteFilePath();
1006 
1007  // Delete previews if cutlist was applied. They will be re-created as
1008  // required. This prevents the user from being stuck with a preview
1009  // from a cut area and ensures that the "dimensioned" previews
1010  // correspond to the new timeline
1011  if (useCutlist)
1012  {
1013  // If unlink fails, keeping the old preview is not a problem.
1014  // The RENAME_TO_NUV check below will attempt to rename the
1015  // file, if required.
1016  if (transUnlink(oldFileName.toLocal8Bit().constData(), pginfo) != -1)
1017  continue;
1018  }
1019 
1020  if (jobArgs == "RENAME_TO_NUV" || jobArgs == "RENAME_TO_MPG")
1021  {
1022  QString newExtension = "mpg";
1023  if (jobArgs == "RENAME_TO_NUV")
1024  newExtension = "nuv";
1025 
1026  QString oldSuffix = previewFile.completeSuffix();
1027 
1028  if (!oldSuffix.startsWith(newExtension))
1029  {
1030  QString newSuffix = oldSuffix;
1031  QString oldExtension = oldSuffix.section(".", 0, 0);
1032  newSuffix.replace(oldExtension, newExtension);
1033 
1034  QString newFileName = oldFileName;
1035  newFileName.replace(oldSuffix, newSuffix);
1036 
1037  if (!QFile::rename(oldFileName, newFileName))
1038  {
1039  LOG(VB_GENERAL, LOG_ERR,
1040  QString("mythtranscode: Error renaming %1 to %2")
1041  .arg(oldFileName).arg(newFileName));
1042  }
1043  }
1044  }
1045  }
1046 
1047  MSqlQuery query(MSqlQuery::InitCon());
1048 
1049  if (useCutlist)
1050  {
1051  query.prepare("DELETE FROM recordedmarkup "
1052  "WHERE chanid = :CHANID "
1053  "AND starttime = :STARTTIME "
1054  "AND type != :BOOKMARK ");
1055  query.bindValue(":CHANID", pginfo->GetChanID());
1056  query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
1057  query.bindValue(":BOOKMARK", MARK_BOOKMARK);
1058 
1059  if (!query.exec())
1060  MythDB::DBError("Error in mythtranscode", query);
1061 
1062  query.prepare("UPDATE recorded "
1063  "SET cutlist = :CUTLIST "
1064  "WHERE chanid = :CHANID "
1065  "AND starttime = :STARTTIME ;");
1066  query.bindValue(":CUTLIST", "0");
1067  query.bindValue(":CHANID", pginfo->GetChanID());
1068  query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
1069 
1070  if (!query.exec())
1071  MythDB::DBError("Error in mythtranscode", query);
1072 
1074  }
1075  else
1076  {
1077  query.prepare("DELETE FROM recordedmarkup "
1078  "WHERE chanid = :CHANID "
1079  "AND starttime = :STARTTIME "
1080  "AND type not in ( :COMM_START, "
1081  " :COMM_END, :BOOKMARK, "
1082  " :CUTLIST_START, :CUTLIST_END) ;");
1083  query.bindValue(":CHANID", pginfo->GetChanID());
1084  query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
1085  query.bindValue(":COMM_START", MARK_COMM_START);
1086  query.bindValue(":COMM_END", MARK_COMM_END);
1087  query.bindValue(":BOOKMARK", MARK_BOOKMARK);
1088  query.bindValue(":CUTLIST_START", MARK_CUT_START);
1089  query.bindValue(":CUTLIST_END", MARK_CUT_END);
1090 
1091  if (!query.exec())
1092  MythDB::DBError("Error in mythtranscode", query);
1093  }
1094 
1095  if (newSize)
1096  pginfo->SaveFilesize(newSize);
1097 
1098  if (jobID >= 0)
1099  JobQueue::ChangeJobStatus(jobID, JOB_FINISHED);
1100  }
1101  else
1102  {
1103  // Not a successful run, so remove the files we created
1104  QString filename_tmp = filename + ".tmp";
1105  QByteArray fname_tmp = filename_tmp.toLocal8Bit();
1106  LOG(VB_GENERAL, LOG_NOTICE, QString("Deleting %1").arg(filename_tmp));
1107  transUnlink(fname_tmp.constData(), pginfo);
1108 
1109  QString filename_map = filename + ".tmp.map";
1110  QByteArray fname_map = filename_map.toLocal8Bit();
1111  unlink(fname_map.constData());
1112 
1113  if (jobID >= 0)
1114  {
1115  if (status == JOB_ABORTING) // Stop command was sent
1116  JobQueue::ChangeJobStatus(jobID, JOB_ABORTED,
1117  QObject::tr("Job Aborted"));
1118  else if (status != JOB_ERRORING) // Recoverable error
1119  exitCode = GENERIC_EXIT_RESTART;
1120  else // Unrecoverable error
1121  JobQueue::ChangeJobStatus(jobID, JOB_ERRORED,
1122  QObject::tr("Unrecoverable error"));
1123  }
1124  }
1125 }
1126 /* vim: set expandtab tabstop=4 shiftwidth=4: */
QDateTime toDateTime(const QString &key) const
Returns stored QVariant as a QDateTime, falling to default if not provided.
#define GENERIC_EXIT_DB_ERROR
Database error.
Definition: exitcodes.h:17
void SetCMDWidth(int width)
Definition: transcode.h:36
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
Startup context for MythTV.
Definition: mythcontext.h:42
#define REENCODE_MPEG2TRANS
Definition: transcodedefs.h:4
static bool DeleteFile(const QString &url)
Definition: remotefile.cpp:418
void SetCMDAudioCodec(const QString &codec)
Definition: transcode.h:33
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
struct exc__state * last
Definition: pxsup2dast.c:98
void SetAVFMode(void)
Definition: transcode.h:28
void SetCMDBitrate(int bitrate)
Definition: transcode.h:37
#define GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:10
#define GENERIC_EXIT_REMOTE_FILE
Can't transcode a remote file.
Definition: exitcodes.h:30
static uint64_t ComputeNewBookmark(uint64_t oldBookmark, frm_dir_map_t *deleteMap)
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
void SetHLSMaxSegments(int segments)
Definition: transcode.h:31
void SaveCommFlagged(CommFlagStatus flag)
Set "commflagged" field in "recorded" table to "flag".
int BuildKeyframeIndex(QString &file, frm_pos_map_t &posMap, frm_pos_map_t &durMap)
Definition: mpeg2fix.cpp:2776
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
void PrintHelp(void) const
Print command line option help.
#define GENERIC_EXIT_RESTART
Need to restart transcoding.
Definition: exitcodes.h:31
bool QueryCutList(frm_dir_map_t &, bool loadAutosave=false) const
#define GENERIC_EXIT_KILLED
Process killed or stopped.
Definition: exitcodes.h:23
static void Init(QList< int > &signallist, QObject *parent=nullptr)
static void WaitToDelete(ProgramInfo *pginfo)
static enum JobCmds GetJobCmd(int jobID)
Definition: jobqueue.cpp:1462
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:34
QString getSymlinkTarget(const QString &start_file, QStringList *intermediaries, unsigned maxLinks)
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:81
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
int Start()
Definition: mpeg2fix.cpp:2039
Definition: lang.c:20
void DisableAudioOnlyHLS(void)
Definition: transcode.h:39
void SetHLSStreamID(int streamid)
Definition: transcode.h:30
#define REPLEX_HDTV
Definition: multiplex.h:40
RecordingFile * GetRecordingFile() const
MythContext * gContext
This global variable contains the MythContext instance for the application.
Definition: mythcontext.cpp:63
static bool isVideo(const QString &mimeType)
Definition: newssite.cpp:293
static QString recorderOptions
void ShowProgress(bool val)
Definition: transcode.h:26
unsigned sleep(unsigned int x)
Definition: compat.h:159
void SetCMDContainer(const QString &container)
Definition: transcode.h:32
QString GetStorageGroup(void) const
Definition: programinfo.h:414
int GetBackendServerPort(void)
Returns the locally defined backend control port.
static bool ChangeJobComment(int jobID, const QString &comment="")
Definition: jobqueue.cpp:1014
QVariant value(int i) const
Definition: mythdbcon.h:198
MarkTypes
Definition: programtypes.h:48
void ApplySettingsOverride(void)
Apply all overrides to the global context.
Holds information on recordings and videos.
Definition: programinfo.h:66
static int transUnlink(const QString &filename, ProgramInfo *pginfo)
virtual void SaveFilesize(uint64_t fsize)
Sets recording file size in database, and sets "filesize" field.
static bool GetJobInfoFromID(int jobID, int &jobType, uint &chanid, QDateTime &recstartts)
Definition: jobqueue.cpp:669
#define GENERIC_EXIT_NO_RECORDING_DATA
No program/recording data.
Definition: exitcodes.h:29
void SetCMDHeight(int height)
Definition: transcode.h:35
static void CompleteJob(int jobID, ProgramInfo *pginfo, bool useCutlist, frm_dir_map_t *deleteMap, int &exitCode, int resultCode, bool forceDelete)
QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
int main(int argc, char *argv[])
bool SaveBasename(const QString &basename)
Sets a recording's basename in the database.
void SetAllAudio(bool keep)
Definition: mpeg2fix.h:130
void SetRecorderOptions(const QString &options)
Definition: transcode.h:27
#define REENCODE_OK
Definition: transcodedefs.h:6
static void load(const QString &module_name)
Load a QTranslator for the user's preferred language.
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
Definition: mythdbcon.cpp:851
uint GetRecordingID(void) const
Definition: programinfo.h:438
void SaveBookmark(uint64_t frame)
TODO Move to RecordingInfo.
string hostname
Definition: caa.py:17
static void Done(void)
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
static void SetHandler(int signum, SigHandlerFunc handler)
MythCommFlagCommandLineParser cmdline
static int BuildKeyframeIndex(MPEG2fixup *m2f, QString &infile, frm_pos_map_t &posMap, frm_pos_map_t &durMap, int jobID)
Holds information on a recording file and it's video and audio streams.
Definition: recordingfile.h:29
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
int FILE
Definition: mythburn.py:110
#define REPLEX_DVD
Definition: multiplex.h:39
void ApplyTranscoderProfileChange(const QString &profile) const
Sets the transcoder profile for a recording.
#define MYTH_APPNAME_MYTHTRANSCODE
#define REENCODE_CUTLIST_CHANGE
Definition: transcodedefs.h:5
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
static bool ChangeJobArgs(int jobID, const QString &args="")
Definition: jobqueue.cpp:1039
static void UpdatePositionMap(frm_pos_map_t &posMap, frm_pos_map_t &durMap, const QString &mapfile, ProgramInfo *pginfo)
QStringList toStringList(const QString &key, const QString &sep="") const
Returns stored QVariant as a QStringList, falling to default if not provided.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
#define REPLEX_MPEG2
Definition: multiplex.h:38
static void UpdateJobQueue(float percent_done)
bool GetBoolSetting(const QString &key, bool defaultval=false)
virtual bool Parse(int argc, const char *const *argv)
Loop through argv and populate arguments with values.
AVContainer m_containerFormat
Definition: recordingfile.h:47
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:364
void SetHLSMode(void)
Definition: transcode.h:29
QString QueryBasename(void) const
Gets the basename, from the DB if necessary.
static void cleanup(VideoFilter *filter)
static uint64_t ReloadBookmark(ProgramInfo *pginfo)
QString GetPlaybackURL(bool checkMaster=false, bool forceCheckLocal=false) const
Returns filename or URL to be used to play back this recording.
#define SIGHUP
Definition: compat.h:213
int TranscodeFile(const QString &inputname, const QString &outputname, const QString &profileName, bool honorCutList, bool framecontrol, int jobID, const QString &fifodir, bool fifo_info, bool cleanCut, frm_dir_map_t &deleteMap, int AudioTrackNo, bool passthru=false)
Definition: transcode.cpp:213
#define MYTH_BINARY_VERSION
Update this whenever the plug-in ABI changes.
Definition: mythversion.h:16
static QString GetJobArgs(int jobID)
Definition: jobqueue.cpp:1483
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:396
void SetCMDVideoCodec(const QString &codec)
Definition: transcode.h:34
static bool ChangeJobStatus(int jobID, int newStatus, const QString &comment="")
Definition: jobqueue.cpp:987
bool Init(const bool gui=true, const bool promptForBackend=false, const bool disableAutoDiscovery=false, const bool ignoreDB=false)
QString GetHostname(void) const
Definition: programinfo.h:413
static int QueueTranscodeJob(ProgramInfo *pginfo, const QString &profile, QString hostname, bool usecutlist)
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
int ConfigureLogging(QString mask="general", unsigned int progress=0)
Read in logging options and initialize the logging interface.
#define GENERIC_EXIT_NO_MYTHCONTEXT
No MythContext available.
Definition: exitcodes.h:13
static int CheckJobQueue()
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
QMap< long long, long long > frm_pos_map_t
Frame # -> File offset map.
Definition: programtypes.h:46
#define GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:15
void SavePositionMap(frm_pos_map_t &, MarkTypes type, int64_t min_frame=-1, int64_t max_frame=-1) const
static enum JobStatus GetJobStatus(int jobID)
Definition: jobqueue.cpp:1525
void SetCMDAudioBitrate(int bitrate)
Definition: transcode.h:38
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
#define REENCODE_STOPPED
Definition: transcodedefs.h:8
static bool QueueJob(int jobType, uint chanid, const QDateTime &recstartts, const QString &args="", const QString &comment="", QString host="", int flags=0, int status=JOB_QUEUED, QDateTime schedruntime=QDateTime())
Definition: jobqueue.cpp:518
void PrintVersion(void) const
Print application version information.
void ClearPositionMap(MarkTypes type) const
Default UTC.
Definition: mythdate.h:14
int toInt(const QString &key) const
Returns stored QVariant as an integer, falling to default if not provided.
#define REPLEX_TS_SD
Definition: multiplex.h:41