9 #include <QCoreApplication>
22 #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")
148 int main(
int argc,
char *argv[])
154 QString profilename = QString(
"autodetect");
155 QString fifodir =
nullptr;
159 bool useCutlist =
false;
160 bool keyframesonly =
false;
161 bool build_index =
false;
162 bool fifosync =
false;
164 bool fifo_info =
false;
165 bool cleanCut =
false;
169 int AudioTrackNo = -1;
171 bool found_starttime =
false;
172 bool found_chanid =
false;
173 bool found_infile =
false;
174 int update_index = 1;
176 bool passthru =
false;
197 QCoreApplication a(argc, argv);
208 QString mask(
"general");
209 bool quiet = (outfile ==
"-") || showprogress;
217 found_starttime =
true;
243 LOG(VB_GENERAL, LOG_CRIT,
"External cutlists are only allowed "
244 "when using the --infile option.");
250 for (
const auto & cut : qAsConst(cutlist))
252 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
253 QStringList startend =
254 cut.split(
"-", QString::SkipEmptyParts);
256 QStringList startend = cut.split(
"-", Qt::SkipEmptyParts);
258 if (startend.size() == 2)
260 uint64_t start = startend.first().toULongLong();
261 uint64_t end = startend.last().toULongLong();
265 LOG(VB_GENERAL, LOG_DEBUG,
266 QString(
"Cutting section %1-%2.")
267 .arg(last).arg(start));
274 LOG(VB_GENERAL, LOG_DEBUG,
275 QString(
"Cutting section %1-%2.")
276 .arg(start).arg(end));
285 if (deleteMap.contains(0) && (deleteMap[0] ==
MARK_CUT_END))
290 LOG(VB_GENERAL, LOG_DEBUG,
291 QString(
"Cutting section %1-999999999.")
296 if (deleteMap.count() >= 2)
298 frm_dir_map_t::iterator cur = deleteMap.begin();
299 frm_dir_map_t::iterator prev;
301 while (cur != deleteMap.end())
303 if (prev.value() == cur.value())
306 QString err(
"Cut %1points found at %3 and %4, with no "
307 "%2 point in between.");
309 err = err.arg(
"end",
"start");
311 err = err.arg(
"start",
"end");
312 LOG(VB_GENERAL, LOG_CRIT,
"Invalid cutlist defined!");
313 LOG(VB_GENERAL, LOG_CRIT, err.arg(prev.key())
318 ((cur.key() - prev.key()) < 2) )
320 LOG(VB_GENERAL, LOG_WARNING, QString(
"Discarding "
321 "insufficiently long cut: %1-%2")
322 .arg(prev.key()).arg(cur.key()));
323 prev = deleteMap.erase(prev);
324 cur = deleteMap.erase(cur);
326 if (cur == deleteMap.end())
335 std::cerr <<
"Cutlist inversion requires an external cutlist be" << std::endl
336 <<
"provided using the --honorcutlist option." << std::endl;
345 keyframesonly =
true;
368 std::cerr <<
"Invalid 'ostream' type: "
391 LOG(VB_GENERAL, LOG_ERR,
"Failed to init MythContext, exiting.");
403 found_starttime =
true;
408 std::cerr <<
"mythtranscode: ERROR: Unable to find DB info for "
409 <<
"JobQueue ID# " <<
jobID << std::endl;
414 if (((!found_infile && !(found_chanid && found_starttime)) ||
415 (found_infile && (found_chanid || found_starttime))) &&
418 std::cerr <<
"Must specify -i OR -c AND -s options!" << std::endl;
423 std::cerr <<
"Must specify --infile to use --video" << std::endl;
426 if (
jobID >= 0 && (found_infile || build_index))
428 std::cerr <<
"Can't specify -j with --buildindex, --video or --infile"
432 if ((
jobID >= 0) && build_index)
434 std::cerr <<
"Can't specify both -j and --buildindex" << std::endl;
437 if (keyframesonly && !fifodir.isEmpty())
439 std::cerr <<
"Cannot specify both --fifodir and --allkeys" << std::endl;
442 if (fifosync && fifodir.isEmpty())
444 std::cerr <<
"Must specify --fifodir to use --fifosync" << std::endl;
447 if (fifo_info && !fifodir.isEmpty())
449 std::cerr <<
"Cannot specify both --fifodir and --fifoinfo" << std::endl;
452 if (cleanCut && fifodir.isEmpty() && !fifo_info)
454 std::cerr <<
"Clean cutting works only in fifodir mode" << std::endl;
457 if (cleanCut && !useCutlist)
459 std::cerr <<
"--cleancut is pointless without --honorcutlist" << std::endl;
467 fifodir =
"DummyFifoPath";
472 LOG(VB_GENERAL, LOG_ERR,
"couldn't open db");
484 if (pginfo ==
nullptr)
490 QFileInfo inf(infile);
491 infile = inf.absoluteFilePath();
494 else if (!found_infile)
500 LOG(VB_GENERAL, LOG_ERR,
501 QString(
"Couldn't find recording for chanid %1 @ %2")
502 .arg(chanid).arg(starttime.toString(
Qt::ISODate)));
514 LOG(VB_GENERAL, LOG_ERR,
515 QString(
"Couldn't find a recording for filename '%1'")
524 LOG(VB_GENERAL, LOG_ERR,
"No program info found!");
534 if (infile.startsWith(
"myth://") && (outfile.isEmpty() || outfile !=
"-") &&
537 LOG(VB_GENERAL, LOG_ERR,
538 QString(
"Attempted to transcode %1. Mythtranscode is currently "
539 "unable to transcode remote files.") .arg(infile));
544 if (outfile.isEmpty() && !build_index && fifodir.isEmpty())
545 outfile = infile +
".tmp";
556 LOG(VB_GENERAL, LOG_NOTICE,
557 QString(
"Transcoding HTTP Live Stream ID %1")
560 else if (fifodir.isEmpty())
562 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to %2")
563 .arg(infile, outfile));
567 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to FIFO")
574 transcode->SetAVFMode();
585 transcode->SetHLSMode();
588 transcode->SetHLSStreamID(
cmdline.
toInt(
"hlsstreamid"));
590 transcode->SetHLSMaxSegments(
cmdline.
toInt(
"maxsegments"));
592 transcode->DisableAudioOnlyHLS();
602 transcode->SetCMDBitrate(
cmdline.
toInt(
"bitrate") * 1000);
604 transcode->SetCMDAudioBitrate(
cmdline.
toInt(
"audiobitrate") * 1000);
608 transcode->ShowProgress(
true);
614 result = transcode->TranscodeFile(infile, outfile,
615 profilename, useCutlist,
616 (fifosync || keyframesonly),
jobID,
617 fifodir, fifo_info, cleanCut, deleteMap,
618 AudioTrackNo, passthru);
639 void (*update_func)(float) =
nullptr;
640 int (*check_func)() =
nullptr;
643 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
644 if (deleteMap.isEmpty())
655 &deleteMap,
nullptr,
false,
false, 20,
656 showprogress, otype, update_func,
661 m2f->SetAllAudio(
true);
680 result = m2f->Start();
715 LOG(VB_GENERAL, LOG_NOTICE, QString(
"%1 %2 done")
716 .arg(build_index ?
"Building Index for" :
"Transcoding", infile));
722 LOG(VB_GENERAL, LOG_NOTICE,
723 QString(
"Transcoding %1 aborted because of cutlist update")
731 LOG(VB_GENERAL, LOG_NOTICE,
732 QString(
"Transcoding %1 stopped because of stop command")
740 LOG(VB_GENERAL, LOG_ERR, QString(
"Transcoding %1 failed").arg(infile));
744 if (deleteOriginal ||
jobID >= 0)
745 CompleteJob(
jobID, pginfo, useCutlist, &deleteMap, exitcode, result, deleteOriginal);
747 transcode->deleteLater();
760 QString basename =
filename.section(
'/', -1);
764 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Requesting delete for file '%1'.")
771 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting file '%1'.").arg(
filename));
772 return unlink(
filename.toLocal8Bit().constData());
778 if (deleteMap ==
nullptr)
781 uint64_t subtraction = 0;
782 uint64_t startOfCutRegion = 0;
784 bool withinCut =
false;
785 bool firstMark =
true;
786 while (!delMap.empty() && delMap.begin().key() <= oldBookmark)
788 uint64_t key = delMap.begin().key();
794 startOfCutRegion = key;
803 subtraction += (key - startOfCutRegion);
809 subtraction += (oldBookmark - startOfCutRegion);
810 return oldBookmark - subtraction;
816 uint64_t currentBookmark = 0;
817 query.
prepare(
"SELECT DISTINCT mark FROM recordedmarkup "
818 "WHERE chanid = :CHANID "
819 "AND starttime = :STARTIME "
820 "AND type = :MARKTYPE ;");
826 currentBookmark = query.
value(0).toLongLong();
828 return currentBookmark;
833 LOG(VB_GENERAL, LOG_NOTICE,
834 "Transcode: delete old file: waiting while program is in use.");
840 query.
prepare(
"SELECT count(*) FROM inuseprograms "
841 "WHERE chanid = :CHANID "
842 "AND starttime = :STARTTIME "
843 "AND recusage = 'player' ;");
848 LOG(VB_GENERAL, LOG_ERR,
849 "Transcode: delete old file: in-use query failed;");
854 inUse = (query.
value(0).toUInt() != 0);
859 const unsigned kSecondsToWait = 10;
860 LOG(VB_GENERAL, LOG_NOTICE,
861 QString(
"Transcode: program in use, rechecking in %1 seconds.")
862 .arg(kSecondsToWait));
863 sleep(kSecondsToWait);
866 LOG(VB_GENERAL, LOG_NOTICE,
"Transcode: program is no longer in use.");
870 frm_dir_map_t *deleteMap,
int &exitCode,
int resultCode,
bool forceDelete)
872 int status = JOB_UNKNOWN;
880 QObject::tr(
"Job errored, unable to find Program Info for job"));
881 LOG(VB_GENERAL, LOG_CRIT,
"MythTranscode: Cleanup errored, unable to find Program Info");
886 const QByteArray fname =
filename.toLocal8Bit();
894 uint64_t previousBookmark =
902 const QString tmpfile =
filename +
".tmp";
903 const QByteArray atmpfile = tmpfile.toLocal8Bit();
906 const QString oldfile =
filename +
".old";
907 const QByteArray aoldfile = oldfile.toLocal8Bit();
909 QFileInfo st(tmpfile);
917 if (
filename.endsWith(
".mpg") && jobArgs ==
"RENAME_TO_NUV")
920 cnf.replace(
".mpg",
".nuv");
921 newbase.replace(
".mpg",
".nuv");
924 else if (
filename.endsWith(
".ts") &&
925 (jobArgs ==
"RENAME_TO_MPG"))
929 cnf.replace(
".ts",
".mpg");
930 newbase.replace(
".ts",
".mpg");
935 const QString newfile = cnf;
936 const QByteArray anewfile = newfile.toLocal8Bit();
938 if (rename(fname.constData(), aoldfile.constData()) == -1)
940 LOG(VB_GENERAL, LOG_ERR,
941 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
945 if (rename(atmpfile.constData(), anewfile.constData()) == -1)
947 LOG(VB_GENERAL, LOG_ERR,
948 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
949 .arg(tmpfile, newfile) +
ENO);
957 LOG(VB_FILE, LOG_INFO,
958 QString(
"mythtranscode: About to unlink/delete file: %1")
961 QFileInfo finfo(oldfile);
962 if (followLinks && finfo.isSymLink())
965 QByteArray alink = link.toLocal8Bit();
969 LOG(VB_GENERAL, LOG_ERR,
970 QString(
"mythtranscode: Error deleting '%1' "
971 "pointed to by '%2'")
972 .arg(alink.constData(), aoldfile.constData()) +
ENO);
975 err = unlink(aoldfile.constData());
978 LOG(VB_GENERAL, LOG_ERR,
979 QString(
"mythtranscode: Error deleting '%1', "
980 "a link pointing to '%2'")
981 .arg(aoldfile.constData(), alink.constData()) +
ENO);
986 int err =
transUnlink(aoldfile.constData(), pginfo);
989 LOG(VB_GENERAL, LOG_ERR,
990 QString(
"mythtranscode: Error deleting '%1': ")
991 .arg(oldfile) +
ENO);
1001 QStringList nameFilters;
1002 nameFilters.push_back(fInfo.fileName() +
"*.png");
1003 nameFilters.push_back(fInfo.fileName() +
"*.jpg");
1005 QDir dir (fInfo.path());
1006 QFileInfoList previewFiles = dir.entryInfoList(nameFilters);
1008 for (
const auto & previewFile : qAsConst(previewFiles))
1010 QString oldFileName = previewFile.absoluteFilePath();
1021 if (
transUnlink(oldFileName.toLocal8Bit().constData(), pginfo) != -1)
1025 if (jobArgs ==
"RENAME_TO_NUV" || jobArgs ==
"RENAME_TO_MPG")
1027 QString newExtension =
"mpg";
1028 if (jobArgs ==
"RENAME_TO_NUV")
1029 newExtension =
"nuv";
1031 QString oldSuffix = previewFile.completeSuffix();
1033 if (!oldSuffix.startsWith(newExtension))
1035 QString newSuffix = oldSuffix;
1036 QString oldExtension = oldSuffix.section(
".", 0, 0);
1037 newSuffix.replace(oldExtension, newExtension);
1039 QString newFileName = oldFileName;
1040 newFileName.replace(oldSuffix, newSuffix);
1042 if (!QFile::rename(oldFileName, newFileName))
1044 LOG(VB_GENERAL, LOG_ERR,
1045 QString(
"mythtranscode: Error renaming %1 to %2")
1046 .arg(oldFileName, newFileName));
1056 query.
prepare(
"DELETE FROM recordedmarkup "
1057 "WHERE chanid = :CHANID "
1058 "AND starttime = :STARTTIME "
1059 "AND type != :BOOKMARK ");
1067 query.
prepare(
"UPDATE recorded "
1068 "SET cutlist = :CUTLIST "
1069 "WHERE chanid = :CHANID "
1070 "AND starttime = :STARTTIME ;");
1082 query.
prepare(
"DELETE FROM recordedmarkup "
1083 "WHERE chanid = :CHANID "
1084 "AND starttime = :STARTTIME "
1085 "AND type not in ( :COMM_START, "
1086 " :COMM_END, :BOOKMARK, "
1087 " :CUTLIST_START, :CUTLIST_END) ;");
1109 QString filename_tmp =
filename +
".tmp";
1110 QByteArray fname_tmp = filename_tmp.toLocal8Bit();
1111 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting %1").arg(filename_tmp));
1114 QString filename_map =
filename +
".tmp.map";
1115 QByteArray fname_map = filename_map.toLocal8Bit();
1116 unlink(fname_map.constData());
1120 if (status == JOB_ABORTING)
1123 QObject::tr(
"Job Aborted"));
1125 else if (status != JOB_ERRORING)
1132 QObject::tr(
"Unrecoverable error"));