Go to the documentation of this file.
8 #include <QCoreApplication>
35 int resultCode,
bool forceDelete);
43 if (pginfo && mapfile.isEmpty())
50 else if (!mapfile.isEmpty())
53 FILE *mapfh = fopen(mapfile.toLocal8Bit().constData(),
"w");
56 LOG(VB_GENERAL, LOG_ERR, QString(
"Could not open map file '%1'")
60 frm_pos_map_t::const_iterator it;
61 fprintf (mapfh,
"Type: %d\n", keyType);
62 for (it = posMap.cbegin(); it != posMap.cend(); ++it)
64 QString str = QString(
"%1 %2\n").arg(it.key()).arg(*it);
65 fprintf(mapfh,
"%s", qPrintable(str));
81 QObject::tr(
"Generating Keyframe Index"));
87 QObject::tr(
"Transcode Completed"));
95 QString(
"%1% ").
arg(percent_done, 0,
'f', 1) +
96 QObject::tr(
"Completed"));
103 LOG(VB_GENERAL, LOG_NOTICE,
"Transcoding stopped by JobQueue");
110 const QString&
hostname,
bool usecutlist)
123 LOG(VB_GENERAL, LOG_NOTICE,
124 QString(
"Queued transcode job for chanid %1 @ %2")
130 LOG(VB_GENERAL, LOG_ERR, QString(
"Error queuing job for chanid %1 @ %2")
146 int main(
int argc,
char *argv[])
152 QString profilename = QString(
"autodetect");
153 QString fifodir =
nullptr;
157 bool useCutlist =
false;
158 bool keyframesonly =
false;
159 bool build_index =
false;
160 bool fifosync =
false;
162 bool fifo_info =
false;
163 bool cleanCut =
false;
167 int AudioTrackNo = -1;
169 bool found_starttime =
false;
170 bool found_chanid =
false;
171 bool found_infile =
false;
172 int update_index = 1;
174 bool passthru =
false;
195 QCoreApplication a(argc, argv);
206 QString mask(
"general");
207 bool quiet = (outfile ==
"-") || showprogress;
215 found_starttime =
true;
241 LOG(VB_GENERAL, LOG_CRIT,
"External cutlists are only allowed "
242 "when using the --infile option.");
248 for (
const auto & cut : qAsConst(cutlist))
250 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
251 QStringList startend =
252 cut.split(
"-", QString::SkipEmptyParts);
254 QStringList startend = cut.split(
"-", Qt::SkipEmptyParts);
256 if (startend.size() == 2)
258 uint64_t start = startend.first().toULongLong();
259 uint64_t end = startend.last().toULongLong();
263 LOG(VB_GENERAL, LOG_DEBUG,
264 QString(
"Cutting section %1-%2.")
272 LOG(VB_GENERAL, LOG_DEBUG,
273 QString(
"Cutting section %1-%2.")
283 if (deleteMap.contains(0) && (deleteMap[0] ==
MARK_CUT_END))
288 LOG(VB_GENERAL, LOG_DEBUG,
289 QString(
"Cutting section %1-999999999.")
294 if (deleteMap.count() >= 2)
296 frm_dir_map_t::iterator cur = deleteMap.begin();
297 frm_dir_map_t::iterator prev;
299 while (cur != deleteMap.end())
301 if (prev.value() == cur.value())
304 QString err(
"Cut %1points found at %3 and %4, with no "
305 "%2 point in between.");
307 err = err.arg(
"end").arg(
"start");
309 err = err.arg(
"start").arg(
"end");
310 LOG(VB_GENERAL, LOG_CRIT,
"Invalid cutlist defined!");
311 LOG(VB_GENERAL, LOG_CRIT, err.arg(prev.key())
316 ((cur.key() - prev.key()) < 2) )
318 LOG(VB_GENERAL, LOG_WARNING, QString(
"Discarding "
319 "insufficiently long cut: %1-%2")
320 .
arg(prev.key()).arg(cur.key()));
321 prev = deleteMap.erase(prev);
322 cur = deleteMap.erase(cur);
324 if (cur == deleteMap.end())
333 std::cerr <<
"Cutlist inversion requires an external cutlist be" << std::endl
334 <<
"provided using the --honorcutlist option." << std::endl;
343 keyframesonly =
true;
366 std::cerr <<
"Invalid 'ostream' type: "
382 QList<int> signallist;
383 signallist << SIGINT << SIGTERM << SIGSEGV << SIGABRT << SIGBUS << SIGFPE
386 signallist << SIGRTMIN;
396 LOG(VB_GENERAL, LOG_ERR,
"Failed to init MythContext, exiting.");
408 found_starttime =
true;
413 std::cerr <<
"mythtranscode: ERROR: Unable to find DB info for "
414 <<
"JobQueue ID# " <<
jobID << std::endl;
419 if (((!found_infile && !(found_chanid && found_starttime)) ||
420 (found_infile && (found_chanid || found_starttime))) &&
423 std::cerr <<
"Must specify -i OR -c AND -s options!" << std::endl;
428 std::cerr <<
"Must specify --infile to use --video" << std::endl;
431 if (
jobID >= 0 && (found_infile || build_index))
433 std::cerr <<
"Can't specify -j with --buildindex, --video or --infile"
437 if ((
jobID >= 0) && build_index)
439 std::cerr <<
"Can't specify both -j and --buildindex" << std::endl;
442 if (keyframesonly && !fifodir.isEmpty())
444 std::cerr <<
"Cannot specify both --fifodir and --allkeys" << std::endl;
447 if (fifosync && fifodir.isEmpty())
449 std::cerr <<
"Must specify --fifodir to use --fifosync" << std::endl;
452 if (fifo_info && !fifodir.isEmpty())
454 std::cerr <<
"Cannot specify both --fifodir and --fifoinfo" << std::endl;
457 if (cleanCut && fifodir.isEmpty() && !fifo_info)
459 std::cerr <<
"Clean cutting works only in fifodir mode" << std::endl;
462 if (cleanCut && !useCutlist)
464 std::cerr <<
"--cleancut is pointless without --honorcutlist" << std::endl;
472 fifodir =
"DummyFifoPath";
477 LOG(VB_GENERAL, LOG_ERR,
"couldn't open db");
489 if (pginfo ==
nullptr)
495 QFileInfo inf(infile);
496 infile = inf.absoluteFilePath();
499 else if (!found_infile)
505 LOG(VB_GENERAL, LOG_ERR,
506 QString(
"Couldn't find recording for chanid %1 @ %2")
519 LOG(VB_GENERAL, LOG_ERR,
520 QString(
"Couldn't find a recording for filename '%1'")
529 LOG(VB_GENERAL, LOG_ERR,
"No program info found!");
539 if (infile.startsWith(
"myth://") && (outfile.isEmpty() || outfile !=
"-") &&
542 LOG(VB_GENERAL, LOG_ERR,
543 QString(
"Attempted to transcode %1. Mythtranscode is currently "
544 "unable to transcode remote files.") .
arg(infile));
549 if (outfile.isEmpty() && !build_index && fifodir.isEmpty())
550 outfile = infile +
".tmp";
561 LOG(VB_GENERAL, LOG_NOTICE,
562 QString(
"Transcoding HTTP Live Stream ID %1")
565 else if (fifodir.isEmpty())
567 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to %2")
568 .
arg(infile).
arg(outfile));
572 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to FIFO")
579 transcode->SetAVFMode();
590 transcode->SetHLSMode();
593 transcode->SetHLSStreamID(
cmdline.
toInt(
"hlsstreamid"));
595 transcode->SetHLSMaxSegments(
cmdline.
toInt(
"maxsegments"));
597 transcode->DisableAudioOnlyHLS();
607 transcode->SetCMDBitrate(
cmdline.
toInt(
"bitrate") * 1000);
609 transcode->SetCMDAudioBitrate(
cmdline.
toInt(
"audiobitrate") * 1000);
613 transcode->ShowProgress(
true);
619 result = transcode->TranscodeFile(infile, outfile,
620 profilename, useCutlist,
621 (fifosync || keyframesonly),
jobID,
622 fifodir, fifo_info, cleanCut, deleteMap,
623 AudioTrackNo, passthru);
644 void (*update_func)(float) =
nullptr;
645 int (*check_func)() =
nullptr;
648 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
649 if (deleteMap.isEmpty())
660 &deleteMap,
nullptr,
false,
false, 20,
661 showprogress, otype, update_func,
666 m2f->SetAllAudio(
true);
685 result = m2f->Start();
720 LOG(VB_GENERAL, LOG_NOTICE, QString(
"%1 %2 done")
721 .
arg(build_index ?
"Building Index for" :
"Transcoding")
728 LOG(VB_GENERAL, LOG_NOTICE,
729 QString(
"Transcoding %1 aborted because of cutlist update")
737 LOG(VB_GENERAL, LOG_NOTICE,
738 QString(
"Transcoding %1 stopped because of stop command")
746 LOG(VB_GENERAL, LOG_ERR, QString(
"Transcoding %1 failed").
arg(infile));
750 if (deleteOriginal ||
jobID >= 0)
751 CompleteJob(
jobID, pginfo, useCutlist, &deleteMap, exitcode, result, deleteOriginal);
753 transcode->deleteLater();
766 QString basename =
filename.section(
'/', -1);
770 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Requesting delete for file '%1'.")
777 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting file '%1'.").
arg(
filename));
778 return unlink(
filename.toLocal8Bit().constData());
784 if (deleteMap ==
nullptr)
787 uint64_t subtraction = 0;
788 uint64_t startOfCutRegion = 0;
790 bool withinCut =
false;
791 bool firstMark =
true;
792 while (!delMap.empty() && delMap.begin().key() <= oldBookmark)
794 uint64_t key = delMap.begin().key();
800 startOfCutRegion = key;
809 subtraction += (key - startOfCutRegion);
815 subtraction += (oldBookmark - startOfCutRegion);
816 return oldBookmark - subtraction;
822 uint64_t currentBookmark = 0;
823 query.
prepare(
"SELECT DISTINCT mark FROM recordedmarkup "
824 "WHERE chanid = :CHANID "
825 "AND starttime = :STARTIME "
826 "AND type = :MARKTYPE ;");
832 currentBookmark =
query.
value(0).toLongLong();
834 return currentBookmark;
839 LOG(VB_GENERAL, LOG_NOTICE,
840 "Transcode: delete old file: waiting while program is in use.");
847 "WHERE chanid = :CHANID "
848 "AND starttime = :STARTTIME "
849 "AND recusage = 'player' ;");
854 LOG(VB_GENERAL, LOG_ERR,
855 "Transcode: delete old file: in-use query failed;");
865 const unsigned kSecondsToWait = 10;
866 LOG(VB_GENERAL, LOG_NOTICE,
867 QString(
"Transcode: program in use, rechecking in %1 seconds.")
868 .
arg(kSecondsToWait));
869 sleep(kSecondsToWait);
872 LOG(VB_GENERAL, LOG_NOTICE,
"Transcode: program is no longer in use.");
876 frm_dir_map_t *deleteMap,
int &exitCode,
int resultCode,
bool forceDelete)
878 int status = JOB_UNKNOWN;
886 QObject::tr(
"Job errored, unable to find Program Info for job"));
887 LOG(VB_GENERAL, LOG_CRIT,
"MythTranscode: Cleanup errored, unable to find Program Info");
892 const QByteArray fname =
filename.toLocal8Bit();
900 uint64_t previousBookmark =
908 const QString tmpfile =
filename +
".tmp";
909 const QByteArray atmpfile = tmpfile.toLocal8Bit();
912 const QString oldfile =
filename +
".old";
913 const QByteArray aoldfile = oldfile.toLocal8Bit();
915 QFileInfo st(tmpfile);
923 if (
filename.endsWith(
".mpg") && jobArgs ==
"RENAME_TO_NUV")
926 cnf.replace(
".mpg",
".nuv");
927 newbase.replace(
".mpg",
".nuv");
930 else if (
filename.endsWith(
".ts") &&
931 (jobArgs ==
"RENAME_TO_MPG"))
935 cnf.replace(
".ts",
".mpg");
936 newbase.replace(
".ts",
".mpg");
941 const QString newfile = cnf;
942 const QByteArray anewfile = newfile.toLocal8Bit();
944 if (rename(fname.constData(), aoldfile.constData()) == -1)
946 LOG(VB_GENERAL, LOG_ERR,
947 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
951 if (rename(atmpfile.constData(), anewfile.constData()) == -1)
953 LOG(VB_GENERAL, LOG_ERR,
954 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
963 LOG(VB_FILE, LOG_INFO,
964 QString(
"mythtranscode: About to unlink/delete file: %1")
967 QFileInfo finfo(oldfile);
968 if (followLinks && finfo.isSymLink())
971 QByteArray alink = link.toLocal8Bit();
975 LOG(VB_GENERAL, LOG_ERR,
976 QString(
"mythtranscode: Error deleting '%1' "
977 "pointed to by '%2'")
978 .
arg(alink.constData())
979 .arg(aoldfile.constData()) +
ENO);
982 err = unlink(aoldfile.constData());
985 LOG(VB_GENERAL, LOG_ERR,
986 QString(
"mythtranscode: Error deleting '%1', "
987 "a link pointing to '%2'")
988 .
arg(aoldfile.constData())
989 .arg(alink.constData()) +
ENO);
994 int err =
transUnlink(aoldfile.constData(), pginfo);
997 LOG(VB_GENERAL, LOG_ERR,
998 QString(
"mythtranscode: Error deleting '%1': ")
1009 QStringList nameFilters;
1010 nameFilters.push_back(fInfo.fileName() +
"*.png");
1011 nameFilters.push_back(fInfo.fileName() +
"*.jpg");
1013 QDir
dir (fInfo.path());
1014 QFileInfoList previewFiles =
dir.entryInfoList(nameFilters);
1016 for (
const auto & previewFile : qAsConst(previewFiles))
1018 QString oldFileName = previewFile.absoluteFilePath();
1029 if (
transUnlink(oldFileName.toLocal8Bit().constData(), pginfo) != -1)
1033 if (jobArgs ==
"RENAME_TO_NUV" || jobArgs ==
"RENAME_TO_MPG")
1035 QString newExtension =
"mpg";
1036 if (jobArgs ==
"RENAME_TO_NUV")
1037 newExtension =
"nuv";
1039 QString oldSuffix = previewFile.completeSuffix();
1041 if (!oldSuffix.startsWith(newExtension))
1043 QString newSuffix = oldSuffix;
1044 QString oldExtension = oldSuffix.section(
".", 0, 0);
1045 newSuffix.replace(oldExtension, newExtension);
1047 QString newFileName = oldFileName;
1048 newFileName.replace(oldSuffix, newSuffix);
1050 if (!QFile::rename(oldFileName, newFileName))
1052 LOG(VB_GENERAL, LOG_ERR,
1053 QString(
"mythtranscode: Error renaming %1 to %2")
1054 .
arg(oldFileName).
arg(newFileName));
1065 "WHERE chanid = :CHANID "
1066 "AND starttime = :STARTTIME "
1067 "AND type != :BOOKMARK ");
1076 "SET cutlist = :CUTLIST "
1077 "WHERE chanid = :CHANID "
1078 "AND starttime = :STARTTIME ;");
1091 "WHERE chanid = :CHANID "
1092 "AND starttime = :STARTTIME "
1093 "AND type not in ( :COMM_START, "
1094 " :COMM_END, :BOOKMARK, "
1095 " :CUTLIST_START, :CUTLIST_END) ;");
1117 QString filename_tmp =
filename +
".tmp";
1118 QByteArray fname_tmp = filename_tmp.toLocal8Bit();
1119 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting %1").
arg(filename_tmp));
1122 QString filename_map =
filename +
".tmp.map";
1123 QByteArray fname_map = filename_map.toLocal8Bit();
1124 unlink(fname_map.constData());
1128 if (status == JOB_ABORTING)
1131 QObject::tr(
"Job Aborted"));
1133 else if (status != JOB_ERRORING)
1140 QObject::tr(
"Unrecoverable error"));
QString GetSourceFile(void) const
static enum JobCmds GetJobCmd(int jobID)
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
QSqlQuery wrapper that fetches a DB connection from the connection pool.
virtual void SaveFilesize(uint64_t fsize)
Sets recording file size in database, and sets "filesize" field.
#define ENO
This can be appended to the LOG args with "+".
#define GENERIC_EXIT_OK
Exited with no error.
void SaveBookmark(uint64_t frame)
TODO Move to RecordingInfo.
QString GetHostname(void) const
QString QueryBasename(void) const
Gets the basename, from the DB if necessary.
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())
static void CompleteJob(int jobID, ProgramInfo *pginfo, bool useCutlist, frm_dir_map_t *deleteMap, int &exitCode, int resultCode, bool forceDelete)
Holds information on a TV Program one might wish to record.
uint GetRecordingID(void) const
Startup context for MythTV.
#define GENERIC_EXIT_REMOTE_FILE
Can't transcode a remote file.
QVariant value(int i) const
arg(title).arg(filename).arg(doDelete))
static QString recorderOptions
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
QString getSymlinkTarget(const QString &start_file, QStringList *intermediaries, unsigned maxLinks)
static bool GetJobInfoFromID(int jobID, int &jobType, uint &chanid, QDateTime &recstartts)
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
static void UpdateJobQueue(float percent_done)
Holds information on a recording file and it's video and audio streams.
void SaveCommFlagged(CommFlagStatus flag)
Set "commflagged" field in "recorded" table to "flag".
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
int BuildKeyframeIndex(const QString &file, frm_pos_map_t &posMap, frm_pos_map_t &durMap)
#define GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
static bool isVideo(const QString &mimeType)
void ApplyTranscoderProfileChange(const QString &profile) const
Sets the transcoder profile for a recording.
unsigned sleep(unsigned int x)
static int CheckJobQueue()
virtual bool Parse(int argc, const char *const *argv)
Loop through argv and populate arguments with values.
static int BuildKeyframeIndex(MPEG2fixup *m2f, const QString &infile, frm_pos_map_t &posMap, frm_pos_map_t &durMap, int jobID)
RecordingFile * GetRecordingFile() const
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
static uint64_t ComputeNewBookmark(uint64_t oldBookmark, frm_dir_map_t *deleteMap)
static bool ChangeJobComment(int jobID, const QString &comment="")
int GetBackendServerPort(void)
Returns the locally defined backend control port.
static QString GetJobArgs(int jobID)
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
static uint64_t ReloadBookmark(ProgramInfo *pginfo)
static enum JobStatus GetJobStatus(int jobID)
static void DBError(const QString &where, const MSqlQuery &query)
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
AVContainer m_containerFormat
#define GENERIC_EXIT_NO_RECORDING_DATA
No program/recording data.
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
#define GENERIC_EXIT_RESTART
Need to restart transcoding.
static void PrintVersion(void)
Print application version information.
static void WaitToDelete(ProgramInfo *pginfo)
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
void PrintHelp(void) const
Print command line option help.
bool SaveBasename(const QString &basename)
Sets a recording's basename in the database.
static bool DeleteFile(const QString &url)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void ApplySettingsOverride(void)
Apply all overrides to the global context.
QString GetStorageGroup(void) const
static QString cleanup(const QString &str)
QMap< long long, long long > frm_pos_map_t
Frame # -> File offset map.
bool GetBoolSetting(const QString &key, bool defaultval=false)
#define MYTH_BINARY_VERSION
Update this whenever the plug-in ABI changes.
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
#define GENERIC_EXIT_DB_ERROR
Database error.
MythCommFlagCommandLineParser cmdline
Holds information on recordings and videos.
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
void ClearPositionMap(MarkTypes type) const
static int transUnlink(const QString &filename, ProgramInfo *pginfo)
QString GetPlaybackURL(bool checkMaster=false, bool forceCheckLocal=false)
Returns filename or URL to be used to play back this recording.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
QStringList toStringList(const QString &key, const QString &sep="") const
Returns stored QVariant as a QStringList, falling to default if not provided.
#define GENERIC_EXIT_NO_MYTHCONTEXT
No MythContext available.
static void SetHandler(int signum, SigHandlerFunc handler)
int main(int argc, char *argv[])
#define REENCODE_CUTLIST_CHANGE
int ConfigureLogging(const QString &mask="general", bool progress=false)
Read in logging options and initialize the logging interface.
static bool ChangeJobArgs(int jobID, const QString &args="")
#define REENCODE_MPEG2TRANS
static void load(const QString &module_name)
Load a QTranslator for the user's preferred language.
int toInt(const QString &key) const
Returns stored QVariant as an integer, falling to default if not provided.
static bool ChangeJobStatus(int jobID, int newStatus, const QString &comment="")
MSqlQuery query(MSqlQuery::InitCon())
static int QueueTranscodeJob(ProgramInfo *pginfo, const QString &profile, const QString &hostname, bool usecutlist)
QDateTime toDateTime(const QString &key) const
Returns stored QVariant as a QDateTime, falling to default if not provided.
MythContext * gContext
This global variable contains the MythContext instance for the application.
bool Init(bool gui=true, bool promptForBackend=false, bool disableAutoDiscovery=false, bool ignoreDB=false)
#define MYTH_APPNAME_MYTHTRANSCODE
void SavePositionMap(frm_pos_map_t &posMap, MarkTypes type, int64_t min_frame=-1, int64_t max_frame=-1) const
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
#define GENERIC_EXIT_KILLED
Process killed or stopped.
static void Init(QList< int > &signallist, QObject *parent=nullptr)
static void UpdatePositionMap(frm_pos_map_t &posMap, frm_pos_map_t &durMap, const QString &mapfile, ProgramInfo *pginfo)