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 : std::as_const(cutlist))
252 QStringList startend = cut.split(
"-", Qt::SkipEmptyParts);
253 if (startend.size() == 2)
255 uint64_t start = startend.first().toULongLong();
256 uint64_t end = startend.last().toULongLong();
260 LOG(VB_GENERAL, LOG_DEBUG,
261 QString(
"Cutting section %1-%2.")
262 .arg(last).arg(start));
269 LOG(VB_GENERAL, LOG_DEBUG,
270 QString(
"Cutting section %1-%2.")
271 .arg(start).arg(end));
280 if (deleteMap.contains(0) && (deleteMap[0] ==
MARK_CUT_END))
285 LOG(VB_GENERAL, LOG_DEBUG,
286 QString(
"Cutting section %1-999999999.")
291 if (deleteMap.count() >= 2)
293 frm_dir_map_t::iterator cur = deleteMap.begin();
294 frm_dir_map_t::iterator prev;
296 while (cur != deleteMap.end())
298 if (prev.value() == cur.value())
301 QString err(
"Cut %1points found at %3 and %4, with no "
302 "%2 point in between.");
304 err = err.arg(
"end",
"start");
306 err = err.arg(
"start",
"end");
307 LOG(VB_GENERAL, LOG_CRIT,
"Invalid cutlist defined!");
308 LOG(VB_GENERAL, LOG_CRIT, err.arg(prev.key())
313 ((cur.key() - prev.key()) < 2) )
315 LOG(VB_GENERAL, LOG_WARNING, QString(
"Discarding "
316 "insufficiently long cut: %1-%2")
317 .arg(prev.key()).arg(cur.key()));
318 prev = deleteMap.erase(prev);
319 cur = deleteMap.erase(cur);
321 if (cur == deleteMap.end())
330 std::cerr <<
"Cutlist inversion requires an external cutlist be" << std::endl
331 <<
"provided using the --honorcutlist option." << std::endl;
340 keyframesonly =
true;
363 std::cerr <<
"Invalid 'ostream' type: "
386 LOG(VB_GENERAL, LOG_ERR,
"Failed to init MythContext, exiting.");
398 found_starttime =
true;
403 std::cerr <<
"mythtranscode: ERROR: Unable to find DB info for "
404 <<
"JobQueue ID# " <<
jobID << std::endl;
409 if (((!found_infile && !(found_chanid && found_starttime)) ||
410 (found_infile && (found_chanid || found_starttime))) &&
413 std::cerr <<
"Must specify -i OR -c AND -s options!" << std::endl;
418 std::cerr <<
"Must specify --infile to use --video" << std::endl;
421 if (
jobID >= 0 && (found_infile || build_index))
423 std::cerr <<
"Can't specify -j with --buildindex, --video or --infile"
427 if ((
jobID >= 0) && build_index)
429 std::cerr <<
"Can't specify both -j and --buildindex" << std::endl;
432 if (keyframesonly && !fifodir.isEmpty())
434 std::cerr <<
"Cannot specify both --fifodir and --allkeys" << std::endl;
437 if (fifosync && fifodir.isEmpty())
439 std::cerr <<
"Must specify --fifodir to use --fifosync" << std::endl;
442 if (fifo_info && !fifodir.isEmpty())
444 std::cerr <<
"Cannot specify both --fifodir and --fifoinfo" << std::endl;
447 if (cleanCut && fifodir.isEmpty() && !fifo_info)
449 std::cerr <<
"Clean cutting works only in fifodir mode" << std::endl;
452 if (cleanCut && !useCutlist)
454 std::cerr <<
"--cleancut is pointless without --honorcutlist" << std::endl;
462 fifodir =
"DummyFifoPath";
467 LOG(VB_GENERAL, LOG_ERR,
"couldn't open db");
479 if (pginfo ==
nullptr)
485 QFileInfo inf(infile);
486 infile = inf.absoluteFilePath();
489 else if (!found_infile)
495 LOG(VB_GENERAL, LOG_ERR,
496 QString(
"Couldn't find recording for chanid %1 @ %2")
497 .arg(chanid).arg(starttime.toString(
Qt::ISODate)));
509 LOG(VB_GENERAL, LOG_ERR,
510 QString(
"Couldn't find a recording for filename '%1'")
519 LOG(VB_GENERAL, LOG_ERR,
"No program info found!");
529 if (infile.startsWith(
"myth://") && (outfile.isEmpty() || outfile !=
"-") &&
532 LOG(VB_GENERAL, LOG_ERR,
533 QString(
"Attempted to transcode %1. Mythtranscode is currently "
534 "unable to transcode remote files.") .arg(infile));
539 if (outfile.isEmpty() && !build_index && fifodir.isEmpty())
540 outfile = infile +
".tmp";
551 LOG(VB_GENERAL, LOG_NOTICE,
552 QString(
"Transcoding HTTP Live Stream ID %1")
555 else if (fifodir.isEmpty())
557 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to %2")
558 .arg(infile, outfile));
562 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to FIFO")
569 transcode->SetAVFMode();
580 transcode->SetHLSMode();
583 transcode->SetHLSStreamID(
cmdline.
toInt(
"hlsstreamid"));
585 transcode->SetHLSMaxSegments(
cmdline.
toInt(
"maxsegments"));
587 transcode->DisableAudioOnlyHLS();
597 transcode->SetCMDBitrate(
cmdline.
toInt(
"bitrate") * 1000);
599 transcode->SetCMDAudioBitrate(
cmdline.
toInt(
"audiobitrate") * 1000);
603 transcode->ShowProgress(
true);
609 result = transcode->TranscodeFile(infile, outfile,
610 profilename, useCutlist,
611 (fifosync || keyframesonly),
jobID,
612 fifodir, fifo_info, cleanCut, deleteMap,
613 AudioTrackNo, passthru);
634 void (*update_func)(float) =
nullptr;
635 int (*check_func)() =
nullptr;
638 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
639 if (deleteMap.isEmpty())
650 &deleteMap,
nullptr,
false,
false, 20,
651 showprogress, otype, update_func,
656 m2f->SetAllAudio(
true);
675 result = m2f->Start();
710 LOG(VB_GENERAL, LOG_NOTICE, QString(
"%1 %2 done")
711 .arg(build_index ?
"Building Index for" :
"Transcoding", infile));
717 LOG(VB_GENERAL, LOG_NOTICE,
718 QString(
"Transcoding %1 aborted because of cutlist update")
726 LOG(VB_GENERAL, LOG_NOTICE,
727 QString(
"Transcoding %1 stopped because of stop command")
735 LOG(VB_GENERAL, LOG_ERR, QString(
"Transcoding %1 failed").arg(infile));
739 if (deleteOriginal ||
jobID >= 0)
740 CompleteJob(
jobID, pginfo, useCutlist, &deleteMap, exitcode, result, deleteOriginal);
742 transcode->deleteLater();
755 QString basename =
filename.section(
'/', -1);
759 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Requesting delete for file '%1'.")
766 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting file '%1'.").arg(
filename));
767 return unlink(
filename.toLocal8Bit().constData());
773 if (deleteMap ==
nullptr)
776 uint64_t subtraction = 0;
777 uint64_t startOfCutRegion = 0;
779 bool withinCut =
false;
780 bool firstMark =
true;
781 while (!delMap.empty() && delMap.begin().key() <= oldBookmark)
783 uint64_t key = delMap.begin().key();
789 startOfCutRegion = key;
798 subtraction += (key - startOfCutRegion);
804 subtraction += (oldBookmark - startOfCutRegion);
805 return oldBookmark - subtraction;
811 uint64_t currentBookmark = 0;
812 query.
prepare(
"SELECT DISTINCT mark FROM recordedmarkup "
813 "WHERE chanid = :CHANID "
814 "AND starttime = :STARTIME "
815 "AND type = :MARKTYPE ;");
821 currentBookmark = query.
value(0).toLongLong();
823 return currentBookmark;
828 LOG(VB_GENERAL, LOG_NOTICE,
829 "Transcode: delete old file: waiting while program is in use.");
835 query.
prepare(
"SELECT count(*) FROM inuseprograms "
836 "WHERE chanid = :CHANID "
837 "AND starttime = :STARTTIME "
838 "AND recusage = 'player' ;");
843 LOG(VB_GENERAL, LOG_ERR,
844 "Transcode: delete old file: in-use query failed;");
849 inUse = (query.
value(0).toUInt() != 0);
854 const unsigned kSecondsToWait = 10;
855 LOG(VB_GENERAL, LOG_NOTICE,
856 QString(
"Transcode: program in use, rechecking in %1 seconds.")
857 .arg(kSecondsToWait));
858 sleep(kSecondsToWait);
861 LOG(VB_GENERAL, LOG_NOTICE,
"Transcode: program is no longer in use.");
865 frm_dir_map_t *deleteMap,
int &exitCode,
int resultCode,
bool forceDelete)
867 int status = JOB_UNKNOWN;
875 QObject::tr(
"Job errored, unable to find Program Info for job"));
876 LOG(VB_GENERAL, LOG_CRIT,
"MythTranscode: Cleanup errored, unable to find Program Info");
881 const QByteArray fname =
filename.toLocal8Bit();
889 uint64_t previousBookmark =
897 const QString tmpfile =
filename +
".tmp";
898 const QByteArray atmpfile = tmpfile.toLocal8Bit();
901 const QString oldfile =
filename +
".old";
902 const QByteArray aoldfile = oldfile.toLocal8Bit();
904 QFileInfo st(tmpfile);
912 if (
filename.endsWith(
".mpg") && jobArgs ==
"RENAME_TO_NUV")
915 cnf.replace(
".mpg",
".nuv");
916 newbase.replace(
".mpg",
".nuv");
919 else if (
filename.endsWith(
".ts") &&
920 (jobArgs ==
"RENAME_TO_MPG"))
924 cnf.replace(
".ts",
".mpg");
925 newbase.replace(
".ts",
".mpg");
930 const QString newfile = cnf;
931 const QByteArray anewfile = newfile.toLocal8Bit();
933 if (rename(fname.constData(), aoldfile.constData()) == -1)
935 LOG(VB_GENERAL, LOG_ERR,
936 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
940 if (rename(atmpfile.constData(), anewfile.constData()) == -1)
942 LOG(VB_GENERAL, LOG_ERR,
943 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
944 .arg(tmpfile, newfile) +
ENO);
952 LOG(VB_FILE, LOG_INFO,
953 QString(
"mythtranscode: About to unlink/delete file: %1")
956 QFileInfo finfo(oldfile);
957 if (followLinks && finfo.isSymLink())
960 QByteArray alink = link.toLocal8Bit();
964 LOG(VB_GENERAL, LOG_ERR,
965 QString(
"mythtranscode: Error deleting '%1' "
966 "pointed to by '%2'")
967 .arg(alink.constData(), aoldfile.constData()) +
ENO);
970 err = unlink(aoldfile.constData());
973 LOG(VB_GENERAL, LOG_ERR,
974 QString(
"mythtranscode: Error deleting '%1', "
975 "a link pointing to '%2'")
976 .arg(aoldfile.constData(), alink.constData()) +
ENO);
981 int err =
transUnlink(aoldfile.constData(), pginfo);
984 LOG(VB_GENERAL, LOG_ERR,
985 QString(
"mythtranscode: Error deleting '%1': ")
986 .arg(oldfile) +
ENO);
996 QStringList nameFilters;
997 nameFilters.push_back(fInfo.fileName() +
"*.png");
998 nameFilters.push_back(fInfo.fileName() +
"*.jpg");
1000 QDir dir (fInfo.path());
1001 QFileInfoList previewFiles = dir.entryInfoList(nameFilters);
1003 for (
const auto & previewFile : std::as_const(previewFiles))
1005 QString oldFileName = previewFile.absoluteFilePath();
1016 if (
transUnlink(oldFileName.toLocal8Bit().constData(), pginfo) != -1)
1020 if (jobArgs ==
"RENAME_TO_NUV" || jobArgs ==
"RENAME_TO_MPG")
1022 QString newExtension =
"mpg";
1023 if (jobArgs ==
"RENAME_TO_NUV")
1024 newExtension =
"nuv";
1026 QString oldSuffix = previewFile.completeSuffix();
1028 if (!oldSuffix.startsWith(newExtension))
1030 QString newSuffix = oldSuffix;
1031 QString oldExtension = oldSuffix.section(
".", 0, 0);
1032 newSuffix.replace(oldExtension, newExtension);
1034 QString newFileName = oldFileName;
1035 newFileName.replace(oldSuffix, newSuffix);
1037 if (!QFile::rename(oldFileName, newFileName))
1039 LOG(VB_GENERAL, LOG_ERR,
1040 QString(
"mythtranscode: Error renaming %1 to %2")
1041 .arg(oldFileName, newFileName));
1051 query.
prepare(
"DELETE FROM recordedmarkup "
1052 "WHERE chanid = :CHANID "
1053 "AND starttime = :STARTTIME "
1054 "AND type != :BOOKMARK ");
1062 query.
prepare(
"UPDATE recorded "
1063 "SET cutlist = :CUTLIST "
1064 "WHERE chanid = :CHANID "
1065 "AND starttime = :STARTTIME ;");
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) ;");
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));
1109 QString filename_map =
filename +
".tmp.map";
1110 QByteArray fname_map = filename_map.toLocal8Bit();
1111 unlink(fname_map.constData());
1115 if (status == JOB_ABORTING)
1118 QObject::tr(
"Job Aborted"));
1120 else if (status != JOB_ERRORING)
1127 QObject::tr(
"Unrecoverable error"));