MythTV  0.26-pre
main.cpp
Go to the documentation of this file.
00001 // POSIX headers
00002 #include <fcntl.h> // for open flags
00003 
00004 // C++ headers
00005 #include <iostream>
00006 #include <fstream>
00007 #include <cerrno>
00008 using namespace std;
00009 
00010 // Qt headers
00011 #include <QCoreApplication>
00012 #include <QDir>
00013 
00014 // MythTV headers
00015 #include "exitcodes.h"
00016 #include "programinfo.h"
00017 #include "jobqueue.h"
00018 #include "mythcontext.h"
00019 #include "mythdb.h"
00020 #include "mythversion.h"
00021 #include "mythmiscutil.h"
00022 #include "transcode.h"
00023 #include "mpeg2fix.h"
00024 #include "remotefile.h"
00025 #include "mythtranslation.h"
00026 #include "mythlogging.h"
00027 #include "commandlineparser.h"
00028 #include "recordinginfo.h"
00029 
00030 static void CompleteJob(int jobID, ProgramInfo *pginfo, bool useCutlist,
00031                         frm_dir_map_t *deleteMap, int &resultCode);
00032 
00033 static int glbl_jobID = -1;
00034 static QString recorderOptions = "";
00035 
00036 static void UpdatePositionMap(frm_pos_map_t &posMap, QString mapfile,
00037                        ProgramInfo *pginfo)
00038 {
00039     if (pginfo && mapfile.isEmpty())
00040     {
00041         pginfo->ClearPositionMap(MARK_KEYFRAME);
00042         pginfo->ClearPositionMap(MARK_GOP_START);
00043         pginfo->SavePositionMap(posMap, MARK_GOP_BYFRAME);
00044     }
00045     else if (!mapfile.isEmpty())
00046     {
00047         FILE *mapfh = fopen(mapfile.toLocal8Bit().constData(), "w");
00048         if (!mapfh)
00049         {
00050             LOG(VB_GENERAL, LOG_ERR, QString("Could not open map file '%1'")
00051                     .arg(mapfile) + ENO);
00052             return;
00053         }
00054         frm_pos_map_t::const_iterator it;
00055         fprintf (mapfh, "Type: %d\n", MARK_GOP_BYFRAME);
00056         for (it = posMap.begin(); it != posMap.end(); ++it)
00057             fprintf(mapfh, "%lld %lld\n",
00058                     (unsigned long long)it.key(), (unsigned long long)*it);
00059         fclose(mapfh);
00060     }
00061 }
00062 
00063 static int BuildKeyframeIndex(MPEG2fixup *m2f, QString &infile,
00064                        frm_pos_map_t &posMap, int jobID)
00065 {
00066     if (jobID < 0 || JobQueue::GetJobCmd(jobID) != JOB_STOP)
00067     {
00068         if (jobID >= 0)
00069             JobQueue::ChangeJobComment(jobID,
00070                 QString(QObject::tr("Generating Keyframe Index")));
00071         int err = m2f->BuildKeyframeIndex(infile, posMap);
00072         if (err)
00073             return err;
00074         if (jobID >= 0)
00075             JobQueue::ChangeJobComment(jobID,
00076                 QString(QObject::tr("Transcode Completed")));
00077     }
00078     return 0;
00079 }
00080 
00081 static void UpdateJobQueue(float percent_done)
00082 {
00083     JobQueue::ChangeJobComment(glbl_jobID,
00084                                QString("%1% " + QObject::tr("Completed"))
00085                                .arg(percent_done, 0, 'f', 1));
00086 }
00087 
00088 static int CheckJobQueue()
00089 {
00090     if (JobQueue::GetJobCmd(glbl_jobID) == JOB_STOP)
00091     {
00092         LOG(VB_GENERAL, LOG_NOTICE, "Transcoding stopped by JobQueue");
00093         return 1;
00094     }
00095     return 0;
00096 }
00097 
00098 static int QueueTranscodeJob(ProgramInfo *pginfo, QString profile,
00099                             QString hostname, bool usecutlist)
00100 {
00101     RecordingInfo recinfo(*pginfo);
00102     if (!profile.isEmpty())
00103         recinfo.ApplyTranscoderProfileChange(profile);
00104 
00105     if (JobQueue::QueueJob(JOB_TRANSCODE, pginfo->GetChanID(),
00106                            pginfo->GetRecordingStartTime(),
00107                            hostname, "", "",
00108                            usecutlist ? JOB_USE_CUTLIST : 0))
00109     {
00110         LOG(VB_GENERAL, LOG_NOTICE,
00111             QString("Queued transcode job for chanid %1 @ %2")
00112               .arg(pginfo->GetChanID())
00113               .arg(pginfo->GetRecordingStartTime().toString("yyyyMMddhhmmss")));
00114         return GENERIC_EXIT_OK;
00115     }
00116 
00117     LOG(VB_GENERAL, LOG_ERR, QString("Error queuing job for chanid %1 @ %2")
00118             .arg(pginfo->GetChanID())
00119             .arg(pginfo->GetRecordingStartTime().toString("yyyyMMddhhmmss")));
00120     return GENERIC_EXIT_DB_ERROR;
00121 }
00122 
00123 namespace
00124 {
00125     void cleanup()
00126     {
00127         delete gContext;
00128         gContext = NULL;
00129 
00130     }
00131 
00132     class CleanupGuard
00133     {
00134       public:
00135         typedef void (*CleanupFunc)();
00136 
00137       public:
00138         CleanupGuard(CleanupFunc cleanFunction) :
00139             m_cleanFunction(cleanFunction) {}
00140 
00141         ~CleanupGuard()
00142         {
00143             m_cleanFunction();
00144         }
00145 
00146       private:
00147         CleanupFunc m_cleanFunction;
00148     };
00149 }
00150 
00151 int main(int argc, char *argv[])
00152 {
00153     uint chanid;
00154     QDateTime starttime;
00155     QString infile, outfile;
00156     QString profilename = QString("autodetect");
00157     QString fifodir = NULL;
00158     int jobID = -1;
00159     int jobType = JOB_NONE;
00160     int otype = REPLEX_MPEG2;
00161     bool useCutlist = false, keyframesonly = false;
00162     bool build_index = false, fifosync = false;
00163     bool mpeg2 = false;
00164     bool fifo_info = false;
00165     bool cleanCut = false;
00166     QMap<QString, QString> settingsOverride;
00167     frm_dir_map_t deleteMap;
00168     frm_pos_map_t posMap;
00169     int AudioTrackNo = -1;
00170 
00171     int found_starttime = 0;
00172     int found_chanid = 0;
00173     int found_infile = 0;
00174     int update_index = 1;
00175     int isVideo = 0;
00176     bool passthru = false;
00177 
00178     MythTranscodeCommandLineParser cmdline;
00179     if (!cmdline.Parse(argc, argv))
00180     {
00181         cmdline.PrintHelp();
00182         return GENERIC_EXIT_INVALID_CMDLINE;
00183     }
00184 
00185     if (cmdline.toBool("showhelp"))
00186     {
00187         cmdline.PrintHelp();
00188         return GENERIC_EXIT_OK;
00189     }
00190 
00191     if (cmdline.toBool("showversion"))
00192     {
00193         cmdline.PrintVersion();
00194         return GENERIC_EXIT_OK;
00195     }
00196 
00197     QCoreApplication a(argc, argv);
00198     QCoreApplication::setApplicationName(MYTH_APPNAME_MYTHTRANSCODE);
00199 
00200     if (cmdline.toBool("outputfile"))
00201     {
00202         outfile = cmdline.toString("outputfile");
00203         update_index = 0;
00204     }
00205 
00206     bool showprogress = cmdline.toBool("showprogress");
00207 
00208     int retval;
00209     QString mask("general");
00210     bool quiet = (outfile == "-") || showprogress;
00211     if ((retval = cmdline.ConfigureLogging(mask, quiet)) != GENERIC_EXIT_OK)
00212         return retval;
00213 
00214     if (cmdline.toBool("starttime"))
00215     {
00216         starttime = cmdline.toDateTime("starttime");
00217         found_starttime = 1;
00218     }
00219     if (cmdline.toBool("chanid"))
00220     {
00221         chanid = cmdline.toUInt("chanid");
00222         found_chanid = 1;
00223     }
00224     if (cmdline.toBool("jobid"))
00225         jobID = cmdline.toInt("jobid");
00226     if (cmdline.toBool("inputfile"))
00227     {
00228         infile = cmdline.toString("inputfile");
00229         found_infile = 1;
00230     }
00231     if (cmdline.toBool("video"))
00232         isVideo = true;
00233     if (cmdline.toBool("profile"))
00234         profilename = cmdline.toString("profile");
00235 
00236     if (cmdline.toBool("usecutlist"))    
00237     {
00238         useCutlist = true;
00239         if (!cmdline.toString("usecutlist").isEmpty())
00240         {
00241             if (!cmdline.toBool("inputfile") && !cmdline.toBool("hls"))
00242             {
00243                 LOG(VB_GENERAL, LOG_CRIT, "External cutlists are only allowed "
00244                                           "when using the --infile option.");
00245                 return GENERIC_EXIT_INVALID_CMDLINE;
00246             }
00247 
00248             uint64_t last = 0, start, end;
00249             QStringList cutlist = cmdline.toStringList("usecutlist", " ");
00250             QStringList::iterator it;
00251             for (it = cutlist.begin(); it != cutlist.end(); ++it)
00252             {
00253                 QStringList startend =
00254                     (*it).split("-", QString::SkipEmptyParts);
00255                 if (startend.size() == 2)
00256                 {
00257                     start = startend.first().toULongLong();
00258                     end = startend.last().toULongLong();
00259 
00260                     if (cmdline.toBool("inversecut"))
00261                     {
00262                         LOG(VB_GENERAL, LOG_DEBUG,
00263                                 QString("Cutting section %1-%2.")
00264                                     .arg(last).arg(start));
00265                         deleteMap[start] = MARK_CUT_END;
00266                         deleteMap[end] = MARK_CUT_START;
00267                         last = end;
00268                     }
00269                     else
00270                     {
00271                         LOG(VB_GENERAL, LOG_DEBUG,
00272                                 QString("Cutting section %1-%2.")
00273                                     .arg(start).arg(end));
00274                         deleteMap[start] = MARK_CUT_START;
00275                         deleteMap[end] = MARK_CUT_END;
00276                     }
00277                 }
00278             }
00279 
00280             if (cmdline.toBool("inversecut"))
00281             {
00282                 if (deleteMap.contains(0) && (deleteMap[0] == MARK_CUT_END))
00283                     deleteMap.remove(0);
00284                 else
00285                     deleteMap[0] = MARK_CUT_START;
00286                 deleteMap[999999999] = MARK_CUT_END;
00287                 LOG(VB_GENERAL, LOG_DEBUG,
00288                     QString("Cutting section %1-999999999.")
00289                                     .arg(last));
00290             }
00291 
00292             // sanitize cutlist
00293             if (deleteMap.count() >= 2)
00294             {
00295                 frm_dir_map_t::iterator cur = deleteMap.begin(), prev;
00296                 prev = cur++;
00297                 while (cur != deleteMap.end())
00298                 {
00299                     if (prev.value() == cur.value())
00300                     {
00301                         // two of the same type next to each other
00302                         QString err("Cut %1points found at %3 and %4, with no "
00303                                     "%2 point in between.");
00304                         if (prev.value() == MARK_CUT_END)
00305                             err = err.arg("end").arg("start");
00306                         else
00307                             err = err.arg("start").arg("end");
00308                         LOG(VB_GENERAL, LOG_CRIT, "Invalid cutlist defined!");
00309                         LOG(VB_GENERAL, LOG_CRIT, err.arg(prev.key())
00310                                                      .arg(cur.key()));
00311                         return GENERIC_EXIT_INVALID_CMDLINE;
00312                     }
00313                     else if ( (prev.value() == MARK_CUT_START) &&
00314                               ((cur.key() - prev.key()) < 2) )
00315                     {
00316                         LOG(VB_GENERAL, LOG_WARNING, QString("Discarding "
00317                                           "insufficiently long cut: %1-%2")
00318                                             .arg(prev.key()).arg(cur.key()));
00319                         prev = deleteMap.erase(prev);
00320                         cur  = deleteMap.erase(cur);
00321 
00322                         if (cur == deleteMap.end())
00323                             continue;
00324                     }
00325                     prev = cur++;
00326                 }
00327             }
00328         }
00329         else if (cmdline.toBool("inversecut"))
00330         {
00331             cerr << "Cutlist inversion requires an external cutlist be" << endl
00332                  << "provided using the --honorcutlist option." << endl;
00333             return GENERIC_EXIT_INVALID_CMDLINE;
00334         }
00335     }
00336 
00337     if (cmdline.toBool("cleancut"))
00338         cleanCut = true;
00339 
00340     if (cmdline.toBool("allkeys"))
00341         keyframesonly = true;
00342     if (cmdline.toBool("reindex"))
00343         build_index = true;
00344     if (cmdline.toBool("fifodir"))
00345         fifodir = cmdline.toString("fifodir");
00346     if (cmdline.toBool("fifoinfo"))
00347         fifo_info = true;
00348     if (cmdline.toBool("fifosync"))
00349         fifosync = true;
00350     if (cmdline.toBool("recopt"))
00351         recorderOptions = cmdline.toString("recopt");
00352     if (cmdline.toBool("mpeg2"))
00353         mpeg2 = true;
00354     if (cmdline.toBool("ostream"))
00355     {
00356         if (cmdline.toString("ostream") == "dvd")
00357             otype = REPLEX_DVD;
00358         else if (cmdline.toString("ostream") == "ts")
00359             otype = REPLEX_TS_SD;
00360         else
00361         {
00362             cerr << "Invalid 'ostream' type: "
00363                  << cmdline.toString("ostream").toLocal8Bit().constData()
00364                  << endl;
00365             return GENERIC_EXIT_INVALID_CMDLINE;
00366         }
00367     }
00368     if (cmdline.toBool("audiotrack"))
00369         AudioTrackNo = cmdline.toInt("audiotrack");
00370     if (cmdline.toBool("passthru"))
00371         passthru = true;
00372 
00373     CleanupGuard callCleanup(cleanup);
00374     //  Load the context
00375     gContext = new MythContext(MYTH_BINARY_VERSION);
00376     if (!gContext->Init(false))
00377     {
00378         LOG(VB_GENERAL, LOG_ERR, "Failed to init MythContext, exiting.");
00379         return GENERIC_EXIT_NO_MYTHCONTEXT;
00380     }
00381 
00382     MythTranslation::load("mythfrontend");
00383 
00384     cmdline.ApplySettingsOverride();
00385 
00386     if (jobID != -1)
00387     {
00388         if (JobQueue::GetJobInfoFromID(jobID, jobType, chanid, starttime))
00389         {
00390             found_starttime = 1;
00391             found_chanid = 1;
00392         }
00393         else
00394         {
00395             cerr << "mythtranscode: ERROR: Unable to find DB info for "
00396                  << "JobQueue ID# " << jobID << endl;
00397             return GENERIC_EXIT_NO_RECORDING_DATA;
00398         }
00399     }
00400 
00401     if (((!found_infile && !(found_chanid && found_starttime)) ||
00402          (found_infile && (found_chanid || found_starttime))) &&
00403         (!cmdline.toBool("hls")))
00404     {
00405          cerr << "Must specify -i OR -c AND -s options!" << endl;
00406          return GENERIC_EXIT_INVALID_CMDLINE;
00407     }
00408     if (isVideo && !found_infile && !cmdline.toBool("hls"))
00409     {
00410          cerr << "Must specify --infile to use --video" << endl;
00411          return GENERIC_EXIT_INVALID_CMDLINE;
00412     }
00413     if (jobID >= 0 && (found_infile || build_index))
00414     {
00415          cerr << "Can't specify -j with --buildindex, --video or --infile"
00416               << endl;
00417          return GENERIC_EXIT_INVALID_CMDLINE;
00418     }
00419     if ((jobID >= 0) && build_index)
00420     {
00421          cerr << "Can't specify both -j and --buildindex" << endl;
00422          return GENERIC_EXIT_INVALID_CMDLINE;
00423     }
00424     if (keyframesonly && !fifodir.isEmpty())
00425     {
00426          cerr << "Cannot specify both --fifodir and --allkeys" << endl;
00427          return GENERIC_EXIT_INVALID_CMDLINE;
00428     }
00429     if (fifosync && fifodir.isEmpty())
00430     {
00431          cerr << "Must specify --fifodir to use --fifosync" << endl;
00432          return GENERIC_EXIT_INVALID_CMDLINE;
00433     }
00434     if (fifo_info && !fifodir.isEmpty())
00435     {
00436         cerr << "Cannot specify both --fifodir and --fifoinfo" << endl;
00437         return GENERIC_EXIT_INVALID_CMDLINE;
00438     }
00439     if (cleanCut && fifodir.isEmpty() && !fifo_info)
00440     {
00441         cerr << "Clean cutting works only in fifodir mode" << endl;
00442         return GENERIC_EXIT_INVALID_CMDLINE;
00443     }
00444     if (cleanCut && !useCutlist)
00445     {
00446         cerr << "--cleancut is pointless without --honorcutlist" << endl;
00447         return GENERIC_EXIT_INVALID_CMDLINE;
00448     }
00449 
00450     if (fifo_info)
00451     {
00452         // Setup a dummy fifodir path, so that the "fifodir" code path
00453         // is taken. The path wont actually be used.
00454         fifodir = "DummyFifoPath";
00455     }
00456 
00457     if (!MSqlQuery::testDBConnection())
00458     {
00459         LOG(VB_GENERAL, LOG_ERR, "couldn't open db");
00460         return GENERIC_EXIT_DB_ERROR;
00461     }
00462 
00463     ProgramInfo *pginfo = NULL;
00464     if (cmdline.toBool("hls"))
00465     {
00466         pginfo = new ProgramInfo();
00467     }
00468     else if (isVideo)
00469     {
00470         // We want the absolute file path for the filemarkup table
00471         QFileInfo inf(infile);
00472         infile = inf.absoluteFilePath();
00473         pginfo = new ProgramInfo(infile);
00474     }
00475     else if (!found_infile)
00476     {
00477         pginfo = new ProgramInfo(chanid, starttime);
00478 
00479         if (!pginfo->GetChanID())
00480         {
00481             LOG(VB_GENERAL, LOG_ERR,
00482                 QString("Couldn't find recording for chanid %1 @ %2")
00483                     .arg(chanid).arg(starttime.toString("yyyyMMddhhmmss")));
00484             delete pginfo;
00485             return GENERIC_EXIT_NO_RECORDING_DATA;
00486         }
00487 
00488         infile = pginfo->GetPlaybackURL(false, true);
00489     }
00490     else
00491     {
00492         pginfo = new ProgramInfo(infile);
00493         if (!pginfo->GetChanID())
00494         {
00495             LOG(VB_GENERAL, LOG_ERR,
00496                 QString("Couldn't find a recording for filename '%1'")
00497                     .arg(infile));
00498             delete pginfo;
00499             return GENERIC_EXIT_NO_RECORDING_DATA;
00500         }
00501     }
00502 
00503     if (!pginfo)
00504     {
00505         LOG(VB_GENERAL, LOG_ERR, "No program info found!");
00506         return GENERIC_EXIT_NO_RECORDING_DATA;
00507     }
00508 
00509     if (cmdline.toBool("queue"))
00510     {
00511         QString hostname = cmdline.toString("queue");
00512         return QueueTranscodeJob(pginfo, profilename, hostname, useCutlist);
00513     }
00514 
00515     if (infile.left(7) == "myth://" && (outfile.isEmpty() || outfile != "-") &&
00516         fifodir.isEmpty() && !cmdline.toBool("hls") && !cmdline.toBool("avf"))
00517     {
00518         LOG(VB_GENERAL, LOG_ERR,
00519             QString("Attempted to transcode %1. Mythtranscode is currently "
00520                     "unable to transcode remote files.") .arg(infile));
00521         return GENERIC_EXIT_REMOTE_FILE;
00522     }
00523 
00524     if (outfile.isEmpty() && !build_index && fifodir.isEmpty())
00525         outfile = infile + ".tmp";
00526 
00527     if (jobID >= 0)
00528         JobQueue::ChangeJobStatus(jobID, JOB_RUNNING);
00529 
00530     Transcode *transcode = new Transcode(pginfo);
00531 
00532     if (!build_index)
00533     {
00534         if (cmdline.toBool("hlsstreamid"))
00535             LOG(VB_GENERAL, LOG_NOTICE,
00536                 QString("Transcoding HTTP Live Stream ID %1")
00537                         .arg(cmdline.toInt("hlsstreamid")));
00538         else if (fifodir.isEmpty())
00539             LOG(VB_GENERAL, LOG_NOTICE, QString("Transcoding from %1 to %2")
00540                     .arg(infile).arg(outfile));
00541         else
00542             LOG(VB_GENERAL, LOG_NOTICE, QString("Transcoding from %1 to FIFO")
00543                     .arg(infile));
00544     }
00545 
00546     if (cmdline.toBool("avf"))
00547     {
00548         transcode->SetAVFMode();
00549 
00550         if (cmdline.toBool("container"))
00551             transcode->SetCMDContainer(cmdline.toString("container"));
00552         if (cmdline.toBool("acodec"))
00553             transcode->SetCMDAudioCodec(cmdline.toString("acodec"));
00554         if (cmdline.toBool("vcodec"))
00555             transcode->SetCMDVideoCodec(cmdline.toString("vcodec"));
00556     }
00557     else if (cmdline.toBool("hls"))
00558     {
00559         transcode->SetHLSMode();
00560 
00561         if (cmdline.toBool("hlsstreamid"))
00562             transcode->SetHLSStreamID(cmdline.toInt("hlsstreamid"));
00563         if (cmdline.toBool("maxsegments"))
00564             transcode->SetHLSMaxSegments(cmdline.toInt("maxsegments"));
00565         if (cmdline.toBool("noaudioonly"))
00566             transcode->DisableAudioOnlyHLS();
00567     }
00568 
00569     if (cmdline.toBool("avf") || cmdline.toBool("hls"))
00570     {
00571         if (cmdline.toBool("width"))
00572             transcode->SetCMDWidth(cmdline.toInt("width"));
00573         if (cmdline.toBool("height"))
00574             transcode->SetCMDHeight(cmdline.toInt("height"));
00575         if (cmdline.toBool("bitrate"))
00576             transcode->SetCMDBitrate(cmdline.toInt("bitrate") * 1000);
00577         if (cmdline.toBool("audiobitrate"))
00578             transcode->SetCMDAudioBitrate(cmdline.toInt("audiobitrate") * 1000);
00579     }
00580 
00581     if (showprogress)
00582         transcode->ShowProgress(true);
00583     if (!recorderOptions.isEmpty())
00584         transcode->SetRecorderOptions(recorderOptions);
00585     int result = 0;
00586     if ((!mpeg2 && !build_index) || cmdline.toBool("hls"))
00587     {
00588         result = transcode->TranscodeFile(infile, outfile,
00589                                           profilename, useCutlist,
00590                                           (fifosync || keyframesonly), jobID,
00591                                           fifodir, fifo_info, cleanCut, deleteMap,
00592                                           AudioTrackNo, passthru);
00593         if ((result == REENCODE_OK) && (jobID >= 0))
00594             JobQueue::ChangeJobArgs(jobID, "RENAME_TO_NUV");
00595     }
00596 
00597     if (fifo_info)
00598     {
00599         delete transcode;
00600         return GENERIC_EXIT_OK;
00601     }
00602 
00603     int exitcode = GENERIC_EXIT_OK;
00604     if ((result == REENCODE_MPEG2TRANS) || mpeg2 || build_index)
00605     {
00606         void (*update_func)(float) = NULL;
00607         int (*check_func)() = NULL;
00608         if (useCutlist && !found_infile)
00609             pginfo->QueryCutList(deleteMap);
00610         if (jobID >= 0)
00611         {
00612            glbl_jobID = jobID;
00613            update_func = &UpdateJobQueue;
00614            check_func = &CheckJobQueue;
00615         }
00616 
00617         MPEG2fixup *m2f = new MPEG2fixup(infile, outfile,
00618                                          &deleteMap, NULL, false, false, 20,
00619                                          showprogress, otype, update_func,
00620                                          check_func);
00621 
00622         if (build_index)
00623         {
00624             int err = BuildKeyframeIndex(m2f, infile, posMap, jobID);
00625             if (err)
00626                 return err;
00627             if (update_index)
00628                 UpdatePositionMap(posMap, NULL, pginfo);
00629             else
00630                 UpdatePositionMap(posMap, outfile + QString(".map"), pginfo);
00631         }
00632         else
00633         {
00634             result = m2f->Start();
00635             if (result == REENCODE_OK)
00636             {
00637                 result = BuildKeyframeIndex(m2f, outfile, posMap, jobID);
00638                 if (result == REENCODE_OK)
00639                 {
00640                     if (update_index)
00641                         UpdatePositionMap(posMap, NULL, pginfo);
00642                     else
00643                         UpdatePositionMap(posMap, outfile + QString(".map"),
00644                                           pginfo);
00645                 }
00646             }
00647         }
00648         delete m2f;
00649     }
00650 
00651     if (result == REENCODE_OK)
00652     {
00653         if (jobID >= 0)
00654             JobQueue::ChangeJobStatus(jobID, JOB_STOPPING);
00655         LOG(VB_GENERAL, LOG_NOTICE, QString("%1 %2 done")
00656                 .arg(build_index ? "Building Index for" : "Transcoding")
00657                 .arg(infile));
00658     }
00659     else if (result == REENCODE_CUTLIST_CHANGE)
00660     {
00661         if (jobID >= 0)
00662             JobQueue::ChangeJobStatus(jobID, JOB_RETRY);
00663         LOG(VB_GENERAL, LOG_NOTICE,
00664             QString("Transcoding %1 aborted because of cutlist update")
00665                 .arg(infile));
00666         exitcode = GENERIC_EXIT_RESTART;
00667     }
00668     else if (result == REENCODE_STOPPED)
00669     {
00670         if (jobID >= 0)
00671             JobQueue::ChangeJobStatus(jobID, JOB_ABORTING);
00672         LOG(VB_GENERAL, LOG_NOTICE,
00673             QString("Transcoding %1 stopped because of stop command")
00674                 .arg(infile));
00675         exitcode = GENERIC_EXIT_KILLED;
00676     }
00677     else
00678     {
00679         if (jobID >= 0)
00680             JobQueue::ChangeJobStatus(jobID, JOB_ERRORING);
00681         LOG(VB_GENERAL, LOG_ERR, QString("Transcoding %1 failed").arg(infile));
00682         exitcode = result;
00683     }
00684 
00685     if (jobID >= 0)
00686         CompleteJob(jobID, pginfo, useCutlist, &deleteMap, exitcode);
00687 
00688     transcode->deleteLater();
00689 
00690     return exitcode;
00691 }
00692 
00693 static int transUnlink(QString filename, ProgramInfo *pginfo)
00694 {
00695     if (pginfo != NULL && !pginfo->GetStorageGroup().isEmpty() &&
00696         !pginfo->GetHostname().isEmpty())
00697     {
00698         QString ip = gCoreContext->GetBackendServerIP(pginfo->GetHostname());
00699         QString port = gCoreContext->GetSettingOnHost("BackendServerPort",
00700                                                       pginfo->GetHostname());
00701         QString basename = filename.section('/', -1);
00702         QString uri = gCoreContext->GenMythURL(ip, port, basename,
00703                                                pginfo->GetStorageGroup());
00704 
00705         LOG(VB_GENERAL, LOG_NOTICE, QString("Requesting delete for file '%1'.")
00706                 .arg(uri));
00707         bool ok = RemoteFile::DeleteFile(uri);
00708         if (ok)
00709             return 0;
00710     }
00711 
00712     LOG(VB_GENERAL, LOG_NOTICE, QString("Deleting file '%1'.").arg(filename));
00713     return unlink(filename.toLocal8Bit().constData());
00714 }
00715 
00716 static uint64_t ComputeNewBookmark(uint64_t oldBookmark,
00717                                    frm_dir_map_t *deleteMap)
00718 {
00719     if (deleteMap == NULL)
00720         return oldBookmark;
00721 
00722     uint64_t subtraction = 0;
00723     uint64_t startOfCutRegion = 0;
00724     frm_dir_map_t delMap = *deleteMap;
00725     bool withinCut = false;
00726     bool firstMark = true;
00727     while (delMap.count() && delMap.begin().key() <= oldBookmark)
00728     {
00729         uint64_t key = delMap.begin().key();
00730         MarkTypes mark = delMap.begin().value();
00731 
00732         if (mark == MARK_CUT_START && !withinCut)
00733         {
00734             withinCut = true;
00735             startOfCutRegion = key;
00736         }
00737         else if (mark == MARK_CUT_END && firstMark)
00738         {
00739             subtraction += key;
00740         }
00741         else if (mark == MARK_CUT_END && withinCut)
00742         {
00743             withinCut = false;
00744             subtraction += (key - startOfCutRegion);
00745         }
00746         delMap.remove(key);
00747         firstMark = false;
00748     }
00749     if (withinCut)
00750         subtraction += (oldBookmark - startOfCutRegion);
00751     return oldBookmark - subtraction;
00752 }
00753 
00754 static uint64_t ReloadBookmark(ProgramInfo *pginfo)
00755 {
00756     MSqlQuery query(MSqlQuery::InitCon());
00757     uint64_t currentBookmark = 0;
00758     query.prepare("SELECT DISTINCT mark FROM recordedmarkup "
00759                   "WHERE chanid = :CHANID "
00760                   "AND starttime = :STARTIME "
00761                   "AND type = :MARKTYPE ;");
00762     query.bindValue(":CHANID", pginfo->GetChanID());
00763     query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
00764     query.bindValue(":MARKTYPE", MARK_BOOKMARK);
00765     if (query.exec() && query.next())
00766     {
00767         currentBookmark = query.value(0).toLongLong();
00768     }
00769     return currentBookmark;
00770 }
00771 
00772 static void WaitToDelete(ProgramInfo *pginfo)
00773 {
00774     LOG(VB_GENERAL, LOG_NOTICE,
00775         "Transcode: delete old file: waiting while program is in use.");
00776 
00777     bool inUse = true;
00778     MSqlQuery query(MSqlQuery::InitCon());
00779     while (inUse)
00780     {
00781         query.prepare("SELECT count(*) FROM inuseprograms "
00782                       "WHERE chanid = :CHANID "
00783                       "AND starttime = :STARTTIME "
00784                       "AND recusage = 'player' ;");
00785         query.bindValue(":CHANID", pginfo->GetChanID());
00786         query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
00787         if (!query.exec() || !query.next())
00788         {
00789             LOG(VB_GENERAL, LOG_ERR,
00790                 "Transcode: delete old file: in-use query failed;");
00791             inUse = false;
00792         }
00793         else
00794         {
00795             inUse = (query.value(0).toUInt() != 0);
00796         }
00797 
00798         if (inUse)
00799         {
00800             const unsigned kSecondsToWait = 10;
00801             LOG(VB_GENERAL, LOG_NOTICE,
00802                 QString("Transcode: program in use, rechecking in %1 seconds.")
00803                     .arg(kSecondsToWait));
00804             sleep(kSecondsToWait);
00805         }
00806     }
00807     LOG(VB_GENERAL, LOG_NOTICE, "Transcode: program is no longer in use.");
00808 }
00809 
00810 static void CompleteJob(int jobID, ProgramInfo *pginfo, bool useCutlist,
00811                  frm_dir_map_t *deleteMap, int &resultCode)
00812 {
00813     int status = JobQueue::GetJobStatus(jobID);
00814 
00815     if (!pginfo)
00816     {
00817         JobQueue::ChangeJobStatus(jobID, JOB_ERRORED,
00818                   "Job errored, unable to find Program Info for job");
00819         return;
00820     }
00821 
00822     WaitToDelete(pginfo);
00823 
00824     const QString filename = pginfo->GetPlaybackURL(false, true);
00825     const QByteArray fname = filename.toLocal8Bit();
00826 
00827     if (status == JOB_STOPPING)
00828     {
00829         // Transcoding may take several minutes.  Reload the bookmark
00830         // in case it changed, then save its translated value back.
00831         uint64_t previousBookmark =
00832             ComputeNewBookmark(ReloadBookmark(pginfo), deleteMap);
00833         pginfo->SaveBookmark(previousBookmark);
00834 
00835         const QString jobArgs = JobQueue::GetJobArgs(jobID);
00836 
00837         const QString tmpfile = filename + ".tmp";
00838         const QByteArray atmpfile = tmpfile.toLocal8Bit();
00839 
00840         // To save the original file...
00841         const QString oldfile = filename + ".old";
00842         const QByteArray aoldfile = oldfile.toLocal8Bit();
00843 
00844         QFileInfo st(tmpfile);
00845         qint64 newSize = 0;
00846         if (st.exists()) 
00847             newSize = st.size();
00848 
00849         QString cnf = filename;
00850         if ((jobArgs == "RENAME_TO_NUV") &&
00851             (filename.contains(QRegExp("mpg$"))))
00852         {
00853             QString newbase = pginfo->QueryBasename();
00854 
00855             cnf.replace(QRegExp("mpg$"), "nuv");
00856             newbase.replace(QRegExp("mpg$"), "nuv");
00857             pginfo->SaveBasename(newbase);
00858         }
00859 
00860         const QString newfile = cnf;
00861         const QByteArray anewfile = newfile.toLocal8Bit();
00862 
00863         if (rename(fname.constData(), aoldfile.constData()) == -1)
00864         {
00865             LOG(VB_GENERAL, LOG_ERR,
00866                 QString("mythtranscode: Error Renaming '%1' to '%2'")
00867                     .arg(filename).arg(oldfile) + ENO);
00868         }
00869 
00870         if (rename(atmpfile.constData(), anewfile.constData()) == -1)
00871         {
00872             LOG(VB_GENERAL, LOG_ERR,
00873                 QString("mythtranscode: Error Renaming '%1' to '%2'")
00874                     .arg(tmpfile).arg(newfile) + ENO);
00875         }
00876 
00877         if (!gCoreContext->GetNumSetting("SaveTranscoding", 0))
00878         {
00879             int err;
00880             bool followLinks =
00881                 gCoreContext->GetNumSetting("DeletesFollowLinks", 0);
00882 
00883             LOG(VB_FILE, LOG_INFO,
00884                 QString("mythtranscode: About to unlink/delete file: %1")
00885                     .arg(oldfile));
00886 
00887             QFileInfo finfo(oldfile);
00888             if (followLinks && finfo.isSymLink())
00889             {
00890                 QString link = getSymlinkTarget(oldfile);
00891                 QByteArray alink = link.toLocal8Bit();
00892                 err = transUnlink(alink.constData(), pginfo);
00893 
00894                 if (err)
00895                 {
00896                     LOG(VB_GENERAL, LOG_ERR,
00897                         QString("mythtranscode: Error deleting '%1' "
00898                                 "pointed to by '%2'")
00899                             .arg(alink.constData())
00900                             .arg(aoldfile.constData()) + ENO);
00901                 }
00902 
00903                 err = unlink(aoldfile.constData());
00904                 if (err)
00905                 {
00906                     LOG(VB_GENERAL, LOG_ERR,
00907                         QString("mythtranscode: Error deleting '%1', "
00908                                 "a link pointing to '%2'")
00909                             .arg(aoldfile.constData())
00910                             .arg(alink.constData()) + ENO);
00911                 }
00912             }
00913             else
00914             {
00915                 if ((err = transUnlink(aoldfile.constData(), pginfo)))
00916                     LOG(VB_GENERAL, LOG_ERR,
00917                         QString("mythtranscode: Error deleting '%1': ")
00918                             .arg(oldfile) + ENO);
00919             }
00920         }
00921 
00922         // Delete previews if cutlist was applied.  They will be re-created as
00923         // required.  This prevents the user from being stuck with a preview
00924         // from a cut area and ensures that the "dimensioned" previews
00925         // correspond to the new timeline
00926         if (useCutlist)
00927         {
00928             QFileInfo fInfo(filename);
00929             QString nameFilter = fInfo.fileName() + "*.png";
00930             // QDir's nameFilter uses spaces or semicolons to separate globs,
00931             // so replace them with the "match any character" wildcard
00932             // since mythrename.pl may have included them in filenames
00933             nameFilter.replace(QRegExp("( |;)"), "?");
00934             QDir dir (fInfo.path(), nameFilter);
00935 
00936             for (uint nIdx = 0; nIdx < dir.count(); nIdx++)
00937             {
00938                 // If unlink fails, keeping the old preview is not a problem.
00939                 // The RENAME_TO_NUV check below will attempt to rename the
00940                 // file, if required.
00941                 const QString oldfileop = QString("%1/%2")
00942                     .arg(fInfo.path()).arg(dir[nIdx]);
00943                 const QByteArray aoldfileop = oldfileop.toLocal8Bit();
00944                 transUnlink(aoldfileop.constData(), pginfo);
00945             }
00946         }
00947 
00948         /* Rename all preview thumbnails. */
00949         if (jobArgs == "RENAME_TO_NUV")
00950         {
00951             QFileInfo fInfo(filename);
00952             QString nameFilter = fInfo.fileName() + "*.png";
00953             // QDir's nameFilter uses spaces or semicolons to separate globs,
00954             // so replace them with the "match any character" wildcard
00955             // since mythrename.pl may have included them in filenames
00956             nameFilter.replace(QRegExp("( |;)"), "?");
00957 
00958             QDir dir (fInfo.path(), nameFilter);
00959 
00960             for (uint nIdx = 0; nIdx < dir.count(); nIdx++)
00961             {
00962                 const QString oldfileprev = QString("%1/%2")
00963                     .arg(fInfo.path()).arg(dir[nIdx]);
00964                 const QByteArray aoldfileprev = oldfileprev.toLocal8Bit();
00965 
00966                 QString newfileprev = oldfileprev;
00967                 QRegExp re("mpg(\\..*)?\\.png$");
00968                 if (re.indexIn(newfileprev))
00969                 {
00970                     newfileprev.replace(
00971                         re, QString("nuv%1.png").arg(re.cap(1)));
00972                 }
00973                 const QByteArray anewfileprev = newfileprev.toLocal8Bit();
00974 
00975                 QFile checkFile(oldfileprev);
00976 
00977                 if ((oldfileprev != newfileprev) && (checkFile.exists()))
00978                     rename(aoldfileprev.constData(), anewfileprev.constData());
00979             }
00980         }
00981 
00982         MSqlQuery query(MSqlQuery::InitCon());
00983 
00984         if (useCutlist)
00985         {
00986             query.prepare("DELETE FROM recordedmarkup "
00987                           "WHERE chanid = :CHANID "
00988                           "AND starttime = :STARTTIME "
00989                           "AND type != :BOOKMARK ");
00990             query.bindValue(":CHANID", pginfo->GetChanID());
00991             query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
00992             query.bindValue(":BOOKMARK", MARK_BOOKMARK);
00993 
00994             if (!query.exec())
00995                 MythDB::DBError("Error in mythtranscode", query);
00996 
00997             query.prepare("UPDATE recorded "
00998                           "SET cutlist = :CUTLIST "
00999                           "WHERE chanid = :CHANID "
01000                           "AND starttime = :STARTTIME ;");
01001             query.bindValue(":CUTLIST", "0");
01002             query.bindValue(":CHANID", pginfo->GetChanID());
01003             query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
01004 
01005             if (!query.exec())
01006                 MythDB::DBError("Error in mythtranscode", query);
01007 
01008             pginfo->SaveCommFlagged(COMM_FLAG_NOT_FLAGGED);
01009         }
01010         else
01011         {
01012             query.prepare("DELETE FROM recordedmarkup "
01013                           "WHERE chanid = :CHANID "
01014                           "AND starttime = :STARTTIME "
01015                           "AND type not in ( :COMM_START, "
01016                           "    :COMM_END, :BOOKMARK, "
01017                           "    :CUTLIST_START, :CUTLIST_END) ;");
01018             query.bindValue(":CHANID", pginfo->GetChanID());
01019             query.bindValue(":STARTTIME", pginfo->GetRecordingStartTime());
01020             query.bindValue(":COMM_START", MARK_COMM_START);
01021             query.bindValue(":COMM_END", MARK_COMM_END);
01022             query.bindValue(":BOOKMARK", MARK_BOOKMARK);
01023             query.bindValue(":CUTLIST_START", MARK_CUT_START);
01024             query.bindValue(":CUTLIST_END", MARK_CUT_END);
01025 
01026             if (!query.exec())
01027                 MythDB::DBError("Error in mythtranscode", query);
01028         }
01029 
01030         if (newSize)
01031             pginfo->SaveFilesize(newSize);
01032 
01033         JobQueue::ChangeJobStatus(jobID, JOB_FINISHED);
01034 
01035     } else {
01036         // Not a successful run, so remove the files we created
01037         QString filename_tmp = filename + ".tmp";
01038         QByteArray fname_tmp = filename_tmp.toLocal8Bit();
01039         LOG(VB_GENERAL, LOG_NOTICE, QString("Deleting %1").arg(filename_tmp));
01040         transUnlink(fname_tmp.constData(), pginfo);
01041 
01042         QString filename_map = filename + ".tmp.map";
01043         QByteArray fname_map = filename_map.toLocal8Bit();
01044         unlink(fname_map.constData());
01045 
01046         if (status == JOB_ABORTING)                     // Stop command was sent
01047             JobQueue::ChangeJobStatus(jobID, JOB_ABORTED, "Job Aborted");
01048         else if (status != JOB_ERRORING)                // Recoverable error
01049             resultCode = GENERIC_EXIT_RESTART;
01050         else                                            // Unrecoverable error
01051             JobQueue::ChangeJobStatus(jobID, JOB_ERRORED,
01052                                       "Unrecoverable error");
01053     }
01054 }
01055 /* vim: set expandtab tabstop=4 shiftwidth=4: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends