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