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")
138int 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"));
QString GetSourceFile(void) const
static QString GetJobArgs(int jobID)
static bool GetJobInfoFromID(int jobID, int &jobType, uint &chanid, QDateTime &recstartts)
static bool ChangeJobArgs(int jobID, const QString &args="")
static enum JobCmds GetJobCmd(int jobID)
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 bool ChangeJobComment(int jobID, const QString &comment="")
static bool ChangeJobStatus(int jobID, int newStatus, const QString &comment="")
static enum JobStatus GetJobStatus(int jobID)
int BuildKeyframeIndex(const QString &file, frm_pos_map_t &posMap, frm_pos_map_t &durMap)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
int toInt(const QString &key) const
Returns stored QVariant as an integer, falling to default if not provided.
virtual bool Parse(int argc, const char *const *argv)
Loop through argv and populate arguments with values.
void ApplySettingsOverride(void)
Apply all overrides to the global context.
int ConfigureLogging(const QString &mask="general", bool progress=false)
Read in logging options and initialize the logging interface.
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
static void PrintVersion(void)
Print application version information.
QDateTime toDateTime(const QString &key) const
Returns stored QVariant as a QDateTime, falling to default if not provided.
QStringList toStringList(const QString &key, const QString &sep="") const
Returns stored QVariant as a QStringList, falling to default if not provided.
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.
Startup context for MythTV.
int GetBackendServerPort(void)
Returns the locally defined backend control port.
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
static void load(const QString &module_name)
Load a QTranslator for the user's preferred language.
Holds information on recordings and videos.
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
virtual void SaveFilesize(uint64_t fsize)
Sets recording file size in database, and sets "filesize" field.
uint GetRecordingID(void) const
void ClearPositionMap(MarkTypes type) const
QString GetHostname(void) const
bool SaveBasename(const QString &basename)
Sets a recording's basename in the database.
QString GetStorageGroup(void) const
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
void SavePositionMap(frm_pos_map_t &posMap, MarkTypes type, int64_t min_frame=-1, int64_t max_frame=-1) const
QString QueryBasename(void) const
Gets the basename, from the DB if necessary.
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
QString GetPlaybackURL(bool checkMaster=false, bool forceCheckLocal=false)
Returns filename or URL to be used to play back this recording.
void SaveCommFlagged(CommFlagStatus flag)
Set "commflagged" field in "recorded" table to "flag".
void SaveBookmark(uint64_t frame)
Clears any existing bookmark in DB and if frame is greater than 0 sets a new bookmark.
Holds information on a recording file and it's video and audio streams.
AVContainer m_containerFormat
Holds information on a TV Program one might wish to record.
RecordingFile * GetRecordingFile() const
void ApplyTranscoderProfileChange(const QString &profile) const
Sets the transcoder profile for a recording.
static bool DeleteFile(const QString &url)
@ GENERIC_EXIT_RESTART
Need to restart transcoding.
@ GENERIC_EXIT_NO_MYTHCONTEXT
No MythContext available.
@ GENERIC_EXIT_OK
Exited with no error.
@ GENERIC_EXIT_NO_RECORDING_DATA
No program/recording data.
@ GENERIC_EXIT_REMOTE_FILE
Can't transcode a remote file.
@ GENERIC_EXIT_KILLED
Process killed or stopped.
@ GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
@ GENERIC_EXIT_DB_ERROR
Database error.
static constexpr const char * MYTH_APPNAME_MYTHTRANSCODE
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define ENO
This can be appended to the LOG args with "+".
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QString getSymlinkTarget(const QString &start_file, QStringList *intermediaries, unsigned maxLinks)
int main(int argc, char *argv[])
static void UpdatePositionMap(frm_pos_map_t &posMap, frm_pos_map_t &durMap, const QString &mapfile, ProgramInfo *pginfo)
static int transUnlink(const QString &filename, ProgramInfo *pginfo)
static QString recorderOptions
static uint64_t ReloadBookmark(ProgramInfo *pginfo)
static int BuildKeyframeIndex(MPEG2fixup *m2f, const QString &infile, frm_pos_map_t &posMap, frm_pos_map_t &durMap, int jobID)
static void WaitToDelete(ProgramInfo *pginfo)
static void CompleteJob(int jobID, ProgramInfo *pginfo, bool useCutlist, frm_dir_map_t *deleteMap, int &exitCode, int resultCode, bool forceDelete)
static void UpdateJobQueue(float percent_done)
static uint64_t ComputeNewBookmark(uint64_t oldBookmark, frm_dir_map_t *deleteMap)
static int QueueTranscodeJob(ProgramInfo *pginfo, const QString &profile, const QString &hostname, bool usecutlist)
static int CheckJobQueue()
MythCommFlagCommandLineParser cmdline
static bool isVideo(const QString &mimeType)
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
QMap< long long, long long > frm_pos_map_t
Frame # -> File offset map.
@ REENCODE_CUTLIST_CHANGE