11 #include <QCoreApplication>
28 #include "commandlineparser.h"
42 if (pginfo && mapfile.isEmpty())
49 else if (!mapfile.isEmpty())
52 FILE *mapfh = fopen(mapfile.toLocal8Bit().constData(),
"w");
55 LOG(VB_GENERAL, LOG_ERR, QString(
"Could not open map file '%1'")
59 frm_pos_map_t::const_iterator it;
60 fprintf (mapfh,
"Type: %d\n", keyType);
61 for (it = posMap.begin(); it != posMap.end(); ++it)
63 if (it.key() == keyType)
65 QString str = QString(
"%1 %2\n").arg(it.key()).arg(*it);
66 fprintf(mapfh,
"%s", qPrintable(str));
80 QObject::tr(
"Generating Keyframe Index"));
86 QObject::tr(
"Transcode Completed"));
94 QString(
"%1% " + QObject::tr(
"Completed"))
95 .arg(percent_done, 0,
'f', 1));
102 LOG(VB_GENERAL, LOG_NOTICE,
"Transcoding stopped by JobQueue");
109 QString hostname,
bool usecutlist)
112 if (!profile.isEmpty())
120 LOG(VB_GENERAL, LOG_NOTICE,
121 QString(
"Queued transcode job for chanid %1 @ %2")
124 return GENERIC_EXIT_OK;
127 LOG(VB_GENERAL, LOG_ERR, QString(
"Error queuing job for chanid %1 @ %2")
130 return GENERIC_EXIT_DB_ERROR;
145 typedef void (*CleanupFunc)();
148 CleanupGuard(CleanupFunc cleanFunction) :
149 m_cleanFunction(cleanFunction) {}
157 CleanupFunc m_cleanFunction;
161 int main(
int argc,
char *argv[])
165 QString infile, outfile;
166 QString profilename = QString(
"autodetect");
167 QString fifodir = NULL;
170 int otype = REPLEX_MPEG2;
171 bool useCutlist =
false, keyframesonly =
false;
172 bool build_index =
false, fifosync =
false;
174 bool fifo_info =
false;
175 bool cleanCut =
false;
176 QMap<QString, QString> settingsOverride;
180 int AudioTrackNo = -1;
182 int found_starttime = 0;
183 int found_chanid = 0;
184 int found_infile = 0;
185 int update_index = 1;
187 bool passthru =
false;
190 if (!cmdline.
Parse(argc, argv))
193 return GENERIC_EXIT_INVALID_CMDLINE;
196 if (cmdline.
toBool(
"showhelp"))
199 return GENERIC_EXIT_OK;
202 if (cmdline.
toBool(
"showversion"))
205 return GENERIC_EXIT_OK;
208 QCoreApplication a(argc, argv);
209 QCoreApplication::setApplicationName(MYTH_APPNAME_MYTHTRANSCODE);
211 if (cmdline.
toBool(
"outputfile"))
213 outfile = cmdline.
toString(
"outputfile");
217 bool showprogress = cmdline.
toBool(
"showprogress");
220 QString mask(
"general");
221 bool quiet = (outfile ==
"-") || showprogress;
225 if (cmdline.
toBool(
"starttime"))
230 if (cmdline.
toBool(
"chanid"))
232 chanid = cmdline.
toUInt(
"chanid");
235 if (cmdline.
toBool(
"jobid"))
236 jobID = cmdline.
toInt(
"jobid");
237 if (cmdline.
toBool(
"inputfile"))
239 infile = cmdline.
toString(
"inputfile");
242 if (cmdline.
toBool(
"video"))
244 if (cmdline.
toBool(
"profile"))
245 profilename = cmdline.
toString(
"profile");
247 if (cmdline.
toBool(
"usecutlist"))
250 if (!cmdline.
toString(
"usecutlist").isEmpty())
252 if (!cmdline.
toBool(
"inputfile") && !cmdline.
toBool(
"hls"))
254 LOG(VB_GENERAL, LOG_CRIT,
"External cutlists are only allowed "
255 "when using the --infile option.");
256 return GENERIC_EXIT_INVALID_CMDLINE;
259 uint64_t
last = 0, start, end;
260 QStringList cutlist = cmdline.
toStringList(
"usecutlist",
" ");
261 QStringList::iterator it;
262 for (it = cutlist.begin(); it != cutlist.end(); ++it)
264 QStringList startend =
265 (*it).split(
"-", QString::SkipEmptyParts);
266 if (startend.size() == 2)
268 start = startend.first().toULongLong();
269 end = startend.last().toULongLong();
271 if (cmdline.
toBool(
"inversecut"))
273 LOG(VB_GENERAL, LOG_DEBUG,
274 QString(
"Cutting section %1-%2.")
275 .arg(last).arg(start));
282 LOG(VB_GENERAL, LOG_DEBUG,
283 QString(
"Cutting section %1-%2.")
284 .arg(start).arg(end));
291 if (cmdline.
toBool(
"inversecut"))
293 if (deleteMap.contains(0) && (deleteMap[0] ==
MARK_CUT_END))
298 LOG(VB_GENERAL, LOG_DEBUG,
299 QString(
"Cutting section %1-999999999.")
304 if (deleteMap.count() >= 2)
306 frm_dir_map_t::iterator cur = deleteMap.begin(), prev;
308 while (cur != deleteMap.end())
310 if (prev.value() == cur.value())
313 QString err(
"Cut %1points found at %3 and %4, with no "
314 "%2 point in between.");
316 err = err.arg(
"end").arg(
"start");
318 err = err.arg(
"start").arg(
"end");
319 LOG(VB_GENERAL, LOG_CRIT,
"Invalid cutlist defined!");
320 LOG(VB_GENERAL, LOG_CRIT, err.arg(prev.key())
322 return GENERIC_EXIT_INVALID_CMDLINE;
325 ((cur.key() - prev.key()) < 2) )
327 LOG(VB_GENERAL, LOG_WARNING, QString(
"Discarding "
328 "insufficiently long cut: %1-%2")
329 .arg(prev.key()).arg(cur.key()));
330 prev = deleteMap.erase(prev);
331 cur = deleteMap.erase(cur);
333 if (cur == deleteMap.end())
340 else if (cmdline.
toBool(
"inversecut"))
342 cerr <<
"Cutlist inversion requires an external cutlist be" << endl
343 <<
"provided using the --honorcutlist option." << endl;
344 return GENERIC_EXIT_INVALID_CMDLINE;
348 if (cmdline.
toBool(
"cleancut"))
351 if (cmdline.
toBool(
"allkeys"))
352 keyframesonly =
true;
353 if (cmdline.
toBool(
"reindex"))
355 if (cmdline.
toBool(
"fifodir"))
356 fifodir = cmdline.
toString(
"fifodir");
357 if (cmdline.
toBool(
"fifoinfo"))
359 if (cmdline.
toBool(
"fifosync"))
361 if (cmdline.
toBool(
"recopt"))
363 if (cmdline.
toBool(
"mpeg2"))
365 if (cmdline.
toBool(
"ostream"))
367 if (cmdline.
toString(
"ostream") ==
"dvd")
369 else if (cmdline.
toString(
"ostream") ==
"ts")
370 otype = REPLEX_TS_SD;
373 cerr <<
"Invalid 'ostream' type: "
374 << cmdline.
toString(
"ostream").toLocal8Bit().constData()
376 return GENERIC_EXIT_INVALID_CMDLINE;
379 if (cmdline.
toBool(
"audiotrack"))
380 AudioTrackNo = cmdline.
toInt(
"audiotrack");
381 if (cmdline.
toBool(
"passthru"))
384 CleanupGuard callCleanup(
cleanup);
387 QList<int> signallist;
388 signallist << SIGINT << SIGTERM << SIGSEGV << SIGABRT << SIGBUS << SIGFPE
391 signallist << SIGRTMIN;
394 signal(SIGHUP, SIG_IGN);
401 LOG(VB_GENERAL, LOG_ERR,
"Failed to init MythContext, exiting.");
402 return GENERIC_EXIT_NO_MYTHCONTEXT;
418 cerr <<
"mythtranscode: ERROR: Unable to find DB info for "
419 <<
"JobQueue ID# " << jobID << endl;
420 return GENERIC_EXIT_NO_RECORDING_DATA;
424 if (((!found_infile && !(found_chanid && found_starttime)) ||
425 (found_infile && (found_chanid || found_starttime))) &&
428 cerr <<
"Must specify -i OR -c AND -s options!" << endl;
429 return GENERIC_EXIT_INVALID_CMDLINE;
431 if (isVideo && !found_infile && !cmdline.
toBool(
"hls"))
433 cerr <<
"Must specify --infile to use --video" << endl;
434 return GENERIC_EXIT_INVALID_CMDLINE;
436 if (jobID >= 0 && (found_infile || build_index))
438 cerr <<
"Can't specify -j with --buildindex, --video or --infile"
440 return GENERIC_EXIT_INVALID_CMDLINE;
442 if ((jobID >= 0) && build_index)
444 cerr <<
"Can't specify both -j and --buildindex" << endl;
445 return GENERIC_EXIT_INVALID_CMDLINE;
447 if (keyframesonly && !fifodir.isEmpty())
449 cerr <<
"Cannot specify both --fifodir and --allkeys" << endl;
450 return GENERIC_EXIT_INVALID_CMDLINE;
452 if (fifosync && fifodir.isEmpty())
454 cerr <<
"Must specify --fifodir to use --fifosync" << endl;
455 return GENERIC_EXIT_INVALID_CMDLINE;
457 if (fifo_info && !fifodir.isEmpty())
459 cerr <<
"Cannot specify both --fifodir and --fifoinfo" << endl;
460 return GENERIC_EXIT_INVALID_CMDLINE;
462 if (cleanCut && fifodir.isEmpty() && !fifo_info)
464 cerr <<
"Clean cutting works only in fifodir mode" << endl;
465 return GENERIC_EXIT_INVALID_CMDLINE;
467 if (cleanCut && !useCutlist)
469 cerr <<
"--cleancut is pointless without --honorcutlist" << endl;
470 return GENERIC_EXIT_INVALID_CMDLINE;
477 fifodir =
"DummyFifoPath";
482 LOG(VB_GENERAL, LOG_ERR,
"couldn't open db");
483 return GENERIC_EXIT_DB_ERROR;
487 if (cmdline.
toBool(
"hls"))
489 if (cmdline.
toBool(
"hlsstreamid"))
500 QFileInfo inf(infile);
501 infile = inf.absoluteFilePath();
504 else if (!found_infile)
510 LOG(VB_GENERAL, LOG_ERR,
511 QString(
"Couldn't find recording for chanid %1 @ %2")
512 .arg(chanid).arg(starttime.toString(
Qt::ISODate)));
514 return GENERIC_EXIT_NO_RECORDING_DATA;
524 LOG(VB_GENERAL, LOG_ERR,
525 QString(
"Couldn't find a recording for filename '%1'")
528 return GENERIC_EXIT_NO_RECORDING_DATA;
534 LOG(VB_GENERAL, LOG_ERR,
"No program info found!");
535 return GENERIC_EXIT_NO_RECORDING_DATA;
538 if (cmdline.
toBool(
"queue"))
540 QString hostname = cmdline.
toString(
"queue");
544 if (infile.startsWith(
"myth://") && (outfile.isEmpty() || outfile !=
"-") &&
545 fifodir.isEmpty() && !cmdline.
toBool(
"hls") && !cmdline.
toBool(
"avf"))
547 LOG(VB_GENERAL, LOG_ERR,
548 QString(
"Attempted to transcode %1. Mythtranscode is currently "
549 "unable to transcode remote files.") .arg(infile));
550 return GENERIC_EXIT_REMOTE_FILE;
553 if (outfile.isEmpty() && !build_index && fifodir.isEmpty())
554 outfile = infile +
".tmp";
563 if (cmdline.
toBool(
"hlsstreamid"))
564 LOG(VB_GENERAL, LOG_NOTICE,
565 QString(
"Transcoding HTTP Live Stream ID %1")
566 .arg(cmdline.
toInt(
"hlsstreamid")));
567 else if (fifodir.isEmpty())
568 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to %2")
569 .arg(infile).arg(outfile));
571 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Transcoding from %1 to FIFO")
575 if (cmdline.
toBool(
"avf"))
579 if (cmdline.
toBool(
"container"))
581 if (cmdline.
toBool(
"acodec"))
583 if (cmdline.
toBool(
"vcodec"))
586 else if (cmdline.
toBool(
"hls"))
590 if (cmdline.
toBool(
"hlsstreamid"))
592 if (cmdline.
toBool(
"maxsegments"))
594 if (cmdline.
toBool(
"noaudioonly"))
600 if (cmdline.
toBool(
"width"))
602 if (cmdline.
toBool(
"height"))
604 if (cmdline.
toBool(
"bitrate"))
606 if (cmdline.
toBool(
"audiobitrate"))
615 if ((!mpeg2 && !build_index) || cmdline.
toBool(
"hls"))
618 profilename, useCutlist,
619 (fifosync || keyframesonly), jobID,
620 fifodir, fifo_info, cleanCut, deleteMap,
621 AudioTrackNo, passthru);
622 if ((result == REENCODE_OK) && (jobID >= 0))
629 return GENERIC_EXIT_OK;
632 int exitcode = GENERIC_EXIT_OK;
633 if ((result == REENCODE_MPEG2TRANS) || mpeg2 || build_index)
635 void (*update_func)(float) = NULL;
636 int (*check_func)() = NULL;
639 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
650 &deleteMap, NULL,
false,
false, 20,
651 showprogress, otype, update_func,
666 result = m2f->
Start();
667 if (result == REENCODE_OK)
670 if (result == REENCODE_OK)
683 if (result == REENCODE_OK)
687 LOG(VB_GENERAL, LOG_NOTICE, QString(
"%1 %2 done")
688 .arg(build_index ?
"Building Index for" :
"Transcoding")
691 else if (result == REENCODE_CUTLIST_CHANGE)
695 LOG(VB_GENERAL, LOG_NOTICE,
696 QString(
"Transcoding %1 aborted because of cutlist update")
698 exitcode = GENERIC_EXIT_RESTART;
700 else if (result == REENCODE_STOPPED)
704 LOG(VB_GENERAL, LOG_NOTICE,
705 QString(
"Transcoding %1 stopped because of stop command")
707 exitcode = GENERIC_EXIT_KILLED;
713 LOG(VB_GENERAL, LOG_ERR, QString(
"Transcoding %1 failed").arg(infile));
718 CompleteJob(jobID, pginfo, useCutlist, &deleteMap, exitcode);
720 transcode->deleteLater();
733 QString basename = filename.section(
'/', -1);
737 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Requesting delete for file '%1'.")
744 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting file '%1'.").arg(filename));
745 return unlink(filename.toLocal8Bit().constData());
751 if (deleteMap == NULL)
754 uint64_t subtraction = 0;
755 uint64_t startOfCutRegion = 0;
757 bool withinCut =
false;
758 bool firstMark =
true;
759 while (delMap.count() && delMap.begin().key() <= oldBookmark)
761 uint64_t key = delMap.begin().key();
767 startOfCutRegion = key;
776 subtraction += (key - startOfCutRegion);
782 subtraction += (oldBookmark - startOfCutRegion);
783 return oldBookmark - subtraction;
789 uint64_t currentBookmark = 0;
790 query.
prepare(
"SELECT DISTINCT mark FROM recordedmarkup "
791 "WHERE chanid = :CHANID "
792 "AND starttime = :STARTIME "
793 "AND type = :MARKTYPE ;");
799 currentBookmark = query.
value(0).toLongLong();
801 return currentBookmark;
806 LOG(VB_GENERAL, LOG_NOTICE,
807 "Transcode: delete old file: waiting while program is in use.");
813 query.
prepare(
"SELECT count(*) FROM inuseprograms "
814 "WHERE chanid = :CHANID "
815 "AND starttime = :STARTTIME "
816 "AND recusage = 'player' ;");
821 LOG(VB_GENERAL, LOG_ERR,
822 "Transcode: delete old file: in-use query failed;");
827 inUse = (query.
value(0).toUInt() != 0);
832 const unsigned kSecondsToWait = 10;
833 LOG(VB_GENERAL, LOG_NOTICE,
834 QString(
"Transcode: program in use, rechecking in %1 seconds.")
835 .arg(kSecondsToWait));
836 sleep(kSecondsToWait);
839 LOG(VB_GENERAL, LOG_NOTICE,
"Transcode: program is no longer in use.");
850 QObject::tr(
"Job errored, unable to find Program Info for job"));
855 const QByteArray fname = filename.toLocal8Bit();
857 if (status == JOB_STOPPING)
863 uint64_t previousBookmark =
869 const QString tmpfile = filename +
".tmp";
870 const QByteArray atmpfile = tmpfile.toLocal8Bit();
873 const QString oldfile = filename +
".old";
874 const QByteArray aoldfile = oldfile.toLocal8Bit();
876 QFileInfo st(tmpfile);
881 QString cnf = filename;
882 if ((jobArgs ==
"RENAME_TO_NUV") &&
883 (filename.contains(QRegExp(
"mpg$"))))
887 cnf.replace(QRegExp(
"mpg$"),
"nuv");
888 newbase.replace(QRegExp(
"mpg$"),
"nuv");
892 const QString newfile = cnf;
893 const QByteArray anewfile = newfile.toLocal8Bit();
895 if (rename(fname.constData(), aoldfile.constData()) == -1)
897 LOG(VB_GENERAL, LOG_ERR,
898 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
899 .arg(filename).arg(oldfile) + ENO);
902 if (rename(atmpfile.constData(), anewfile.constData()) == -1)
904 LOG(VB_GENERAL, LOG_ERR,
905 QString(
"mythtranscode: Error Renaming '%1' to '%2'")
906 .arg(tmpfile).arg(newfile) + ENO);
915 LOG(VB_FILE, LOG_INFO,
916 QString(
"mythtranscode: About to unlink/delete file: %1")
919 QFileInfo finfo(oldfile);
920 if (followLinks && finfo.isSymLink())
923 QByteArray alink = link.toLocal8Bit();
928 LOG(VB_GENERAL, LOG_ERR,
929 QString(
"mythtranscode: Error deleting '%1' "
930 "pointed to by '%2'")
931 .arg(alink.constData())
932 .arg(aoldfile.constData()) + ENO);
935 err = unlink(aoldfile.constData());
938 LOG(VB_GENERAL, LOG_ERR,
939 QString(
"mythtranscode: Error deleting '%1', "
940 "a link pointing to '%2'")
941 .arg(aoldfile.constData())
942 .arg(alink.constData()) + ENO);
947 if ((err =
transUnlink(aoldfile.constData(), pginfo)))
948 LOG(VB_GENERAL, LOG_ERR,
949 QString(
"mythtranscode: Error deleting '%1': ")
950 .arg(oldfile) + ENO);
960 QFileInfo fInfo(filename);
961 QString nameFilter = fInfo.fileName() +
"*.png";
965 nameFilter.replace(QRegExp(
"( |;)"),
"?");
966 QDir dir (fInfo.path(), nameFilter);
968 for (
uint nIdx = 0; nIdx < dir.count(); nIdx++)
973 const QString oldfileop = QString(
"%1/%2")
974 .arg(fInfo.path()).arg(dir[nIdx]);
975 const QByteArray aoldfileop = oldfileop.toLocal8Bit();
981 if (jobArgs ==
"RENAME_TO_NUV")
983 QFileInfo fInfo(filename);
984 QString nameFilter = fInfo.fileName() +
"*.png";
988 nameFilter.replace(QRegExp(
"( |;)"),
"?");
990 QDir dir (fInfo.path(), nameFilter);
992 for (
uint nIdx = 0; nIdx < dir.count(); nIdx++)
994 const QString oldfileprev = QString(
"%1/%2")
995 .arg(fInfo.path()).arg(dir[nIdx]);
996 const QByteArray aoldfileprev = oldfileprev.toLocal8Bit();
998 QString newfileprev = oldfileprev;
999 QRegExp re(
"mpg(\\..*)?\\.png$");
1000 if (re.indexIn(newfileprev))
1002 newfileprev.replace(
1003 re, QString(
"nuv%1.png").arg(re.cap(1)));
1005 const QByteArray anewfileprev = newfileprev.toLocal8Bit();
1007 QFile checkFile(oldfileprev);
1009 if ((oldfileprev != newfileprev) && (checkFile.exists()))
1010 rename(aoldfileprev.constData(), anewfileprev.constData());
1018 query.
prepare(
"DELETE FROM recordedmarkup "
1019 "WHERE chanid = :CHANID "
1020 "AND starttime = :STARTTIME "
1021 "AND type != :BOOKMARK ");
1029 query.
prepare(
"UPDATE recorded "
1030 "SET cutlist = :CUTLIST "
1031 "WHERE chanid = :CHANID "
1032 "AND starttime = :STARTTIME ;");
1044 query.
prepare(
"DELETE FROM recordedmarkup "
1045 "WHERE chanid = :CHANID "
1046 "AND starttime = :STARTTIME "
1047 "AND type not in ( :COMM_START, "
1048 " :COMM_END, :BOOKMARK, "
1049 " :CUTLIST_START, :CUTLIST_END) ;");
1069 QString filename_tmp = filename +
".tmp";
1070 QByteArray fname_tmp = filename_tmp.toLocal8Bit();
1071 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Deleting %1").arg(filename_tmp));
1074 QString filename_map = filename +
".tmp.map";
1075 QByteArray fname_map = filename_map.toLocal8Bit();
1076 unlink(fname_map.constData());
1078 if (status == JOB_ABORTING)
1080 QObject::tr(
"Job Aborted"));
1081 else if (status != JOB_ERRORING)
1082 resultCode = GENERIC_EXIT_RESTART;
1085 QObject::tr(
"Unrecoverable error"));