10#include <QCoreApplication>
24#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\n"
321 <<
"provided using the --honorcutlist option.\n";
330 keyframesonly =
true;
352 std::cerr <<
"Invalid 'ostream' type: "
367 if (!context.Init(
false))
369 LOG(VB_GENERAL, LOG_ERR,
"Failed to init MythContext, exiting.");
381 found_starttime =
true;
386 std::cerr <<
"mythtranscode: ERROR: Unable to find DB info for "
387 <<
"JobQueue ID# " <<
jobID <<
'\n';
392 if (((!found_infile && !(found_chanid && found_starttime)) ||
393 (found_infile && (found_chanid || found_starttime))))
395 std::cerr <<
"Must specify -i OR -c AND -s options!\n";
400 std::cerr <<
"Must specify --infile to use --video\n";
403 if (
jobID >= 0 && (found_infile || build_index))
405 std::cerr <<
"Can't specify -j with --buildindex, --video or --infile\n";
408 if ((
jobID >= 0) && build_index)
410 std::cerr <<
"Can't specify both -j and --buildindex\n";
413 if (keyframesonly && !fifodir.isEmpty())
415 std::cerr <<
"Cannot specify both --fifodir and --allkeys\n";
418 if (fifosync && fifodir.isEmpty())
420 std::cerr <<
"Must specify --fifodir to use --fifosync\n";
423 if (fifo_info && !fifodir.isEmpty())
425 std::cerr <<
"Cannot specify both --fifodir and --fifoinfo\n";
428 if (cleanCut && fifodir.isEmpty() && !fifo_info)
430 std::cerr <<
"Clean cutting works only in fifodir mode\n";
433 if (cleanCut && !useCutlist)
435 std::cerr <<
"--cleancut is pointless without --honorcutlist\n";
443 fifodir =
"DummyFifoPath";
448 LOG(VB_GENERAL, LOG_ERR,
"couldn't open db");
456 QFileInfo inf(infile);
457 infile = inf.absoluteFilePath();
460 else if (!found_infile)
466 LOG(VB_GENERAL, LOG_ERR,
467 QString(
"Couldn't find recording for chanid %1 @ %2")
468 .arg(chanid).arg(starttime.toString(
Qt::ISODate)));
480 LOG(VB_GENERAL, LOG_ERR,
481 QString(
"Couldn't find a recording for filename '%1'")
490 LOG(VB_GENERAL, LOG_ERR,
"No program info found!");
500 if (infile.startsWith(
"myth://") && (outfile.isEmpty() || outfile !=
"-") &&
503 LOG(VB_GENERAL, LOG_ERR,
504 QString(
"Attempted to transcode %1. Mythtranscode is currently "
505 "unable to transcode remote files.") .arg(infile));
510 if (outfile.isEmpty() && !build_index && fifodir.isEmpty())
511 outfile = infile +
".tmp";
520 if (fifodir.isEmpty())
522 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to %2")
523 .arg(infile, outfile));
527 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to FIFO")
534 transcode->SetAVFMode();
551 transcode->SetCMDBitrate(
cmdline.
toInt(
"bitrate") * 1000);
553 transcode->SetCMDAudioBitrate(
cmdline.
toInt(
"audiobitrate") * 1000);
562 transcode->ShowProgress(
true);
566 if ((!mpeg2 && !build_index))
568 result = transcode->TranscodeFile(infile, outfile,
569 profilename, useCutlist,
570 (fifosync || keyframesonly),
jobID,
571 fifodir, fifo_info, cleanCut, deleteMap,
572 AudioTrackNo, passthru);
591 if (mpeg2 || build_index)
593 void (*update_func)(float) =
nullptr;
594 int (*check_func)() =
nullptr;
597 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
598 if (deleteMap.isEmpty())
609 &deleteMap,
nullptr,
false,
false, 20,
610 showprogress, otype, update_func,
615 m2f->SetAllAudio(
true);
634 result = m2f->Start();
669 LOG(VB_GENERAL, LOG_NOTICE, QString(
"%1 %2 done")
670 .arg(build_index ?
"Building Index for" :
"Transcoding", infile));
676 LOG(VB_GENERAL, LOG_NOTICE,
677 QString(
"Transcoding %1 aborted because of cutlist update")
685 LOG(VB_GENERAL, LOG_NOTICE,
686 QString(
"Transcoding %1 stopped because of stop command")
694 LOG(VB_GENERAL, LOG_ERR, QString(
"Transcoding %1 failed").arg(infile));
698 if (deleteOriginal ||
jobID >= 0)
699 CompleteJob(
jobID, pginfo, useCutlist, &deleteMap, exitcode, result, deleteOriginal);
701 transcode->deleteLater();
714 QString basename =
filename.section(
'/', -1);
718 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Requesting delete for file '%1'.")
725 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting file '%1'.").arg(
filename));
726 return unlink(
filename.toLocal8Bit().constData());
732 if (deleteMap ==
nullptr)
735 uint64_t subtraction = 0;
736 uint64_t startOfCutRegion = 0;
738 bool withinCut =
false;
739 bool firstMark =
true;
740 while (!delMap.empty() && delMap.begin().key() <= oldBookmark)
742 uint64_t key = delMap.begin().key();
748 startOfCutRegion = key;
757 subtraction += (key - startOfCutRegion);
763 subtraction += (oldBookmark - startOfCutRegion);
764 return oldBookmark - subtraction;
770 uint64_t currentBookmark = 0;
771 query.
prepare(
"SELECT DISTINCT mark FROM recordedmarkup "
772 "WHERE chanid = :CHANID "
773 "AND starttime = :STARTIME "
774 "AND type = :MARKTYPE ;");
780 currentBookmark = query.
value(0).toLongLong();
782 return currentBookmark;
787 LOG(VB_GENERAL, LOG_NOTICE,
788 "Transcode: delete old file: waiting while program is in use.");
794 query.
prepare(
"SELECT count(*) FROM inuseprograms "
795 "WHERE chanid = :CHANID "
796 "AND starttime = :STARTTIME "
797 "AND recusage = 'player' ;");
802 LOG(VB_GENERAL, LOG_ERR,
803 "Transcode: delete old file: in-use query failed;");
808 inUse = (query.
value(0).toUInt() != 0);
813 constexpr std::chrono::seconds kSecondsToWait = 10s;
814 LOG(VB_GENERAL, LOG_NOTICE,
815 QString(
"Transcode: program in use, rechecking in %1 seconds.")
816 .arg(kSecondsToWait.count()));
817 std::this_thread::sleep_for(kSecondsToWait);
820 LOG(VB_GENERAL, LOG_NOTICE,
"Transcode: program is no longer in use.");
824 frm_dir_map_t *deleteMap,
int &exitCode,
int resultCode,
bool forceDelete)
826 int status = JOB_UNKNOWN;
834 QObject::tr(
"Job errored, unable to find Program Info for job"));
835 LOG(VB_GENERAL, LOG_CRIT,
"MythTranscode: Cleanup errored, unable to find Program Info");
840 const QByteArray fname =
filename.toLocal8Bit();
848 uint64_t previousBookmark =
856 const QString tmpfile =
filename +
".tmp";
857 const QByteArray atmpfile = tmpfile.toLocal8Bit();
860 const QString oldfile =
filename +
".old";
861 const QByteArray aoldfile = oldfile.toLocal8Bit();
863 QFileInfo st(tmpfile);
871 if (
filename.endsWith(
".mpg") && jobArgs ==
"RENAME_TO_NUV")
874 cnf.replace(
".mpg",
".nuv");
875 newbase.replace(
".mpg",
".nuv");
878 else if (
filename.endsWith(
".ts") &&
879 (jobArgs ==
"RENAME_TO_MPG"))
883 cnf.replace(
".ts",
".mpg");
884 newbase.replace(
".ts",
".mpg");
889 const QString newfile = cnf;
890 const QByteArray anewfile = newfile.toLocal8Bit();
892 if (rename(fname.constData(), aoldfile.constData()) == -1)
894 LOG(VB_GENERAL, LOG_ERR,
895 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
899 if (rename(atmpfile.constData(), anewfile.constData()) == -1)
901 LOG(VB_GENERAL, LOG_ERR,
902 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
903 .arg(tmpfile, newfile) +
ENO);
911 LOG(VB_FILE, LOG_INFO,
912 QString(
"mythtranscode: About to unlink/delete file: %1")
915 QFileInfo finfo(oldfile);
916 if (followLinks && finfo.isSymLink())
919 QByteArray alink = link.toLocal8Bit();
923 LOG(VB_GENERAL, LOG_ERR,
924 QString(
"mythtranscode: Error deleting '%1' "
925 "pointed to by '%2'")
926 .arg(alink.constData(), aoldfile.constData()) +
ENO);
929 err = unlink(aoldfile.constData());
932 LOG(VB_GENERAL, LOG_ERR,
933 QString(
"mythtranscode: Error deleting '%1', "
934 "a link pointing to '%2'")
935 .arg(aoldfile.constData(), alink.constData()) +
ENO);
940 int err =
transUnlink(aoldfile.constData(), pginfo);
943 LOG(VB_GENERAL, LOG_ERR,
944 QString(
"mythtranscode: Error deleting '%1': ")
945 .arg(oldfile) +
ENO);
955 QStringList nameFilters;
956 nameFilters.push_back(fInfo.fileName() +
"*.png");
957 nameFilters.push_back(fInfo.fileName() +
"*.jpg");
959 QDir dir (fInfo.path());
960 QFileInfoList previewFiles = dir.entryInfoList(nameFilters);
962 for (
const auto & previewFile : std::as_const(previewFiles))
964 QString oldFileName = previewFile.absoluteFilePath();
975 if (
transUnlink(oldFileName.toLocal8Bit().constData(), pginfo) != -1)
979 if (jobArgs ==
"RENAME_TO_NUV" || jobArgs ==
"RENAME_TO_MPG")
981 QString newExtension =
"mpg";
982 if (jobArgs ==
"RENAME_TO_NUV")
983 newExtension =
"nuv";
985 QString oldSuffix = previewFile.completeSuffix();
987 if (!oldSuffix.startsWith(newExtension))
989 QString newSuffix = oldSuffix;
990 QString oldExtension = oldSuffix.section(
".", 0, 0);
991 newSuffix.replace(oldExtension, newExtension);
993 QString newFileName = oldFileName;
994 newFileName.replace(oldSuffix, newSuffix);
996 if (!QFile::rename(oldFileName, newFileName))
998 LOG(VB_GENERAL, LOG_ERR,
999 QString(
"mythtranscode: Error renaming %1 to %2")
1000 .arg(oldFileName, newFileName));
1010 query.
prepare(
"DELETE FROM recordedmarkup "
1011 "WHERE chanid = :CHANID "
1012 "AND starttime = :STARTTIME "
1013 "AND type != :BOOKMARK ");
1021 query.
prepare(
"UPDATE recorded "
1022 "SET cutlist = :CUTLIST "
1023 "WHERE chanid = :CHANID "
1024 "AND starttime = :STARTTIME ;");
1036 query.
prepare(
"DELETE FROM recordedmarkup "
1037 "WHERE chanid = :CHANID "
1038 "AND starttime = :STARTTIME "
1039 "AND type not in ( :COMM_START, "
1040 " :COMM_END, :BOOKMARK, "
1041 " :CUTLIST_START, :CUTLIST_END) ;");
1063 QString filename_tmp =
filename +
".tmp";
1064 QByteArray fname_tmp = filename_tmp.toLocal8Bit();
1065 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting %1").arg(filename_tmp));
1068 QString filename_map =
filename +
".tmp.map";
1069 QByteArray fname_map = filename_map.toLocal8Bit();
1070 unlink(fname_map.constData());
1074 if (status == JOB_ABORTING)
1077 QObject::tr(
"Job Aborted"));
1079 else if (status != JOB_ERRORING)
1086 QObject::tr(
"Unrecoverable error"));
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