9 #include <QCoreApplication>
23 #include "libmythbase/mythversion.h"
37 int resultCode,
bool forceDelete);
45 if (pginfo && mapfile.isEmpty())
52 else if (!mapfile.isEmpty())
55 FILE *mapfh = fopen(mapfile.toLocal8Bit().constData(),
"w");
58 LOG(VB_GENERAL, LOG_ERR, QString(
"Could not open map file '%1'")
62 frm_pos_map_t::const_iterator it;
63 fprintf (mapfh,
"Type: %d\n", keyType);
64 for (it = posMap.cbegin(); it != posMap.cend(); ++it)
66 QString str = QString(
"%1 %2\n").arg(it.key()).arg(*it);
67 fprintf(mapfh,
"%s", qPrintable(str));
83 QObject::tr(
"Generating Keyframe Index"));
89 QObject::tr(
"Transcode Completed"));
97 QString(
"%1% ").arg(percent_done, 0,
'f', 1) +
98 QObject::tr(
"Completed"));
105 LOG(VB_GENERAL, LOG_NOTICE,
"Transcoding stopped by JobQueue");
112 const QString&
hostname,
bool usecutlist)
125 LOG(VB_GENERAL, LOG_NOTICE,
126 QString(
"Queued transcode job for chanid %1 @ %2")
132 LOG(VB_GENERAL, LOG_ERR, QString(
"Error queuing job for chanid %1 @ %2")
138 int main(
int argc,
char *argv[])
144 QString profilename = QString(
"autodetect");
145 QString fifodir =
nullptr;
149 bool useCutlist =
false;
150 bool keyframesonly =
false;
151 bool build_index =
false;
152 bool fifosync =
false;
154 bool fifo_info =
false;
155 bool cleanCut =
false;
159 int AudioTrackNo = -1;
161 bool found_starttime =
false;
162 bool found_chanid =
false;
163 bool found_infile =
false;
164 int update_index = 1;
166 bool passthru =
false;
187 QCoreApplication a(argc, argv);
198 QString mask(
"general");
199 bool quiet = (outfile ==
"-") || showprogress;
207 found_starttime =
true;
233 LOG(VB_GENERAL, LOG_CRIT,
"External cutlists are only allowed "
234 "when using the --infile option.");
240 for (
const auto & cut : std::as_const(cutlist))
242 QStringList startend = cut.split(
"-", Qt::SkipEmptyParts);
243 if (startend.size() == 2)
245 uint64_t start = startend.first().toULongLong();
246 uint64_t end = startend.last().toULongLong();
250 LOG(VB_GENERAL, LOG_DEBUG,
251 QString(
"Cutting section %1-%2.")
252 .arg(last).arg(start));
259 LOG(VB_GENERAL, LOG_DEBUG,
260 QString(
"Cutting section %1-%2.")
261 .arg(start).arg(end));
270 if (deleteMap.contains(0) && (deleteMap[0] ==
MARK_CUT_END))
275 LOG(VB_GENERAL, LOG_DEBUG,
276 QString(
"Cutting section %1-999999999.")
281 if (deleteMap.count() >= 2)
283 frm_dir_map_t::iterator cur = deleteMap.begin();
284 frm_dir_map_t::iterator prev;
286 while (cur != deleteMap.end())
288 if (prev.value() == cur.value())
291 QString err(
"Cut %1points found at %3 and %4, with no "
292 "%2 point in between.");
294 err = err.arg(
"end",
"start");
296 err = err.arg(
"start",
"end");
297 LOG(VB_GENERAL, LOG_CRIT,
"Invalid cutlist defined!");
298 LOG(VB_GENERAL, LOG_CRIT, err.arg(prev.key())
303 ((cur.key() - prev.key()) < 2) )
305 LOG(VB_GENERAL, LOG_WARNING, QString(
"Discarding "
306 "insufficiently long cut: %1-%2")
307 .arg(prev.key()).arg(cur.key()));
308 prev = deleteMap.erase(prev);
309 cur = deleteMap.erase(cur);
311 if (cur == deleteMap.end())
320 std::cerr <<
"Cutlist inversion requires an external cutlist be" << std::endl
321 <<
"provided using the --honorcutlist option." << std::endl;
330 keyframesonly =
true;
353 std::cerr <<
"Invalid 'ostream' type: "
368 if (!context.Init(
false))
370 LOG(VB_GENERAL, LOG_ERR,
"Failed to init MythContext, exiting.");
382 found_starttime =
true;
387 std::cerr <<
"mythtranscode: ERROR: Unable to find DB info for "
388 <<
"JobQueue ID# " <<
jobID << std::endl;
393 if (((!found_infile && !(found_chanid && found_starttime)) ||
394 (found_infile && (found_chanid || found_starttime))) &&
397 std::cerr <<
"Must specify -i OR -c AND -s options!" << std::endl;
402 std::cerr <<
"Must specify --infile to use --video" << std::endl;
405 if (
jobID >= 0 && (found_infile || build_index))
407 std::cerr <<
"Can't specify -j with --buildindex, --video or --infile"
411 if ((
jobID >= 0) && build_index)
413 std::cerr <<
"Can't specify both -j and --buildindex" << std::endl;
416 if (keyframesonly && !fifodir.isEmpty())
418 std::cerr <<
"Cannot specify both --fifodir and --allkeys" << std::endl;
421 if (fifosync && fifodir.isEmpty())
423 std::cerr <<
"Must specify --fifodir to use --fifosync" << std::endl;
426 if (fifo_info && !fifodir.isEmpty())
428 std::cerr <<
"Cannot specify both --fifodir and --fifoinfo" << std::endl;
431 if (cleanCut && fifodir.isEmpty() && !fifo_info)
433 std::cerr <<
"Clean cutting works only in fifodir mode" << std::endl;
436 if (cleanCut && !useCutlist)
438 std::cerr <<
"--cleancut is pointless without --honorcutlist" << std::endl;
446 fifodir =
"DummyFifoPath";
451 LOG(VB_GENERAL, LOG_ERR,
"couldn't open db");
463 if (pginfo ==
nullptr)
469 QFileInfo inf(infile);
470 infile = inf.absoluteFilePath();
473 else if (!found_infile)
479 LOG(VB_GENERAL, LOG_ERR,
480 QString(
"Couldn't find recording for chanid %1 @ %2")
481 .arg(chanid).arg(starttime.toString(
Qt::ISODate)));
493 LOG(VB_GENERAL, LOG_ERR,
494 QString(
"Couldn't find a recording for filename '%1'")
503 LOG(VB_GENERAL, LOG_ERR,
"No program info found!");
513 if (infile.startsWith(
"myth://") && (outfile.isEmpty() || outfile !=
"-") &&
516 LOG(VB_GENERAL, LOG_ERR,
517 QString(
"Attempted to transcode %1. Mythtranscode is currently "
518 "unable to transcode remote files.") .arg(infile));
523 if (outfile.isEmpty() && !build_index && fifodir.isEmpty())
524 outfile = infile +
".tmp";
535 LOG(VB_GENERAL, LOG_NOTICE,
536 QString(
"Transcoding HTTP Live Stream ID %1")
539 else if (fifodir.isEmpty())
541 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to %2")
542 .arg(infile, outfile));
546 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to FIFO")
553 transcode->SetAVFMode();
564 transcode->SetHLSMode();
567 transcode->SetHLSStreamID(
cmdline.
toInt(
"hlsstreamid"));
569 transcode->SetHLSMaxSegments(
cmdline.
toInt(
"maxsegments"));
571 transcode->DisableAudioOnlyHLS();
581 transcode->SetCMDBitrate(
cmdline.
toInt(
"bitrate") * 1000);
583 transcode->SetCMDAudioBitrate(
cmdline.
toInt(
"audiobitrate") * 1000);
587 transcode->ShowProgress(
true);
593 result = transcode->TranscodeFile(infile, outfile,
594 profilename, useCutlist,
595 (fifosync || keyframesonly),
jobID,
596 fifodir, fifo_info, cleanCut, deleteMap,
597 AudioTrackNo, passthru);
618 void (*update_func)(float) =
nullptr;
619 int (*check_func)() =
nullptr;
622 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
623 if (deleteMap.isEmpty())
634 &deleteMap,
nullptr,
false,
false, 20,
635 showprogress, otype, update_func,
640 m2f->SetAllAudio(
true);
659 result = m2f->Start();
694 LOG(VB_GENERAL, LOG_NOTICE, QString(
"%1 %2 done")
695 .arg(build_index ?
"Building Index for" :
"Transcoding", infile));
701 LOG(VB_GENERAL, LOG_NOTICE,
702 QString(
"Transcoding %1 aborted because of cutlist update")
710 LOG(VB_GENERAL, LOG_NOTICE,
711 QString(
"Transcoding %1 stopped because of stop command")
719 LOG(VB_GENERAL, LOG_ERR, QString(
"Transcoding %1 failed").arg(infile));
723 if (deleteOriginal ||
jobID >= 0)
724 CompleteJob(
jobID, pginfo, useCutlist, &deleteMap, exitcode, result, deleteOriginal);
726 transcode->deleteLater();
739 QString basename =
filename.section(
'/', -1);
743 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Requesting delete for file '%1'.")
750 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting file '%1'.").arg(
filename));
751 return unlink(
filename.toLocal8Bit().constData());
757 if (deleteMap ==
nullptr)
760 uint64_t subtraction = 0;
761 uint64_t startOfCutRegion = 0;
763 bool withinCut =
false;
764 bool firstMark =
true;
765 while (!delMap.empty() && delMap.begin().key() <= oldBookmark)
767 uint64_t key = delMap.begin().key();
773 startOfCutRegion = key;
782 subtraction += (key - startOfCutRegion);
788 subtraction += (oldBookmark - startOfCutRegion);
789 return oldBookmark - subtraction;
795 uint64_t currentBookmark = 0;
796 query.
prepare(
"SELECT DISTINCT mark FROM recordedmarkup "
797 "WHERE chanid = :CHANID "
798 "AND starttime = :STARTIME "
799 "AND type = :MARKTYPE ;");
805 currentBookmark = query.
value(0).toLongLong();
807 return currentBookmark;
812 LOG(VB_GENERAL, LOG_NOTICE,
813 "Transcode: delete old file: waiting while program is in use.");
819 query.
prepare(
"SELECT count(*) FROM inuseprograms "
820 "WHERE chanid = :CHANID "
821 "AND starttime = :STARTTIME "
822 "AND recusage = 'player' ;");
827 LOG(VB_GENERAL, LOG_ERR,
828 "Transcode: delete old file: in-use query failed;");
833 inUse = (query.
value(0).toUInt() != 0);
838 const unsigned kSecondsToWait = 10;
839 LOG(VB_GENERAL, LOG_NOTICE,
840 QString(
"Transcode: program in use, rechecking in %1 seconds.")
841 .arg(kSecondsToWait));
842 sleep(kSecondsToWait);
845 LOG(VB_GENERAL, LOG_NOTICE,
"Transcode: program is no longer in use.");
849 frm_dir_map_t *deleteMap,
int &exitCode,
int resultCode,
bool forceDelete)
851 int status = JOB_UNKNOWN;
859 QObject::tr(
"Job errored, unable to find Program Info for job"));
860 LOG(VB_GENERAL, LOG_CRIT,
"MythTranscode: Cleanup errored, unable to find Program Info");
865 const QByteArray fname =
filename.toLocal8Bit();
873 uint64_t previousBookmark =
881 const QString tmpfile =
filename +
".tmp";
882 const QByteArray atmpfile = tmpfile.toLocal8Bit();
885 const QString oldfile =
filename +
".old";
886 const QByteArray aoldfile = oldfile.toLocal8Bit();
888 QFileInfo st(tmpfile);
896 if (
filename.endsWith(
".mpg") && jobArgs ==
"RENAME_TO_NUV")
899 cnf.replace(
".mpg",
".nuv");
900 newbase.replace(
".mpg",
".nuv");
903 else if (
filename.endsWith(
".ts") &&
904 (jobArgs ==
"RENAME_TO_MPG"))
908 cnf.replace(
".ts",
".mpg");
909 newbase.replace(
".ts",
".mpg");
914 const QString newfile = cnf;
915 const QByteArray anewfile = newfile.toLocal8Bit();
917 if (rename(fname.constData(), aoldfile.constData()) == -1)
919 LOG(VB_GENERAL, LOG_ERR,
920 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
924 if (rename(atmpfile.constData(), anewfile.constData()) == -1)
926 LOG(VB_GENERAL, LOG_ERR,
927 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
928 .arg(tmpfile, newfile) +
ENO);
936 LOG(VB_FILE, LOG_INFO,
937 QString(
"mythtranscode: About to unlink/delete file: %1")
940 QFileInfo finfo(oldfile);
941 if (followLinks && finfo.isSymLink())
944 QByteArray alink = link.toLocal8Bit();
948 LOG(VB_GENERAL, LOG_ERR,
949 QString(
"mythtranscode: Error deleting '%1' "
950 "pointed to by '%2'")
951 .arg(alink.constData(), aoldfile.constData()) +
ENO);
954 err = unlink(aoldfile.constData());
957 LOG(VB_GENERAL, LOG_ERR,
958 QString(
"mythtranscode: Error deleting '%1', "
959 "a link pointing to '%2'")
960 .arg(aoldfile.constData(), alink.constData()) +
ENO);
965 int err =
transUnlink(aoldfile.constData(), pginfo);
968 LOG(VB_GENERAL, LOG_ERR,
969 QString(
"mythtranscode: Error deleting '%1': ")
970 .arg(oldfile) +
ENO);
980 QStringList nameFilters;
981 nameFilters.push_back(fInfo.fileName() +
"*.png");
982 nameFilters.push_back(fInfo.fileName() +
"*.jpg");
984 QDir dir (fInfo.path());
985 QFileInfoList previewFiles = dir.entryInfoList(nameFilters);
987 for (
const auto & previewFile : std::as_const(previewFiles))
989 QString oldFileName = previewFile.absoluteFilePath();
1000 if (
transUnlink(oldFileName.toLocal8Bit().constData(), pginfo) != -1)
1004 if (jobArgs ==
"RENAME_TO_NUV" || jobArgs ==
"RENAME_TO_MPG")
1006 QString newExtension =
"mpg";
1007 if (jobArgs ==
"RENAME_TO_NUV")
1008 newExtension =
"nuv";
1010 QString oldSuffix = previewFile.completeSuffix();
1012 if (!oldSuffix.startsWith(newExtension))
1014 QString newSuffix = oldSuffix;
1015 QString oldExtension = oldSuffix.section(
".", 0, 0);
1016 newSuffix.replace(oldExtension, newExtension);
1018 QString newFileName = oldFileName;
1019 newFileName.replace(oldSuffix, newSuffix);
1021 if (!QFile::rename(oldFileName, newFileName))
1023 LOG(VB_GENERAL, LOG_ERR,
1024 QString(
"mythtranscode: Error renaming %1 to %2")
1025 .arg(oldFileName, newFileName));
1035 query.
prepare(
"DELETE FROM recordedmarkup "
1036 "WHERE chanid = :CHANID "
1037 "AND starttime = :STARTTIME "
1038 "AND type != :BOOKMARK ");
1046 query.
prepare(
"UPDATE recorded "
1047 "SET cutlist = :CUTLIST "
1048 "WHERE chanid = :CHANID "
1049 "AND starttime = :STARTTIME ;");
1061 query.
prepare(
"DELETE FROM recordedmarkup "
1062 "WHERE chanid = :CHANID "
1063 "AND starttime = :STARTTIME "
1064 "AND type not in ( :COMM_START, "
1065 " :COMM_END, :BOOKMARK, "
1066 " :CUTLIST_START, :CUTLIST_END) ;");
1088 QString filename_tmp =
filename +
".tmp";
1089 QByteArray fname_tmp = filename_tmp.toLocal8Bit();
1090 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting %1").arg(filename_tmp));
1093 QString filename_map =
filename +
".tmp.map";
1094 QByteArray fname_map = filename_map.toLocal8Bit();
1095 unlink(fname_map.constData());
1099 if (status == JOB_ABORTING)
1102 QObject::tr(
"Job Aborted"));
1104 else if (status != JOB_ERRORING)
1111 QObject::tr(
"Unrecoverable error"));