MythTV  0.26-pre
main.cpp
Go to the documentation of this file.
00001 /* -*- Mode: c++ -*-
00002  * vim: set expandtab tabstop=4 shiftwidth=4:
00003  *
00004  * Original Project
00005  *      MythTV      http://www.mythtv.org
00006  *
00007  * Copyright (c) 2004, 2005 John Pullan <john@pullan.org>
00008  * Copyright (c) 2009, Janne Grunau <janne-mythtv@grunau.be>
00009  *
00010  * This program is free software; you can redistribute it and/or
00011  * modify it under the terms of the GNU General Public License
00012  * as published by the Free Software Foundation; either version 2
00013  * of the License, or (at your option) any later version.
00014  *
00015  * This program is distributed in the hope that it will be useful,
00016  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  * GNU General Public License for more details.
00019  *
00020  * You should have received a copy of the GNU General Public License
00021  * along with this program; if not, write to the Free Software
00022  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00023  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
00024  *
00025  */
00026 
00027 #include <iostream>
00028 #include <stdint.h>
00029 #include <sys/wait.h>  // for WIFEXITED and WEXITSTATUS
00030 #include <unistd.h>
00031 #include <cstdlib>
00032 
00033 #include <mythconfig.h>
00034 #if CONFIG_DARWIN or defined(__FreeBSD__)
00035 #include <sys/param.h>
00036 #include <sys/mount.h>
00037 #elif __linux__
00038 #include <sys/vfs.h>
00039 #endif
00040 
00041 using namespace std;
00042 
00043 
00044 // Qt headers
00045 #include <QApplication>
00046 #include <QFile>
00047 #include <QDir>
00048 #include <QDomElement>
00049 #include <QImage>
00050 #include <QMutex>
00051 #include <QMutexLocker>
00052 #include <QTextStream>
00053 
00054 // MythTV headers
00055 #include <mythcommandlineparser.h>
00056 #include <mythcontext.h>
00057 #include <mythcoreutil.h>
00058 #include <mythversion.h>
00059 #include <exitcodes.h>
00060 #include <mythdb.h>
00061 #include <programinfo.h>
00062 #include <mythdirs.h>
00063 #include <mythconfig.h>
00064 #include <mythsystem.h>
00065 #include <mythmiscutil.h>
00066 #include <mythlogging.h>
00067 
00068 extern "C" {
00069 #include <avcodec.h>
00070 #include <avformat.h>
00071 #include <swscale.h>
00072 #include "pxsup2dast.h"
00073 }
00074 
00075 // mytharchive headers
00076 #include "../mytharchive/archiveutil.h"
00077 
00078 namespace
00079 {
00080     void cleanup()
00081     {
00082         delete gContext;
00083         gContext = NULL;
00084     }
00085 
00086     class CleanupGuard
00087     {
00088       public:
00089         typedef void (*CleanupFunc)();
00090 
00091       public:
00092         CleanupGuard(CleanupFunc cleanFunction) :
00093             m_cleanFunction(cleanFunction) {}
00094 
00095         ~CleanupGuard()
00096         {
00097             m_cleanFunction();
00098         }
00099 
00100       private:
00101         CleanupFunc m_cleanFunction;
00102     };
00103 }
00104 
00105 class NativeArchive
00106 {
00107   public:
00108       NativeArchive(void);
00109       ~NativeArchive(void);
00110 
00111       int doNativeArchive(const QString &jobFile);
00112       int doImportArchive(const QString &xmlFile, int chanID);
00113       bool copyFile(const QString &source, const QString &destination);
00114       int importRecording(const QDomElement &itemNode,
00115                           const QString &xmlFile, int chanID);
00116       int importVideo(const QDomElement &itemNode, const QString &xmlFile);
00117       int exportRecording(QDomElement &itemNode, const QString &saveDirectory);
00118       int exportVideo(QDomElement &itemNode, const QString &saveDirectory);
00119   private:
00120       QString findNodeText(const QDomElement &elem, const QString &nodeName);
00121 };
00122 
00123 NativeArchive::NativeArchive(void)
00124 {
00125     // create the lock file so the UI knows we're running
00126     QString tempDir = getTempDirectory();
00127     QFile file(tempDir + "/logs/mythburn.lck");
00128 
00129     if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
00130         LOG(VB_GENERAL, LOG_ERR, "NativeArchive: Failed to create lock file");
00131 
00132     QString pid = QString("%1").arg(getpid());
00133     file.write(pid.toAscii());
00134     file.close();
00135 }
00136 
00137 NativeArchive::~NativeArchive(void)
00138 {
00139     // remove lock file
00140     QString tempDir = getTempDirectory();
00141     if (QFile::exists(tempDir + "/logs/mythburn.lck"))
00142         QFile::remove(tempDir + "/logs/mythburn.lck");
00143 }
00144 
00145 bool NativeArchive::copyFile(const QString &source, const QString &destination)
00146 {
00147     QFile srcFile(source), destFile(destination);
00148 
00149     LOG(VB_JOBQUEUE, LOG_INFO, QString("copying from %1").arg(source));
00150     LOG(VB_JOBQUEUE, LOG_INFO, QString("to %2").arg(destination));
00151 
00152     if (!srcFile.open(QIODevice::ReadOnly))
00153     {
00154         LOG(VB_JOBQUEUE, LOG_ERR, "Unable to open source file");
00155         return false;
00156     }
00157 
00158     if (!destFile.open(QIODevice::WriteOnly))
00159     {
00160         LOG(VB_JOBQUEUE, LOG_ERR, "Unable to open destination file");
00161         LOG(VB_JOBQUEUE, LOG_ERR, "Do you have write access to the directory?");
00162         srcFile.close();
00163         return false;
00164     }
00165 
00166     // get free space available on destination
00167     int64_t dummy;
00168     int64_t freeSpace = getDiskSpace(destination, dummy, dummy);
00169 
00170     int srcLen, destLen, percent = 0, lastPercent = 0;
00171     int64_t wroteSize = 0, totalSize = srcFile.size();
00172     char buffer[1024*1024];
00173 
00174     if (freeSpace != -1 && freeSpace < totalSize / 1024)
00175     {
00176         LOG(VB_JOBQUEUE, LOG_ERR,
00177             "Not enough free space available on destination filesystem.");
00178         LOG(VB_JOBQUEUE, LOG_ERR, QString("Available: %1 Needed %2")
00179                              .arg(freeSpace).arg(totalSize));
00180         destFile.close();
00181         srcFile.close();
00182         return false;
00183     }
00184 
00185     while ((srcLen = srcFile.read(buffer, sizeof(buffer))) > 0)
00186     {
00187         destLen = destFile.write(buffer, srcLen);
00188 
00189         if (destLen == -1 || srcLen != destLen)
00190         {
00191             LOG(VB_JOBQUEUE, LOG_ERR,
00192                 "While trying to write to destination file.");
00193             srcFile.close();
00194             destFile.close();
00195             return false;
00196         }
00197         wroteSize += destLen;
00198         percent = (int) ((100.0 * wroteSize) / totalSize);
00199         if (percent % 5 == 0  && percent != lastPercent)
00200         {
00201             LOG(VB_JOBQUEUE, LOG_INFO, QString("%1 out of %2 (%3%) completed")
00202                     .arg(formatSize(wroteSize/1024))
00203                     .arg(formatSize(totalSize/1024)).arg(percent));
00204             lastPercent = percent;
00205         }
00206     }
00207 
00208     srcFile.close();
00209     destFile.close();
00210     if (srcFile.size() != destFile.size())
00211     {
00212         LOG(VB_JOBQUEUE, LOG_ERR, "Copy not completed OK - "
00213             "Source and destination file sizes do not match!!");
00214         LOG(VB_JOBQUEUE, LOG_ERR,
00215             QString("Source is %1 bytes, Destination is %2 bytes")
00216                 .arg(srcFile.size()).arg(destFile.size()));
00217         return false;
00218     }
00219     else
00220         LOG(VB_JOBQUEUE, LOG_INFO, "Copy completed OK");
00221 
00222     return true;
00223 }
00224 
00225 static bool createISOImage(QString &sourceDirectory)
00226 {
00227     LOG(VB_JOBQUEUE, LOG_INFO, "Creating ISO image");
00228 
00229     QString tempDirectory = getTempDirectory();
00230 
00231     tempDirectory += "work/";
00232 
00233     QString mkisofs = gCoreContext->GetSetting("MythArchiveMkisofsCmd", "mkisofs");
00234     QString command = mkisofs + " -R -J -V 'MythTV Archive' -o ";
00235     command += tempDirectory + "mythburn.iso " + sourceDirectory;
00236 
00237     uint res = myth_system(command);
00238     if (res != GENERIC_EXIT_OK)
00239     {
00240         LOG(VB_JOBQUEUE, LOG_ERR,
00241             QString("Failed while running mkisofs. Result: %1") .arg(res));
00242         return false;
00243     }
00244 
00245     LOG(VB_JOBQUEUE, LOG_INFO, "Finished creating ISO image");
00246     return true;
00247 }
00248 
00249 static int burnISOImage(int mediaType, bool bEraseDVDRW, bool nativeFormat)
00250 {
00251     QString dvdDrive = gCoreContext->GetSetting("MythArchiveDVDLocation",
00252                                             "/dev/dvd");
00253     LOG(VB_JOBQUEUE, LOG_INFO, "Burning ISO image to " + dvdDrive);
00254 
00255     int     driveSpeed    = gCoreContext->GetNumSetting("MythArchiveDriveSpeed");
00256     QString tempDirectory = getTempDirectory();
00257 
00258     tempDirectory += "work/";
00259 
00260     QString command = gCoreContext->GetSetting("MythArchiveGrowisofsCmd",
00261                                            "growisofs");
00262 
00263     if (driveSpeed)
00264         command += " -speed=" + QString::number(driveSpeed);
00265 
00266     if (nativeFormat)
00267     {
00268         if (mediaType == AD_DVD_RW && bEraseDVDRW == true)
00269         {
00270             command += " -use-the-force-luke -Z " + dvdDrive;
00271             command += " -V 'MythTV Archive' -R -J " + tempDirectory;
00272         }
00273         else
00274         {
00275             command += " -Z " + dvdDrive;
00276             command += " -V 'MythTV Archive' -R -J " + tempDirectory;
00277         }
00278     }
00279     else
00280     {
00281         if (mediaType == AD_DVD_RW && bEraseDVDRW == true)
00282         {
00283             command += " -dvd-compat -use-the-force-luke -Z " + dvdDrive;
00284             command += " -dvd-video -V 'MythTV DVD' " + tempDirectory + "/dvd";
00285         }
00286         else
00287         {
00288             command += " -dvd-compat -Z " + dvdDrive;
00289             command += " -dvd-video -V 'MythTV DVD' " + tempDirectory + "/dvd";
00290         }
00291     }
00292 
00293     uint res = myth_system(command);
00294     if (res != GENERIC_EXIT_OK)
00295         LOG(VB_JOBQUEUE, LOG_ERR,
00296             QString("Failed while running growisofs. Result: %1") .arg(res));
00297     else
00298         LOG(VB_JOBQUEUE, LOG_INFO, "Finished burning ISO image");
00299 
00300     return res;
00301 }
00302 
00303 static int doBurnDVD(int mediaType, bool bEraseDVDRW, bool nativeFormat)
00304 {
00305     gCoreContext->SaveSetting("MythArchiveLastRunStart",
00306         QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm"));
00307     gCoreContext->SaveSetting("MythArchiveLastRunStatus", "Running");
00308 
00309     int res = burnISOImage(mediaType, bEraseDVDRW, nativeFormat);
00310 
00311     gCoreContext->SaveSetting("MythArchiveLastRunEnd",
00312         QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm"));
00313     gCoreContext->SaveSetting("MythArchiveLastRunStatus", "Success");
00314     return res;
00315 }
00316 
00317 int NativeArchive::doNativeArchive(const QString &jobFile)
00318 {
00319     QString tempDir = getTempDirectory();
00320 
00321     QDomDocument doc("archivejob");
00322     QFile file(jobFile);
00323     if (!file.open(QIODevice::ReadOnly))
00324     {
00325         LOG(VB_JOBQUEUE, LOG_ERR, "Could not open job file: " + jobFile);
00326         return 1;
00327     }
00328 
00329     if (!doc.setContent(&file))
00330     {
00331         LOG(VB_JOBQUEUE, LOG_ERR, "Could not load job file: " + jobFile);
00332         file.close();
00333         return 1;
00334     }
00335 
00336     file.close();
00337 
00338     // get options from job file
00339     bool bCreateISO = false;
00340     bool bEraseDVDRW = false;
00341     bool bDoBurn = false;
00342     QString saveDirectory;
00343     int mediaType = 0;
00344 
00345     QDomNodeList nodeList = doc.elementsByTagName("options");
00346     if (nodeList.count() == 1)
00347     {
00348         QDomNode node = nodeList.item(0);
00349         QDomElement options = node.toElement();
00350         if (!options.isNull())
00351         {
00352             bCreateISO = (options.attribute("createiso", "0") == "1");
00353             bEraseDVDRW = (options.attribute("erasedvdrw", "0") == "1");
00354             bDoBurn = (options.attribute("doburn", "0") == "1");
00355             mediaType = options.attribute("mediatype", "0").toInt();
00356             saveDirectory = options.attribute("savedirectory", "");
00357             if (!saveDirectory.endsWith("/"))
00358                 saveDirectory += "/";
00359         }
00360     }
00361     else
00362     {
00363         LOG(VB_JOBQUEUE, LOG_ERR,
00364             QString("Found %1 options nodes - should be 1")
00365                 .arg(nodeList.count()));
00366         return 1;
00367     }
00368     LOG(VB_JOBQUEUE, LOG_INFO,
00369         QString("Options - createiso: %1,"
00370                 " doburn: %2, mediatype: %3, erasedvdrw: %4")
00371             .arg(bCreateISO).arg(bDoBurn).arg(mediaType).arg(bEraseDVDRW));
00372     LOG(VB_JOBQUEUE, LOG_INFO, QString("savedirectory: %1").arg(saveDirectory));
00373 
00374     // figure out where to save files
00375     if (mediaType != AD_FILE)
00376     {
00377         saveDirectory = tempDir;
00378         if (!saveDirectory.endsWith("/"))
00379             saveDirectory += "/";
00380 
00381         saveDirectory += "work/";
00382 
00383         QDir dir(saveDirectory);
00384         if (dir.exists())
00385         {
00386             if (!MythRemoveDirectory(dir))
00387                 LOG(VB_GENERAL, LOG_ERR,
00388                     "NativeArchive: Failed to clear work directory");
00389         }
00390         dir.mkpath(saveDirectory);
00391     }
00392 
00393     LOG(VB_JOBQUEUE, LOG_INFO,
00394         QString("Saving files to : %1").arg(saveDirectory));
00395 
00396     // get list of file nodes from the job file
00397     nodeList = doc.elementsByTagName("file");
00398     if (nodeList.count() < 1)
00399     {
00400         LOG(VB_JOBQUEUE, LOG_ERR, "Cannot find any file nodes?");
00401         return 1;
00402     }
00403 
00404     // loop though file nodes and archive each file
00405     QDomNode node;
00406     QDomElement elem;
00407     QString type = "";
00408 
00409     for (int x = 0; x < nodeList.count(); x++)
00410     {
00411         node = nodeList.item(x);
00412         elem = node.toElement();
00413         if (!elem.isNull())
00414         {
00415             type = elem.attribute("type");
00416 
00417             if (type.toLower() == "recording")
00418                 exportRecording(elem, saveDirectory);
00419             else if (type.toLower() == "video")
00420                 exportVideo(elem, saveDirectory);
00421             else
00422             {
00423                 LOG(VB_JOBQUEUE, LOG_ERR,
00424                     QString("Don't know how to archive items of type '%1'")
00425                         .arg(type.toLower()));
00426                 continue;
00427             }
00428         }
00429     }
00430 
00431     // burn the dvd if needed
00432     if (mediaType != AD_FILE && bDoBurn)
00433     {
00434         if (!burnISOImage(mediaType, bEraseDVDRW, true))
00435         {
00436             LOG(VB_JOBQUEUE, LOG_ERR,
00437                 "Native archive job failed to completed");
00438             return 1;
00439         }
00440     }
00441 
00442     // create an iso image if needed
00443     if (bCreateISO)
00444     {
00445         if (!createISOImage(saveDirectory))
00446         {
00447             LOG(VB_JOBQUEUE, LOG_ERR, "Native archive job failed to completed");
00448             return 1;
00449         }
00450     }
00451 
00452     LOG(VB_JOBQUEUE, LOG_INFO, "Native archive job completed OK");
00453 
00454     return 0;
00455 }
00456 
00457 static QRegExp badChars = QRegExp("(/|\\\\|:|\'|\"|\\?|\\|)");
00458 
00459 static QString fixFilename(const QString &filename)
00460 {
00461     QString ret = filename;
00462     ret.replace(badChars, "_");
00463     return ret;
00464 }
00465 
00466 int NativeArchive::exportRecording(QDomElement   &itemNode,
00467                                    const QString &saveDirectory)
00468 {
00469     QString chanID, startTime, title = "", filename = "";
00470     bool doDelete = false;
00471     QString dbVersion = gCoreContext->GetSetting("DBSchemaVer", "");
00472 
00473     title = fixFilename(itemNode.attribute("title"));
00474     filename = itemNode.attribute("filename");
00475     doDelete = (itemNode.attribute("delete", "0") == "0");
00476     LOG(VB_JOBQUEUE, LOG_INFO, QString("Archiving %1 (%2), do delete: %3")
00477             .arg(title).arg(filename).arg(doDelete));
00478 
00479     if (title == "" || filename == "")
00480     {
00481         LOG(VB_JOBQUEUE, LOG_ERR, "Bad title or filename");
00482         return 0;
00483     }
00484 
00485     if (!extractDetailsFromFilename(filename, chanID, startTime))
00486     {
00487         LOG(VB_JOBQUEUE, LOG_ERR,
00488             QString("Failed to extract chanID and startTime from '%1'")
00489                 .arg(filename));
00490         return 0;
00491     }
00492 
00493     // create the directory to hold this items files
00494     QDir dir(saveDirectory + title);
00495     if (!dir.exists())
00496         dir.mkpath(saveDirectory + title);
00497     if (!dir.exists())
00498         LOG(VB_GENERAL, LOG_ERR, "Failed to create savedir: " + ENO);
00499 
00500     LOG(VB_JOBQUEUE, LOG_INFO, "Creating xml file for " + title);
00501     QDomDocument doc("MYTHARCHIVEITEM");
00502 
00503     QDomElement root = doc.createElement("item");
00504     doc.appendChild(root);
00505     root.setAttribute("type", "recording");
00506     root.setAttribute("databaseversion", dbVersion);
00507 
00508     QDomElement recorded = doc.createElement("recorded");
00509     root.appendChild(recorded);
00510 
00511     // get details from recorded
00512     MSqlQuery query(MSqlQuery::InitCon());
00513     query.prepare("SELECT chanid, starttime, endtime, title, subtitle,"
00514                   " description, category, hostname, bookmark, editing,"
00515                   " cutlist, autoexpire, commflagged, recgroup, recordid,"
00516                   " seriesid, programid, lastmodified, filesize, stars,"
00517                   " previouslyshown, originalairdate, preserve, findid,"
00518                   " deletepending, transcoder, timestretch, recpriority,"
00519                   " basename, progstart, progend, playgroup, profile,"
00520                   " duplicate, transcoded FROM recorded "
00521             "WHERE chanid = :CHANID and starttime = :STARTTIME;");
00522     query.bindValue(":CHANID", chanID);
00523     query.bindValue(":STARTTIME", startTime);
00524 
00525     if (query.exec() && query.next())
00526     {
00527         QDomElement elem;
00528         QDomText text;
00529 
00530         elem = doc.createElement("chanid");
00531         text = doc.createTextNode(query.value(0).toString());
00532         elem.appendChild(text);
00533         recorded.appendChild(elem);
00534 
00535         elem = doc.createElement("starttime");
00536         text = doc.createTextNode(query.value(1).toString());
00537         elem.appendChild(text);
00538         recorded.appendChild(elem);
00539 
00540         elem = doc.createElement("endtime");
00541         text = doc.createTextNode(query.value(2).toString());
00542         elem.appendChild(text);
00543         recorded.appendChild(elem);
00544 
00545         elem = doc.createElement("title");
00546         text = doc.createTextNode(query.value(3).toString());
00547         elem.appendChild(text);
00548         recorded.appendChild(elem);
00549 
00550         elem = doc.createElement("subtitle");
00551         text = doc.createTextNode(query.value(4).toString());
00552         elem.appendChild(text);
00553         recorded.appendChild(elem);
00554 
00555         elem = doc.createElement("description");
00556         text = doc.createTextNode(query.value(5).toString());
00557         elem.appendChild(text);
00558         recorded.appendChild(elem);
00559 
00560         elem = doc.createElement("category");
00561         text = doc.createTextNode(query.value(6).toString());
00562         elem.appendChild(text);
00563         recorded.appendChild(elem);
00564 
00565         elem = doc.createElement("hostname");
00566         text = doc.createTextNode(query.value(7).toString());
00567         elem.appendChild(text);
00568         recorded.appendChild(elem);
00569 
00570         elem = doc.createElement("bookmark");
00571         text = doc.createTextNode(query.value(8).toString());
00572         elem.appendChild(text);
00573         recorded.appendChild(elem);
00574 
00575         elem = doc.createElement("editing");
00576         text = doc.createTextNode(query.value(9).toString());
00577         elem.appendChild(text);
00578         recorded.appendChild(elem);
00579 
00580         elem = doc.createElement("cutlist");
00581         text = doc.createTextNode(query.value(10).toString());
00582         elem.appendChild(text);
00583         recorded.appendChild(elem);
00584 
00585         elem = doc.createElement("autoexpire");
00586         text = doc.createTextNode(query.value(11).toString());
00587         elem.appendChild(text);
00588         recorded.appendChild(elem);
00589 
00590         elem = doc.createElement("commflagged");
00591         text = doc.createTextNode(query.value(12).toString());
00592         elem.appendChild(text);
00593         recorded.appendChild(elem);
00594 
00595         elem = doc.createElement("recgroup");
00596         text = doc.createTextNode(query.value(13).toString());
00597         elem.appendChild(text);
00598         recorded.appendChild(elem);
00599 
00600         elem = doc.createElement("recordid");
00601         text = doc.createTextNode(query.value(14).toString());
00602         elem.appendChild(text);
00603         recorded.appendChild(elem);
00604 
00605         elem = doc.createElement("seriesid");
00606         text = doc.createTextNode(query.value(15).toString());
00607         elem.appendChild(text);
00608         recorded.appendChild(elem);
00609 
00610         elem = doc.createElement("programid");
00611         text = doc.createTextNode(query.value(16).toString());
00612         elem.appendChild(text);
00613         recorded.appendChild(elem);
00614 
00615         elem = doc.createElement("lastmodified");
00616         text = doc.createTextNode(query.value(17).toString());
00617         elem.appendChild(text);
00618         recorded.appendChild(elem);
00619 
00620         elem = doc.createElement("filesize");
00621         text = doc.createTextNode(query.value(18).toString());
00622         elem.appendChild(text);
00623         recorded.appendChild(elem);
00624 
00625         elem = doc.createElement("stars");
00626         text = doc.createTextNode(query.value(19).toString());
00627         elem.appendChild(text);
00628         recorded.appendChild(elem);
00629 
00630         elem = doc.createElement("previouslyshown");
00631         text = doc.createTextNode(query.value(20).toString());
00632         elem.appendChild(text);
00633         recorded.appendChild(elem);
00634 
00635         elem = doc.createElement("originalairdate");
00636         text = doc.createTextNode(query.value(21).toString());
00637         elem.appendChild(text);
00638         recorded.appendChild(elem);
00639 
00640         elem = doc.createElement("preserve");
00641         text = doc.createTextNode(query.value(22).toString());
00642         elem.appendChild(text);
00643         recorded.appendChild(elem);
00644 
00645         elem = doc.createElement("findid");
00646         text = doc.createTextNode(query.value(23).toString());
00647         elem.appendChild(text);
00648         recorded.appendChild(elem);
00649 
00650         elem = doc.createElement("deletepending");
00651         text = doc.createTextNode(query.value(24).toString());
00652         elem.appendChild(text);
00653         recorded.appendChild(elem);
00654 
00655         elem = doc.createElement("transcoder");
00656         text = doc.createTextNode(query.value(25).toString());
00657         elem.appendChild(text);
00658         recorded.appendChild(elem);
00659 
00660         elem = doc.createElement("timestretch");
00661         text = doc.createTextNode(query.value(26).toString());
00662         elem.appendChild(text);
00663         recorded.appendChild(elem);
00664 
00665         elem = doc.createElement("recpriority");
00666         text = doc.createTextNode(query.value(27).toString());
00667         elem.appendChild(text);
00668         recorded.appendChild(elem);
00669 
00670         elem = doc.createElement("basename");
00671         text = doc.createTextNode(query.value(28).toString());
00672         elem.appendChild(text);
00673         recorded.appendChild(elem);
00674 
00675         elem = doc.createElement("progstart");
00676         text = doc.createTextNode(query.value(29).toString());
00677         elem.appendChild(text);
00678         recorded.appendChild(elem);
00679 
00680         elem = doc.createElement("progend");
00681         text = doc.createTextNode(query.value(30).toString());
00682         elem.appendChild(text);
00683         recorded.appendChild(elem);
00684 
00685         elem = doc.createElement("playgroup");
00686         text = doc.createTextNode(query.value(31).toString());
00687         elem.appendChild(text);
00688         recorded.appendChild(elem);
00689 
00690         elem = doc.createElement("profile");
00691         text = doc.createTextNode(query.value(32).toString());
00692         elem.appendChild(text);
00693         recorded.appendChild(elem);
00694 
00695         elem = doc.createElement("duplicate");
00696         text = doc.createTextNode(query.value(33).toString());
00697         elem.appendChild(text);
00698         recorded.appendChild(elem);
00699 
00700         elem = doc.createElement("transcoded");
00701         text = doc.createTextNode(query.value(34).toString());
00702         elem.appendChild(text);
00703         recorded.appendChild(elem);
00704         LOG(VB_JOBQUEUE, LOG_INFO, "Created recorded element for " + title);
00705     }
00706 
00707     // add channel details
00708     query.prepare("SELECT chanid, channum, callsign, name "
00709             "FROM channel WHERE chanid = :CHANID;");
00710     query.bindValue(":CHANID", chanID);
00711 
00712     if (query.exec() && query.next())
00713     {
00714         QDomElement channel = doc.createElement("channel");
00715         channel.setAttribute("chanid", query.value(0).toString());
00716         channel.setAttribute("channum", query.value(1).toString());
00717         channel.setAttribute("callsign", query.value(2).toString());
00718         channel.setAttribute("name", query.value(3).toString());
00719         root.appendChild(channel);
00720         LOG(VB_JOBQUEUE, LOG_INFO, "Created channel element for " + title);
00721     }
00722     else
00723     {
00724         // cannot find the original channel so create a default channel element
00725         LOG(VB_JOBQUEUE, LOG_ERR,
00726             "Cannot find channel details for chanid " + chanID);
00727         QDomElement channel = doc.createElement("channel");
00728         channel.setAttribute("chanid", chanID);
00729         channel.setAttribute("channum", "unknown");
00730         channel.setAttribute("callsign", "unknown");
00731         channel.setAttribute("name", "unknown");
00732         root.appendChild(channel);
00733         LOG(VB_JOBQUEUE, LOG_INFO,
00734             "Created a default channel element for " + title);
00735     }
00736 
00737     // add any credits
00738     query.prepare("SELECT credits.person, role, people.name "
00739             "FROM recordedcredits AS credits "
00740             "LEFT JOIN people ON credits.person = people.person "
00741             "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
00742     query.bindValue(":CHANID", chanID);
00743     query.bindValue(":STARTTIME", startTime);
00744 
00745     if (query.exec() && query.size())
00746     {
00747         QDomElement credits = doc.createElement("credits");
00748         while (query.next())
00749         {
00750             QDomElement credit = doc.createElement("credit");
00751             credit.setAttribute("personid", query.value(0).toString());
00752             credit.setAttribute("name", query.value(2).toString());
00753             credit.setAttribute("role", query.value(1).toString());
00754             credits.appendChild(credit);
00755         }
00756         root.appendChild(credits);
00757         LOG(VB_JOBQUEUE, LOG_INFO, "Created credits element for " + title);
00758     }
00759 
00760     // add any rating
00761     query.prepare("SELECT system, rating FROM recordedrating "
00762             "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
00763     query.bindValue(":CHANID", chanID);
00764     query.bindValue(":STARTTIME", startTime);
00765 
00766     if (query.exec() && query.next())
00767     {
00768         QDomElement rating = doc.createElement("rating");
00769         rating.setAttribute("system", query.value(0).toString());
00770         rating.setAttribute("rating", query.value(1).toString());
00771         root.appendChild(rating);
00772         LOG(VB_JOBQUEUE, LOG_INFO, "Created rating element for " + title);
00773     }
00774 
00775     // add the recordedmarkup table
00776     QDomElement recordedmarkup = doc.createElement("recordedmarkup");
00777     query.prepare("SELECT chanid, starttime, mark, offset, type "
00778             "FROM recordedmarkup "
00779             "WHERE chanid = :CHANID and starttime = :STARTTIME;");
00780     query.bindValue(":CHANID", chanID);
00781     query.bindValue(":STARTTIME", startTime);
00782     if (query.exec() && query.size())
00783     {
00784         while (query.next())
00785         {
00786             QDomElement mark = doc.createElement("mark");
00787             mark.setAttribute("mark", query.value(2).toString());
00788             mark.setAttribute("offset", query.value(3).toString());
00789             mark.setAttribute("type", query.value(4).toString());
00790             recordedmarkup.appendChild(mark);
00791         }
00792         root.appendChild(recordedmarkup);
00793         LOG(VB_JOBQUEUE, LOG_INFO,
00794             "Created recordedmarkup element for " + title);
00795     }
00796 
00797     // add the recordedseek table
00798     QDomElement recordedseek = doc.createElement("recordedseek");
00799     query.prepare("SELECT chanid, starttime, mark, offset, type "
00800             "FROM recordedseek "
00801             "WHERE chanid = :CHANID and starttime = :STARTTIME;");
00802     query.bindValue(":CHANID", chanID);
00803     query.bindValue(":STARTTIME", startTime);
00804     if (query.exec() && query.size())
00805     {
00806         while (query.next())
00807         {
00808             QDomElement mark = doc.createElement("mark");
00809             mark.setAttribute("mark", query.value(2).toString());
00810             mark.setAttribute("offset", query.value(3).toString());
00811             mark.setAttribute("type", query.value(4).toString());
00812             recordedseek.appendChild(mark);
00813         }
00814         root.appendChild(recordedseek);
00815         LOG(VB_JOBQUEUE, LOG_INFO, "Created recordedseek element for " + title);
00816     }
00817 
00818     // finally save the xml to the file
00819     QString baseName = getBaseName(filename);
00820     QString xmlFile = saveDirectory + title + "/" + baseName + ".xml";
00821     QFile f(xmlFile);
00822     if (!f.open(QIODevice::WriteOnly))
00823     {
00824         LOG(VB_JOBQUEUE, LOG_ERR,
00825             "MythNativeWizard: Failed to open file for writing - " + xmlFile);
00826         return 0;
00827     }
00828 
00829     QTextStream t(&f);
00830     t << doc.toString(4);
00831     f.close();
00832 
00833     // copy the file
00834     LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");
00835     bool res = copyFile(filename, saveDirectory + title + "/" + baseName);
00836     if (!res)
00837         return 0;
00838 
00839     // copy preview image
00840     if (QFile::exists(filename + ".png"))
00841     {
00842         LOG(VB_JOBQUEUE, LOG_INFO, "Copying preview image");
00843         res = copyFile(filename + ".png", saveDirectory
00844                        + title + "/" + baseName + ".png");
00845         if (!res)
00846             return 0;
00847     }
00848 
00849     LOG(VB_JOBQUEUE, LOG_INFO, "Item Archived OK");
00850 
00851     return 1;
00852 }
00853 
00854 int NativeArchive::exportVideo(QDomElement   &itemNode,
00855                                const QString &saveDirectory)
00856 {
00857     QString title = "", filename = "";
00858     bool doDelete = false;
00859     QString dbVersion = gCoreContext->GetSetting("DBSchemaVer", "");
00860     int intID = 0, categoryID = 0;
00861     QString coverFile = "";
00862 
00863     title = fixFilename(itemNode.attribute("title"));
00864     filename = itemNode.attribute("filename");
00865     doDelete = (itemNode.attribute("delete", "0") == "0");
00866     LOG(VB_JOBQUEUE, LOG_INFO, QString("Archiving %1 (%2), do delete: %3")
00867             .arg(title).arg(filename).arg(doDelete));
00868 
00869     if (title == "" || filename == "")
00870     {
00871         LOG(VB_JOBQUEUE, LOG_ERR, "Bad title or filename");
00872         return 0;
00873     }
00874 
00875     // create the directory to hold this items files
00876     QDir dir(saveDirectory + title);
00877     if (!dir.exists())
00878         dir.mkdir(saveDirectory + title);
00879 
00880     LOG(VB_JOBQUEUE, LOG_INFO, "Creating xml file for " + title);
00881     QDomDocument doc("MYTHARCHIVEITEM");
00882 
00883     QDomElement root = doc.createElement("item");
00884     doc.appendChild(root);
00885     root.setAttribute("type", "video");
00886     root.setAttribute("databaseversion", dbVersion);
00887 
00888     QDomElement video = doc.createElement("videometadata");
00889     root.appendChild(video);
00890 
00891     // get details from videometadata
00892     MSqlQuery query(MSqlQuery::InitCon());
00893     query.prepare("SELECT intid, title, director, plot, rating, inetref, "
00894             "year, userrating, length, showlevel, filename, coverfile, "
00895             "childid, browse, playcommand, category "
00896             "FROM videometadata WHERE filename = :FILENAME;");
00897     query.bindValue(":FILENAME", filename);
00898 
00899     if (query.exec() && query.next())
00900     {
00901         QDomElement elem;
00902         QDomText text;
00903 
00904         elem = doc.createElement("intid");
00905         text = doc.createTextNode(query.value(0).toString());
00906         intID = query.value(0).toInt();
00907         elem.appendChild(text);
00908         video.appendChild(elem);
00909 
00910         elem = doc.createElement("title");
00911         text = doc.createTextNode(query.value(1).toString());
00912         elem.appendChild(text);
00913         video.appendChild(elem);
00914 
00915         elem = doc.createElement("director");
00916         text = doc.createTextNode(query.value(2).toString());
00917         elem.appendChild(text);
00918         video.appendChild(elem);
00919 
00920         elem = doc.createElement("plot");
00921         text = doc.createTextNode(query.value(3).toString());
00922         elem.appendChild(text);
00923         video.appendChild(elem);
00924 
00925         elem = doc.createElement("rating");
00926         text = doc.createTextNode(query.value(4).toString());
00927         elem.appendChild(text);
00928         video.appendChild(elem);
00929 
00930         elem = doc.createElement("inetref");
00931         text = doc.createTextNode(query.value(5).toString());
00932         elem.appendChild(text);
00933         video.appendChild(elem);
00934 
00935         elem = doc.createElement("year");
00936         text = doc.createTextNode(query.value(6).toString());
00937         elem.appendChild(text);
00938         video.appendChild(elem);
00939 
00940         elem = doc.createElement("userrating");
00941         text = doc.createTextNode(query.value(7).toString());
00942         elem.appendChild(text);
00943         video.appendChild(elem);
00944 
00945         elem = doc.createElement("length");
00946         text = doc.createTextNode(query.value(8).toString());
00947         elem.appendChild(text);
00948         video.appendChild(elem);
00949 
00950         elem = doc.createElement("showlevel");
00951         text = doc.createTextNode(query.value(9).toString());
00952         elem.appendChild(text);
00953         video.appendChild(elem);
00954 
00955         // remove the VideoStartupDir part of the filename
00956         QString fname = query.value(10).toString();
00957         if (fname.startsWith(gCoreContext->GetSetting("VideoStartupDir")))
00958             fname = fname.remove(gCoreContext->GetSetting("VideoStartupDir"));
00959 
00960         elem = doc.createElement("filename");
00961         text = doc.createTextNode(fname);
00962         elem.appendChild(text);
00963         video.appendChild(elem);
00964 
00965         elem = doc.createElement("coverfile");
00966         text = doc.createTextNode(query.value(11).toString());
00967         coverFile = query.value(11).toString();
00968         elem.appendChild(text);
00969         video.appendChild(elem);
00970 
00971         elem = doc.createElement("childid");
00972         text = doc.createTextNode(query.value(12).toString());
00973         elem.appendChild(text);
00974         video.appendChild(elem);
00975 
00976         elem = doc.createElement("browse");
00977         text = doc.createTextNode(query.value(13).toString());
00978         elem.appendChild(text);
00979         video.appendChild(elem);
00980 
00981         elem = doc.createElement("playcommand");
00982         text = doc.createTextNode(query.value(14).toString());
00983         elem.appendChild(text);
00984         video.appendChild(elem);
00985 
00986         elem = doc.createElement("categoryid");
00987         text = doc.createTextNode(query.value(15).toString());
00988         categoryID = query.value(15).toInt();
00989         elem.appendChild(text);
00990         video.appendChild(elem);
00991 
00992         LOG(VB_JOBQUEUE, LOG_INFO,
00993             "Created videometadata element for " + title);
00994     }
00995 
00996     // add category details
00997     query.prepare("SELECT intid, category "
00998             "FROM videocategory WHERE intid = :INTID;");
00999     query.bindValue(":INTID", categoryID);
01000 
01001     if (query.exec() && query.next())
01002     {
01003         QDomElement category = doc.createElement("category");
01004         category.setAttribute("intid", query.value(0).toString());
01005         category.setAttribute("category", query.value(1).toString());
01006         root.appendChild(category);
01007         LOG(VB_JOBQUEUE, LOG_INFO,
01008             "Created videocategory element for " + title);
01009     }
01010 
01011     //add video country details
01012     QDomElement countries = doc.createElement("countries");
01013     root.appendChild(countries);
01014 
01015     query.prepare("SELECT intid, country "
01016             "FROM videometadatacountry INNER JOIN videocountry "
01017             "ON videometadatacountry.idcountry = videocountry.intid "
01018             "WHERE idvideo = :INTID;");
01019     query.bindValue(":INTID", intID);
01020 
01021     if (!query.exec())
01022         MythDB::DBError("select countries", query);
01023 
01024     if (query.isActive() && query.size())
01025     {
01026         while (query.next())
01027         {
01028             QDomElement country = doc.createElement("country");
01029             country.setAttribute("intid", query.value(0).toString());
01030             country.setAttribute("country", query.value(1).toString());
01031             countries.appendChild(country);
01032         }
01033         LOG(VB_JOBQUEUE, LOG_INFO, "Created videocountry element for " + title);
01034     }
01035 
01036     // add video genre details
01037     QDomElement genres = doc.createElement("genres");
01038     root.appendChild(genres);
01039 
01040     query.prepare("SELECT intid, genre "
01041             "FROM videometadatagenre INNER JOIN videogenre "
01042             "ON videometadatagenre.idgenre = videogenre.intid "
01043             "WHERE idvideo = :INTID;");
01044     query.bindValue(":INTID", intID);
01045 
01046     if (!query.exec())
01047         MythDB::DBError("select genres", query);
01048 
01049     if (query.isActive() && query.size())
01050     {
01051         while (query.next())
01052         {
01053             QDomElement genre = doc.createElement("genre");
01054             genre.setAttribute("intid", query.value(0).toString());
01055             genre.setAttribute("genre", query.value(1).toString());
01056             genres.appendChild(genre);
01057         }
01058         LOG(VB_JOBQUEUE, LOG_INFO, "Created videogenre element for " + title);
01059     }
01060 
01061     // finally save the xml to the file
01062     QFileInfo fileInfo(filename);
01063     QString xmlFile = saveDirectory + title + "/"
01064                       + fileInfo.fileName() + ".xml";
01065     QFile f(xmlFile);
01066     if (!f.open(QIODevice::WriteOnly))
01067     {
01068         LOG(VB_JOBQUEUE, LOG_INFO,
01069             "MythNativeWizard: Failed to open file for writing - " + xmlFile);
01070         return 0;
01071     }
01072 
01073     QTextStream t(&f);
01074     t << doc.toString(4);
01075     f.close();
01076 
01077     // copy the file
01078     LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");
01079     bool res = copyFile(filename, saveDirectory + title
01080                         + "/" + fileInfo.fileName());
01081     if (!res)
01082     {
01083         return 0;
01084     }
01085 
01086     // copy the cover image
01087     fileInfo.setFile(coverFile);
01088     if (fileInfo.exists())
01089     {
01090         LOG(VB_JOBQUEUE, LOG_INFO, "Copying cover file");
01091         bool res = copyFile(coverFile, saveDirectory + title
01092                             + "/" + fileInfo.fileName());
01093         if (!res)
01094         {
01095             return 0;
01096         }
01097     }
01098 
01099     LOG(VB_JOBQUEUE, LOG_INFO, "Item Archived OK");
01100 
01101     return 1;
01102 }
01103 
01104 int NativeArchive::doImportArchive(const QString &xmlFile, int chanID)
01105 {
01106     // open xml file
01107     QDomDocument doc("mydocument");
01108     QFile file(xmlFile);
01109     if (!file.open(QIODevice::ReadOnly))
01110     {
01111         LOG(VB_JOBQUEUE, LOG_ERR,
01112             "Failed to open file for reading - " + xmlFile);
01113         return 1;
01114     }
01115 
01116     if (!doc.setContent(&file))
01117     {
01118         file.close();
01119         LOG(VB_JOBQUEUE, LOG_ERR,
01120             "Failed to read from xml file - " + xmlFile);
01121         return 1;
01122     }
01123     file.close();
01124 
01125     QString docType = doc.doctype().name();
01126     QString type, dbVersion;
01127     QDomNodeList itemNodeList;
01128     QDomNode node;
01129     QDomElement itemNode;
01130 
01131     if (docType == "MYTHARCHIVEITEM")
01132     {
01133         itemNodeList = doc.elementsByTagName("item");
01134 
01135         if (itemNodeList.count() < 1)
01136         {
01137             LOG(VB_JOBQUEUE, LOG_ERR,
01138                 "Couldn't find an 'item' element in XML file");
01139             return 1;
01140         }
01141 
01142         node = itemNodeList.item(0);
01143         itemNode = node.toElement();
01144         type = itemNode.attribute("type");
01145         dbVersion = itemNode.attribute("databaseversion");
01146 
01147         LOG(VB_JOBQUEUE, LOG_INFO,
01148             QString("Archive DB version: %1, Local DB version: %2")
01149                 .arg(dbVersion).arg(gCoreContext->GetSetting("DBSchemaVer")));
01150     }
01151     else
01152     {
01153         LOG(VB_JOBQUEUE, LOG_ERR, "Not a native archive xml file - " + xmlFile);
01154         return 1;
01155     }
01156 
01157     if (type == "recording")
01158     {
01159         return importRecording(itemNode, xmlFile, chanID);
01160     }
01161     else if (type == "video")
01162     {
01163         return importVideo(itemNode, xmlFile);
01164     }
01165 
01166     return 1;
01167 }
01168 
01169 int NativeArchive::importRecording(const QDomElement &itemNode,
01170                                    const QString     &xmlFile, int chanID)
01171 {
01172     LOG(VB_JOBQUEUE, LOG_INFO,
01173         QString("Import recording using chanID: %1").arg(chanID));
01174     LOG(VB_JOBQUEUE, LOG_INFO,
01175         QString("Archived recording xml file: %1").arg(xmlFile));
01176 
01177     QString videoFile = xmlFile.left(xmlFile.length() - 4);
01178     QString basename = videoFile;
01179     int pos = videoFile.lastIndexOf('/');
01180     if (pos > 0)
01181         basename = videoFile.mid(pos + 1);
01182 
01183     QDomNodeList nodeList = itemNode.elementsByTagName("recorded");
01184     if (nodeList.count() < 1)
01185     {
01186         LOG(VB_JOBQUEUE, LOG_ERR,
01187             "Couldn't find a 'recorded' element in XML file");
01188         return 1;
01189     }
01190 
01191     QDomNode n = nodeList.item(0);
01192     QDomElement recordedNode = n.toElement();
01193     QString startTime = findNodeText(recordedNode, "starttime");
01194 
01195     // check this recording doesn't already exist
01196     MSqlQuery query(MSqlQuery::InitCon());
01197     query.prepare("SELECT * FROM recorded "
01198             "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
01199     query.bindValue(":CHANID", chanID);
01200     query.bindValue(":STARTTIME", startTime);
01201     if (query.exec())
01202     {
01203         if (query.isActive() && query.size())
01204         {
01205             LOG(VB_JOBQUEUE, LOG_ERR,
01206                 "This recording appears to already exist!!");
01207             return 1;
01208         }
01209     }
01210 
01211     // find the default storage location for this host
01212     QString storageDir = "";
01213     query.prepare("SELECT dirname FROM storagegroup "
01214             "WHERE groupname = :GROUPNAME AND hostname = :HOSTNAME;");
01215     query.bindValue(":GROUPNAME", "Default");
01216     query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
01217     if (query.exec())
01218     {
01219         query.first();
01220         storageDir = query.value(0).toString();
01221     }
01222     else
01223     {
01224         LOG(VB_JOBQUEUE, LOG_ERR,
01225             "Failed to get 'Default' storage directory for this host");
01226         return 1;
01227     }
01228 
01229     // copy file to recording directory
01230     LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file.");
01231     if (!copyFile(videoFile,  storageDir + "/" + basename))
01232         return 1;
01233 
01234     // copy any preview image to recording directory
01235     if (QFile::exists(videoFile + ".png"))
01236     {
01237         LOG(VB_JOBQUEUE, LOG_INFO, "Copying preview image file.");
01238         if (!copyFile(videoFile + ".png", storageDir + "/" + basename + ".png"))
01239             return 1;
01240     }
01241 
01242     // copy recorded to database
01243     query.prepare("INSERT INTO recorded (chanid,starttime,endtime,"
01244             "title,subtitle,description,category,hostname,bookmark,"
01245             "editing,cutlist,autoexpire, commflagged,recgroup,"
01246             "recordid, seriesid,programid,lastmodified,filesize,stars,"
01247             "previouslyshown,originalairdate,preserve,findid,deletepending,"
01248             "transcoder,timestretch,recpriority,basename,progstart,progend,"
01249             "playgroup,profile,duplicate,transcoded) "
01250             "VALUES(:CHANID,:STARTTIME,:ENDTIME,:TITLE,"
01251             ":SUBTITLE,:DESCRIPTION,:CATEGORY,:HOSTNAME,"
01252             ":BOOKMARK,:EDITING,:CUTLIST,:AUTOEXPIRE,"
01253             ":COMMFLAGGED,:RECGROUP,:RECORDID,:SERIESID,"
01254             ":PROGRAMID,:LASTMODIFIED,:FILESIZE,:STARS,"
01255             ":PREVIOUSLYSHOWN,:ORIGINALAIRDATE,:PRESERVE,:FINDID,"
01256             ":DELETEPENDING,:TRANSCODER,:TIMESTRETCH,:RECPRIORITY,"
01257             ":BASENAME,:PROGSTART,:PROGEND,:PLAYGROUP,:PROFILE,:DUPLICATE,:TRANSCODED);");
01258     query.bindValue(":CHANID", chanID);
01259     query.bindValue(":STARTTIME", startTime);
01260     query.bindValue(":ENDTIME", findNodeText(recordedNode, "endtime"));
01261     query.bindValue(":TITLE", findNodeText(recordedNode, "title"));
01262     query.bindValue(":SUBTITLE", findNodeText(recordedNode, "subtitle"));
01263     query.bindValue(":DESCRIPTION", findNodeText(recordedNode, "description"));
01264     query.bindValue(":CATEGORY", findNodeText(recordedNode, "category"));
01265     query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
01266     query.bindValue(":BOOKMARK", findNodeText(recordedNode, "bookmark"));
01267     query.bindValue(":EDITING", findNodeText(recordedNode, "editing"));
01268     query.bindValue(":CUTLIST", findNodeText(recordedNode, "cutlist"));
01269     query.bindValue(":AUTOEXPIRE", findNodeText(recordedNode, "autoexpire"));
01270     query.bindValue(":COMMFLAGGED", findNodeText(recordedNode, "commflagged"));
01271     query.bindValue(":RECGROUP", findNodeText(recordedNode, "recgroup"));
01272     query.bindValue(":RECORDID", findNodeText(recordedNode, "recordid"));
01273     query.bindValue(":SERIESID", findNodeText(recordedNode, "seriesid"));
01274     query.bindValue(":PROGRAMID", findNodeText(recordedNode, "programid"));
01275     query.bindValue(":LASTMODIFIED", findNodeText(recordedNode, "lastmodified"));
01276     query.bindValue(":FILESIZE", findNodeText(recordedNode, "filesize"));
01277     query.bindValue(":STARS", findNodeText(recordedNode, "stars"));
01278     query.bindValue(":PREVIOUSLYSHOWN", findNodeText(recordedNode, "previouslyshown"));
01279     query.bindValue(":ORIGINALAIRDATE", findNodeText(recordedNode, "originalairdate"));
01280     query.bindValue(":PRESERVE", findNodeText(recordedNode, "preserve"));
01281     query.bindValue(":FINDID", findNodeText(recordedNode, "findid"));
01282     query.bindValue(":DELETEPENDING", findNodeText(recordedNode, "deletepending"));
01283     query.bindValue(":TRANSCODER", findNodeText(recordedNode, "transcoder"));
01284     query.bindValue(":TIMESTRETCH", findNodeText(recordedNode, "timestretch"));
01285     query.bindValue(":RECPRIORITY", findNodeText(recordedNode, "recpriority"));
01286     query.bindValue(":BASENAME", findNodeText(recordedNode, "basename"));
01287     query.bindValue(":PROGSTART", findNodeText(recordedNode, "progstart"));
01288     query.bindValue(":PROGEND", findNodeText(recordedNode, "progend"));
01289     query.bindValue(":PLAYGROUP", findNodeText(recordedNode, "playgroup"));
01290     query.bindValue(":PROFILE", findNodeText(recordedNode, "profile"));
01291     query.bindValue(":DUPLICATE", findNodeText(recordedNode, "duplicate"));
01292     query.bindValue(":TRANSCODED", findNodeText(recordedNode, "transcoded"));
01293 
01294     if (query.exec())
01295         LOG(VB_JOBQUEUE, LOG_INFO, "Inserted recorded details into database");
01296     else
01297         MythDB::DBError("recorded insert", query);
01298 
01299     // copy recordedmarkup to db
01300     nodeList = itemNode.elementsByTagName("recordedmarkup");
01301     if (nodeList.count() < 1)
01302     {
01303         LOG(VB_JOBQUEUE, LOG_WARNING,
01304             "Couldn't find a 'recordedmarkup' element in XML file");
01305     }
01306     else
01307     {
01308         n = nodeList.item(0);
01309         QDomElement markupNode = n.toElement();
01310 
01311         nodeList = markupNode.elementsByTagName("mark");
01312         if (nodeList.count() < 1)
01313         {
01314             LOG(VB_JOBQUEUE, LOG_WARNING,
01315                 "Couldn't find any 'mark' elements in XML file");
01316         }
01317         else
01318         {
01319             for (int x = 0; x < nodeList.count(); x++)
01320             {
01321                 n = nodeList.item(x);
01322                 QDomElement e = n.toElement();
01323                 query.prepare("INSERT INTO recordedmarkup (chanid, starttime, "
01324                         "mark, offset, type)"
01325                         "VALUES(:CHANID,:STARTTIME,:MARK,:OFFSET,:TYPE);");
01326                 query.bindValue(":CHANID", chanID);
01327                 query.bindValue(":STARTTIME", startTime);
01328                 query.bindValue(":MARK", e.attribute("mark"));
01329                 query.bindValue(":OFFSET", e.attribute("offset"));
01330                 query.bindValue(":TYPE", e.attribute("type"));
01331 
01332                 if (!query.exec())
01333                 {
01334                     MythDB::DBError("recordedmark insert", query);
01335                     return 1;
01336                 }
01337             }
01338 
01339             LOG(VB_JOBQUEUE, LOG_INFO,
01340                 "Inserted recordedmarkup details into database");
01341         }
01342     }
01343 
01344     // copy recordedseek to db
01345     nodeList = itemNode.elementsByTagName("recordedseek");
01346     if (nodeList.count() < 1)
01347     {
01348         LOG(VB_JOBQUEUE, LOG_WARNING,
01349             "Couldn't find a 'recordedseek' element in XML file");
01350     }
01351     else
01352     {
01353         n = nodeList.item(0);
01354         QDomElement markupNode = n.toElement();
01355 
01356         nodeList = markupNode.elementsByTagName("mark");
01357         if (nodeList.count() < 1)
01358         {
01359             LOG(VB_JOBQUEUE, LOG_WARNING,
01360                 "Couldn't find any 'mark' elements in XML file");
01361         }
01362         else
01363         {
01364             for (int x = 0; x < nodeList.count(); x++)
01365             {
01366                 n = nodeList.item(x);
01367                 QDomElement e = n.toElement();
01368                 query.prepare("INSERT INTO recordedseek (chanid, starttime, "
01369                         "mark, offset, type)"
01370                         "VALUES(:CHANID,:STARTTIME,:MARK,:OFFSET,:TYPE);");
01371                 query.bindValue(":CHANID", chanID);
01372                 query.bindValue(":STARTTIME", startTime);
01373                 query.bindValue(":MARK", e.attribute("mark"));
01374                 query.bindValue(":OFFSET", e.attribute("offset"));
01375                 query.bindValue(":TYPE", e.attribute("type"));
01376 
01377                 if (!query.exec())
01378                 {
01379                     MythDB::DBError("recordedseek insert", query);
01380                     return 1;
01381                 }
01382             }
01383 
01384             LOG(VB_JOBQUEUE, LOG_INFO,
01385                 "Inserted recordedseek details into database");
01386         }
01387     }
01388 
01389     // FIXME are these needed?
01390     // copy credits to DB
01391     // copy rating to DB
01392 
01393     LOG(VB_JOBQUEUE, LOG_INFO, "Import completed OK");
01394 
01395     return 0;
01396 }
01397 
01398 int NativeArchive::importVideo(const QDomElement &itemNode, const QString &xmlFile)
01399 {
01400     LOG(VB_JOBQUEUE, LOG_INFO, "Importing video");
01401     LOG(VB_JOBQUEUE, LOG_INFO,
01402         QString("Archived video xml file: %1").arg(xmlFile));
01403 
01404     QString videoFile = xmlFile.left(xmlFile.length() - 4);
01405     QFileInfo fileInfo(videoFile);
01406     QString basename = fileInfo.fileName();
01407 
01408     QDomNodeList nodeList = itemNode.elementsByTagName("videometadata");
01409     if (nodeList.count() < 1)
01410     {
01411         LOG(VB_JOBQUEUE, LOG_ERR,
01412             "Couldn't find a 'videometadata' element in XML file");
01413         return 1;
01414     }
01415 
01416     QDomNode n = nodeList.item(0);
01417     QDomElement videoNode = n.toElement();
01418 
01419     // copy file to video directory
01420     QString path = gCoreContext->GetSetting("VideoStartupDir");
01421     QString origFilename = findNodeText(videoNode, "filename");
01422     QStringList dirList = origFilename.split("/", QString::SkipEmptyParts);
01423     QDir dir;
01424     for (int x = 0; x < dirList.count() - 1; x++)
01425     {
01426         path += "/" + dirList[x];
01427         if (!dir.exists(path))
01428         {
01429             if (!dir.mkdir(path))
01430             {
01431                 LOG(VB_JOBQUEUE, LOG_ERR,
01432                     QString("Couldn't create directory '%1'").arg(path));
01433                 return 1;
01434             }
01435         }
01436     }
01437 
01438     LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");
01439     if (!copyFile(videoFile, path + "/" + basename))
01440     {
01441         return 1;
01442     }
01443 
01444     // copy cover image to Video Artwork dir
01445     QString artworkDir = gCoreContext->GetSetting("VideoArtworkDir");
01446     // get archive path
01447     fileInfo.setFile(videoFile);
01448     QString archivePath = fileInfo.absolutePath();
01449     // get coverfile filename
01450     QString coverFilename = findNodeText(videoNode, "coverfile");
01451     fileInfo.setFile(coverFilename);
01452     coverFilename = fileInfo.fileName();
01453     //check file exists
01454     fileInfo.setFile(archivePath + "/" + coverFilename);
01455     if (fileInfo.exists())
01456     {
01457         LOG(VB_JOBQUEUE, LOG_INFO, "Copying cover file");
01458 
01459         if (!copyFile(archivePath + "/" + coverFilename, artworkDir + "/" + coverFilename))
01460         {
01461             return 1;
01462         }
01463     }
01464     else
01465         coverFilename = "No Cover";
01466 
01467     // copy videometadata to database
01468     MSqlQuery query(MSqlQuery::InitCon());
01469     query.prepare("INSERT INTO videometadata (title, director, plot, rating, inetref, "
01470             "year, userrating, length, showlevel, filename, coverfile, "
01471             "childid, browse, playcommand, category) "
01472             "VALUES(:TITLE,:DIRECTOR,:PLOT,:RATING,:INETREF,:YEAR,"
01473             ":USERRATING,:LENGTH,:SHOWLEVEL,:FILENAME,:COVERFILE,"
01474             ":CHILDID,:BROWSE,:PLAYCOMMAND,:CATEGORY);");
01475     query.bindValue(":TITLE", findNodeText(videoNode, "title"));
01476     query.bindValue(":DIRECTOR", findNodeText(videoNode, "director"));
01477     query.bindValue(":PLOT", findNodeText(videoNode, "plot"));
01478     query.bindValue(":RATING", findNodeText(videoNode, "rating"));
01479     query.bindValue(":INETREF", findNodeText(videoNode, "inetref"));
01480     query.bindValue(":YEAR", findNodeText(videoNode, "year"));
01481     query.bindValue(":USERRATING", findNodeText(videoNode, "userrating"));
01482     query.bindValue(":LENGTH", findNodeText(videoNode, "length"));
01483     query.bindValue(":SHOWLEVEL", findNodeText(videoNode, "showlevel"));
01484     query.bindValue(":FILENAME", path + "/" + basename);
01485     query.bindValue(":COVERFILE", artworkDir + "/" + coverFilename);
01486     query.bindValue(":CHILDID", findNodeText(videoNode, "childid"));
01487     query.bindValue(":BROWSE", findNodeText(videoNode, "browse"));
01488     query.bindValue(":PLAYCOMMAND", findNodeText(videoNode, "playcommand"));
01489     query.bindValue(":CATEGORY", 0);
01490 
01491     if (query.exec())
01492         LOG(VB_JOBQUEUE, LOG_INFO,
01493             "Inserted videometadata details into database");
01494     else
01495     {
01496         MythDB::DBError("videometadata insert", query);
01497         return 1;
01498     }
01499 
01500     // get intid field for inserted record
01501     int intid;
01502     query.prepare("SELECT intid FROM videometadata WHERE filename = :FILENAME;");
01503     query.bindValue(":FILENAME", path + "/" + basename);
01504     if (query.exec() && query.next())
01505     {
01506         intid = query.value(0).toInt();
01507     }
01508     else
01509     {
01510         MythDB::DBError("Failed to get intid", query);
01511         return 1;
01512     }
01513 
01514     LOG(VB_JOBQUEUE, LOG_INFO,
01515         QString("'intid' of inserted video is: %1").arg(intid));
01516 
01517     // copy genre to db
01518     nodeList = itemNode.elementsByTagName("genres");
01519     if (nodeList.count() < 1)
01520     {
01521         LOG(VB_JOBQUEUE, LOG_ERR, "No 'genres' element found in XML file");
01522     }
01523     else
01524     {
01525         n = nodeList.item(0);
01526         QDomElement genresNode = n.toElement();
01527 
01528         nodeList = genresNode.elementsByTagName("genre");
01529         if (nodeList.count() < 1)
01530         {
01531             LOG(VB_JOBQUEUE, LOG_WARNING,
01532                 "Couldn't find any 'genre' elements in XML file");
01533         }
01534         else
01535         {
01536             for (int x = 0; x < nodeList.count(); x++)
01537             {
01538                 n = nodeList.item(x);
01539                 QDomElement e = n.toElement();
01540                 int genreID = e.attribute("intid").toInt();
01541                 QString genre = e.attribute("genre");
01542 
01543                 // see if this genre already exists
01544                 query.prepare("SELECT intid FROM videogenre "
01545                         "WHERE genre = :GENRE");
01546                 query.bindValue(":GENRE", genre);
01547                 if (query.exec() && query.next())
01548                 {
01549                     genreID = query.value(0).toInt();
01550                 }
01551                 else
01552                 {
01553                     // genre doesn't exist so add it
01554                     query.prepare("INSERT INTO videogenre (genre) VALUES(:GENRE);");
01555                     query.bindValue(":GENRE", genre);
01556                     if (!query.exec())
01557                         MythDB::DBError("NativeArchive::importVideo - "
01558                                         "insert videogenre", query);
01559 
01560                     // get new intid of genre
01561                     query.prepare("SELECT intid FROM videogenre "
01562                             "WHERE genre = :GENRE");
01563                     query.bindValue(":GENRE", genre);
01564                     if (query.exec() && query.next())
01565                     {
01566                         genreID = query.value(0).toInt();
01567                     }
01568                     else
01569                     {
01570                         LOG(VB_JOBQUEUE, LOG_ERR,
01571                             "Couldn't add genre to database");
01572                         continue;
01573                     }
01574                 }
01575 
01576                 // now link the genre to the videometadata
01577                 query.prepare("INSERT INTO videometadatagenre (idvideo, idgenre)"
01578                         "VALUES (:IDVIDEO, :IDGENRE);");
01579                 query.bindValue(":IDVIDEO", intid);
01580                 query.bindValue(":IDGENRE", genreID);
01581                 if (!query.exec())
01582                     MythDB::DBError("NativeArchive::importVideo - "
01583                                     "insert videometadatagenre", query);
01584             }
01585 
01586             LOG(VB_JOBQUEUE, LOG_INFO, "Inserted genre details into database");
01587         }
01588     }
01589 
01590     // copy country to db
01591     nodeList = itemNode.elementsByTagName("countries");
01592     if (nodeList.count() < 1)
01593     {
01594         LOG(VB_JOBQUEUE, LOG_INFO, "No 'countries' element found in XML file");
01595     }
01596     else
01597     {
01598         n = nodeList.item(0);
01599         QDomElement countriesNode = n.toElement();
01600 
01601         nodeList = countriesNode.elementsByTagName("country");
01602         if (nodeList.count() < 1)
01603         {
01604             LOG(VB_JOBQUEUE, LOG_WARNING,
01605                 "Couldn't find any 'country' elements in XML file");
01606         }
01607         else
01608         {
01609             for (int x = 0; x < nodeList.count(); x++)
01610             {
01611                 n = nodeList.item(x);
01612                 QDomElement e = n.toElement();
01613                 int countryID = e.attribute("intid").toInt();
01614                 QString country = e.attribute("country");
01615 
01616                 // see if this country already exists
01617                 query.prepare("SELECT intid FROM videocountry "
01618                         "WHERE country = :COUNTRY");
01619                 query.bindValue(":COUNTRY", country);
01620                 if (query.exec() && query.next())
01621                 {
01622                     countryID = query.value(0).toInt();
01623                 }
01624                 else
01625                 {
01626                     // country doesn't exist so add it
01627                     query.prepare("INSERT INTO videocountry (country) VALUES(:COUNTRY);");
01628                     query.bindValue(":COUNTRY", country);
01629                     if (!query.exec())
01630                         MythDB::DBError("NativeArchive::importVideo - "
01631                                         "insert videocountry", query);
01632 
01633                     // get new intid of country
01634                     query.prepare("SELECT intid FROM videocountry "
01635                             "WHERE country = :COUNTRY");
01636                     query.bindValue(":COUNTRY", country);
01637                     if (query.exec() && query.next())
01638                     {
01639                         countryID = query.value(0).toInt();
01640                     }
01641                     else
01642                     {
01643                         LOG(VB_JOBQUEUE, LOG_ERR,
01644                             "Couldn't add country to database");
01645                         continue;
01646                     }
01647                 }
01648 
01649                 // now link the country to the videometadata
01650                 query.prepare("INSERT INTO videometadatacountry (idvideo, idcountry)"
01651                         "VALUES (:IDVIDEO, :IDCOUNTRY);");
01652                 query.bindValue(":IDVIDEO", intid);
01653                 query.bindValue(":IDCOUNTRY", countryID);
01654                 if (!query.exec())
01655                     MythDB::DBError("NativeArchive::importVideo - "
01656                                     "insert videometadatacountry", query);
01657             }
01658 
01659             LOG(VB_JOBQUEUE, LOG_INFO,
01660                 "Inserted country details into database");
01661         }
01662     }
01663 
01664     // fix the category id
01665     nodeList = itemNode.elementsByTagName("category");
01666     if (nodeList.count() < 1)
01667     {
01668         LOG(VB_JOBQUEUE, LOG_ERR, "No 'category' element found in XML file");
01669     }
01670     else
01671     {
01672         n = nodeList.item(0);
01673         QDomElement e = n.toElement();
01674         int categoryID = e.attribute("intid").toInt();
01675         QString category = e.attribute("category");
01676         // see if this category already exists
01677         query.prepare("SELECT intid FROM videocategory "
01678                 "WHERE category = :CATEGORY");
01679         query.bindValue(":CATEGORY", category);
01680         if (query.exec() && query.next())
01681         {
01682             categoryID = query.value(0).toInt();
01683         }
01684         else
01685         {
01686             // category doesn't exist so add it
01687             query.prepare("INSERT INTO videocategory (category) VALUES(:CATEGORY);");
01688             query.bindValue(":CATEGORY", category);
01689             if (!query.exec())
01690                 MythDB::DBError("NativeArchive::importVideo - "
01691                                 "insert videocategory", query);
01692 
01693             // get new intid of category
01694             query.prepare("SELECT intid FROM videocategory "
01695                     "WHERE category = :CATEGORY");
01696             query.bindValue(":CATEGORY", category);
01697             if (query.exec() && query.next())
01698             {
01699                 categoryID = query.value(0).toInt();
01700             }
01701             else
01702             {
01703                 LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't add category to database");
01704                 categoryID = 0;
01705             }
01706         }
01707 
01708         // now fix the categoryid in the videometadata
01709         query.prepare("UPDATE videometadata "
01710                 "SET category = :CATEGORY "
01711                 "WHERE intid = :INTID;");
01712         query.bindValue(":CATEGORY", categoryID);
01713         query.bindValue(":INTID", intid);
01714         if (!query.exec())
01715             MythDB::DBError("NativeArchive::importVideo - "
01716                             "update category", query);
01717 
01718         LOG(VB_JOBQUEUE, LOG_INFO, "Fixed the category in the database");
01719     }
01720 
01721     LOG(VB_JOBQUEUE, LOG_INFO, "Import completed OK");
01722 
01723     return 0;
01724 }
01725 
01726 QString NativeArchive::findNodeText(const QDomElement &elem, const QString &nodeName)
01727 {
01728     QDomNodeList nodeList = elem.elementsByTagName(nodeName);
01729     if (nodeList.count() < 1)
01730     {
01731         LOG(VB_GENERAL, LOG_ERR,
01732             QString("Couldn't find a '%1' element in XML file") .arg(nodeName));
01733         return "";
01734     }
01735 
01736     QDomNode n = nodeList.item(0);
01737     QDomElement e = n.toElement();
01738     QString res = "";
01739 
01740     for (QDomNode node = e.firstChild(); !node.isNull();
01741          node = node.nextSibling())
01742     {
01743         QDomText t = node.toText();
01744         if (!t.isNull())
01745         {
01746             res = t.data();
01747             break;
01748         }
01749     }
01750 
01751     // some fixups
01752     // FIXME could be a lot smarter
01753     if (nodeName == "recgroup")
01754     {
01755         res = "Default";
01756     }
01757     else if (nodeName == "recordid")
01758     {
01759         res = "";
01760     }
01761     else if (nodeName == "seriesid")
01762     {
01763         res = "";
01764     }
01765     else if (nodeName == "programid")
01766     {
01767         res = "";
01768     }
01769     else if (nodeName == "playgroup")
01770     {
01771         res = "Default";
01772     }
01773     else if (nodeName == "profile")
01774     {
01775         res = "";
01776     }
01777 
01778     return res;
01779 }
01780 
01781 static void clearArchiveTable(void)
01782 {
01783     MSqlQuery query(MSqlQuery::InitCon());
01784     query.prepare("DELETE FROM archiveitems;");
01785 
01786     if (!query.exec())
01787         MythDB::DBError("delete archiveitems", query);
01788 }
01789 
01790 static int doNativeArchive(const QString &jobFile)
01791 {
01792     gCoreContext->SaveSetting("MythArchiveLastRunType", "Native Export");
01793     gCoreContext->SaveSetting("MythArchiveLastRunStart", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm"));
01794     gCoreContext->SaveSetting("MythArchiveLastRunStatus", "Running");
01795 
01796     NativeArchive na;
01797     int res = na.doNativeArchive(jobFile);
01798     gCoreContext->SaveSetting("MythArchiveLastRunEnd", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm"));
01799     gCoreContext->SaveSetting("MythArchiveLastRunStatus", (res == 0 ? "Success" : "Failed"));
01800 
01801     // clear the archiveitems table if succesful
01802     if (res == 0)
01803         clearArchiveTable();
01804 
01805     return res;
01806 }
01807 
01808 static int doImportArchive(const QString &inFile, int chanID)
01809 {
01810     NativeArchive na;
01811     return na.doImportArchive(inFile, chanID);
01812 }
01813 
01814 // Note: copied this function from myth_imgconvert.cpp -- dtk 2009-08-17
01815 static int myth_sws_img_convert(
01816     AVPicture *dst, PixelFormat dst_pix_fmt, AVPicture *src,
01817     PixelFormat pix_fmt, int width, int height)
01818 {
01819     static QMutex lock;
01820     QMutexLocker locker(&lock);
01821 
01822     static struct SwsContext *convert_ctx;
01823 
01824     convert_ctx = sws_getCachedContext(convert_ctx, width, height, pix_fmt,
01825                                        width, height, dst_pix_fmt,
01826                                        SWS_FAST_BILINEAR, NULL, NULL, NULL);
01827     if (!convert_ctx)
01828     {
01829         LOG(VB_GENERAL, LOG_ERR, "myth_sws_img_convert: Cannot initialize "
01830                                  "the image conversion context");
01831         return -1;
01832     }
01833 
01834     sws_scale(convert_ctx, src->data, src->linesize,
01835               0, height, dst->data, dst->linesize);
01836 
01837     return 0;
01838 }
01839 
01840 static int grabThumbnail(QString inFile, QString thumbList, QString outFile, int frameCount)
01841 {
01842     av_register_all();
01843 
01844     AVFormatContext *inputFC = NULL;
01845 
01846     // Open recording
01847     LOG(VB_JOBQUEUE, LOG_INFO, QString("grabThumbnail(): Opening '%1'")
01848             .arg(inFile));
01849 
01850     QByteArray inFileBA = inFile.toLocal8Bit();
01851 
01852     int ret = avformat_open_input(&inputFC, inFileBA.constData(), NULL, NULL);
01853     if (ret)
01854     {
01855         LOG(VB_JOBQUEUE, LOG_ERR, "grabThumbnail(): Couldn't open input file" +
01856                                   ENO);
01857         return 1;
01858     }
01859 
01860     // Getting stream information
01861     if ((ret = avformat_find_stream_info(inputFC, NULL)) < 0)
01862     {
01863         LOG(VB_JOBQUEUE, LOG_ERR,
01864             QString("Couldn't get stream info, error #%1").arg(ret));
01865         avformat_close_input(&inputFC);
01866         inputFC = NULL;
01867         return 1;
01868     }
01869 
01870     // find the first video stream
01871     int videostream = -1, width, height;
01872     float fps;
01873 
01874     for (uint i = 0; i < inputFC->nb_streams; i++)
01875     {
01876         AVStream *st = inputFC->streams[i];
01877         if (inputFC->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
01878         {
01879             videostream = i;
01880             width = st->codec->width;
01881             height = st->codec->height;
01882             if (st->r_frame_rate.den && st->r_frame_rate.num)
01883                 fps = av_q2d(st->r_frame_rate);
01884             else
01885                 fps = 1/av_q2d(st->time_base);
01886             break;
01887         }
01888     }
01889 
01890     if (videostream == -1)
01891     {
01892         LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't find a video stream");
01893         return 1;
01894     }
01895 
01896     // get the codec context for the video stream
01897     AVCodecContext *codecCtx = inputFC->streams[videostream]->codec;
01898 
01899     // get decoder for video stream
01900     AVCodec * codec = avcodec_find_decoder(codecCtx->codec_id);
01901 
01902     if (codec == NULL)
01903     {
01904         LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't find codec for video stream");
01905         return 1;
01906     }
01907 
01908     // open codec
01909     if (avcodec_open2(codecCtx, codec, NULL) < 0)
01910     {
01911         LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't open codec for video stream");
01912         return 1;
01913     }
01914 
01915     // get list of required thumbs
01916     QStringList list = thumbList.split(",", QString::SkipEmptyParts);
01917     AVFrame *frame = avcodec_alloc_frame();
01918     AVPacket pkt;
01919     AVPicture orig;
01920     AVPicture retbuf;
01921     memset(&orig, 0, sizeof(AVPicture));
01922     memset(&retbuf, 0, sizeof(AVPicture));
01923 
01924     int bufflen = width * height * 4;
01925     unsigned char *outputbuf = new unsigned char[bufflen];
01926 
01927     int frameNo = -1, thumbCount = 0;
01928     int frameFinished = 0;
01929     int keyFrame;
01930 
01931     while (av_read_frame(inputFC, &pkt) >= 0)
01932     {
01933         if (pkt.stream_index == videostream)
01934         {
01935             frameNo++;
01936             if (list[thumbCount].toInt() == (int)(frameNo / fps))
01937             {
01938                 thumbCount++;
01939 
01940                 avcodec_flush_buffers(codecCtx);
01941                 avcodec_decode_video2(codecCtx, frame, &frameFinished, &pkt);
01942                 keyFrame = frame->key_frame;
01943 
01944                 while (!frameFinished || !keyFrame)
01945                 {
01946                     av_free_packet(&pkt);
01947                     int res = av_read_frame(inputFC, &pkt);
01948                     if (res < 0)
01949                         break;
01950                     if (pkt.stream_index == videostream)
01951                     {
01952                         frameNo++;
01953                         avcodec_decode_video2(codecCtx, frame, &frameFinished, &pkt);
01954                         keyFrame = frame->key_frame;
01955                     }
01956                 }
01957 
01958                 if (frameFinished)
01959                 {
01960                     // work out what format to save to
01961                     QString saveFormat = "JPEG";
01962                     if (outFile.right(4) == ".png")
01963                         saveFormat = "PNG";
01964 
01965                     int count = 0;
01966                     while (count < frameCount)
01967                     {
01968                         QString filename = outFile;
01969                         if (filename.contains("%1") && filename.contains("%2"))
01970                             filename = filename.arg(thumbCount).arg(count+1);
01971                         else if (filename.contains("%1"))
01972                             filename = filename.arg(thumbCount);
01973 
01974                         avpicture_fill(&retbuf, outputbuf,
01975                                        PIX_FMT_RGB32, width, height);
01976 
01977                         avpicture_deinterlace((AVPicture*)frame,
01978                                               (AVPicture*)frame,
01979                                               codecCtx->pix_fmt, width, height);
01980 
01981 
01982                         myth_sws_img_convert(
01983                                     &retbuf, PIX_FMT_RGB32,
01984                                     (AVPicture*) frame,
01985                                     codecCtx->pix_fmt, width, height);
01986 
01987                         QImage img(outputbuf, width, height,
01988                                    QImage::Format_RGB32);
01989 
01990                         if (!img.save(filename, qPrintable(saveFormat)))
01991                         {
01992                             LOG(VB_GENERAL, LOG_ERR,
01993                                 QString("grabThumbnail(): Failed to save "
01994                                         "thumb: '%1'")
01995                                     .arg(filename));
01996                         }
01997 
01998                         count++;
01999 
02000                         if (count <= frameCount)
02001                         {
02002                             //grab next frame
02003                             frameFinished = false;
02004                             while (!frameFinished)
02005                             {
02006                                 int res = av_read_frame(inputFC, &pkt);
02007                                 if (res < 0)
02008                                     break;
02009                                 if (pkt.stream_index == videostream)
02010                                 {
02011                                     frameNo++;
02012                                     avcodec_decode_video2(codecCtx, frame,
02013                                                          &frameFinished,
02014                                                          &pkt);
02015                                 }
02016                             }
02017                         }
02018                     }
02019                 }
02020 
02021                 if (thumbCount >= (int) list.count())
02022                     break;
02023             }
02024         }
02025 
02026         av_free_packet(&pkt);
02027     }
02028 
02029     if (outputbuf)
02030         delete[] outputbuf;
02031 
02032     // free the frame
02033     av_free(frame);
02034 
02035     // close the codec
02036     avcodec_close(codecCtx);
02037 
02038     // close the video file
02039     avformat_close_input(&inputFC);
02040 
02041     return 0;
02042 }
02043 
02044 static int64_t getFrameCount(AVFormatContext *inputFC, int vid_id)
02045 {
02046     AVPacket pkt;
02047     int64_t count = 0;
02048 
02049     LOG(VB_JOBQUEUE, LOG_INFO, "Calculating frame count");
02050 
02051     av_init_packet(&pkt);
02052 
02053     while (av_read_frame(inputFC, &pkt) >= 0)
02054     {
02055         if (pkt.stream_index == vid_id)
02056         {
02057             count++;
02058         }
02059         av_free_packet(&pkt);
02060     }
02061 
02062     return count;
02063 }
02064 
02065 static int64_t getCutFrames(const QString &filename, int64_t lastFrame)
02066 {
02067     // only wont the filename
02068     QString basename = filename;
02069     int pos = filename.lastIndexOf('/');
02070     if (pos > 0)
02071         basename = filename.mid(pos + 1);
02072 
02073     ProgramInfo *progInfo = getProgramInfoForFile(basename);
02074     if (!progInfo)
02075         return 0;
02076 
02077     if (progInfo->IsVideo())
02078     {
02079         delete progInfo;
02080         return 0;
02081     }
02082 
02083     frm_dir_map_t cutlist;
02084     frm_dir_map_t::iterator it;
02085     uint64_t frames = 0;
02086 
02087     progInfo->QueryCutList(cutlist);
02088 
02089     if (cutlist.size() == 0)
02090     {
02091         delete progInfo;
02092         return 0;
02093     }
02094 
02095     for (it = cutlist.begin(); it != cutlist.end();)
02096     {
02097         uint64_t start = 0, end = 0;
02098 
02099         if (it.value() == MARK_CUT_START)
02100         {
02101             start = it.key();
02102             ++it;
02103             if (it != cutlist.end())
02104             {
02105                 end = it.key();
02106                 ++it;
02107             }
02108             else
02109                 end = lastFrame;
02110         }
02111         else if (it.value() == MARK_CUT_END)
02112         {
02113             start = 0;
02114             end = it.key();
02115             ++it;
02116         }
02117         else
02118         {
02119             ++it;
02120             continue;
02121         }
02122 
02123         frames += end - start;
02124     }
02125 
02126     delete progInfo;
02127     return frames;
02128 }
02129 
02130 static int64_t getFrameCount(const QString &filename, float fps)
02131 {
02132     // only wont the filename
02133     QString basename = filename;
02134     int pos = filename.lastIndexOf('/');
02135     if (pos > 0)
02136         basename = filename.mid(pos + 1);
02137 
02138     int keyframedist = -1;
02139     frm_pos_map_t posMap;
02140 
02141     ProgramInfo *progInfo = getProgramInfoForFile(basename);
02142     if (!progInfo)
02143         return 0;
02144 
02145     progInfo->QueryPositionMap(posMap, MARK_GOP_BYFRAME);
02146     if (!posMap.empty())
02147     {
02148         keyframedist = 1;
02149     }
02150     else
02151     {
02152         progInfo->QueryPositionMap(posMap, MARK_GOP_START);
02153         if (!posMap.empty())
02154         {
02155             keyframedist = 15;
02156             if (fps < 26 && fps > 24)
02157                 keyframedist = 12;
02158         }
02159         else
02160         {
02161             progInfo->QueryPositionMap(posMap, MARK_KEYFRAME);
02162             if (!posMap.empty())
02163             {
02164                 // keyframedist should be set in the fileheader so no
02165                 // need to try to determine it in this case
02166                 return 0;
02167             }
02168         }
02169     }
02170 
02171     if (posMap.empty())
02172         return 0; // no position map in recording
02173 
02174     frm_pos_map_t::const_iterator it = posMap.end();
02175     --it;
02176     uint64_t totframes = it.key() * keyframedist;
02177     return totframes;
02178 }
02179 
02180 static int getFileInfo(QString inFile, QString outFile, int lenMethod)
02181 {
02182     const char *type = NULL;
02183 
02184     av_register_all();
02185 
02186     AVFormatContext *inputFC = NULL;
02187     AVInputFormat *fmt = NULL;
02188 
02189     if (type)
02190         fmt = av_find_input_format(type);
02191 
02192     // Open recording
02193     LOG(VB_JOBQUEUE, LOG_INFO, QString("getFileInfo(): Opening '%1'")
02194             .arg(inFile));
02195 
02196     QByteArray inFileBA = inFile.toLocal8Bit();
02197 
02198     int ret = avformat_open_input(&inputFC, inFileBA.constData(), fmt, NULL);
02199 
02200     if (ret)
02201     {
02202         LOG(VB_JOBQUEUE, LOG_ERR, "getFileInfo(): Couldn't open input file" +
02203                                   ENO);
02204         return 1;
02205     }
02206 
02207     // Getting stream information
02208     ret = avformat_find_stream_info(inputFC, NULL);
02209 
02210     if (ret < 0)
02211     {
02212         LOG(VB_JOBQUEUE, LOG_ERR,
02213             QString("Couldn't get stream info, error #%1").arg(ret));
02214         avformat_close_input(&inputFC);
02215         inputFC = NULL;
02216         return 1;
02217     }
02218 
02219     av_estimate_timings(inputFC, 0);
02220 
02221     // Dump stream information
02222     av_dump_format(inputFC, 0, inFileBA.constData(), 0);
02223 
02224     QDomDocument doc("FILEINFO");
02225 
02226     QDomElement root = doc.createElement("file");
02227     doc.appendChild(root);
02228     root.setAttribute("type", inputFC->iformat->name);
02229     root.setAttribute("filename", inFile);
02230 
02231     QDomElement streams = doc.createElement("streams");
02232 
02233     root.appendChild(streams);
02234     streams.setAttribute("count", inputFC->nb_streams);
02235     int ffmpegIndex = 0;
02236     uint duration = 0;
02237 
02238     for (uint i = 0; i < inputFC->nb_streams; i++)
02239     {
02240         AVStream *st = inputFC->streams[i];
02241         char buf[256];
02242 
02243         avcodec_string(buf, sizeof(buf), st->codec, false);
02244 
02245         switch (inputFC->streams[i]->codec->codec_type)
02246         {
02247             case AVMEDIA_TYPE_VIDEO:
02248             {
02249                 QStringList param = QString(buf).split(',', QString::SkipEmptyParts);
02250                 QString codec = param[0].remove("Video:", Qt::CaseInsensitive);
02251                 QDomElement stream = doc.createElement("video");
02252                 stream.setAttribute("streamindex", i);
02253                 stream.setAttribute("ffmpegindex", ffmpegIndex++);
02254                 stream.setAttribute("codec", codec.trimmed());
02255                 stream.setAttribute("width", st->codec->width);
02256                 stream.setAttribute("height", st->codec->height);
02257                 stream.setAttribute("bitrate", st->codec->bit_rate);
02258 
02259                 float fps;
02260                 if (st->r_frame_rate.den && st->r_frame_rate.num)
02261                     fps = av_q2d(st->r_frame_rate);
02262                 else
02263                     fps = 1/av_q2d(st->time_base);
02264 
02265                 stream.setAttribute("fps", fps);
02266 
02267                 if (st->codec->sample_aspect_ratio.den && st->codec->sample_aspect_ratio.num)
02268                 {
02269                     float aspect_ratio = av_q2d(st->codec->sample_aspect_ratio);
02270                     if (QString(inputFC->iformat->name) != "nuv")
02271                         aspect_ratio = ((float)st->codec->width / st->codec->height) * aspect_ratio;
02272 
02273                     stream.setAttribute("aspectratio", aspect_ratio);
02274                 }
02275                 else
02276                     stream.setAttribute("aspectratio", "N/A");
02277 
02278                 stream.setAttribute("id", st->id);
02279 
02280                 if (st->start_time != (int) AV_NOPTS_VALUE)
02281                 {
02282                     int secs, us;
02283                     secs = st->start_time / AV_TIME_BASE;
02284                     us = st->start_time % AV_TIME_BASE;
02285                     stream.setAttribute("start_time", QString("%1.%2")
02286                             .arg(secs).arg(av_rescale(us, 1000000, AV_TIME_BASE)));
02287                 }
02288                 else
02289                     stream.setAttribute("start_time", 0);
02290 
02291                 streams.appendChild(stream);
02292 
02293                 // TODO: probably should add a better way to choose which
02294                 // video stream we use to calc the duration
02295                 if (duration == 0)
02296                 {
02297                     int64_t frameCount = 0;
02298 
02299                     switch (lenMethod)
02300                     {
02301                         case 0:
02302                         {
02303                             // use duration guess from avformat
02304                             if (inputFC->duration != (uint) AV_NOPTS_VALUE)
02305                             {
02306                                 duration = (uint) (inputFC->duration / AV_TIME_BASE);
02307                                 root.setAttribute("duration", duration);
02308                                 LOG(VB_JOBQUEUE, LOG_INFO,
02309                                     QString("duration = %1") .arg(duration));
02310                                 frameCount = (int64_t)(duration * fps);
02311                             }
02312                             else
02313                                 root.setAttribute("duration", "N/A");
02314                             break;
02315                         }
02316                         case 1:
02317                         {
02318                             // calc duration of the file by counting the video frames
02319                             frameCount = getFrameCount(inputFC, i);
02320                             LOG(VB_JOBQUEUE, LOG_INFO,
02321                                 QString("frames = %1").arg(frameCount));
02322                             duration = (uint)(frameCount / fps);
02323                             LOG(VB_JOBQUEUE, LOG_INFO,
02324                                 QString("duration = %1").arg(duration));
02325                             root.setAttribute("duration", duration);
02326                             break;
02327                         }
02328                         case 2:
02329                         {
02330                             // use info from pos map in db
02331                             // (only useful if the file is a myth recording)
02332                             frameCount = getFrameCount(inFile, fps);
02333                             LOG(VB_JOBQUEUE, LOG_INFO,
02334                                 QString("frames = %1").arg(frameCount));
02335                             duration = (uint)(frameCount / fps);
02336                             LOG(VB_JOBQUEUE, LOG_INFO,
02337                                 QString("duration = %1").arg(duration));
02338                             root.setAttribute("duration", duration);
02339                             break;
02340                         }
02341                         default:
02342                             root.setAttribute("duration", "N/A");
02343                             LOG(VB_JOBQUEUE, LOG_ERR,
02344                                 QString("Unknown lenMethod (%1)")
02345                                     .arg(lenMethod));
02346                     }
02347 
02348                     // add duration after all cuts are removed
02349                     int64_t cutFrames = getCutFrames(inFile, frameCount);
02350                     LOG(VB_JOBQUEUE, LOG_INFO,
02351                         QString("cutframes = %1").arg(cutFrames));
02352                     int cutduration = (int)(cutFrames / fps);
02353                     LOG(VB_JOBQUEUE, LOG_INFO,
02354                         QString("cutduration = %1").arg(cutduration));
02355                     root.setAttribute("cutduration", duration - cutduration);
02356                 }
02357 
02358                 break;
02359             }
02360 
02361             case AVMEDIA_TYPE_AUDIO:
02362             {
02363                 QStringList param = QString(buf).split(',', QString::SkipEmptyParts);
02364                 QString codec = param[0].remove("Audio:", Qt::CaseInsensitive);
02365 
02366                 QDomElement stream = doc.createElement("audio");
02367                 stream.setAttribute("streamindex", i);
02368                 stream.setAttribute("ffmpegindex", ffmpegIndex++);
02369 
02370                 // change any streams identified as "liba52" to "AC3" which is what
02371                 // the mythburn.py script expects to get.
02372                 if (codec.trimmed().toLower() == "liba52")
02373                     stream.setAttribute("codec", "AC3");
02374                 else
02375                     stream.setAttribute("codec", codec.trimmed());
02376 
02377                 stream.setAttribute("channels", st->codec->channels);
02378 
02379                 AVDictionaryEntry *metatag =
02380                     av_dict_get(st->metadata, "language", NULL, 0);
02381                 if (metatag)
02382                     stream.setAttribute("language", metatag->value);
02383                 else
02384                     stream.setAttribute("language", "N/A");
02385 
02386                 stream.setAttribute("id", st->id);
02387 
02388                 stream.setAttribute("samplerate", st->codec->sample_rate);
02389                 stream.setAttribute("bitrate", st->codec->bit_rate);
02390 
02391                 if (st->start_time != (int) AV_NOPTS_VALUE)
02392                 {
02393                     int secs, us;
02394                     secs = st->start_time / AV_TIME_BASE;
02395                     us = st->start_time % AV_TIME_BASE;
02396                     stream.setAttribute("start_time", QString("%1.%2")
02397                             .arg(secs).arg(av_rescale(us, 1000000, AV_TIME_BASE)));
02398                 }
02399                 else
02400                     stream.setAttribute("start_time", 0);
02401 
02402                 streams.appendChild(stream);
02403 
02404                 break;
02405             }
02406 
02407             case AVMEDIA_TYPE_SUBTITLE:
02408             {
02409                 QStringList param = QString(buf).split(',', QString::SkipEmptyParts);
02410                 QString codec = param[0].remove("Subtitle:", Qt::CaseInsensitive);
02411 
02412                 QDomElement stream = doc.createElement("subtitle");
02413                 stream.setAttribute("streamindex", i);
02414                 stream.setAttribute("ffmpegindex", ffmpegIndex++);
02415                 stream.setAttribute("codec", codec.trimmed());
02416 
02417                 AVDictionaryEntry *metatag =
02418                     av_dict_get(st->metadata, "language", NULL, 0);
02419                 if (metatag)
02420                     stream.setAttribute("language", metatag->value);
02421                 else
02422                     stream.setAttribute("language", "N/A");
02423 
02424                 stream.setAttribute("id", st->id);
02425 
02426                 streams.appendChild(stream);
02427 
02428                 break;
02429             }
02430 
02431             case AVMEDIA_TYPE_DATA:
02432             {
02433                 QDomElement stream = doc.createElement("data");
02434                 stream.setAttribute("streamindex", i);
02435                 stream.setAttribute("codec", buf);
02436                 streams.appendChild(stream);
02437 
02438                 break;
02439             }
02440 
02441             default:
02442                 LOG(VB_JOBQUEUE, LOG_ERR,
02443                     QString("Skipping unsupported codec %1 on stream %2")
02444                         .arg(inputFC->streams[i]->codec->codec_type).arg(i));
02445                 break;
02446         }
02447     }
02448 
02449     // finally save the xml to the file
02450     QFile f(outFile);
02451     if (!f.open(QIODevice::WriteOnly))
02452     {
02453         LOG(VB_JOBQUEUE, LOG_ERR,
02454             "Failed to open file for writing - " + outFile);
02455         return 1;
02456     }
02457 
02458     QTextStream t(&f);
02459     t << doc.toString(4);
02460     f.close();
02461 
02462     // Close input file
02463     avformat_close_input(&inputFC);
02464     inputFC = NULL;
02465 
02466     return 0;
02467 }
02468 
02469 static int getDBParamters(QString outFile)
02470 {
02471     DatabaseParams params = gContext->GetDatabaseParams();
02472 
02473     // save the db paramters to the file
02474     QFile f(outFile);
02475     if (!f.open(QIODevice::WriteOnly))
02476     {
02477         LOG(VB_GENERAL, LOG_ERR,
02478             QString("MythArchiveHelper: Failed to open file for writing - %1")
02479                 .arg(outFile));
02480         return 1;
02481     }
02482 
02483     QTextStream t(&f);
02484     t << params.dbHostName << endl;
02485     t << params.dbUserName << endl;
02486     t << params.dbPassword << endl;
02487     t << params.dbName << endl;
02488     t << gCoreContext->GetHostName() << endl;
02489     t << GetInstallPrefix() << endl;
02490     f.close();
02491 
02492     return 0;
02493 }
02494 
02495 static int isRemote(QString filename)
02496 {
02497     // check if the file exists
02498     if (!QFile::exists(filename))
02499         return 0;
02500 
02501     struct statfs statbuf;
02502     memset(&statbuf, 0, sizeof(statbuf));
02503 
02504 #if CONFIG_DARWIN
02505     if ((statfs(qPrintable(filename), &statbuf) == 0) &&
02506         ((!strcmp(statbuf.f_fstypename, "nfs")) ||      // NFS|FTP
02507             (!strcmp(statbuf.f_fstypename, "afpfs")) || // ApplShr
02508             (!strcmp(statbuf.f_fstypename, "smbfs"))))  // SMB
02509         return 2;
02510 #elif __linux__
02511     if ((statfs(qPrintable(filename), &statbuf) == 0) &&
02512         ((statbuf.f_type == 0x6969) ||      // NFS
02513             (statbuf.f_type == 0x517B)))    // SMB
02514         return 2;
02515 #endif
02516 
02517     return 1;
02518 }
02519 
02520 class MPUBLIC MythArchiveHelperCommandLineParser : public MythCommandLineParser
02521 {
02522   public:
02523     MythArchiveHelperCommandLineParser();
02524     void LoadArguments(void);
02525 };
02526 
02527 MythArchiveHelperCommandLineParser::MythArchiveHelperCommandLineParser() :
02528     MythCommandLineParser("mytharchivehelper")
02529 { LoadArguments(); }
02530 
02531 void MythArchiveHelperCommandLineParser::LoadArguments(void)
02532 {
02533     addHelp();
02534     addVersion();
02535     addLogging();
02536 
02537     add(QStringList( QStringList() << "-t" << "--createthumbnail" ),
02538             "createthumbnail", false,
02539             "Create one or more thumbnails\n"
02540             "Requires: --infile, --thumblist, --outfile\n"
02541             "Optional: --framecount", "");
02542     add("--infile", "infile", "",
02543             "Input file name\n"
02544             "Used with: --createthumbnail, --getfileinfo, --isremote, "
02545             "--sup2dast, --importarchive", "");
02546     add("--outfile", "outfile", "",
02547             "Output file name\n"
02548             "Used with: --createthumbnail, --getfileinfo, --getdbparameters, "
02549             "--nativearchive\n"
02550             "When used with --createthumbnail: eg 'thumb%1-%2.jpg'\n"
02551             "  %1 will be replaced with the no. of the thumb\n"
02552             "  %2 will be replaced with the frame no.", "");
02553     add("--thumblist", "thumblist", "",
02554             "Comma-separated list of required thumbs (in seconds)\n"
02555             "Used with: --createthumbnail","");
02556     add("--framecount", "framecount", 1,
02557             "Number of frames to grab (default 1)\n"
02558             "Used with: --createthumbnail", "");
02559 
02560     add(QStringList( QStringList() << "-i" << "--getfileinfo" ),
02561             "getfileinfo", false,
02562             "Write file info about infile to outfile\n"
02563             "Requires: --infile, --outfile, --method", "");
02564     add("--method", "method", 0,
02565             "Method of file duration calculation\n"
02566             "Used with: --getfileinfo\n"
02567             "  0 = use av_estimate_timings() (quick but not very accurate - "
02568             "default)\n"
02569             "  1 = read all frames (most accurate but slow)\n"
02570             "  2 = use position map in DB (quick, only works for MythTV "
02571             "recordings)", "");
02572 
02573     add(QStringList( QStringList() << "-p" << "--getdbparameters" ),
02574             "getdbparameters", false,
02575             "Write the mysql database parameters to outfile\n"
02576             "Requires: --outfile", "");
02577 
02578     add(QStringList( QStringList() << "-n" << "--nativearchive" ),
02579             "nativearchive", false,
02580             "Archive files to a native archive format\n"
02581             "Requires: --outfile", "");
02582 
02583     add(QStringList( QStringList() << "-f" << "--importarchive" ),
02584             "importarchive", false,
02585             "Import an archived file\n"
02586             "Requires: --infile, --chanid", "");
02587     add("--chanid", "chanid", -1,
02588             "Channel ID to use when inserting records in DB\n"
02589             "Used with: --importarchive", "");
02590 
02591     add(QStringList( QStringList() << "-r" << "--isremote" ),
02592             "isremote", false,
02593             "Check if infile is on a remote filesystem\n"
02594             "Requires: --infile\n"
02595             "Returns:   0 on error or file not found\n"
02596             "         - 1 file is on a local filesystem\n"
02597             "         - 2 file is on a remote filesystem", "");
02598 
02599     add(QStringList( QStringList() << "-b" << "--burndvd" ),
02600             "burndvd", false,
02601             "Burn a created DVD to a blank disc\n"
02602             "Optional: --mediatype, --erasedvdrw, --nativeformat", "");
02603     add("--mediatype", "mediatype", 0,
02604             "Type of media to burn\n"
02605             "Used with: --burndvd\n"
02606             "  0 = single layer DVD (default)\n"
02607             "  1 = dual layer DVD\n"
02608             "  2 = rewritable DVD", "");
02609     add("--erasedvdrw", "erasedvdrw", false,
02610             "Force an erase of DVD-R/W Media\n"
02611             "Used with: --burndvd (optional)", "");
02612     add("--nativeformat", "nativeformat", false,
02613             "Archive is a native archive format\n"
02614             "Used with: --burndvd (optional)", "");
02615 
02616     add(QStringList( QStringList() << "-s" << "--sup2dast" ),
02617             "sup2dast", false,
02618             "Convert projectX subtitles to DVD subtitles\n"
02619             "Requires: --infile, --ifofile, --delay", "");
02620     add("--ifofile", "ifofile", "",
02621             "Filename of ifo file\n"
02622             "Used with: --sup2dast", "");
02623     add("--delay", "delay", 0,
02624             "Delay in ms to add to subtitles (default 0)\n"
02625             "Used with: --sup2dast", "");
02626 }
02627 
02628 
02629 
02630 int main(int argc, char **argv)
02631 {
02632     MythArchiveHelperCommandLineParser cmdline;
02633     if (!cmdline.Parse(argc, argv))
02634     {
02635         cmdline.PrintHelp();
02636         return GENERIC_EXIT_INVALID_CMDLINE;
02637     }
02638 
02639     if (cmdline.toBool("showhelp"))
02640     {
02641         cmdline.PrintHelp();
02642         return GENERIC_EXIT_OK;
02643     }
02644 
02645     if (cmdline.toBool("showversion"))
02646     {
02647         cmdline.PrintVersion();
02648         return GENERIC_EXIT_OK;
02649     }
02650 
02651     QCoreApplication a(argc, argv);
02652     QCoreApplication::setApplicationName("mytharchivehelper");
02653 
02654     // by default we only output our messages
02655     int retval;
02656     QString mask("jobqueue");
02657     if ((retval = cmdline.ConfigureLogging(mask)) != GENERIC_EXIT_OK)
02658         return retval;
02659 
02661     // Don't listen to console input
02662     close(0);
02663 
02664     //CleanupGuard callCleanup(cleanup);
02665     gContext = new MythContext(MYTH_BINARY_VERSION);
02666     if (!gContext->Init(false))
02667     {
02668         LOG(VB_GENERAL, LOG_ERR, "Failed to init MythContext, exiting.");
02669         delete gContext;
02670         gContext = NULL;
02671         return GENERIC_EXIT_NO_MYTHCONTEXT;
02672     }
02673 
02674     int res = 0;
02675     bool bGrabThumbnail   = cmdline.toBool("createthumbnail");
02676     bool bGetDBParameters = cmdline.toBool("getdbparameters");
02677     bool bNativeArchive   = cmdline.toBool("nativearchive");
02678     bool bImportArchive   = cmdline.toBool("importarchive");
02679     bool bGetFileInfo     = cmdline.toBool("getfileinfo");
02680     bool bIsRemote        = cmdline.toBool("isremote");
02681     bool bDoBurn          = cmdline.toBool("burndvd");
02682     bool bEraseDVDRW      = cmdline.toBool("erasedvdrw");
02683     bool bNativeFormat    = cmdline.toBool("nativeformat");;
02684     bool bSup2Dast        = cmdline.toBool("sup2dast");
02685 
02686     QString thumbList     = cmdline.toString("thumblist");
02687     QString inFile        = cmdline.toString("infile");
02688     QString outFile       = cmdline.toString("outfile");
02689     QString ifoFile       = cmdline.toString("ifofile");
02690 
02691     int mediaType         = cmdline.toUInt("mediatype");
02692     int lenMethod         = cmdline.toUInt("method");
02693     int chanID            = cmdline.toInt("chanid");
02694     int frameCount        = cmdline.toUInt("framecount");
02695     int delay             = cmdline.toUInt("delay");
02696 
02697     //  Check command line arguments
02698     if (bGrabThumbnail)
02699     {
02700         if (inFile.isEmpty())
02701         {
02702             LOG(VB_GENERAL, LOG_ERR, "Missing --infile in -t/--grabthumbnail "
02703                                      "option");
02704             return GENERIC_EXIT_INVALID_CMDLINE;
02705         }
02706 
02707         if (thumbList.isEmpty())
02708         {
02709             LOG(VB_GENERAL, LOG_ERR, "Missing --thumblist in -t/--grabthumbnail"
02710                                      " option");
02711             return GENERIC_EXIT_INVALID_CMDLINE;
02712         }
02713 
02714         if (outFile.isEmpty())
02715         {
02716             LOG(VB_GENERAL, LOG_ERR, "Missing --outfile in -t/--grabthumbnail "
02717                                      "option");
02718             return GENERIC_EXIT_INVALID_CMDLINE;
02719         }
02720     }
02721 
02722     if (bGetDBParameters)
02723     {
02724         if (outFile.isEmpty())
02725         {
02726             LOG(VB_GENERAL, LOG_ERR, "Missing argument to -p/--getdbparameters "
02727                                      "option");
02728             return GENERIC_EXIT_INVALID_CMDLINE;
02729         }
02730     }
02731 
02732     if (bIsRemote)
02733     {
02734         if (inFile.isEmpty())
02735         {
02736             LOG(VB_GENERAL, LOG_ERR,
02737                 "Missing argument to -r/--isremote option");
02738             return GENERIC_EXIT_INVALID_CMDLINE;
02739         }
02740     }
02741 
02742     if (bDoBurn)
02743     {
02744         if (mediaType < 0 || mediaType > 2)
02745         {
02746             LOG(VB_GENERAL, LOG_ERR, QString("Invalid mediatype given: %1")
02747                     .arg(mediaType));
02748             return GENERIC_EXIT_INVALID_CMDLINE;
02749         }
02750     }
02751 
02752     if (bNativeArchive)
02753     {
02754         if (outFile.isEmpty())
02755         {
02756             LOG(VB_GENERAL, LOG_ERR, "Missing argument to -n/--nativearchive "
02757                                      "option");
02758             return GENERIC_EXIT_INVALID_CMDLINE;
02759         }
02760     }
02761 
02762     if (bImportArchive)
02763     {
02764         if (inFile.isEmpty())
02765         {
02766             LOG(VB_GENERAL, LOG_ERR, "Missing --infile argument to "
02767                                      "-f/--importarchive option");
02768             return GENERIC_EXIT_INVALID_CMDLINE;
02769         }
02770     }
02771 
02772     if (bGetFileInfo)
02773     {
02774         if (inFile.isEmpty())
02775         {
02776             LOG(VB_GENERAL, LOG_ERR, "Missing --infile in -i/--getfileinfo "
02777                                      "option");
02778             return GENERIC_EXIT_INVALID_CMDLINE;
02779         }
02780 
02781         if (outFile.isEmpty())
02782         {
02783             LOG(VB_GENERAL, LOG_ERR, "Missing --outfile in -i/--getfileinfo "
02784                                      "option");
02785             return GENERIC_EXIT_INVALID_CMDLINE;
02786         }
02787     }
02788 
02789     if (bSup2Dast)
02790     {
02791         if (inFile.isEmpty())
02792         {
02793             LOG(VB_GENERAL, LOG_ERR,
02794                 "Missing --infile in -s/--sup2dast option");
02795             return GENERIC_EXIT_INVALID_CMDLINE;
02796         }
02797 
02798         if (ifoFile.isEmpty())
02799         {
02800             LOG(VB_GENERAL, LOG_ERR,
02801                 "Missing --ifofile in -s/--sup2dast option");
02802             return GENERIC_EXIT_INVALID_CMDLINE;
02803         }
02804     }
02805 
02806     if (bGrabThumbnail)
02807         res = grabThumbnail(inFile, thumbList, outFile, frameCount);
02808     else if (bGetDBParameters)
02809         res = getDBParamters(outFile);
02810     else if (bNativeArchive)
02811         res = doNativeArchive(outFile);
02812     else if (bImportArchive)
02813         res = doImportArchive(inFile, chanID);
02814     else if (bGetFileInfo)
02815         res = getFileInfo(inFile, outFile, lenMethod);
02816     else if (bIsRemote)
02817         res = isRemote(inFile);
02818     else if (bDoBurn)
02819         res = doBurnDVD(mediaType, bEraseDVDRW, bNativeFormat);
02820     else if (bSup2Dast)
02821     {
02822         QByteArray inFileBA = inFile.toLocal8Bit();
02823         QByteArray ifoFileBA = ifoFile.toLocal8Bit();
02824         res = sup2dast(inFileBA.constData(), ifoFileBA.constData(), delay);
02825     }
02826     else
02827         cmdline.PrintHelp();
02828 
02829     delete gContext;
02830     gContext = NULL;
02831 
02832     exit(res);
02833 }
02834 
02835 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends