00001 #include <qapplication.h>
00002 #include <qsqldatabase.h>
00003 #include <qfile.h>
00004 #include <qmap.h>
00005 #include <qfileinfo.h>
00006 #include <qregexp.h>
00007 #include <qdir.h>
00008
00009 #include <unistd.h>
00010 #include <sys/types.h>
00011 #include <sys/stat.h>
00012 #include <fcntl.h>
00013 #include <iostream>
00014 #include <fstream>
00015 using namespace std;
00016
00017 #include "exitcodes.h"
00018 #include "programinfo.h"
00019 #include "jobqueue.h"
00020 #include "mythcontext.h"
00021 #include "mythdbcon.h"
00022 #include "transcode.h"
00023 #include "mpeg2fix.h"
00024
00025 void StoreTranscodeState(ProgramInfo *pginfo, int status, bool useCutlist);
00026 void UpdatePositionMap(QMap <long long, long long> &posMap, QString mapfile,
00027 ProgramInfo *pginfo);
00028 int BuildKeyframeIndex(MPEG2fixup *m2f, QString &infile,
00029 QMap <long long, long long> &posMap, int jobID);
00030 void CompleteJob(int jobID, ProgramInfo *pginfo, bool useCutlist, int &resultCode);
00031 void UpdateJobQueue(float percent_done);
00032 int CheckJobQueue();
00033 static int glbl_jobID = -1;
00034
00035 void usage(char *progname)
00036 {
00037 cerr << "Usage: " << progname << " <--chanid <channelid>>\n";
00038 cerr << "\t<--starttime <starttime>> <--profile <profile>>\n";
00039 cerr << "\t[options]\n\n";
00040 cerr << "\t--mpeg2 or -m: Perform MPEG2 to MPEG2 transcode.\n";
00041 cerr << "\t--ostream <type> or -e: Output stream type. Options: dvd, ps.\n";
00042 cerr << "\t--chanid or -c: Takes a channel id. REQUIRED\n";
00043 cerr << "\t--starttime or -s: Takes a starttime for the\n";
00044 cerr << "\t recording. REQUIRED\n";
00045 cerr << "\t--infile or -i: Input file (Alternative to -c and -s)\n";
00046 cerr << "\t--outfile or -o: Output file\n";
00047 cerr << "\t--profile or -p: Takes a profile number or 'autodetect'\n";
00048 cerr << "\t recording profile. REQUIRED\n";
00049 cerr << "\t--honorcutlist or -l: Specifies whether to use the cutlist.\n";
00050 cerr << " Optionally takes a cutlist as an argument\n";
00051 cerr << "\t when used with --infile.\n";
00052 cerr << "\t--inversecut : Specifies a list of frames to keep\n";
00053 cerr << "\t while cutting everything else out.\n";
00054 cerr << "\t Only works with --infile.\n";
00055 cerr << "\t--allkeys or -k: Specifies that the output file\n";
00056 cerr << "\t should be made entirely of keyframes.\n";
00057 cerr << "\t--fifodir or -f: Directory to write fifos to\n";
00058 cerr << "\t If --fifodir is specified, 'audout' and 'vidout'\n";
00059 cerr << "\t will be created in the specified directory\n";
00060 cerr << "\t--fifosync : Enforce fifo sync\n";
00061 cerr << "\t--buildindex or -b: Build a new keyframe index\n";
00062 cerr << "\t (use only if audio and video fifos are read independantly)\n";
00063 cerr << "\t--video : Specifies that this is not a mythtv recording\n";
00064 cerr << "\t (must be used with --infile)\n";
00065 cerr << "\t--showprogress : Display status info to the stdout\n";
00066 cerr << "\t--verbose level or -v: Use '-v help' for level info\n";
00067 cerr << "\t--help or -h: Prints this help statement.\n";
00068 }
00069
00070 int main(int argc, char *argv[])
00071 {
00072 QString chanid, starttime, infile, outfile;
00073 QString profilename = QString("autodetect");
00074 QString fifodir = NULL;
00075 int jobID = -1;
00076 QDateTime startts;
00077 int jobType = JOB_NONE;
00078 int otype = REPLEX_MPEG2;
00079 bool useCutlist = false, keyframesonly = false;
00080 bool build_index = false, fifosync = false, showprogress = false, mpeg2 = false;
00081 QMap<QString, QString> settingsOverride;
00082 QMap<long long, int> deleteMap;
00083 QMap<long long, long long> posMap;
00084 srand(time(NULL));
00085
00086 QApplication a(argc, argv, false);
00087
00088 print_verbose_messages = VB_IMPORTANT;
00089 verboseString = "important";
00090
00091 int found_starttime = 0;
00092 int found_chanid = 0;
00093 int found_infile = 0;
00094 int update_index = 1;
00095 int isVideo = 0;
00096
00097 for (int argpos = 1; argpos < a.argc(); ++argpos)
00098 {
00099 if (!strcmp(a.argv()[argpos],"-s") ||
00100 !strcmp(a.argv()[argpos],"--starttime"))
00101 {
00102 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00103 {
00104 starttime = a.argv()[argpos + 1];
00105 found_starttime = 1;
00106 ++argpos;
00107 }
00108 else
00109 {
00110 cerr << "Missing argument to -s/--starttime option\n";
00111 usage(a.argv()[0]);
00112 return TRANSCODE_EXIT_INVALID_CMDLINE;
00113 }
00114 }
00115 else if (!strcmp(a.argv()[argpos],"-c") ||
00116 !strcmp(a.argv()[argpos],"--chanid"))
00117 {
00118 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00119 {
00120 chanid = a.argv()[argpos + 1];
00121 found_chanid = 1;
00122 ++argpos;
00123 }
00124 else
00125 {
00126 cerr << "Missing argument to -c/--chanid option\n";
00127 usage(a.argv()[0]);
00128 return TRANSCODE_EXIT_INVALID_CMDLINE;
00129 }
00130 }
00131 else if (!strcmp(a.argv()[argpos], "-j"))
00132 {
00133 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00134 {
00135 jobID = QString(a.argv()[++argpos]).toInt();
00136 }
00137 else
00138 {
00139 cerr << "Missing argument to -j option\n";
00140 usage(a.argv()[0]);
00141 return TRANSCODE_EXIT_INVALID_CMDLINE;
00142 }
00143 }
00144 else if (!strcmp(a.argv()[argpos],"-i") ||
00145 !strcmp(a.argv()[argpos],"--infile"))
00146 {
00147 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00148 {
00149 infile = a.argv()[argpos + 1];
00150 found_infile = 1;
00151 ++argpos;
00152 }
00153 else
00154 {
00155 cerr << "Missing argument to -i/--infile option\n";
00156 usage(a.argv()[0]);
00157 return TRANSCODE_EXIT_INVALID_CMDLINE;
00158 }
00159 }
00160 else if (!strcmp(a.argv()[argpos],"--video"))
00161 {
00162 isVideo = 1;
00163
00164 }
00165 else if (!strcmp(a.argv()[argpos],"-o") ||
00166 !strcmp(a.argv()[argpos],"--outfile"))
00167 {
00168 if ((a.argc()-1 > argpos) &&
00169 (a.argv()[argpos+1][0] != '-' || a.argv()[argpos+1][1] == 0x0))
00170 {
00171 outfile = a.argv()[argpos + 1];
00172 update_index = 0;
00173 ++argpos;
00174 }
00175 else
00176 {
00177 cerr << "Missing argument to -o/--outfile option\n";
00178 usage(a.argv()[0]);
00179 return TRANSCODE_EXIT_INVALID_CMDLINE;
00180 }
00181 }
00182 else if (!strcmp(a.argv()[argpos],"-V"))
00183 {
00184 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00185 {
00186 QString temp = a.argv()[++argpos];
00187 print_verbose_messages = temp.toInt();
00188 }
00189 else
00190 {
00191 cerr << "Missing argument to -V option\n";
00192 return TRANSCODE_EXIT_INVALID_CMDLINE;
00193 }
00194 }
00195 else if (!strcmp(a.argv()[argpos],"-v") ||
00196 !strcmp(a.argv()[argpos],"--verbose"))
00197 {
00198 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00199 {
00200 if (parse_verbose_arg(a.argv()[argpos+1]) ==
00201 GENERIC_EXIT_INVALID_CMDLINE)
00202 return TRANSCODE_EXIT_INVALID_CMDLINE;
00203
00204 ++argpos;
00205 } else
00206 {
00207 cerr << "Missing argument to -v/--verbose option\n";
00208 return TRANSCODE_EXIT_INVALID_CMDLINE;
00209 }
00210 }
00211 else if (!strcmp(a.argv()[argpos],"-p") ||
00212 !strcmp(a.argv()[argpos],"--profile"))
00213 {
00214 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00215 {
00216 profilename = a.argv()[argpos + 1];
00217 ++argpos;
00218 }
00219 else
00220 {
00221 cerr << "Missing argument to -p/--profile option\n";
00222 usage(a.argv()[0]);
00223 return TRANSCODE_EXIT_INVALID_CMDLINE;
00224 }
00225 }
00226 else if (!strcmp(a.argv()[argpos],"-l") ||
00227 !strcmp(a.argv()[argpos],"--honorcutlist"))
00228 {
00229 useCutlist = true;
00230 if (!found_infile)
00231 continue;
00232
00233 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00234 {
00235 QStringList cutlist;
00236 cutlist = QStringList::split( " ", a.argv()[argpos + 1]);
00237 ++argpos;
00238 for (QStringList::Iterator it = cutlist.begin();
00239 it != cutlist.end(); ++it )
00240 {
00241 QStringList startend;
00242 startend = QStringList::split("-", *it);
00243 if (startend.count() == 2)
00244 {
00245 cerr << "Cutting from: " << startend.first().toInt()
00246 << " to: " << startend.last().toInt() <<"\n";
00247 deleteMap[startend.first().toInt()] = 1;
00248 deleteMap[startend.last().toInt()] = 0;
00249 }
00250 }
00251 }
00252 else
00253 {
00254 cerr << "Missing argument to -l/--honorcutlist option\n";
00255 usage(a.argv()[0]);
00256 return TRANSCODE_EXIT_INVALID_CMDLINE;
00257 }
00258 }
00259 else if (!strcmp(a.argv()[argpos],"--inversecut"))
00260 {
00261 useCutlist = true;
00262 if (!found_infile)
00263 {
00264 cerr << "--inversecut option can only be used with --infile\n";
00265 usage(a.argv()[0]);
00266 return TRANSCODE_EXIT_INVALID_CMDLINE;
00267 }
00268
00269 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00270 {
00271 long long last = 0;
00272 QStringList cutlist;
00273 cutlist = QStringList::split( " ", a.argv()[argpos + 1]);
00274 ++argpos;
00275 deleteMap[0] = 1;
00276 for (QStringList::Iterator it = cutlist.begin();
00277 it != cutlist.end(); ++it )
00278 {
00279 QStringList startend;
00280 startend = QStringList::split("-", *it);
00281 if (startend.count() == 2)
00282 {
00283 cerr << "Cutting from: " << last
00284 << " to: " << startend.first().toInt() <<"\n";
00285 deleteMap[startend.first().toInt()] = 0;
00286 deleteMap[startend.last().toInt()] = 1;
00287 last = startend.last().toInt();
00288 }
00289 }
00290 cerr << "Cutting from: " << last
00291 << " to the end\n";
00292 deleteMap[999999999] = 0;
00293 }
00294 else
00295 {
00296 cerr << "Missing argument to --inversecut option\n";
00297 usage(a.argv()[0]);
00298 return TRANSCODE_EXIT_INVALID_CMDLINE;
00299 }
00300 }
00301 else if (!strcmp(a.argv()[argpos],"-k") ||
00302 !strcmp(a.argv()[argpos],"--allkeys"))
00303 {
00304 keyframesonly = true;
00305 }
00306 else if (!strcmp(a.argv()[argpos],"-b") ||
00307 !strcmp(a.argv()[argpos],"--buildindex"))
00308 {
00309 build_index = true;
00310 }
00311 else if (!strcmp(a.argv()[argpos],"-f") ||
00312 !strcmp(a.argv()[argpos],"--fifodir"))
00313 {
00314 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00315 {
00316 fifodir = a.argv()[argpos + 1];
00317 ++argpos;
00318 }
00319 else
00320 {
00321 cerr << "Missing argument to -f/--fifodir option\n";
00322 usage(a.argv()[0]);
00323 return TRANSCODE_EXIT_INVALID_CMDLINE;
00324 }
00325 }
00326 else if (!strcmp(a.argv()[argpos],"--fifosync"))
00327 {
00328 fifosync = true;
00329 }
00330 else if (!strcmp(a.argv()[argpos],"--showprogress"))
00331 {
00332 showprogress = true;
00333 }
00334 else if (!strcmp(a.argv()[argpos],"-m") ||
00335 !strcmp(a.argv()[argpos],"--mpeg2"))
00336 {
00337 mpeg2 = true;
00338 }
00339 else if (!strcmp(a.argv()[argpos],"-e") ||
00340 !strcmp(a.argv()[argpos],"--ostream"))
00341 {
00342 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00343 {
00344 if(!strcmp(a.argv()[argpos + 1], "dvd"))
00345 otype = REPLEX_DVD;
00346 if(!strcmp(a.argv()[argpos + 1], "ts"))
00347 otype = REPLEX_TS_SD;
00348
00349 ++argpos;
00350 }
00351 else
00352 {
00353 cerr << "Missing argument to -e/--ostream option\n";
00354 usage(a.argv()[0]);
00355 return TRANSCODE_EXIT_INVALID_CMDLINE;
00356 }
00357 }
00358 else if (!strcmp(a.argv()[argpos],"-O") ||
00359 !strcmp(a.argv()[argpos],"--override-setting"))
00360 {
00361 if (a.argc()-1 > argpos && a.argv()[argpos+1][0] != '-')
00362 {
00363 QStringList pairs = QStringList::split(",", a.argv()[argpos+1]);
00364 for (unsigned int index = 0; index < pairs.size(); ++index)
00365 {
00366 QStringList tokens = QStringList::split("=", pairs[index]);
00367 tokens[0].replace(QRegExp("^[\"']"), "");
00368 tokens[0].replace(QRegExp("[\"']$"), "");
00369 tokens[1].replace(QRegExp("^[\"']"), "");
00370 tokens[1].replace(QRegExp("[\"']$"), "");
00371 settingsOverride[tokens[0]] = tokens[1];
00372 }
00373 }
00374 else
00375 {
00376 cerr << "Invalid or missing argument to -O/--override-setting "
00377 "option\n";
00378 usage(a.argv()[0]);
00379 return TRANSCODE_EXIT_INVALID_CMDLINE;
00380 }
00381
00382 ++argpos;
00383 }
00384 else if (!strcmp(a.argv()[argpos],"-h") ||
00385 !strcmp(a.argv()[argpos],"--help"))
00386 {
00387 usage(a.argv()[0]);
00388 return TRANSCODE_EXIT_OK;
00389 }
00390 else
00391 {
00392 cerr << "Unknown option: " << a.argv()[argpos] << endl;
00393 usage(a.argv()[0]);
00394 return TRANSCODE_EXIT_INVALID_CMDLINE;
00395 }
00396 }
00397
00398
00399 gContext = NULL;
00400 gContext = new MythContext(MYTH_BINARY_VERSION);
00401 if (!gContext->Init(false))
00402 {
00403 VERBOSE(VB_IMPORTANT, "Failed to init MythContext, exiting.");
00404 return TRANSCODE_EXIT_NO_MYTHCONTEXT;
00405 }
00406
00407 if (settingsOverride.size())
00408 {
00409 QMap<QString, QString>::iterator it;
00410 for (it = settingsOverride.begin(); it != settingsOverride.end(); ++it)
00411 {
00412 VERBOSE(VB_IMPORTANT, QString("Setting '%1' being forced to '%2'")
00413 .arg(it.key()).arg(it.data()));
00414 gContext->OverrideSettingForSession(it.key(), it.data());
00415 }
00416 }
00417
00418 if (jobID != -1)
00419 {
00420 if (JobQueue::GetJobInfoFromID(jobID, jobType, chanid, startts))
00421 {
00422 starttime = startts.toString(Qt::ISODate);
00423 found_starttime = 1;
00424 found_chanid = 1;
00425 }
00426 else
00427 {
00428 cerr << "mythtranscode: ERROR: Unable to find DB info for "
00429 << "JobQueue ID# " << jobID << endl;
00430 return TRANSCODE_EXIT_NO_RECORDING_DATA;
00431 }
00432 }
00433
00434 if ((! found_infile && !(found_chanid && found_starttime)) ||
00435 (found_infile && (found_chanid || found_starttime)) )
00436 {
00437 cerr << "Must specify -i OR -c AND -s options!\n";
00438 return TRANSCODE_EXIT_INVALID_CMDLINE;
00439 }
00440 if (isVideo && !found_infile)
00441 {
00442 cerr << "Must specify --infile to use --video\n";
00443 return TRANSCODE_EXIT_INVALID_CMDLINE;
00444 }
00445 if (jobID >= 0 && (found_infile || build_index))
00446 {
00447 cerr << "Can't specify -j with --buildindex, --video or --infile\n";
00448 return TRANSCODE_EXIT_INVALID_CMDLINE;
00449 }
00450 if ((jobID >= 0) && build_index)
00451 {
00452 cerr << "Can't specify both -j and --buildindex\n";
00453 return TRANSCODE_EXIT_INVALID_CMDLINE;
00454 }
00455 if (keyframesonly && fifodir != NULL)
00456 {
00457 cerr << "Cannot specify both --fifodir and --allkeys\n";
00458 return TRANSCODE_EXIT_INVALID_CMDLINE;
00459 }
00460 if (fifosync && fifodir == NULL)
00461 {
00462 cerr << "Must specify --fifodir to use --fifosync\n";
00463 return TRANSCODE_EXIT_INVALID_CMDLINE;
00464 }
00465
00466 VERBOSE(VB_IMPORTANT, QString("Enabled verbose msgs: %1").arg(verboseString));
00467
00468 if (!MSqlQuery::testDBConnection())
00469 {
00470 printf("couldn't open db\n");
00471 return TRANSCODE_EXIT_DB_ERROR;
00472 }
00473
00474 ProgramInfo *pginfo = NULL;
00475 if (isVideo)
00476 {
00477
00478 QFileInfo inf(infile);
00479 infile = inf.absFilePath();
00480
00481
00482 pginfo = new ProgramInfo;
00483 pginfo->isVideo = 1;
00484 pginfo->pathname = infile;
00485 }
00486 else if (!found_infile)
00487 {
00488 pginfo = ProgramInfo::GetProgramFromRecorded(chanid, starttime);
00489
00490 if (!pginfo)
00491 {
00492 cerr << "Couldn't find recording for chanid " << chanid << " @ "
00493 << starttime << endl;
00494 return TRANSCODE_EXIT_NO_RECORDING_DATA;
00495 }
00496
00497 infile = pginfo->GetPlaybackURL(false, true);
00498 }
00499 else
00500 {
00501 QFileInfo inf(infile);
00502 pginfo = ProgramInfo::GetProgramFromBasename(inf.fileName());
00503 if (!pginfo)
00504 {
00505 QString base = inf.baseName();
00506 QRegExp r(
00507 "(\\d*)_(\\d\\d\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)");
00508 int pos = r.search(base);
00509 if (pos > -1)
00510 {
00511 chanid = r.cap(1);
00512 QDateTime startts(
00513 QDate(r.cap(2).toInt(), r.cap(3).toInt(), r.cap(4).toInt()),
00514 QTime(r.cap(5).toInt(), r.cap(6).toInt(), r.cap(7).toInt()));
00515 pginfo = ProgramInfo::GetProgramFromRecorded(chanid, startts);
00516
00517 if (!pginfo)
00518 {
00519 VERBOSE(VB_IMPORTANT,
00520 QString("Couldn't find a recording on channel %1 "
00521 "starting at %2 in the database.")
00522 .arg(chanid).arg(startts.toString()));
00523 }
00524 }
00525 else
00526 {
00527 VERBOSE(VB_IMPORTANT,
00528 QString("Couldn't deduce channel and start time from "
00529 "%1 ").arg(base));
00530 }
00531 }
00532 }
00533
00534 if (infile.left(7) == "myth://") {
00535 VERBOSE(VB_IMPORTANT, QString("Attempted to transcode %1. "
00536 "Mythtranscode is currently unable to transcode remote "
00537 "files.")
00538 .arg(infile));
00539 return TRANSCODE_EXIT_REMOTE_FILE;
00540 }
00541
00542 if (outfile.isNull())
00543 outfile = infile + ".tmp";
00544
00545 if (jobID >= 0)
00546 JobQueue::ChangeJobStatus(jobID, JOB_RUNNING);
00547
00548 Transcode *transcode = new Transcode(pginfo);
00549
00550 VERBOSE(VB_GENERAL, QString("Transcoding from %1 to %2")
00551 .arg(infile).arg(outfile));
00552
00553 if (showprogress)
00554 transcode->ShowProgress(true);
00555 int result = 0;
00556 if (!mpeg2)
00557 {
00558 result = transcode->TranscodeFile((char *)infile.ascii(),
00559 (char *)outfile.ascii(),
00560 profilename, useCutlist,
00561 (fifosync || keyframesonly), jobID,
00562 fifodir, deleteMap);
00563 if ((result == REENCODE_OK) && (jobID >= 0))
00564 JobQueue::ChangeJobArgs(jobID, "RENAME_TO_NUV");
00565 }
00566
00567 int exitcode = TRANSCODE_EXIT_OK;
00568 if ((result == REENCODE_MPEG2TRANS) || mpeg2)
00569 {
00570 void (*update_func)(float) = NULL;
00571 int (*check_func)() = NULL;
00572 if (useCutlist && !found_infile)
00573 pginfo->GetCutList(deleteMap);
00574 if (jobID >= 0)
00575 {
00576 glbl_jobID = jobID;
00577 update_func = &UpdateJobQueue;
00578 check_func = &CheckJobQueue;
00579 }
00580 MPEG2fixup *m2f = new MPEG2fixup(infile.ascii(), outfile.ascii(),
00581 &deleteMap, NULL, false, false, 20,
00582 showprogress, otype, update_func,
00583 check_func);
00584 if (build_index)
00585 {
00586 int err = BuildKeyframeIndex(m2f, infile, posMap, jobID);
00587 if (err)
00588 return err;
00589 if (update_index)
00590 UpdatePositionMap(posMap, NULL, pginfo);
00591 else
00592 UpdatePositionMap(posMap, outfile + QString(".map"), pginfo);
00593 }
00594 else
00595 {
00596 result = m2f->Start();
00597 if (result == REENCODE_OK)
00598 {
00599 result = BuildKeyframeIndex(m2f, outfile, posMap, jobID);
00600 if (result == REENCODE_OK)
00601 {
00602 if (update_index)
00603 UpdatePositionMap(posMap, NULL, pginfo);
00604 else
00605 UpdatePositionMap(posMap, outfile + QString(".map"),
00606 pginfo);
00607 }
00608 }
00609 }
00610 delete m2f;
00611 }
00612
00613 if (result == REENCODE_OK)
00614 {
00615 if (jobID >= 0)
00616 JobQueue::ChangeJobStatus(jobID, JOB_STOPPING);
00617 VERBOSE(VB_GENERAL, QString("Transcoding %1 done").arg(infile));
00618 }
00619 else if (result == REENCODE_CUTLIST_CHANGE)
00620 {
00621 if (jobID >= 0)
00622 JobQueue::ChangeJobStatus(jobID, JOB_RETRY);
00623 VERBOSE(VB_GENERAL, QString("Transcoding %1 aborted because of "
00624 "cutlist update").arg(infile));
00625 exitcode = TRANSCODE_EXIT_ERROR_CUTLIST_UPDATE;
00626 }
00627 else if (result == REENCODE_STOPPED)
00628 {
00629 if (jobID >= 0)
00630 JobQueue::ChangeJobStatus(jobID, JOB_ABORTING);
00631 VERBOSE(VB_GENERAL, QString("Transcoding %1 stopped because of "
00632 "stop command").arg(infile));
00633 exitcode = TRANSCODE_EXIT_STOPPED;
00634 }
00635 else
00636 {
00637 if (jobID >= 0)
00638 JobQueue::ChangeJobStatus(jobID, JOB_ERRORING);
00639 VERBOSE(VB_GENERAL, QString("Transcoding %1 failed").arg(infile));
00640 exitcode = result;
00641 }
00642
00643 delete transcode;
00644
00645 if (jobID >= 0)
00646 CompleteJob(jobID, pginfo, useCutlist, exitcode);
00647
00648 delete pginfo;
00649 delete gContext;
00650 return exitcode;
00651 }
00652
00653 void UpdatePositionMap(QMap <long long, long long> &posMap, QString mapfile,
00654 ProgramInfo *pginfo)
00655 {
00656 if (pginfo && ! mapfile)
00657 {
00658 pginfo->ClearPositionMap(MARK_KEYFRAME);
00659 pginfo->ClearPositionMap(MARK_GOP_START);
00660 pginfo->SetPositionMap(posMap, MARK_GOP_BYFRAME);
00661 }
00662 else if (mapfile)
00663 {
00664 FILE *mapfh = fopen(mapfile, "w");
00665 QMap<long long, long long>::Iterator i;
00666 fprintf (mapfh, "Type: %d\n", MARK_GOP_BYFRAME);
00667 for (i = posMap.begin(); i != posMap.end(); ++i)
00668 fprintf(mapfh, "%lld %lld\n", i.key(), i.data());
00669 fclose(mapfh);
00670 }
00671 }
00672
00673 int BuildKeyframeIndex(MPEG2fixup *m2f, QString &infile,
00674 QMap <long long, long long> &posMap, int jobID)
00675 {
00676 if (jobID < 0 || JobQueue::GetJobCmd(jobID) != JOB_STOP)
00677 {
00678 if (jobID >= 0)
00679 JobQueue::ChangeJobComment(jobID,
00680 QString(QObject::tr("Generating Keyframe Index")));
00681 int err = m2f->BuildKeyframeIndex(infile, posMap);
00682 if (err)
00683 return err;
00684 if (jobID >= 0)
00685 JobQueue::ChangeJobComment(jobID,
00686 QString(QObject::tr("Transcode Completed")));
00687 }
00688 return 0;
00689 }
00690
00691 int slowDelete(QString filename)
00692 {
00693 bool inBackground = true;
00694 int increment =
00695 gContext->GetNumSetting("TruncateIncrement", 10 * 1024 * 1024);
00696 QString msg = QString("Error Truncating '%1'").arg(filename.local8Bit());
00697
00698
00699 struct stat st;
00700 if (stat(filename.ascii(), &st) != 0)
00701 {
00702 VERBOSE(VB_IMPORTANT, msg + " could not stat " + ENO +
00703 ", unlinking immediately.");
00704 return unlink(filename.local8Bit());
00705 }
00706 off_t fsize = st.st_size;
00707
00708 int fd = open(filename.local8Bit(), O_WRONLY);
00709 if (fd == -1)
00710 {
00711 VERBOSE(VB_IMPORTANT, msg + " could not open " + ENO +
00712 ", unlinking immediately.");
00713 return unlink(filename.local8Bit());
00714 }
00715
00716 if (unlink(filename.local8Bit()))
00717 {
00718 close(fd);
00719 VERBOSE(VB_IMPORTANT, msg + " could not unlink " + ENO);
00720 return unlink(filename.local8Bit());
00721 }
00722
00723 VERBOSE(VB_FILE, QString("Truncating %1.").arg(filename));
00724
00725 #ifndef USING_MINGW
00726 pid_t child = fork();
00727
00728 if (child > 0)
00729 {
00730 VERBOSE(VB_FILE,
00731 QString("Truncating %1 in the background.").arg(filename));
00732 close(fd);
00733 return 0;
00734 }
00735 else if (child < 0)
00736 {
00737 inBackground = false;
00738 VERBOSE(VB_IMPORTANT,
00739 QString("Fork() failed, truncating %1 in the foreground.")
00740 .arg(filename));
00741 }
00742 #else
00743 inBackground = false;
00744 #endif
00745
00746 while (fsize > 0) {
00747 int err = ftruncate(fd, fsize);
00748
00749 if (err) {
00750 if (inBackground)
00751 exit(1);
00752 else
00753 VERBOSE(VB_IMPORTANT, QString("ERROR truncating %1, file "
00754 "immediately removed.").arg(filename));
00755 return 0;
00756 }
00757
00758 fsize -= increment;
00759
00760 usleep(500000);
00761 }
00762
00763 if (inBackground)
00764 exit(0);
00765 else
00766 VERBOSE(VB_IMPORTANT,
00767 QString("Finished truncating %1.").arg(filename));
00768
00769 return 0;
00770 }
00771
00772 int transUnlink(QString filename)
00773 {
00774 if (gContext->GetNumSetting("TruncateDeletesSlowly", 0))
00775 return slowDelete(filename);
00776
00777 return unlink(filename);
00778 }
00779
00780 void CompleteJob(int jobID, ProgramInfo *pginfo, bool useCutlist, int &resultCode)
00781 {
00782 int status = JobQueue::GetJobStatus(jobID);
00783
00784 if (!pginfo)
00785 {
00786 JobQueue::ChangeJobStatus(jobID, JOB_ERRORED,
00787 "Job errored, unable to find Program Info for job");
00788 return;
00789 }
00790
00791 QString filename = pginfo->GetPlaybackURL(false, true);
00792
00793 if (status == JOB_STOPPING)
00794 {
00795 QString tmpfile = filename + ".tmp";
00796
00797
00798 QString oldfile = filename + ".old";
00799 QString newfile = filename;
00800 QString jobArgs = JobQueue::GetJobArgs(jobID);
00801
00802 if ((jobArgs == "RENAME_TO_NUV") &&
00803 (filename.contains(QRegExp("mpg$"))))
00804 {
00805 QString newbase = pginfo->GetRecordBasename();
00806
00807 newfile.replace(QRegExp("mpg$"), "nuv");
00808 newbase.replace(QRegExp("mpg$"), "nuv");
00809 pginfo->SetRecordBasename(newbase);
00810 }
00811
00812 if (rename(filename.local8Bit(), oldfile.local8Bit()) == -1)
00813 perror(QString("mythtranscode: Error Renaming '%1' to '%2'")
00814 .arg(filename).arg(oldfile).ascii());
00815
00816 if (rename(tmpfile.local8Bit(), newfile.local8Bit()) == -1)
00817 perror(QString("mythtranscode: Error Renaming '%1' to '%2'")
00818 .arg(tmpfile).arg(newfile).ascii());
00819
00820 if (!gContext->GetNumSetting("SaveTranscoding", 0))
00821 {
00822 int err;
00823 bool followLinks = gContext->GetNumSetting("DeletesFollowLinks", 0);
00824
00825 VERBOSE(VB_FILE, QString("mythtranscode: About to unlink/delete "
00826 "file: %1").arg(oldfile));
00827 QFileInfo finfo(oldfile);
00828 if (followLinks && finfo.isSymLink())
00829 {
00830 if (err = transUnlink(finfo.readLink().local8Bit()))
00831 {
00832 VERBOSE(VB_IMPORTANT, QString(
00833 "mythtranscode: Error deleting '%1' pointed to by "
00834 "%2, %3")
00835 .arg(finfo.readLink().local8Bit())
00836 .arg(oldfile).arg(strerror(errno)));
00837 }
00838
00839 if (err = unlink(oldfile.local8Bit()))
00840 VERBOSE(VB_IMPORTANT,
00841 QString("mythtranscode: Error deleting '%1' link "
00842 "pointing to '%2', %3").arg(oldfile)
00843 .arg(finfo.readLink().local8Bit())
00844 .arg(strerror(errno)));
00845 }
00846 else
00847 {
00848 if ((err = transUnlink(oldfile.local8Bit())))
00849 VERBOSE(VB_IMPORTANT, QString(
00850 "mythtranscode: Error deleting '%1', %2")
00851 .arg(oldfile).arg(strerror(errno)));
00852 }
00853 }
00854
00855
00856
00857
00858
00859 if (useCutlist)
00860 {
00861 QFileInfo fInfo(filename);
00862 QString nameFilter = fInfo.fileName() + "*.png";
00863
00864
00865
00866 nameFilter.replace(QRegExp("( |;)"), "?");
00867 QDir dir (fInfo.dirPath(), nameFilter);
00868
00869 for (uint nIdx = 0; nIdx < dir.count(); nIdx++)
00870 {
00871 oldfile = QString("%1/%2").arg(fInfo.dirPath() )
00872 .arg(dir[nIdx]);
00873
00874
00875
00876 transUnlink(oldfile.local8Bit());
00877 }
00878 }
00879
00880
00881 if (jobArgs == "RENAME_TO_NUV")
00882 {
00883 QFileInfo fInfo(filename);
00884 QString nameFilter = fInfo.fileName() + "*.png";
00885
00886
00887
00888 nameFilter.replace(QRegExp("( |;)"), "?");
00889
00890 QDir dir (fInfo.dirPath(), nameFilter);
00891
00892 for (uint nIdx = 0; nIdx < dir.count(); nIdx++)
00893 {
00894 oldfile = QString("%1/%2").arg(fInfo.dirPath() )
00895 .arg(dir[nIdx]);
00896 newfile = oldfile;
00897 QRegExp re("mpg(\\..*)?\\.png$");
00898 if (re.search(newfile))
00899 newfile.replace(re, QString("nuv%1.png").arg(re.cap(1)));
00900
00901 QFile checkFile(oldfile);
00902 if ((oldfile != newfile) && (checkFile.exists()))
00903 rename(oldfile.local8Bit(), newfile.local8Bit());
00904 }
00905 }
00906
00907 MSqlQuery query(MSqlQuery::InitCon());
00908
00909 if (useCutlist)
00910 {
00911 query.prepare("DELETE FROM recordedmarkup "
00912 "WHERE chanid = :CHANID "
00913 "AND starttime = :STARTTIME ");
00914 query.bindValue(":CHANID", pginfo->chanid);
00915 query.bindValue(":STARTTIME", pginfo->recstartts);
00916 query.exec();
00917
00918 if (!query.isActive())
00919 MythContext::DBError("Error in mythtranscode", query);
00920
00921 query.prepare("UPDATE recorded "
00922 "SET cutlist = :CUTLIST, bookmark = :BOOKMARK, "
00923 "watched = :WATCHED WHERE chanid = :CHANID "
00924 "AND starttime = :STARTTIME ;");
00925 query.bindValue(":CUTLIST", "0");
00926 query.bindValue(":BOOKMARK", "0");
00927 query.bindValue(":WATCHED", "0");
00928 query.bindValue(":CHANID", pginfo->chanid);
00929 query.bindValue(":STARTTIME", pginfo->recstartts);
00930 query.exec();
00931
00932 if (!query.isActive())
00933 MythContext::DBError("Error in mythtranscode", query);
00934
00935 pginfo->SetCommFlagged(COMM_FLAG_NOT_FLAGGED);
00936 }
00937 else
00938 {
00939 query.prepare("DELETE FROM recordedmarkup "
00940 "WHERE chanid = :CHANID "
00941 "AND starttime = :STARTTIME "
00942 "AND type not in ( :COMM_START, "
00943 " :COMM_END, :BOOKMARK, "
00944 " :CUTLIST_START, :CUTLIST_END) ;");
00945 query.bindValue(":CHANID", pginfo->chanid);
00946 query.bindValue(":STARTTIME", pginfo->recstartts);
00947 query.bindValue(":COMM_START", MARK_COMM_START);
00948 query.bindValue(":COMM_END", MARK_COMM_END);
00949 query.bindValue(":BOOKMARK", MARK_BOOKMARK);
00950 query.bindValue(":CUTLIST_START", MARK_CUT_START);
00951 query.bindValue(":CUTLIST_END", MARK_CUT_END);
00952 query.exec();
00953
00954 if (!query.isActive())
00955 MythContext::DBError("Error in mythtranscode", query);
00956 }
00957
00958 JobQueue::ChangeJobStatus(jobID, JOB_FINISHED);
00959
00960 } else {
00961
00962 filename += ".tmp";
00963 VERBOSE(VB_IMPORTANT, QString("Deleting %1").arg(filename));
00964 transUnlink(filename.local8Bit());
00965
00966 filename += ".map";
00967 unlink(filename.local8Bit());
00968
00969 if (status == JOB_ABORTING)
00970 JobQueue::ChangeJobStatus(jobID, JOB_ABORTED, "Job Aborted");
00971 else if (status != JOB_ERRORING)
00972 resultCode = TRANSCODE_EXIT_RESTART;
00973 else
00974 JobQueue::ChangeJobStatus(jobID, JOB_ERRORED,
00975 "Unrecoverable error");
00976 }
00977 }
00978
00979 void UpdateJobQueue(float percent_done)
00980 {
00981 JobQueue::ChangeJobComment(glbl_jobID,
00982 QString("%1% " + QObject::tr("Completed"))
00983 .arg(percent_done, 0, 'f', 1));
00984 }
00985
00986 int CheckJobQueue()
00987 {
00988 if (JobQueue::GetJobCmd(glbl_jobID) == JOB_STOP)
00989 {
00990 VERBOSE(VB_IMPORTANT, "Transcoding stopped by JobQueue");
00991 return 1;
00992 }
00993 return 0;
00994 }
00995