|
MythTV
0.26-pre
|
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
1.7.6.1