MythTV  master
mytharchivehelper.cpp
Go to the documentation of this file.
1 /* -*- Mode: c++ -*-
2  * vim: set expandtab tabstop=4 shiftwidth=4:
3  *
4  * Original Project
5  * MythTV http://www.mythtv.org
6  *
7  * Copyright (c) 2004, 2005 John Pullan <john@pullan.org>
8  * Copyright (c) 2009, Janne Grunau <janne-mythtv@grunau.be>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
24  *
25  */
26 
27 #include <cstdint>
28 #include <cstdlib>
29 #include <iostream>
30 #include <unistd.h>
31 
32 #include <QtGlobal>
33 #if defined(Q_OS_DARWIN) or defined(__FreeBSD__)
34 #include <sys/param.h>
35 #include <sys/mount.h>
36 #elif defined(__linux__)
37 #include <sys/vfs.h>
38 #endif
39 
40 // Qt headers
41 #include <QApplication>
42 #include <QDir>
43 #include <QDomElement>
44 #include <QFile>
45 #include <QImage>
46 #include <QMutex>
47 #include <QMutexLocker>
48 #include <QRegularExpression>
49 #include <QTextStream>
50 
51 // MythTV headers
52 #include <mythconfig.h>
53 #include <libmyth/mythavframe.h>
54 #include <libmyth/mythcontext.h>
55 #include <libmythbase/exitcodes.h>
58 #include <libmythbase/mythdate.h>
59 #include <libmythbase/mythdb.h>
60 #include <libmythbase/mythdirs.h>
65 #include <libmythbase/mythversion.h>
67 #include <libmythtv/mythavutil.h>
68 
69 extern "C" {
70  #include <libavcodec/avcodec.h>
71  #include <libavformat/avformat.h>
72  #include <libavutil/imgutils.h>
73  #include "external/pxsup2dast.h"
74 }
75 
76 // mytharchive headers
77 #include "../mytharchive/archiveutil.h"
78 #include "../mytharchive/remoteavformatcontext.h"
79 
81 {
82  public:
83  NativeArchive(void);
84  ~NativeArchive(void);
85 
86  static int doNativeArchive(const QString &jobFile);
87  static int doImportArchive(const QString &xmlFile, int chanID);
88  static bool copyFile(const QString &source, const QString &destination);
89  static int importRecording(const QDomElement &itemNode,
90  const QString &xmlFile, int chanID);
91  static int importVideo(const QDomElement &itemNode, const QString &xmlFile);
92  static int exportRecording(QDomElement &itemNode, const QString &saveDirectory);
93  static int exportVideo(QDomElement &itemNode, const QString &saveDirectory);
94  private:
95  static QString findNodeText(const QDomElement &elem, const QString &nodeName);
96  static int getFieldList(QStringList &fieldList, const QString &tableName);
97 };
98 
100 {
101  // create the lock file so the UI knows we're running
102  QString tempDir = getTempDirectory();
103  QFile file(tempDir + "/logs/mythburn.lck");
104 
105  if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
106  LOG(VB_GENERAL, LOG_ERR, "NativeArchive: Failed to create lock file");
107 
108  QString pid = QString("%1").arg(getpid());
109  file.write(pid.toLatin1());
110  file.close();
111 }
112 
114 {
115  // remove lock file
116  QString tempDir = getTempDirectory();
117  if (QFile::exists(tempDir + "/logs/mythburn.lck"))
118  QFile::remove(tempDir + "/logs/mythburn.lck");
119 }
120 
121 bool NativeArchive::copyFile(const QString &source, const QString &destination)
122 {
123  QString command = QString("mythutil --copyfile --infile '%1' --outfile '%2'")
124  .arg(source, destination);
125  uint res = myth_system(command);
126  if (res != GENERIC_EXIT_OK)
127  {
128  LOG(VB_JOBQUEUE, LOG_ERR,
129  QString("Failed while running %1. Result: %2").arg(command).arg(res));
130  return false;
131  }
132 
133  return true;
134 }
135 
136 static bool createISOImage(QString &sourceDirectory)
137 {
138  LOG(VB_JOBQUEUE, LOG_INFO, "Creating ISO image");
139 
140  QString tempDirectory = getTempDirectory();
141 
142  tempDirectory += "work/";
143 
144  QString mkisofs = gCoreContext->GetSetting("MythArchiveMkisofsCmd", "mkisofs");
145  QString command = mkisofs + " -R -J -V 'MythTV Archive' -o ";
146  command += tempDirectory + "mythburn.iso " + sourceDirectory;
147 
148  uint res = myth_system(command);
149  if (res != GENERIC_EXIT_OK)
150  {
151  LOG(VB_JOBQUEUE, LOG_ERR,
152  QString("Failed while running mkisofs. Result: %1") .arg(res));
153  return false;
154  }
155 
156  LOG(VB_JOBQUEUE, LOG_INFO, "Finished creating ISO image");
157  return true;
158 }
159 
160 static int burnISOImage(int mediaType, bool bEraseDVDRW, bool nativeFormat)
161 {
162  QString dvdDrive = gCoreContext->GetSetting("MythArchiveDVDLocation",
163  "/dev/dvd");
164  LOG(VB_JOBQUEUE, LOG_INFO, "Burning ISO image to " + dvdDrive);
165 
166  int driveSpeed = gCoreContext->GetNumSetting("MythArchiveDriveSpeed");
167  QString tempDirectory = getTempDirectory();
168 
169  tempDirectory += "work/";
170 
171  QString command = gCoreContext->GetSetting("MythArchiveGrowisofsCmd",
172  "growisofs");
173 
174  if (driveSpeed)
175  command += " -speed=" + QString::number(driveSpeed);
176 
177  if (nativeFormat)
178  {
179  if (mediaType == AD_DVD_RW && bEraseDVDRW)
180  {
181  command += " -use-the-force-luke -Z " + dvdDrive;
182  command += " -V 'MythTV Archive' -R -J " + tempDirectory;
183  }
184  else
185  {
186  command += " -Z " + dvdDrive;
187  command += " -V 'MythTV Archive' -R -J " + tempDirectory;
188  }
189  }
190  else
191  {
192  if (mediaType == AD_DVD_RW && bEraseDVDRW)
193  {
194  command += " -dvd-compat -use-the-force-luke -Z " + dvdDrive;
195  command += " -dvd-video -V 'MythTV DVD' " + tempDirectory + "/dvd";
196  }
197  else
198  {
199  command += " -dvd-compat -Z " + dvdDrive;
200  command += " -dvd-video -V 'MythTV DVD' " + tempDirectory + "/dvd";
201  }
202  }
203 
204  uint res = myth_system(command);
205  if (res != GENERIC_EXIT_OK)
206  {
207  LOG(VB_JOBQUEUE, LOG_ERR,
208  QString("Failed while running growisofs. Result: %1") .arg(res));
209  }
210  else
211  {
212  LOG(VB_JOBQUEUE, LOG_INFO, "Finished burning ISO image");
213  }
214 
215  return res;
216 }
217 
218 static int doBurnDVD(int mediaType, bool bEraseDVDRW, bool nativeFormat)
219 {
221  "MythArchiveLastRunStart",
223  gCoreContext->SaveSetting("MythArchiveLastRunStatus", "Running");
224 
225  int res = burnISOImage(mediaType, bEraseDVDRW, nativeFormat);
226 
228  "MythArchiveLastRunEnd",
230  gCoreContext->SaveSetting("MythArchiveLastRunStatus", "Success");
231  return res;
232 }
233 
234 int NativeArchive::doNativeArchive(const QString &jobFile)
235 {
236  QString tempDir = getTempDirectory();
237 
238  QDomDocument doc("archivejob");
239  QFile file(jobFile);
240  if (!file.open(QIODevice::ReadOnly))
241  {
242  LOG(VB_JOBQUEUE, LOG_ERR, "Could not open job file: " + jobFile);
243  return 1;
244  }
245 
246  if (!doc.setContent(&file))
247  {
248  LOG(VB_JOBQUEUE, LOG_ERR, "Could not load job file: " + jobFile);
249  file.close();
250  return 1;
251  }
252 
253  file.close();
254 
255  // get options from job file
256  bool bCreateISO = false;
257  bool bEraseDVDRW = false;
258  bool bDoBurn = false;
259  QString saveDirectory;
260  int mediaType = 0;
261 
262  QDomNodeList nodeList = doc.elementsByTagName("options");
263  if (nodeList.count() == 1)
264  {
265  QDomNode node = nodeList.item(0);
266  QDomElement options = node.toElement();
267  if (!options.isNull())
268  {
269  bCreateISO = (options.attribute("createiso", "0") == "1");
270  bEraseDVDRW = (options.attribute("erasedvdrw", "0") == "1");
271  bDoBurn = (options.attribute("doburn", "0") == "1");
272  mediaType = options.attribute("mediatype", "0").toInt();
273  saveDirectory = options.attribute("savedirectory", "");
274  if (!saveDirectory.endsWith("/"))
275  saveDirectory += "/";
276  }
277  }
278  else
279  {
280  LOG(VB_JOBQUEUE, LOG_ERR,
281  QString("Found %1 options nodes - should be 1")
282  .arg(nodeList.count()));
283  return 1;
284  }
285  LOG(VB_JOBQUEUE, LOG_INFO,
286  QString("Options - createiso: %1,"
287  " doburn: %2, mediatype: %3, erasedvdrw: %4")
288  .arg(bCreateISO).arg(bDoBurn).arg(mediaType).arg(bEraseDVDRW));
289  LOG(VB_JOBQUEUE, LOG_INFO, QString("savedirectory: %1").arg(saveDirectory));
290 
291  // figure out where to save files
292  if (mediaType != AD_FILE)
293  {
294  saveDirectory = tempDir;
295  if (!saveDirectory.endsWith("/"))
296  saveDirectory += "/";
297 
298  saveDirectory += "work/";
299 
300  QDir dir(saveDirectory);
301  if (dir.exists())
302  {
303  if (!MythRemoveDirectory(dir))
304  LOG(VB_GENERAL, LOG_ERR,
305  "NativeArchive: Failed to clear work directory");
306  }
307  dir.mkpath(saveDirectory);
308  }
309 
310  LOG(VB_JOBQUEUE, LOG_INFO,
311  QString("Saving files to : %1").arg(saveDirectory));
312 
313  // get list of file nodes from the job file
314  nodeList = doc.elementsByTagName("file");
315  if (nodeList.count() < 1)
316  {
317  LOG(VB_JOBQUEUE, LOG_ERR, "Cannot find any file nodes?");
318  return 1;
319  }
320 
321  // loop though file nodes and archive each file
322  QDomNode node;
323  QDomElement elem;
324  QString type = "";
325 
326  for (int x = 0; x < nodeList.count(); x++)
327  {
328  node = nodeList.item(x);
329  elem = node.toElement();
330  if (!elem.isNull())
331  {
332  type = elem.attribute("type");
333 
334  if (type.toLower() == "recording")
335  exportRecording(elem, saveDirectory);
336  else if (type.toLower() == "video")
337  exportVideo(elem, saveDirectory);
338  else
339  {
340  LOG(VB_JOBQUEUE, LOG_ERR,
341  QString("Don't know how to archive items of type '%1'")
342  .arg(type.toLower()));
343  continue;
344  }
345  }
346  }
347 
348  // burn the dvd if needed
349  if (mediaType != AD_FILE && bDoBurn)
350  {
351  if (!burnISOImage(mediaType, bEraseDVDRW, true))
352  {
353  LOG(VB_JOBQUEUE, LOG_ERR,
354  "Native archive job failed to complete");
355  return 1;
356  }
357  }
358 
359  // create an iso image if needed
360  if (bCreateISO)
361  {
362  if (!createISOImage(saveDirectory))
363  {
364  LOG(VB_JOBQUEUE, LOG_ERR, "Native archive job failed to complete");
365  return 1;
366  }
367  }
368 
369  LOG(VB_JOBQUEUE, LOG_INFO, "Native archive job completed OK");
370 
371  return 0;
372 }
373 
374 static const QRegularExpression badChars { R"((/|\\|:|'|"|\?|\|))" };
375 
376 static QString fixFilename(const QString &filename)
377 {
378  QString ret = filename;
379  ret.replace(badChars, "_");
380  return ret;
381 }
382 
383 int NativeArchive::getFieldList(QStringList &fieldList, const QString &tableName)
384 {
385  fieldList.clear();
386 
387  MSqlQuery query(MSqlQuery::InitCon());
388  if (query.exec("DESCRIBE " + tableName))
389  {
390  while (query.next())
391  {
392  fieldList.append(query.value(0).toString());
393  }
394  }
395  else
396  {
397  MythDB::DBError("describe table", query);
398  }
399 
400  return fieldList.count();
401 }
402 
403 int NativeArchive::exportRecording(QDomElement &itemNode,
404  const QString &saveDirectory)
405 {
406  QString chanID;
407  QString startTime;
408  QString dbVersion = gCoreContext->GetSetting("DBSchemaVer", "");
409 
410  QString title = fixFilename(itemNode.attribute("title"));
411  QString filename = itemNode.attribute("filename");
412  bool doDelete = (itemNode.attribute("delete", "0") == "0");
413  LOG(VB_JOBQUEUE, LOG_INFO, QString("Archiving %1 (%2), do delete: %3")
414  .arg(title, filename, doDelete ? "true" : "false"));
415 
416  if (title == "" || filename == "")
417  {
418  LOG(VB_JOBQUEUE, LOG_ERR, "Bad title or filename");
419  return 0;
420  }
421 
422  if (!extractDetailsFromFilename(filename, chanID, startTime))
423  {
424  LOG(VB_JOBQUEUE, LOG_ERR,
425  QString("Failed to extract chanID and startTime from '%1'")
426  .arg(filename));
427  return 0;
428  }
429 
430  // create the directory to hold this items files
431  QDir dir(saveDirectory + title);
432  if (!dir.exists())
433  dir.mkpath(saveDirectory + title);
434  if (!dir.exists())
435  LOG(VB_GENERAL, LOG_ERR, "Failed to create savedir: " + ENO);
436 
437  LOG(VB_JOBQUEUE, LOG_INFO, "Creating xml file for " + title);
438  QDomDocument doc("MYTHARCHIVEITEM");
439 
440  QDomElement root = doc.createElement("item");
441  doc.appendChild(root);
442  root.setAttribute("type", "recording");
443  root.setAttribute("databaseversion", dbVersion);
444 
445  QDomElement recorded = doc.createElement("recorded");
446  root.appendChild(recorded);
447 
448  // get details from recorded
449  QStringList fieldList;
450  getFieldList(fieldList, "recorded");
451 
452  MSqlQuery query(MSqlQuery::InitCon());
453  query.prepare("SELECT " + fieldList.join(",")
454  + " FROM recorded"
455  " WHERE chanid = :CHANID and starttime = :STARTTIME;");
456  query.bindValue(":CHANID", chanID);
457  query.bindValue(":STARTTIME", startTime);
458 
459  if (query.exec() && query.next())
460  {
461  QDomElement elem;
462  QDomText text;
463 
464  for (int x = 0; x < fieldList.size(); x++)
465  {
466  elem = doc.createElement(fieldList[x]);
467  text = doc.createTextNode(query.value(x).toString());
468  elem.appendChild(text);
469  recorded.appendChild(elem);
470  }
471 
472  LOG(VB_JOBQUEUE, LOG_INFO, "Created recorded element for " + title);
473  }
474  else
475  {
476  LOG(VB_JOBQUEUE, LOG_INFO, "Failed to get recorded field list");
477  }
478 
479  // add channel details
480  query.prepare("SELECT chanid, channum, callsign, name "
481  "FROM channel WHERE chanid = :CHANID;");
482  query.bindValue(":CHANID", chanID);
483 
484  if (query.exec() && query.next())
485  {
486  QDomElement channel = doc.createElement("channel");
487  channel.setAttribute("chanid", query.value(0).toString());
488  channel.setAttribute("channum", query.value(1).toString());
489  channel.setAttribute("callsign", query.value(2).toString());
490  channel.setAttribute("name", query.value(3).toString());
491  root.appendChild(channel);
492  LOG(VB_JOBQUEUE, LOG_INFO, "Created channel element for " + title);
493  }
494  else
495  {
496  // cannot find the original channel so create a default channel element
497  LOG(VB_JOBQUEUE, LOG_ERR,
498  "Cannot find channel details for chanid " + chanID);
499  QDomElement channel = doc.createElement("channel");
500  channel.setAttribute("chanid", chanID);
501  channel.setAttribute("channum", "unknown");
502  channel.setAttribute("callsign", "unknown");
503  channel.setAttribute("name", "unknown");
504  root.appendChild(channel);
505  LOG(VB_JOBQUEUE, LOG_INFO,
506  "Created a default channel element for " + title);
507  }
508 
509  // add any credits
510  query.prepare("SELECT credits.person, role, people.name "
511  "FROM recordedcredits AS credits "
512  "LEFT JOIN people ON credits.person = people.person "
513  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
514  query.bindValue(":CHANID", chanID);
515  query.bindValue(":STARTTIME", startTime);
516 
517  if (query.exec() && query.size())
518  {
519  QDomElement credits = doc.createElement("credits");
520  while (query.next())
521  {
522  QDomElement credit = doc.createElement("credit");
523  credit.setAttribute("personid", query.value(0).toString());
524  credit.setAttribute("name", query.value(2).toString());
525  credit.setAttribute("role", query.value(1).toString());
526  credits.appendChild(credit);
527  }
528  root.appendChild(credits);
529  LOG(VB_JOBQUEUE, LOG_INFO, "Created credits element for " + title);
530  }
531 
532  // add any rating
533  query.prepare("SELECT `system`, rating FROM recordedrating "
534  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
535  query.bindValue(":CHANID", chanID);
536  query.bindValue(":STARTTIME", startTime);
537 
538  if (query.exec() && query.next())
539  {
540  QDomElement rating = doc.createElement("rating");
541  rating.setAttribute("system", query.value(0).toString());
542  rating.setAttribute("rating", query.value(1).toString());
543  root.appendChild(rating);
544  LOG(VB_JOBQUEUE, LOG_INFO, "Created rating element for " + title);
545  }
546 
547  // add the recordedmarkup table
548  QDomElement recordedmarkup = doc.createElement("recordedmarkup");
549  query.prepare("SELECT chanid, starttime, mark, type, data "
550  "FROM recordedmarkup "
551  "WHERE chanid = :CHANID and starttime = :STARTTIME;");
552  query.bindValue(":CHANID", chanID);
553  query.bindValue(":STARTTIME", startTime);
554  if (query.exec() && query.size())
555  {
556  while (query.next())
557  {
558  QDomElement mark = doc.createElement("mark");
559  mark.setAttribute("mark", query.value(2).toString());
560  mark.setAttribute("type", query.value(3).toString());
561  mark.setAttribute("data", query.value(4).toString());
562  recordedmarkup.appendChild(mark);
563  }
564  root.appendChild(recordedmarkup);
565  LOG(VB_JOBQUEUE, LOG_INFO, "Created recordedmarkup element for " + title);
566  }
567 
568  // add the recordedseek table
569  QDomElement recordedseek = doc.createElement("recordedseek");
570  query.prepare("SELECT chanid, starttime, mark, `offset`, type "
571  "FROM recordedseek "
572  "WHERE chanid = :CHANID and starttime = :STARTTIME;");
573  query.bindValue(":CHANID", chanID);
574  query.bindValue(":STARTTIME", startTime);
575  if (query.exec() && query.size())
576  {
577  while (query.next())
578  {
579  QDomElement mark = doc.createElement("mark");
580  mark.setAttribute("mark", query.value(2).toString());
581  mark.setAttribute("offset", query.value(3).toString());
582  mark.setAttribute("type", query.value(4).toString());
583  recordedseek.appendChild(mark);
584  }
585  root.appendChild(recordedseek);
586  LOG(VB_JOBQUEUE, LOG_INFO,
587  "Created recordedseek element for " + title);
588  }
589 
590  // finally save the xml to the file
591  QString baseName = getBaseName(filename);
592  QString xmlFile = saveDirectory + title + "/" + baseName + ".xml";
593  QFile f(xmlFile);
594  if (!f.open(QIODevice::WriteOnly))
595  {
596  LOG(VB_JOBQUEUE, LOG_ERR,
597  "MythNativeWizard: Failed to open file for writing - " + xmlFile);
598  return 0;
599  }
600 
601  QTextStream t(&f);
602  t << doc.toString(4);
603  f.close();
604 
605  // copy the file
606  LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");
607  bool res = copyFile(filename, saveDirectory + title + "/" + baseName);
608  if (!res)
609  return 0;
610 
611  // copy preview image
612  if (QFile::exists(filename + ".png"))
613  {
614  LOG(VB_JOBQUEUE, LOG_INFO, "Copying preview image");
615  res = copyFile(filename + ".png", saveDirectory
616  + title + "/" + baseName + ".png");
617  if (!res)
618  return 0;
619  }
620 
621  LOG(VB_JOBQUEUE, LOG_INFO, "Item Archived OK");
622 
623  return 1;
624 }
625 
626 int NativeArchive::exportVideo(QDomElement &itemNode,
627  const QString &saveDirectory)
628 {
629  QString dbVersion = gCoreContext->GetSetting("DBSchemaVer", "");
630  int intID = 0;
631  int categoryID = 0;
632  QString coverFile = "";
633 
634  QString title = fixFilename(itemNode.attribute("title"));
635  QString filename = itemNode.attribute("filename");
636  bool doDelete = (itemNode.attribute("delete", "0") == "0");
637  LOG(VB_JOBQUEUE, LOG_INFO, QString("Archiving %1 (%2), do delete: %3")
638  .arg(title, filename, doDelete ? "true" : "false"));
639 
640  if (title == "" || filename == "")
641  {
642  LOG(VB_JOBQUEUE, LOG_ERR, "Bad title or filename");
643  return 0;
644  }
645 
646  // create the directory to hold this items files
647  QDir dir(saveDirectory + title);
648  if (!dir.exists())
649  dir.mkdir(saveDirectory + title);
650 
651  LOG(VB_JOBQUEUE, LOG_INFO, "Creating xml file for " + title);
652  QDomDocument doc("MYTHARCHIVEITEM");
653 
654  QDomElement root = doc.createElement("item");
655  doc.appendChild(root);
656  root.setAttribute("type", "video");
657  root.setAttribute("databaseversion", dbVersion);
658 
659  QDomElement video = doc.createElement("videometadata");
660  root.appendChild(video);
661 
662  // get details from videometadata
663  MSqlQuery query(MSqlQuery::InitCon());
664  query.prepare("SELECT intid, title, director, plot, rating, inetref, "
665  "year, userrating, length, showlevel, filename, coverfile, "
666  "childid, browse, playcommand, category "
667  "FROM videometadata WHERE filename = :FILENAME;");
668  query.bindValue(":FILENAME", filename);
669 
670  if (query.exec() && query.next())
671  {
672  QDomElement elem;
673  QDomText text;
674 
675  elem = doc.createElement("intid");
676  text = doc.createTextNode(query.value(0).toString());
677  intID = query.value(0).toInt();
678  elem.appendChild(text);
679  video.appendChild(elem);
680 
681  elem = doc.createElement("title");
682  text = doc.createTextNode(query.value(1).toString());
683  elem.appendChild(text);
684  video.appendChild(elem);
685 
686  elem = doc.createElement("director");
687  text = doc.createTextNode(query.value(2).toString());
688  elem.appendChild(text);
689  video.appendChild(elem);
690 
691  elem = doc.createElement("plot");
692  text = doc.createTextNode(query.value(3).toString());
693  elem.appendChild(text);
694  video.appendChild(elem);
695 
696  elem = doc.createElement("rating");
697  text = doc.createTextNode(query.value(4).toString());
698  elem.appendChild(text);
699  video.appendChild(elem);
700 
701  elem = doc.createElement("inetref");
702  text = doc.createTextNode(query.value(5).toString());
703  elem.appendChild(text);
704  video.appendChild(elem);
705 
706  elem = doc.createElement("year");
707  text = doc.createTextNode(query.value(6).toString());
708  elem.appendChild(text);
709  video.appendChild(elem);
710 
711  elem = doc.createElement("userrating");
712  text = doc.createTextNode(query.value(7).toString());
713  elem.appendChild(text);
714  video.appendChild(elem);
715 
716  elem = doc.createElement("length");
717  text = doc.createTextNode(query.value(8).toString());
718  elem.appendChild(text);
719  video.appendChild(elem);
720 
721  elem = doc.createElement("showlevel");
722  text = doc.createTextNode(query.value(9).toString());
723  elem.appendChild(text);
724  video.appendChild(elem);
725 
726  // remove the VideoStartupDir part of the filename
727  QString fname = query.value(10).toString();
728  if (fname.startsWith(gCoreContext->GetSetting("VideoStartupDir")))
729  fname = fname.remove(gCoreContext->GetSetting("VideoStartupDir"));
730 
731  elem = doc.createElement("filename");
732  text = doc.createTextNode(fname);
733  elem.appendChild(text);
734  video.appendChild(elem);
735 
736  elem = doc.createElement("coverfile");
737  text = doc.createTextNode(query.value(11).toString());
738  coverFile = query.value(11).toString();
739  elem.appendChild(text);
740  video.appendChild(elem);
741 
742  elem = doc.createElement("childid");
743  text = doc.createTextNode(query.value(12).toString());
744  elem.appendChild(text);
745  video.appendChild(elem);
746 
747  elem = doc.createElement("browse");
748  text = doc.createTextNode(query.value(13).toString());
749  elem.appendChild(text);
750  video.appendChild(elem);
751 
752  elem = doc.createElement("playcommand");
753  text = doc.createTextNode(query.value(14).toString());
754  elem.appendChild(text);
755  video.appendChild(elem);
756 
757  elem = doc.createElement("categoryid");
758  text = doc.createTextNode(query.value(15).toString());
759  categoryID = query.value(15).toInt();
760  elem.appendChild(text);
761  video.appendChild(elem);
762 
763  LOG(VB_JOBQUEUE, LOG_INFO,
764  "Created videometadata element for " + title);
765  }
766 
767  // add category details
768  query.prepare("SELECT intid, category "
769  "FROM videocategory WHERE intid = :INTID;");
770  query.bindValue(":INTID", categoryID);
771 
772  if (query.exec() && query.next())
773  {
774  QDomElement category = doc.createElement("category");
775  category.setAttribute("intid", query.value(0).toString());
776  category.setAttribute("category", query.value(1).toString());
777  root.appendChild(category);
778  LOG(VB_JOBQUEUE, LOG_INFO,
779  "Created videocategory element for " + title);
780  }
781 
782  //add video country details
783  QDomElement countries = doc.createElement("countries");
784  root.appendChild(countries);
785 
786  query.prepare("SELECT intid, country "
787  "FROM videometadatacountry INNER JOIN videocountry "
788  "ON videometadatacountry.idcountry = videocountry.intid "
789  "WHERE idvideo = :INTID;");
790  query.bindValue(":INTID", intID);
791 
792  if (!query.exec())
793  MythDB::DBError("select countries", query);
794 
795  if (query.isActive() && query.size())
796  {
797  while (query.next())
798  {
799  QDomElement country = doc.createElement("country");
800  country.setAttribute("intid", query.value(0).toString());
801  country.setAttribute("country", query.value(1).toString());
802  countries.appendChild(country);
803  }
804  LOG(VB_JOBQUEUE, LOG_INFO, "Created videocountry element for " + title);
805  }
806 
807  // add video genre details
808  QDomElement genres = doc.createElement("genres");
809  root.appendChild(genres);
810 
811  query.prepare("SELECT intid, genre "
812  "FROM videometadatagenre INNER JOIN videogenre "
813  "ON videometadatagenre.idgenre = videogenre.intid "
814  "WHERE idvideo = :INTID;");
815  query.bindValue(":INTID", intID);
816 
817  if (!query.exec())
818  MythDB::DBError("select genres", query);
819 
820  if (query.isActive() && query.size())
821  {
822  while (query.next())
823  {
824  QDomElement genre = doc.createElement("genre");
825  genre.setAttribute("intid", query.value(0).toString());
826  genre.setAttribute("genre", query.value(1).toString());
827  genres.appendChild(genre);
828  }
829  LOG(VB_JOBQUEUE, LOG_INFO, "Created videogenre element for " + title);
830  }
831 
832  // finally save the xml to the file
833  QFileInfo fileInfo(filename);
834  QString xmlFile = saveDirectory + title + "/"
835  + fileInfo.fileName() + ".xml";
836  QFile f(xmlFile);
837  if (!f.open(QIODevice::WriteOnly))
838  {
839  LOG(VB_JOBQUEUE, LOG_INFO,
840  "MythNativeWizard: Failed to open file for writing - " + xmlFile);
841  return 0;
842  }
843 
844  QTextStream t(&f);
845  t << doc.toString(4);
846  f.close();
847 
848  // copy the file
849  LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");
850  bool res = copyFile(filename, saveDirectory + title
851  + "/" + fileInfo.fileName());
852  if (!res)
853  {
854  return 0;
855  }
856 
857  // copy the cover image
858  fileInfo.setFile(coverFile);
859  if (fileInfo.exists())
860  {
861  LOG(VB_JOBQUEUE, LOG_INFO, "Copying cover file");
862  res = copyFile(coverFile, saveDirectory + title
863  + "/" + fileInfo.fileName());
864  if (!res)
865  {
866  return 0;
867  }
868  }
869 
870  LOG(VB_JOBQUEUE, LOG_INFO, "Item Archived OK");
871 
872  return 1;
873 }
874 
875 int NativeArchive::doImportArchive(const QString &xmlFile, int chanID)
876 {
877  // open xml file
878  QDomDocument doc("mydocument");
879  QFile file(xmlFile);
880  if (!file.open(QIODevice::ReadOnly))
881  {
882  LOG(VB_JOBQUEUE, LOG_ERR,
883  "Failed to open file for reading - " + xmlFile);
884  return 1;
885  }
886 
887  if (!doc.setContent(&file))
888  {
889  file.close();
890  LOG(VB_JOBQUEUE, LOG_ERR,
891  "Failed to read from xml file - " + xmlFile);
892  return 1;
893  }
894  file.close();
895 
896  QString docType = doc.doctype().name();
897  QString type;
898  QString dbVersion;
899  QDomNodeList itemNodeList;
900  QDomNode node;
901  QDomElement itemNode;
902 
903  if (docType == "MYTHARCHIVEITEM")
904  {
905  itemNodeList = doc.elementsByTagName("item");
906 
907  if (itemNodeList.count() < 1)
908  {
909  LOG(VB_JOBQUEUE, LOG_ERR,
910  "Couldn't find an 'item' element in XML file");
911  return 1;
912  }
913 
914  node = itemNodeList.item(0);
915  itemNode = node.toElement();
916  type = itemNode.attribute("type");
917  dbVersion = itemNode.attribute("databaseversion");
918 
919  LOG(VB_JOBQUEUE, LOG_INFO,
920  QString("Archive DB version: %1, Local DB version: %2")
921  .arg(dbVersion, gCoreContext->GetSetting("DBSchemaVer")));
922  }
923  else
924  {
925  LOG(VB_JOBQUEUE, LOG_ERR, "Not a native archive xml file - " + xmlFile);
926  return 1;
927  }
928 
929  if (type == "recording")
930  {
931  return importRecording(itemNode, xmlFile, chanID);
932  }
933  if (type == "video")
934  {
935  return importVideo(itemNode, xmlFile);
936  }
937 
938  return 1;
939 }
940 
941 int NativeArchive::importRecording(const QDomElement &itemNode,
942  const QString &xmlFile, int chanID)
943 {
944  LOG(VB_JOBQUEUE, LOG_INFO,
945  QString("Import recording using chanID: %1").arg(chanID));
946  LOG(VB_JOBQUEUE, LOG_INFO,
947  QString("Archived recording xml file: %1").arg(xmlFile));
948 
949  QString videoFile = xmlFile.left(xmlFile.length() - 4);
950  QString basename = videoFile;
951  int pos = videoFile.lastIndexOf('/');
952  if (pos > 0)
953  basename = videoFile.mid(pos + 1);
954 
955  QDomNodeList nodeList = itemNode.elementsByTagName("recorded");
956  if (nodeList.count() < 1)
957  {
958  LOG(VB_JOBQUEUE, LOG_ERR,
959  "Couldn't find a 'recorded' element in XML file");
960  return 1;
961  }
962 
963  QDomNode n = nodeList.item(0);
964  QDomElement recordedNode = n.toElement();
965  QString startTime = findNodeText(recordedNode, "starttime");
966  // check this recording doesn't already exist
967  MSqlQuery query(MSqlQuery::InitCon());
968  query.prepare("SELECT * FROM recorded "
969  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
970  query.bindValue(":CHANID", chanID);
971  query.bindValue(":STARTTIME", startTime);
972  if (query.exec())
973  {
974  if (query.isActive() && query.size())
975  {
976  LOG(VB_JOBQUEUE, LOG_ERR,
977  "This recording appears to already exist!!");
978  return 1;
979  }
980  }
981 
984  basename , "Default");
985 
986  // copy file to recording directory
987  LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file to: " + destFile);
988  if (!copyFile(videoFile, destFile))
989  return 1;
990 
991  // copy any preview image to recording directory
992  if (QFile::exists(videoFile + ".png"))
993  {
994  LOG(VB_JOBQUEUE, LOG_INFO, "Copying preview image file to: " + destFile + ".png");
995  if (!copyFile(videoFile + ".png", destFile + ".png"))
996  return 1;
997  }
998 
999  // get a list of fields from the xmlFile
1000  QStringList fieldList;
1001  QStringList bindList;
1002  QDomNodeList nodes = recordedNode.childNodes();
1003 
1004  for (int x = 0; x < nodes.count(); x++)
1005  {
1006  QDomNode n2 = nodes.item(x);
1007  QString field = n2.nodeName();
1008  fieldList.append(field);
1009  bindList.append(":" + field.toUpper());
1010  }
1011 
1012  // copy recorded to database
1013  query.prepare("INSERT INTO recorded (" + fieldList.join(",") + ") "
1014  "VALUES (" + bindList.join(",") + ");");
1015  query.bindValue(":CHANID", chanID);
1016  query.bindValue(":STARTTIME", startTime);
1017 
1018  for (int x = 0; x < fieldList.count(); x++)
1019  query.bindValue(bindList.at(x), findNodeText(recordedNode, fieldList.at(x)));
1020 
1021  if (query.exec())
1022  LOG(VB_JOBQUEUE, LOG_INFO, "Inserted recorded details into database");
1023  else
1024  MythDB::DBError("recorded insert", query);
1025 
1026  // copy recordedmarkup to db
1027  nodeList = itemNode.elementsByTagName("recordedmarkup");
1028  if (nodeList.count() < 1)
1029  {
1030  LOG(VB_JOBQUEUE, LOG_WARNING,
1031  "Couldn't find a 'recordedmarkup' element in XML file");
1032  }
1033  else
1034  {
1035  QDomNode n3 = nodeList.item(0);
1036  QDomElement markupNode = n3.toElement();
1037 
1038  nodeList = markupNode.elementsByTagName("mark");
1039  if (nodeList.count() < 1)
1040  {
1041  LOG(VB_JOBQUEUE, LOG_WARNING,
1042  "Couldn't find any 'mark' elements in XML file");
1043  }
1044  else
1045  {
1046  // delete any records for this recordings
1047  query.prepare("DELETE FROM recordedmarkup "
1048  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
1049  query.bindValue(":CHANID", chanID);
1050  query.bindValue(":STARTTIME", startTime);
1051 
1052  if (!query.exec())
1053  MythDB::DBError("recordedmarkup delete", query);
1054 
1055  // add any new records for this recording
1056  for (int x = 0; x < nodeList.count(); x++)
1057  {
1058  QDomNode n4 = nodeList.item(x);
1059  QDomElement e = n4.toElement();
1060  query.prepare("INSERT INTO recordedmarkup (chanid, starttime, "
1061  "mark, type, data)"
1062  "VALUES(:CHANID,:STARTTIME,:MARK,:TYPE,:DATA);");
1063  query.bindValue(":CHANID", chanID);
1064  query.bindValue(":STARTTIME", startTime);
1065  query.bindValue(":MARK", e.attribute("mark"));
1066  query.bindValue(":TYPE", e.attribute("type"));
1067  query.bindValue(":DATA", e.attribute("data"));
1068 
1069  if (!query.exec())
1070  {
1071  MythDB::DBError("recordedmark insert", query);
1072  return 1;
1073  }
1074  }
1075 
1076  LOG(VB_JOBQUEUE, LOG_INFO,
1077  "Inserted recordedmarkup details into database");
1078  }
1079  }
1080 
1081  // copy recordedseek to db
1082  nodeList = itemNode.elementsByTagName("recordedseek");
1083  if (nodeList.count() < 1)
1084  {
1085  LOG(VB_JOBQUEUE, LOG_WARNING,
1086  "Couldn't find a 'recordedseek' element in XML file");
1087  }
1088  else
1089  {
1090  QDomNode n5 = nodeList.item(0);
1091  QDomElement markupNode = n5.toElement();
1092 
1093  nodeList = markupNode.elementsByTagName("mark");
1094  if (nodeList.count() < 1)
1095  {
1096  LOG(VB_JOBQUEUE, LOG_WARNING,
1097  "Couldn't find any 'mark' elements in XML file");
1098  }
1099  else
1100  {
1101  // delete any records for this recordings
1102  query.prepare("DELETE FROM recordedseek "
1103  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
1104  query.bindValue(":CHANID", chanID);
1105  query.bindValue(":STARTTIME", startTime);
1106  query.exec();
1107 
1108  // add the new records for this recording
1109  for (int x = 0; x < nodeList.count(); x++)
1110  {
1111  QDomNode n6 = nodeList.item(x);
1112  QDomElement e = n6.toElement();
1113  query.prepare("INSERT INTO recordedseek (chanid, starttime, "
1114  "mark, `offset`, type)"
1115  "VALUES(:CHANID,:STARTTIME,:MARK,:OFFSET,:TYPE);");
1116  query.bindValue(":CHANID", chanID);
1117  query.bindValue(":STARTTIME", startTime);
1118  query.bindValue(":MARK", e.attribute("mark"));
1119  query.bindValue(":OFFSET", e.attribute("offset"));
1120  query.bindValue(":TYPE", e.attribute("type"));
1121 
1122  if (!query.exec())
1123  {
1124  MythDB::DBError("recordedseek insert", query);
1125  return 1;
1126  }
1127  }
1128 
1129  LOG(VB_JOBQUEUE, LOG_INFO,
1130  "Inserted recordedseek details into database");
1131  }
1132  }
1133 
1134  // FIXME are these needed?
1135  // copy credits to DB
1136  // copy rating to DB
1137 
1138  LOG(VB_JOBQUEUE, LOG_INFO, "Import completed OK");
1139 
1140  return 0;
1141 }
1142 
1143 int NativeArchive::importVideo(const QDomElement &itemNode, const QString &xmlFile)
1144 {
1145  LOG(VB_JOBQUEUE, LOG_INFO, "Importing video");
1146  LOG(VB_JOBQUEUE, LOG_INFO,
1147  QString("Archived video xml file: %1").arg(xmlFile));
1148 
1149  QString videoFile = xmlFile.left(xmlFile.length() - 4);
1150  QFileInfo fileInfo(videoFile);
1151  QString basename = fileInfo.fileName();
1152 
1153  QDomNodeList nodeList = itemNode.elementsByTagName("videometadata");
1154  if (nodeList.count() < 1)
1155  {
1156  LOG(VB_JOBQUEUE, LOG_ERR,
1157  "Couldn't find a 'videometadata' element in XML file");
1158  return 1;
1159  }
1160 
1161  QDomNode n = nodeList.item(0);
1162  QDomElement videoNode = n.toElement();
1163 
1164  // copy file to video directory
1165  QString path = gCoreContext->GetSetting("VideoStartupDir");
1166  QString origFilename = findNodeText(videoNode, "filename");
1167  QStringList dirList = origFilename.split("/", Qt::SkipEmptyParts);
1168  QDir dir;
1169  for (int x = 0; x < dirList.count() - 1; x++)
1170  {
1171  path += "/" + dirList[x];
1172  if (!dir.exists(path))
1173  {
1174  if (!dir.mkdir(path))
1175  {
1176  LOG(VB_JOBQUEUE, LOG_ERR,
1177  QString("Couldn't create directory '%1'").arg(path));
1178  return 1;
1179  }
1180  }
1181  }
1182 
1183  LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");
1184  if (!copyFile(videoFile, path + "/" + basename))
1185  {
1186  return 1;
1187  }
1188 
1189  // copy cover image to Video Artwork dir
1190  QString artworkDir = gCoreContext->GetSetting("VideoArtworkDir");
1191  // get archive path
1192  fileInfo.setFile(videoFile);
1193  QString archivePath = fileInfo.absolutePath();
1194  // get coverfile filename
1195  QString coverFilename = findNodeText(videoNode, "coverfile");
1196  fileInfo.setFile(coverFilename);
1197  coverFilename = fileInfo.fileName();
1198  //check file exists
1199  fileInfo.setFile(archivePath + "/" + coverFilename);
1200  if (fileInfo.exists())
1201  {
1202  LOG(VB_JOBQUEUE, LOG_INFO, "Copying cover file");
1203 
1204  if (!copyFile(archivePath + "/" + coverFilename, artworkDir + "/" + coverFilename))
1205  {
1206  return 1;
1207  }
1208  }
1209  else
1210  {
1211  coverFilename = "No Cover";
1212  }
1213 
1214  // copy videometadata to database
1215  MSqlQuery query(MSqlQuery::InitCon());
1216  query.prepare("INSERT INTO videometadata (title, director, plot, rating, inetref, "
1217  "year, userrating, length, showlevel, filename, coverfile, "
1218  "childid, browse, playcommand, category) "
1219  "VALUES(:TITLE,:DIRECTOR,:PLOT,:RATING,:INETREF,:YEAR,"
1220  ":USERRATING,:LENGTH,:SHOWLEVEL,:FILENAME,:COVERFILE,"
1221  ":CHILDID,:BROWSE,:PLAYCOMMAND,:CATEGORY);");
1222  query.bindValue(":TITLE", findNodeText(videoNode, "title"));
1223  query.bindValue(":DIRECTOR", findNodeText(videoNode, "director"));
1224  query.bindValue(":PLOT", findNodeText(videoNode, "plot"));
1225  query.bindValue(":RATING", findNodeText(videoNode, "rating"));
1226  query.bindValue(":INETREF", findNodeText(videoNode, "inetref"));
1227  query.bindValue(":YEAR", findNodeText(videoNode, "year"));
1228  query.bindValue(":USERRATING", findNodeText(videoNode, "userrating"));
1229  query.bindValue(":LENGTH", findNodeText(videoNode, "length"));
1230  query.bindValue(":SHOWLEVEL", findNodeText(videoNode, "showlevel"));
1231  query.bindValue(":FILENAME", path + "/" + basename);
1232  query.bindValue(":COVERFILE", artworkDir + "/" + coverFilename);
1233  query.bindValue(":CHILDID", findNodeText(videoNode, "childid"));
1234  query.bindValue(":BROWSE", findNodeText(videoNode, "browse"));
1235  query.bindValue(":PLAYCOMMAND", findNodeText(videoNode, "playcommand"));
1236  query.bindValue(":CATEGORY", 0);
1237 
1238  if (query.exec())
1239  {
1240  LOG(VB_JOBQUEUE, LOG_INFO,
1241  "Inserted videometadata details into database");
1242  }
1243  else
1244  {
1245  MythDB::DBError("videometadata insert", query);
1246  return 1;
1247  }
1248 
1249  // get intid field for inserted record
1250  int intid = 0;
1251  query.prepare("SELECT intid FROM videometadata WHERE filename = :FILENAME;");
1252  query.bindValue(":FILENAME", path + "/" + basename);
1253  if (query.exec() && query.next())
1254  {
1255  intid = query.value(0).toInt();
1256  }
1257  else
1258  {
1259  MythDB::DBError("Failed to get intid", query);
1260  return 1;
1261  }
1262 
1263  LOG(VB_JOBQUEUE, LOG_INFO,
1264  QString("'intid' of inserted video is: %1").arg(intid));
1265 
1266  // copy genre to db
1267  nodeList = itemNode.elementsByTagName("genres");
1268  if (nodeList.count() < 1)
1269  {
1270  LOG(VB_JOBQUEUE, LOG_ERR, "No 'genres' element found in XML file");
1271  }
1272  else
1273  {
1274  n = nodeList.item(0);
1275  QDomElement genresNode = n.toElement();
1276 
1277  nodeList = genresNode.elementsByTagName("genre");
1278  if (nodeList.count() < 1)
1279  {
1280  LOG(VB_JOBQUEUE, LOG_WARNING,
1281  "Couldn't find any 'genre' elements in XML file");
1282  }
1283  else
1284  {
1285  for (int x = 0; x < nodeList.count(); x++)
1286  {
1287  n = nodeList.item(x);
1288  QDomElement e = n.toElement();
1289  int genreID = 0;
1290  QString genre = e.attribute("genre");
1291 
1292  // see if this genre already exists
1293  query.prepare("SELECT intid FROM videogenre "
1294  "WHERE genre = :GENRE");
1295  query.bindValue(":GENRE", genre);
1296  if (query.exec() && query.next())
1297  {
1298  genreID = query.value(0).toInt();
1299  }
1300  else
1301  {
1302  // genre doesn't exist so add it
1303  query.prepare("INSERT INTO videogenre (genre) VALUES(:GENRE);");
1304  query.bindValue(":GENRE", genre);
1305  if (!query.exec())
1306  MythDB::DBError("NativeArchive::importVideo - "
1307  "insert videogenre", query);
1308 
1309  // get new intid of genre
1310  query.prepare("SELECT intid FROM videogenre "
1311  "WHERE genre = :GENRE");
1312  query.bindValue(":GENRE", genre);
1313  if (!query.exec() || !query.next())
1314  {
1315  LOG(VB_JOBQUEUE, LOG_ERR,
1316  "Couldn't add genre to database");
1317  continue;
1318  }
1319  genreID = query.value(0).toInt();
1320  }
1321 
1322  // now link the genre to the videometadata
1323  query.prepare("INSERT INTO videometadatagenre (idvideo, idgenre)"
1324  "VALUES (:IDVIDEO, :IDGENRE);");
1325  query.bindValue(":IDVIDEO", intid);
1326  query.bindValue(":IDGENRE", genreID);
1327  if (!query.exec())
1328  MythDB::DBError("NativeArchive::importVideo - "
1329  "insert videometadatagenre", query);
1330  }
1331 
1332  LOG(VB_JOBQUEUE, LOG_INFO, "Inserted genre details into database");
1333  }
1334  }
1335 
1336  // copy country to db
1337  nodeList = itemNode.elementsByTagName("countries");
1338  if (nodeList.count() < 1)
1339  {
1340  LOG(VB_JOBQUEUE, LOG_INFO, "No 'countries' element found in XML file");
1341  }
1342  else
1343  {
1344  n = nodeList.item(0);
1345  QDomElement countriesNode = n.toElement();
1346 
1347  nodeList = countriesNode.elementsByTagName("country");
1348  if (nodeList.count() < 1)
1349  {
1350  LOG(VB_JOBQUEUE, LOG_WARNING,
1351  "Couldn't find any 'country' elements in XML file");
1352  }
1353  else
1354  {
1355  for (int x = 0; x < nodeList.count(); x++)
1356  {
1357  n = nodeList.item(x);
1358  QDomElement e = n.toElement();
1359  int countryID = 0;
1360  QString country = e.attribute("country");
1361 
1362  // see if this country already exists
1363  query.prepare("SELECT intid FROM videocountry "
1364  "WHERE country = :COUNTRY");
1365  query.bindValue(":COUNTRY", country);
1366  if (query.exec() && query.next())
1367  {
1368  countryID = query.value(0).toInt();
1369  }
1370  else
1371  {
1372  // country doesn't exist so add it
1373  query.prepare("INSERT INTO videocountry (country) VALUES(:COUNTRY);");
1374  query.bindValue(":COUNTRY", country);
1375  if (!query.exec())
1376  MythDB::DBError("NativeArchive::importVideo - "
1377  "insert videocountry", query);
1378 
1379  // get new intid of country
1380  query.prepare("SELECT intid FROM videocountry "
1381  "WHERE country = :COUNTRY");
1382  query.bindValue(":COUNTRY", country);
1383  if (!query.exec() || !query.next())
1384  {
1385  LOG(VB_JOBQUEUE, LOG_ERR,
1386  "Couldn't add country to database");
1387  continue;
1388  }
1389  countryID = query.value(0).toInt();
1390  }
1391 
1392  // now link the country to the videometadata
1393  query.prepare("INSERT INTO videometadatacountry (idvideo, idcountry)"
1394  "VALUES (:IDVIDEO, :IDCOUNTRY);");
1395  query.bindValue(":IDVIDEO", intid);
1396  query.bindValue(":IDCOUNTRY", countryID);
1397  if (!query.exec())
1398  MythDB::DBError("NativeArchive::importVideo - "
1399  "insert videometadatacountry", query);
1400  }
1401 
1402  LOG(VB_JOBQUEUE, LOG_INFO,
1403  "Inserted country details into database");
1404  }
1405  }
1406 
1407  // fix the category id
1408  nodeList = itemNode.elementsByTagName("category");
1409  if (nodeList.count() < 1)
1410  {
1411  LOG(VB_JOBQUEUE, LOG_ERR, "No 'category' element found in XML file");
1412  }
1413  else
1414  {
1415  n = nodeList.item(0);
1416  QDomElement e = n.toElement();
1417  int categoryID = 0;
1418  QString category = e.attribute("category");
1419  // see if this category already exists
1420  query.prepare("SELECT intid FROM videocategory "
1421  "WHERE category = :CATEGORY");
1422  query.bindValue(":CATEGORY", category);
1423  if (query.exec() && query.next())
1424  {
1425  categoryID = query.value(0).toInt();
1426  }
1427  else
1428  {
1429  // category doesn't exist so add it
1430  query.prepare("INSERT INTO videocategory (category) VALUES(:CATEGORY);");
1431  query.bindValue(":CATEGORY", category);
1432  if (!query.exec())
1433  MythDB::DBError("NativeArchive::importVideo - "
1434  "insert videocategory", query);
1435 
1436  // get new intid of category
1437  query.prepare("SELECT intid FROM videocategory "
1438  "WHERE category = :CATEGORY");
1439  query.bindValue(":CATEGORY", category);
1440  if (query.exec() && query.next())
1441  {
1442  categoryID = query.value(0).toInt();
1443  }
1444  else
1445  {
1446  LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't add category to database");
1447  categoryID = 0;
1448  }
1449  }
1450 
1451  // now fix the categoryid in the videometadata
1452  query.prepare("UPDATE videometadata "
1453  "SET category = :CATEGORY "
1454  "WHERE intid = :INTID;");
1455  query.bindValue(":CATEGORY", categoryID);
1456  query.bindValue(":INTID", intid);
1457  if (!query.exec())
1458  MythDB::DBError("NativeArchive::importVideo - "
1459  "update category", query);
1460 
1461  LOG(VB_JOBQUEUE, LOG_INFO, "Fixed the category in the database");
1462  }
1463 
1464  LOG(VB_JOBQUEUE, LOG_INFO, "Import completed OK");
1465 
1466  return 0;
1467 }
1468 
1469 QString NativeArchive::findNodeText(const QDomElement &elem, const QString &nodeName)
1470 {
1471  QDomNodeList nodeList = elem.elementsByTagName(nodeName);
1472  if (nodeList.count() < 1)
1473  {
1474  LOG(VB_GENERAL, LOG_ERR,
1475  QString("Couldn't find a '%1' element in XML file") .arg(nodeName));
1476  return "";
1477  }
1478 
1479  QDomNode n = nodeList.item(0);
1480  QDomElement e = n.toElement();
1481  QString res = "";
1482 
1483  for (QDomNode node = e.firstChild(); !node.isNull();
1484  node = node.nextSibling())
1485  {
1486  QDomText t = node.toText();
1487  if (!t.isNull())
1488  {
1489  res = t.data();
1490  break;
1491  }
1492  }
1493 
1494  // some fixups
1495  // FIXME could be a lot smarter
1496  if ((nodeName == "recgroup") ||
1497  (nodeName == "playgroup"))
1498  {
1499  res = "Default";
1500  }
1501  else if ((nodeName == "recordid") ||
1502  (nodeName == "seriesid") ||
1503  (nodeName == "programid") ||
1504  (nodeName == "profile"))
1505  {
1506  res = "";
1507  }
1508 
1509  return res;
1510 }
1511 
1512 static void clearArchiveTable(void)
1513 {
1514  MSqlQuery query(MSqlQuery::InitCon());
1515  query.prepare("DELETE FROM archiveitems;");
1516 
1517  if (!query.exec())
1518  MythDB::DBError("delete archiveitems", query);
1519 }
1520 
1521 static int doNativeArchive(const QString &jobFile)
1522 {
1523  gCoreContext->SaveSetting("MythArchiveLastRunType", "Native Export");
1525  "MythArchiveLastRunStart",
1527  gCoreContext->SaveSetting("MythArchiveLastRunStatus", "Running");
1528 
1529  int res = NativeArchive::doNativeArchive(jobFile);
1531  "MythArchiveLastRunEnd",
1533  gCoreContext->SaveSetting("MythArchiveLastRunStatus",
1534  (res == 0 ? "Success" : "Failed"));
1535 
1536  // clear the archiveitems table if succesful
1537  if (res == 0)
1539 
1540  return res;
1541 }
1542 
1543 static int doImportArchive(const QString &inFile, int chanID)
1544 {
1545  return NativeArchive::doImportArchive(inFile, chanID);
1546 }
1547 
1548 static int grabThumbnail(const QString& inFile, const QString& thumbList, const QString& outFile, int frameCount)
1549 {
1550  // Open recording
1551  LOG(VB_JOBQUEUE, LOG_INFO, QString("grabThumbnail(): Opening '%1'")
1552  .arg(inFile));
1553 
1554  MythCodecMap codecmap;
1555  ArchiveRemoteAVFormatContext inputFC(inFile);
1556  if (!inputFC.isOpen())
1557  {
1558  LOG(VB_JOBQUEUE, LOG_ERR, "grabThumbnail(): Couldn't open input file" +
1559  ENO);
1560  return 1;
1561  }
1562 
1563  // Getting stream information
1564  int ret = avformat_find_stream_info(inputFC, nullptr);
1565  if (ret < 0)
1566  {
1567  LOG(VB_JOBQUEUE, LOG_ERR,
1568  QString("Couldn't get stream info, error #%1").arg(ret));
1569  return 1;
1570  }
1571 
1572  // find the first video stream
1573  int videostream = -1;
1574  int width = 0;
1575  int height = 0;
1576  float fps = NAN;
1577 
1578  for (uint i = 0; i < inputFC->nb_streams; i++)
1579  {
1580  AVStream *st = inputFC->streams[i];
1581  if (inputFC->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
1582  {
1583  videostream = i;
1584  width = st->codecpar->width;
1585  height = st->codecpar->height;
1586  if (st->r_frame_rate.den && st->r_frame_rate.num)
1587  fps = av_q2d(st->r_frame_rate);
1588  else
1589  fps = 1/av_q2d(st->time_base);
1590  break;
1591  }
1592  }
1593 
1594  if (videostream == -1)
1595  {
1596  LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't find a video stream");
1597  return 1;
1598  }
1599 
1600  // get the codec context for the video stream
1601  AVCodecContext *codecCtx = codecmap.GetCodecContext(inputFC->streams[videostream]);
1602 
1603  // get decoder for video stream
1604  const AVCodec * codec = avcodec_find_decoder(codecCtx->codec_id);
1605 
1606  if (codec == nullptr)
1607  {
1608  LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't find codec for video stream");
1609  return 1;
1610  }
1611 
1612  // open codec
1613  if (avcodec_open2(codecCtx, codec, nullptr) < 0)
1614  {
1615  LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't open codec for video stream");
1616  return 1;
1617  }
1618 
1619  // get list of required thumbs
1620  QStringList list = thumbList.split(",", Qt::SkipEmptyParts);
1621  MythAVFrame frame;
1622  if (!frame)
1623  {
1624  return 1;
1625  }
1626  AVPacket pkt;
1627  AVFrame orig;
1628  AVFrame retbuf;
1629  memset(&orig, 0, sizeof(AVFrame));
1630  memset(&retbuf, 0, sizeof(AVFrame));
1631  MythAVCopy copyframe;
1632 
1633  int bufflen = width * height * 4;
1634  auto *outputbuf = new unsigned char[bufflen];
1635 
1636  int frameNo = -1;
1637  int thumbCount = 0;
1638  bool frameFinished = false;
1639 
1640  while (av_read_frame(inputFC, &pkt) >= 0)
1641  {
1642  if (pkt.stream_index == videostream)
1643  {
1644  frameNo++;
1645  if (list[thumbCount].toInt() == (int)(frameNo / fps))
1646  {
1647  thumbCount++;
1648 
1649  avcodec_flush_buffers(codecCtx);
1650  av_frame_unref(frame);
1651  frameFinished = false;
1652  ret = avcodec_receive_frame(codecCtx, frame);
1653  if (ret == 0)
1654  frameFinished = true;
1655  if (ret == 0 || ret == AVERROR(EAGAIN))
1656  avcodec_send_packet(codecCtx, &pkt);
1657  bool keyFrame = (frame->flags & AV_FRAME_FLAG_KEY) != 0;
1658 
1659  while (!frameFinished || !keyFrame)
1660  {
1661  av_packet_unref(&pkt);
1662  int res = av_read_frame(inputFC, &pkt);
1663  if (res < 0)
1664  break;
1665  if (pkt.stream_index == videostream)
1666  {
1667  frameNo++;
1668  av_frame_unref(frame);
1669  ret = avcodec_receive_frame(codecCtx, frame);
1670  if (ret == 0)
1671  frameFinished = true;
1672  if (ret == 0 || ret == AVERROR(EAGAIN))
1673  avcodec_send_packet(codecCtx, &pkt);
1674  keyFrame = (frame->flags & AV_FRAME_FLAG_KEY) != 0;
1675  }
1676  }
1677 
1678  if (frameFinished)
1679  {
1680  // work out what format to save to
1681  QString saveFormat = "JPEG";
1682  if (outFile.right(4) == ".png")
1683  saveFormat = "PNG";
1684 
1685  int count = 0;
1686  while (count < frameCount)
1687  {
1688  QString filename = outFile;
1689  if (filename.contains("%1") && filename.contains("%2"))
1690  filename = filename.arg(thumbCount).arg(count+1);
1691  else if (filename.contains("%1"))
1692  filename = filename.arg(thumbCount);
1693 
1694  av_image_fill_arrays(retbuf.data, retbuf.linesize, outputbuf,
1695  AV_PIX_FMT_RGB32, width, height, IMAGE_ALIGN);
1696 
1697  AVFrame *tmp = frame;
1699 
1700  copyframe.Copy(&retbuf, AV_PIX_FMT_RGB32, tmp,
1701  codecCtx->pix_fmt, width, height);
1702 
1703  QImage img(outputbuf, width, height,
1704  QImage::Format_RGB32);
1705 
1706  if (!img.save(filename, qPrintable(saveFormat)))
1707  {
1708  LOG(VB_GENERAL, LOG_ERR,
1709  QString("grabThumbnail(): Failed to save "
1710  "thumb: '%1'")
1711  .arg(filename));
1712  }
1713 
1714  count++;
1715 
1716  if (count <= frameCount)
1717  {
1718  //grab next frame
1719  frameFinished = false;
1720  while (!frameFinished)
1721  {
1722  int res = av_read_frame(inputFC, &pkt);
1723  if (res < 0)
1724  break;
1725  if (pkt.stream_index == videostream)
1726  {
1727  frameNo++;
1728  ret = avcodec_receive_frame(codecCtx, frame);
1729  if (ret == 0)
1730  frameFinished = true;
1731  if (ret == 0 || ret == AVERROR(EAGAIN))
1732  avcodec_send_packet(codecCtx, &pkt);
1733  }
1734  }
1735  }
1736  }
1737  }
1738 
1739  if (thumbCount >= list.count())
1740  break;
1741  }
1742  }
1743 
1744  av_packet_unref(&pkt);
1745  }
1746 
1747  delete[] outputbuf;
1748 
1749  // close the codec
1750  codecmap.FreeCodecContext(inputFC->streams[videostream]);
1751 
1752  return 0;
1753 }
1754 
1755 static int64_t getFrameCount(AVFormatContext *inputFC, int vid_id)
1756 {
1757  int64_t count = 0;
1758 
1759  LOG(VB_JOBQUEUE, LOG_INFO, "Calculating frame count");
1760 
1761  AVPacket *pkt = av_packet_alloc();
1762  if (pkt == nullptr)
1763  {
1764  LOG(VB_GENERAL, LOG_ERR, "packet allocation failed");
1765  return 0;
1766  }
1767  while (av_read_frame(inputFC, pkt) >= 0)
1768  {
1769  if (pkt->stream_index == vid_id)
1770  {
1771  count++;
1772  }
1773  av_packet_unref(pkt);
1774  }
1775  av_packet_free(&pkt);
1776 
1777  return count;
1778 }
1779 
1780 static int64_t getCutFrames(const QString &filename, int64_t lastFrame)
1781 {
1782  // only wont the filename
1783  QString basename = filename;
1784  int pos = filename.lastIndexOf('/');
1785  if (pos > 0)
1786  basename = filename.mid(pos + 1);
1787 
1788  ProgramInfo *progInfo = getProgramInfoForFile(basename);
1789  if (!progInfo)
1790  return 0;
1791 
1792  if (progInfo->IsVideo())
1793  {
1794  delete progInfo;
1795  return 0;
1796  }
1797 
1798  frm_dir_map_t cutlist;
1799  frm_dir_map_t::iterator it;
1800  uint64_t frames = 0;
1801 
1802  progInfo->QueryCutList(cutlist);
1803 
1804  if (cutlist.empty())
1805  {
1806  delete progInfo;
1807  return 0;
1808  }
1809 
1810  for (it = cutlist.begin(); it != cutlist.end();)
1811  {
1812  uint64_t start = 0;
1813  uint64_t end = 0;
1814 
1815  if (it.value() == MARK_CUT_START)
1816  {
1817  start = it.key();
1818  ++it;
1819  if (it != cutlist.end())
1820  {
1821  end = it.key();
1822  ++it;
1823  }
1824  else
1825  {
1826  end = lastFrame;
1827  }
1828  }
1829  else if (it.value() == MARK_CUT_END)
1830  {
1831  start = 0;
1832  end = it.key();
1833  ++it;
1834  }
1835  else
1836  {
1837  ++it;
1838  continue;
1839  }
1840 
1841  frames += end - start;
1842  }
1843 
1844  delete progInfo;
1845  return frames;
1846 }
1847 
1848 static int64_t getFrameCount(const QString &filename, float fps)
1849 {
1850  // only wont the filename
1851  QString basename = filename;
1852  int pos = filename.lastIndexOf('/');
1853  if (pos > 0)
1854  basename = filename.mid(pos + 1);
1855 
1856  int keyframedist = -1;
1857  frm_pos_map_t posMap;
1858 
1859  ProgramInfo *progInfo = getProgramInfoForFile(basename);
1860  if (!progInfo)
1861  return 0;
1862 
1863  progInfo->QueryPositionMap(posMap, MARK_GOP_BYFRAME);
1864  if (!posMap.empty())
1865  {
1866  keyframedist = 1;
1867  }
1868  else
1869  {
1870  progInfo->QueryPositionMap(posMap, MARK_GOP_START);
1871  if (!posMap.empty())
1872  {
1873  keyframedist = 15;
1874  if (fps < 26 && fps > 24)
1875  keyframedist = 12;
1876  }
1877  else
1878  {
1879  progInfo->QueryPositionMap(posMap, MARK_KEYFRAME);
1880  if (!posMap.empty())
1881  {
1882  // keyframedist should be set in the fileheader so no
1883  // need to try to determine it in this case
1884  delete progInfo;
1885  return 0;
1886  }
1887  }
1888  }
1889 
1890  delete progInfo;
1891  if (posMap.empty())
1892  return 0; // no position map in recording
1893 
1894  frm_pos_map_t::const_iterator it = posMap.cend();
1895  --it;
1896  uint64_t totframes = it.key() * keyframedist;
1897  return totframes;
1898 }
1899 
1900 static int getFileInfo(const QString& inFile, const QString& outFile, int lenMethod)
1901 {
1902  // Open recording
1903  LOG(VB_JOBQUEUE , LOG_INFO, QString("getFileInfo(): Opening '%1'")
1904  .arg(inFile));
1905 
1906  MythCodecMap codecmap;
1907  ArchiveRemoteAVFormatContext inputFC(inFile);
1908  if (!inputFC.isOpen())
1909  {
1910  LOG(VB_JOBQUEUE, LOG_ERR, "getFileInfo(): Couldn't open input file" +
1911  ENO);
1912  return 1;
1913  }
1914 
1915  // Getting stream information
1916  int ret = avformat_find_stream_info(inputFC, nullptr);
1917 
1918  if (ret < 0)
1919  {
1920  LOG(VB_JOBQUEUE, LOG_ERR,
1921  QString("Couldn't get stream info, error #%1").arg(ret));
1922  return 1;
1923  }
1924 
1925  // Dump stream information
1926  av_dump_format(inputFC, 0, qPrintable(inFile), 0);
1927 
1928  QDomDocument doc("FILEINFO");
1929 
1930  QDomElement root = doc.createElement("file");
1931  doc.appendChild(root);
1932  root.setAttribute("type", inputFC->iformat->name);
1933  root.setAttribute("filename", inFile);
1934 
1935  QDomElement streams = doc.createElement("streams");
1936 
1937  root.appendChild(streams);
1938  streams.setAttribute("count", inputFC->nb_streams);
1939  int ffmpegIndex = 0;
1940  uint duration = 0;
1941 
1942  for (uint i = 0; i < inputFC->nb_streams; i++)
1943  {
1944  AVStream *st = inputFC->streams[i];
1945  std::string buf (256,'\0');
1946  AVCodecContext *avctx = codecmap.GetCodecContext(st);
1947  AVCodecParameters *par = st->codecpar;
1948 
1949  if (avctx)
1950  avcodec_string(buf.data(), buf.size(), avctx, static_cast<int>(false));
1951 
1952  switch (st->codecpar->codec_type)
1953  {
1954  case AVMEDIA_TYPE_VIDEO:
1955  {
1956  QStringList param = QString::fromStdString(buf).split(',', Qt::SkipEmptyParts);
1957  QString codec = param[0].remove("Video:", Qt::CaseInsensitive).remove(QChar::Null);
1958  QDomElement stream = doc.createElement("video");
1959  stream.setAttribute("streamindex", i);
1960  stream.setAttribute("ffmpegindex", ffmpegIndex++);
1961  stream.setAttribute("codec", codec.trimmed());
1962  stream.setAttribute("width", par->width);
1963  stream.setAttribute("height", par->height);
1964  stream.setAttribute("bitrate", (qlonglong)par->bit_rate);
1965 
1966  float fps = NAN;
1967  if (st->r_frame_rate.den && st->r_frame_rate.num)
1968  fps = av_q2d(st->r_frame_rate);
1969  else
1970  fps = 1/av_q2d(st->time_base);
1971 
1972  stream.setAttribute("fps", fps);
1973 
1974  if (par->sample_aspect_ratio.den && par->sample_aspect_ratio.num)
1975  {
1976  float aspect_ratio = av_q2d(par->sample_aspect_ratio);
1977  if (QString(inputFC->iformat->name) != "nuv")
1978  aspect_ratio = ((float)par->width
1979  / par->height) * aspect_ratio;
1980 
1981  stream.setAttribute("aspectratio", aspect_ratio);
1982  }
1983  else
1984  {
1985  stream.setAttribute("aspectratio", "N/A");
1986  }
1987 
1988  stream.setAttribute("id", st->id);
1989 
1990  if (st->start_time != (int) AV_NOPTS_VALUE)
1991  {
1992  int secs = st->start_time / AV_TIME_BASE;
1993  int us = st->start_time % AV_TIME_BASE;
1994  stream.setAttribute("start_time", QString("%1.%2")
1995  .arg(secs).arg(av_rescale(us, 1000000, AV_TIME_BASE)));
1996  }
1997  else
1998  {
1999  stream.setAttribute("start_time", 0);
2000  }
2001 
2002  streams.appendChild(stream);
2003 
2004  // TODO: probably should add a better way to choose which
2005  // video stream we use to calc the duration
2006  if (duration == 0)
2007  {
2008  int64_t frameCount = 0;
2009 
2010  switch (lenMethod)
2011  {
2012  case 0:
2013  {
2014  // use duration guess from avformat
2015  if (inputFC->duration != (uint) AV_NOPTS_VALUE)
2016  {
2017  duration = (uint) (inputFC->duration / AV_TIME_BASE);
2018  root.setAttribute("duration", duration);
2019  LOG(VB_JOBQUEUE, LOG_INFO,
2020  QString("duration = %1") .arg(duration));
2021  frameCount = (int64_t)(duration * fps);
2022  }
2023  else
2024  {
2025  root.setAttribute("duration", "N/A");
2026  }
2027  break;
2028  }
2029  case 1:
2030  {
2031  // calc duration of the file by counting the video frames
2032  frameCount = getFrameCount(inputFC, i);
2033  LOG(VB_JOBQUEUE, LOG_INFO,
2034  QString("frames = %1").arg(frameCount));
2035  duration = (uint)(frameCount / fps);
2036  LOG(VB_JOBQUEUE, LOG_INFO,
2037  QString("duration = %1").arg(duration));
2038  root.setAttribute("duration", duration);
2039  break;
2040  }
2041  case 2:
2042  {
2043  // use info from pos map in db
2044  // (only useful if the file is a myth recording)
2045  frameCount = getFrameCount(inFile, fps);
2046  if (frameCount)
2047  {
2048  LOG(VB_JOBQUEUE, LOG_INFO,
2049  QString("frames = %1").arg(frameCount));
2050  duration = (uint)(frameCount / fps);
2051  LOG(VB_JOBQUEUE, LOG_INFO,
2052  QString("duration = %1").arg(duration));
2053  root.setAttribute("duration", duration);
2054  }
2055  else if (inputFC->duration != (uint) AV_NOPTS_VALUE)
2056  {
2057  duration = (uint) (inputFC->duration / AV_TIME_BASE);
2058  root.setAttribute("duration", duration);
2059  LOG(VB_JOBQUEUE, LOG_INFO,
2060  QString("duration = %1").arg(duration));
2061  frameCount = (int64_t)(duration * fps);
2062  }
2063  else
2064  {
2065  root.setAttribute("duration", "N/A");
2066  }
2067  break;
2068  }
2069  default:
2070  root.setAttribute("duration", "N/A");
2071  LOG(VB_JOBQUEUE, LOG_ERR,
2072  QString("Unknown lenMethod (%1)")
2073  .arg(lenMethod));
2074  }
2075 
2076  // add duration after all cuts are removed
2077  int64_t cutFrames = getCutFrames(inFile, frameCount);
2078  LOG(VB_JOBQUEUE, LOG_INFO,
2079  QString("cutframes = %1").arg(cutFrames));
2080  int cutduration = (int)(cutFrames / fps);
2081  LOG(VB_JOBQUEUE, LOG_INFO,
2082  QString("cutduration = %1").arg(cutduration));
2083  root.setAttribute("cutduration", duration - cutduration);
2084  }
2085 
2086  break;
2087  }
2088 
2089  case AVMEDIA_TYPE_AUDIO:
2090  {
2091  QStringList param = QString::fromStdString(buf).split(',', Qt::SkipEmptyParts);
2092  QString codec = param[0].remove("Audio:", Qt::CaseInsensitive).remove(QChar::Null);
2093 
2094  QDomElement stream = doc.createElement("audio");
2095  stream.setAttribute("streamindex", i);
2096  stream.setAttribute("ffmpegindex", ffmpegIndex++);
2097 
2098  // change any streams identified as "liba52" to "AC3" which is what
2099  // the mythburn.py script expects to get.
2100  if (codec.trimmed().toLower() == "liba52")
2101  stream.setAttribute("codec", "AC3");
2102  else
2103  stream.setAttribute("codec", codec.trimmed());
2104 
2105  stream.setAttribute("channels", par->ch_layout.nb_channels);
2106 
2107  AVDictionaryEntry *metatag =
2108  av_dict_get(st->metadata, "language", nullptr, 0);
2109  if (metatag)
2110  stream.setAttribute("language", metatag->value);
2111  else
2112  stream.setAttribute("language", "N/A");
2113 
2114  stream.setAttribute("id", st->id);
2115 
2116  stream.setAttribute("samplerate", par->sample_rate);
2117  stream.setAttribute("bitrate", (qlonglong)par->bit_rate);
2118 
2119  if (st->start_time != (int) AV_NOPTS_VALUE)
2120  {
2121  int secs = st->start_time / AV_TIME_BASE;
2122  int us = st->start_time % AV_TIME_BASE;
2123  stream.setAttribute("start_time", QString("%1.%2")
2124  .arg(secs).arg(av_rescale(us, 1000000, AV_TIME_BASE)));
2125  }
2126  else
2127  {
2128  stream.setAttribute("start_time", 0);
2129  }
2130 
2131  streams.appendChild(stream);
2132 
2133  break;
2134  }
2135 
2136  case AVMEDIA_TYPE_SUBTITLE:
2137  {
2138  QStringList param = QString::fromStdString(buf).split(',', Qt::SkipEmptyParts);
2139  QString codec = param[0].remove("Subtitle:", Qt::CaseInsensitive).remove(QChar::Null);
2140 
2141  QDomElement stream = doc.createElement("subtitle");
2142  stream.setAttribute("streamindex", i);
2143  stream.setAttribute("ffmpegindex", ffmpegIndex++);
2144  stream.setAttribute("codec", codec.trimmed());
2145 
2146  AVDictionaryEntry *metatag =
2147  av_dict_get(st->metadata, "language", nullptr, 0);
2148  if (metatag)
2149  stream.setAttribute("language", metatag->value);
2150  else
2151  stream.setAttribute("language", "N/A");
2152 
2153  stream.setAttribute("id", st->id);
2154 
2155  streams.appendChild(stream);
2156 
2157  break;
2158  }
2159 
2160  case AVMEDIA_TYPE_DATA:
2161  {
2162  QDomElement stream = doc.createElement("data");
2163  stream.setAttribute("streamindex", i);
2164  stream.setAttribute("codec", QString::fromStdString(buf).remove(QChar::Null));
2165  streams.appendChild(stream);
2166 
2167  break;
2168  }
2169 
2170  default:
2171  LOG(VB_JOBQUEUE, LOG_ERR,
2172  QString("Skipping unsupported codec %1 on stream %2")
2173  .arg(inputFC->streams[i]->codecpar->codec_type).arg(i));
2174  break;
2175  }
2176  codecmap.FreeCodecContext(st);
2177  }
2178 
2179  // finally save the xml to the file
2180  QFile f(outFile);
2181  if (!f.open(QIODevice::WriteOnly))
2182  {
2183  LOG(VB_JOBQUEUE, LOG_ERR,
2184  "Failed to open file for writing - " + outFile);
2185  return 1;
2186  }
2187 
2188  QTextStream t(&f);
2189  t << doc.toString(4);
2190  f.close();
2191 
2192  return 0;
2193 }
2194 
2195 static int getDBParamters(const QString& outFile)
2196 {
2197  DatabaseParams params = GetMythDB()->GetDatabaseParams();
2198 
2199  // save the db paramters to the file
2200  QFile f(outFile);
2201  if (!f.open(QIODevice::WriteOnly))
2202  {
2203  LOG(VB_GENERAL, LOG_ERR,
2204  QString("MythArchiveHelper: Failed to open file for writing - %1")
2205  .arg(outFile));
2206  return 1;
2207  }
2208 
2209  QTextStream t(&f);
2210  t << params.m_dbHostName << Qt::endl;
2211  t << params.m_dbUserName << Qt::endl;
2212  t << params.m_dbPassword << Qt::endl;
2213  t << params.m_dbName << Qt::endl;
2214  t << gCoreContext->GetHostName() << Qt::endl;
2215  t << GetInstallPrefix() << Qt::endl;
2216  f.close();
2217 
2218  return 0;
2219 }
2220 
2221 static int isRemote(const QString& filename)
2222 {
2223  if (filename.startsWith("myth://"))
2224  return 3;
2225 
2226  // check if the file exists
2227  if (!QFile::exists(filename))
2228  return 0;
2229 
2230 // TODO replace with FileSystemInfo?
2231 #ifdef Q_OS_DARWIN
2232  struct statfs statbuf {};
2233  if ((statfs(qPrintable(filename), &statbuf) == 0) &&
2234  ((!strcmp(statbuf.f_fstypename, "nfs")) || // NFS|FTP
2235  (!strcmp(statbuf.f_fstypename, "afpfs")) || // ApplShr
2236  (!strcmp(statbuf.f_fstypename, "smbfs")))) // SMB
2237  return 2;
2238 #elif defined(__linux__)
2239  struct statfs statbuf {};
2240  if ((statfs(qPrintable(filename), &statbuf) == 0) &&
2241  ((statbuf.f_type == 0x6969) || // NFS
2242  (statbuf.f_type == 0x517B))) // SMB
2243  return 2;
2244 #endif
2245 
2246  return 1;
2247 }
2248 
2250 {
2251  public:
2253  void LoadArguments(void) override; // MythCommandLineParser
2254 };
2255 
2257  MythCommandLineParser("mytharchivehelper")
2259 
2261 {
2262  addHelp();
2263  addVersion();
2264  addLogging();
2265 
2266  add(QStringList{"-t", "--createthumbnail"},
2267  "createthumbnail", false,
2268  "Create one or more thumbnails\n"
2269  "Requires: --infile, --thumblist, --outfile\n"
2270  "Optional: --framecount", "");
2271  add("--infile", "infile", "",
2272  "Input file name\n"
2273  "Used with: --createthumbnail, --getfileinfo, --isremote, "
2274  "--sup2dast, --importarchive", "");
2275  add("--outfile", "outfile", "",
2276  "Output file name\n"
2277  "Used with: --createthumbnail, --getfileinfo, --getdbparameters, "
2278  "--nativearchive\n"
2279  "When used with --createthumbnail: eg 'thumb%1-%2.jpg'\n"
2280  " %1 will be replaced with the no. of the thumb\n"
2281  " %2 will be replaced with the frame no.", "");
2282  add("--thumblist", "thumblist", "",
2283  "Comma-separated list of required thumbs (in seconds)\n"
2284  "Used with: --createthumbnail","");
2285  add("--framecount", "framecount", 1,
2286  "Number of frames to grab (default 1)\n"
2287  "Used with: --createthumbnail", "");
2288 
2289  add(QStringList{"-i", "--getfileinfo"},
2290  "getfileinfo", false,
2291  "Write file info about infile to outfile\n"
2292  "Requires: --infile, --outfile, --method", "");
2293  add("--method", "method", 0,
2294  "Method of file duration calculation\n"
2295  "Used with: --getfileinfo\n"
2296  " 0 = use av_estimate_timings() (quick but not very accurate - "
2297  "default)\n"
2298  " 1 = read all frames (most accurate but slow)\n"
2299  " 2 = use position map in DB (quick, only works for MythTV "
2300  "recordings)", "");
2301 
2302  add(QStringList{"-p", "--getdbparameters"},
2303  "getdbparameters", false,
2304  "Write the mysql database parameters to outfile\n"
2305  "Requires: --outfile", "");
2306 
2307  add(QStringList{"-n", "--nativearchive"},
2308  "nativearchive", false,
2309  "Archive files to a native archive format\n"
2310  "Requires: --outfile", "");
2311 
2312  add(QStringList{"-f", "--importarchive"},
2313  "importarchive", false,
2314  "Import an archived file\n"
2315  "Requires: --infile, --chanid", "");
2316  add("--chanid", "chanid", -1,
2317  "Channel ID to use when inserting records in DB\n"
2318  "Used with: --importarchive", "");
2319 
2320  add(QStringList{"-r", "--isremote"},
2321  "isremote", false,
2322  "Check if infile is on a remote filesystem\n"
2323  "Requires: --infile\n"
2324  "Returns: 0 on error or file not found\n"
2325  " - 1 file is on a local filesystem\n"
2326  " - 2 file is on a remote filesystem", "");
2327 
2328  add(QStringList{"-b", "--burndvd"},
2329  "burndvd", false,
2330  "Burn a created DVD to a blank disc\n"
2331  "Optional: --mediatype, --erasedvdrw, --nativeformat", "");
2332  add("--mediatype", "mediatype", 0,
2333  "Type of media to burn\n"
2334  "Used with: --burndvd\n"
2335  " 0 = single layer DVD (default)\n"
2336  " 1 = dual layer DVD\n"
2337  " 2 = rewritable DVD", "");
2338  add("--erasedvdrw", "erasedvdrw", false,
2339  "Force an erase of DVD-R/W Media\n"
2340  "Used with: --burndvd (optional)", "");
2341  add("--nativeformat", "nativeformat", false,
2342  "Archive is a native archive format\n"
2343  "Used with: --burndvd (optional)", "");
2344 
2345  add(QStringList{"-s", "--sup2dast"},
2346  "sup2dast", false,
2347  "Convert projectX subtitles to DVD subtitles\n"
2348  "Requires: --infile, --ifofile, --delay", "");
2349  add("--ifofile", "ifofile", "",
2350  "Filename of ifo file\n"
2351  "Used with: --sup2dast", "");
2352  add("--delay", "delay", 0,
2353  "Delay in ms to add to subtitles (default 0)\n"
2354  "Used with: --sup2dast", "");
2355 }
2356 
2357 
2358 
2359 static int main_local(int argc, char **argv)
2360 {
2362  if (!cmdline.Parse(argc, argv))
2363  {
2364  cmdline.PrintHelp();
2366  }
2367 
2368  if (cmdline.toBool("showhelp"))
2369  {
2370  cmdline.PrintHelp();
2371  return GENERIC_EXIT_OK;
2372  }
2373 
2374  if (cmdline.toBool("showversion"))
2375  {
2377  return GENERIC_EXIT_OK;
2378  }
2379 
2380  QCoreApplication a(argc, argv);
2381  QCoreApplication::setApplicationName("mytharchivehelper");
2382 
2383  // by default we only output our messages
2384  QString mask("jobqueue");
2385  int retval = cmdline.ConfigureLogging(mask);
2386  if (retval != GENERIC_EXIT_OK)
2387  return retval;
2388 
2390  // Don't listen to console input
2391  close(0);
2392 
2393  gContext = new MythContext(MYTH_BINARY_VERSION);
2394  if (!gContext->Init(false))
2395  {
2396  LOG(VB_GENERAL, LOG_ERR, "Failed to init MythContext, exiting.");
2397  delete gContext;
2398  gContext = nullptr;
2400  }
2401 
2402  int res = 0;
2403  bool bGrabThumbnail = cmdline.toBool("createthumbnail");
2404  bool bGetDBParameters = cmdline.toBool("getdbparameters");
2405  bool bNativeArchive = cmdline.toBool("nativearchive");
2406  bool bImportArchive = cmdline.toBool("importarchive");
2407  bool bGetFileInfo = cmdline.toBool("getfileinfo");
2408  bool bIsRemote = cmdline.toBool("isremote");
2409  bool bDoBurn = cmdline.toBool("burndvd");
2410  bool bEraseDVDRW = cmdline.toBool("erasedvdrw");
2411  bool bNativeFormat = cmdline.toBool("nativeformat");;
2412  bool bSup2Dast = cmdline.toBool("sup2dast");
2413 
2414  QString thumbList = cmdline.toString("thumblist");
2415  QString inFile = cmdline.toString("infile");
2416  QString outFile = cmdline.toString("outfile");
2417  QString ifoFile = cmdline.toString("ifofile");
2418 
2419  int mediaType = cmdline.toUInt("mediatype");
2420  int lenMethod = cmdline.toUInt("method");
2421  int chanID = cmdline.toInt("chanid");
2422  int frameCount = cmdline.toUInt("framecount");
2423  int delay = cmdline.toUInt("delay");
2424 
2425  // Check command line arguments
2426  if (bGrabThumbnail)
2427  {
2428  if (inFile.isEmpty())
2429  {
2430  LOG(VB_GENERAL, LOG_ERR, "Missing --infile in -t/--grabthumbnail "
2431  "option");
2433  }
2434 
2435  if (thumbList.isEmpty())
2436  {
2437  LOG(VB_GENERAL, LOG_ERR, "Missing --thumblist in -t/--grabthumbnail"
2438  " option");
2440  }
2441 
2442  if (outFile.isEmpty())
2443  {
2444  LOG(VB_GENERAL, LOG_ERR, "Missing --outfile in -t/--grabthumbnail "
2445  "option");
2447  }
2448  }
2449 
2450  if (bGetDBParameters)
2451  {
2452  if (outFile.isEmpty())
2453  {
2454  LOG(VB_GENERAL, LOG_ERR, "Missing argument to -p/--getdbparameters "
2455  "option");
2457  }
2458  }
2459 
2460  if (bIsRemote)
2461  {
2462  if (inFile.isEmpty())
2463  {
2464  LOG(VB_GENERAL, LOG_ERR,
2465  "Missing argument to -r/--isremote option");
2467  }
2468  }
2469 
2470  if (bDoBurn)
2471  {
2472  if (mediaType < 0 || mediaType > 2)
2473  {
2474  LOG(VB_GENERAL, LOG_ERR, QString("Invalid mediatype given: %1")
2475  .arg(mediaType));
2477  }
2478  }
2479 
2480  if (bNativeArchive)
2481  {
2482  if (outFile.isEmpty())
2483  {
2484  LOG(VB_GENERAL, LOG_ERR, "Missing argument to -n/--nativearchive "
2485  "option");
2487  }
2488  }
2489 
2490  if (bImportArchive)
2491  {
2492  if (inFile.isEmpty())
2493  {
2494  LOG(VB_GENERAL, LOG_ERR, "Missing --infile argument to "
2495  "-f/--importarchive option");
2497  }
2498  }
2499 
2500  if (bGetFileInfo)
2501  {
2502  if (inFile.isEmpty())
2503  {
2504  LOG(VB_GENERAL, LOG_ERR, "Missing --infile in -i/--getfileinfo "
2505  "option");
2507  }
2508 
2509  if (outFile.isEmpty())
2510  {
2511  LOG(VB_GENERAL, LOG_ERR, "Missing --outfile in -i/--getfileinfo "
2512  "option");
2514  }
2515  }
2516 
2517  if (bSup2Dast)
2518  {
2519  if (inFile.isEmpty())
2520  {
2521  LOG(VB_GENERAL, LOG_ERR,
2522  "Missing --infile in -s/--sup2dast option");
2524  }
2525 
2526  if (ifoFile.isEmpty())
2527  {
2528  LOG(VB_GENERAL, LOG_ERR,
2529  "Missing --ifofile in -s/--sup2dast option");
2531  }
2532  }
2533 
2534  if (bGrabThumbnail)
2535  res = grabThumbnail(inFile, thumbList, outFile, frameCount);
2536  else if (bGetDBParameters)
2537  res = getDBParamters(outFile);
2538  else if (bNativeArchive)
2539  res = doNativeArchive(outFile);
2540  else if (bImportArchive)
2541  res = doImportArchive(inFile, chanID);
2542  else if (bGetFileInfo)
2543  res = getFileInfo(inFile, outFile, lenMethod);
2544  else if (bIsRemote)
2545  res = isRemote(inFile);
2546  else if (bDoBurn)
2547  res = doBurnDVD(mediaType, bEraseDVDRW, bNativeFormat);
2548  else if (bSup2Dast)
2549  {
2550  QByteArray inFileBA = inFile.toLocal8Bit();
2551  QByteArray ifoFileBA = ifoFile.toLocal8Bit();
2552  res = sup2dast(inFileBA.constData(), ifoFileBA.constData(), delay);
2553  }
2554  else
2555  {
2556  cmdline.PrintHelp();
2557  }
2558 
2559  delete gContext;
2560  gContext = nullptr;
2561 
2562  exit(res);
2563 }
2564 
2565 int main(int argc, char **argv)
2566 {
2567  int result = main_local(argc, argv);
2568  logStop();
2569  return result;
2570 }
MSqlQuery::isActive
bool isActive(void) const
Definition: mythdbcon.h:215
NativeArchive::exportVideo
static int exportVideo(QDomElement &itemNode, const QString &saveDirectory)
Definition: mytharchivehelper.cpp:626
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:127
ArchiveRemoteAVFormatContext::isOpen
bool isOpen() const
Definition: remoteavformatcontext.h:104
badChars
static const QRegularExpression badChars
Definition: mytharchivehelper.cpp:374
pxsup2dast.h
MythDate::toString
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:214
AD_FILE
@ AD_FILE
Definition: archiveutil.h:21
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
MythCoreContext::GetMasterHostName
QString GetMasterHostName(void)
Definition: mythcorecontext.cpp:811
DatabaseParams::m_dbHostName
QString m_dbHostName
database server
Definition: mythdbparams.h:22
MARK_KEYFRAME
@ MARK_KEYFRAME
Definition: programtypes.h:61
MythCommandLineParser
Parent class for defining application command line parsers.
Definition: mythcommandlineparser.h:116
mythdb.h
ProgramInfo::QueryPositionMap
void QueryPositionMap(frm_pos_map_t &posMap, MarkTypes type) const
Definition: programinfo.cpp:3784
MythCodecMap::GetCodecContext
AVCodecContext * GetCodecContext(const AVStream *Stream, const AVCodec *Codec=nullptr, bool NullCodec=false)
Definition: mythavutil.cpp:288
MARK_GOP_START
@ MARK_GOP_START
Definition: programtypes.h:60
NativeArchive::NativeArchive
NativeArchive(void)
Definition: mytharchivehelper.cpp:99
cmdline
MythCommFlagCommandLineParser cmdline
Definition: mythcommflag.cpp:72
DatabaseParams
Structure containing the basic Database parameters.
Definition: mythdbparams.h:10
NativeArchive
Definition: mytharchivehelper.cpp:80
MythArchiveHelperCommandLineParser
Definition: mytharchivehelper.cpp:2249
MythContext
Startup context for MythTV.
Definition: mythcontext.h:43
MythAVFrame
MythAVFrame little utility class that act as a safe way to allocate an AVFrame which can then be allo...
Definition: mythavframe.h:26
MythAVUtil::DeinterlaceAVFrame
static void DeinterlaceAVFrame(AVFrame *Frame)
Deinterlace an AVFrame.
Definition: mythavutil.cpp:135
mythcoreutil.h
AD_DVD_RW
@ AD_DVD_RW
Definition: archiveutil.h:20
xbmcvfs.exists
bool exists(str path)
Definition: xbmcvfs.py:51
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:204
MythCommandLineParser::addVersion
void addVersion(void)
Canned argument definition for –version.
Definition: mythcommandlineparser.cpp:2552
frm_dir_map_t
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:117
NativeArchive::doImportArchive
static int doImportArchive(const QString &xmlFile, int chanID)
Definition: mytharchivehelper.cpp:875
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
NativeArchive::doNativeArchive
static int doNativeArchive(const QString &jobFile)
Definition: mytharchivehelper.cpp:234
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
GetMythDB
MythDB * GetMythDB(void)
Definition: mythdb.cpp:50
build_compdb.file
file
Definition: build_compdb.py:55
mythavframe.h
burnISOImage
static int burnISOImage(int mediaType, bool bEraseDVDRW, bool nativeFormat)
Definition: mytharchivehelper.cpp:160
mythdirs.h
myth_system
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
Definition: mythsystemlegacy.cpp:506
getTempDirectory
QString getTempDirectory(bool showError)
Definition: archiveutil.cpp:46
doBurnDVD
static int doBurnDVD(int mediaType, bool bEraseDVDRW, bool nativeFormat)
Definition: mytharchivehelper.cpp:218
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
close
#define close
Definition: compat.h:43
tmp
static guint32 * tmp
Definition: goom_core.cpp:26
MythCommandLineParser::addLogging
void addLogging(const QString &defaultVerbosity="general", LogLevel_t defaultLogLevel=LOG_INFO)
Canned argument definition for all logging options, including –verbose, –logpath, –quiet,...
Definition: mythcommandlineparser.cpp:2656
mythsystemlegacy.h
MythArchiveHelperCommandLineParser::MythArchiveHelperCommandLineParser
MythArchiveHelperCommandLineParser()
Definition: mytharchivehelper.cpp:2256
MythRemoveDirectory
bool MythRemoveDirectory(QDir &aDir)
Definition: mythmiscutil.cpp:759
getFrameCount
static int64_t getFrameCount(AVFormatContext *inputFC, int vid_id)
Definition: mytharchivehelper.cpp:1755
mythpluginexport.h
GetInstallPrefix
QString GetInstallPrefix(void)
Definition: mythdirs.cpp:252
AVFrame
struct AVFrame AVFrame
Definition: BorderDetector.h:15
MythCommandLineParser::Parse
virtual bool Parse(int argc, const char *const *argv)
Loop through argv and populate arguments with values.
Definition: mythcommandlineparser.cpp:1552
MythCommandLineParser::add
CommandLineArg * add(const QString &arg, const QString &name, bool def, QString help, QString longhelp)
Definition: mythcommandlineparser.cpp:1770
NativeArchive::copyFile
static bool copyFile(const QString &source, const QString &destination)
Definition: mytharchivehelper.cpp:121
mythdate.h
programinfo.h
GENERIC_EXIT_INVALID_CMDLINE
@ GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:18
mythlogging.h
MythCoreContext::GenMythURL
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
Definition: mythcorecontext.cpp:764
hardwareprofile.i18n.t
t
Definition: i18n.py:36
statfs
int statfs(const char *path, struct statfs *buffer)
Definition: compat.h:105
GENERIC_EXIT_OK
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
mark
Definition: lang.cpp:22
getCutFrames
static int64_t getCutFrames(const QString &filename, int64_t lastFrame)
Definition: mytharchivehelper.cpp:1780
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
hardwareprofile.scan.rating
def rating(profile, smoonURL, gate)
Definition: scan.py:37
NativeArchive::~NativeArchive
~NativeArchive(void)
Definition: mytharchivehelper.cpp:113
ProgramInfo::QueryCutList
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
Definition: programinfo.cpp:3475
MARK_CUT_START
@ MARK_CUT_START
Definition: programtypes.h:55
MythCommandLineParser::PrintVersion
static void PrintVersion(void)
Print application version information.
Definition: mythcommandlineparser.cpp:1380
MythCodecMap::FreeCodecContext
void FreeCodecContext(const AVStream *Stream)
Definition: mythavutil.cpp:336
MythCommandLineParser::toUInt
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2246
MythCommandLineParser::PrintHelp
void PrintHelp(void) const
Print command line option help.
Definition: mythcommandlineparser.cpp:1396
DatabaseParams::m_dbPassword
QString m_dbPassword
DB password.
Definition: mythdbparams.h:26
ArchiveRemoteAVFormatContext
Definition: remoteavformatcontext.h:16
DatabaseParams::m_dbName
QString m_dbName
database name
Definition: mythdbparams.h:27
MythCodecMap
Definition: mythavutil.h:27
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
NativeArchive::findNodeText
static QString findNodeText(const QDomElement &elem, const QString &nodeName)
Definition: mytharchivehelper.cpp:1469
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:916
getProgramInfoForFile
ProgramInfo * getProgramInfoForFile(const QString &inFile)
Definition: archiveutil.cpp:163
logStop
void logStop(void)
Entry point for stopping logging for an application.
Definition: logging.cpp:686
MARK_GOP_BYFRAME
@ MARK_GOP_BYFRAME
Definition: programtypes.h:63
frm_pos_map_t
QMap< long long, long long > frm_pos_map_t
Frame # -> File offset map.
Definition: programtypes.h:44
grabThumbnail
static int grabThumbnail(const QString &inFile, const QString &thumbList, const QString &outFile, int frameCount)
Definition: mytharchivehelper.cpp:1548
NativeArchive::importRecording
static int importRecording(const QDomElement &itemNode, const QString &xmlFile, int chanID)
Definition: mytharchivehelper.cpp:941
MythCoreContext::GetMasterServerPort
static int GetMasterServerPort(void)
Returns the Master Backend control port If no master server port has been defined in the database,...
Definition: mythcorecontext.cpp:984
NativeArchive::getFieldList
static int getFieldList(QStringList &fieldList, const QString &tableName)
Definition: mytharchivehelper.cpp:383
getFileInfo
static int getFileInfo(const QString &inFile, const QString &outFile, int lenMethod)
Definition: mytharchivehelper.cpp:1900
doNativeArchive
static int doNativeArchive(const QString &jobFile)
Definition: mytharchivehelper.cpp:1521
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:67
mythmiscutil.h
MythCommandLineParser::toString
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2354
MARK_CUT_END
@ MARK_CUT_END
Definition: programtypes.h:54
NativeArchive::exportRecording
static int exportRecording(QDomElement &itemNode, const QString &saveDirectory)
Definition: mytharchivehelper.cpp:403
MythCommandLineParser::addHelp
void addHelp(void)
Canned argument definition for –help.
Definition: mythcommandlineparser.cpp:2539
MythCommandLineParser::toBool
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
Definition: mythcommandlineparser.cpp:2197
statfs
Definition: compat.h:93
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
DatabaseParams::m_dbUserName
QString m_dbUserName
DB user name.
Definition: mythdbparams.h:25
MPLUGIN_PUBLIC
#define MPLUGIN_PUBLIC
Definition: mythpluginexport.h:9
ProgramInfo::IsVideo
bool IsVideo(void) const
Definition: programinfo.h:489
mythavutil.h
NativeArchive::importVideo
static int importVideo(const QDomElement &itemNode, const QString &xmlFile)
Definition: mytharchivehelper.cpp:1143
mythcontext.h
main_local
static int main_local(int argc, char **argv)
Definition: mytharchivehelper.cpp:2359
MythArchiveHelperCommandLineParser::LoadArguments
void LoadArguments(void) override
Definition: mytharchivehelper.cpp:2260
MythAVCopy
Definition: mythavutil.h:44
doImportArchive
static int doImportArchive(const QString &inFile, int chanID)
Definition: mytharchivehelper.cpp:1543
createISOImage
static bool createISOImage(QString &sourceDirectory)
Definition: mytharchivehelper.cpp:136
MythDate::kDatabase
@ kDatabase
Default UTC, database format.
Definition: mythdate.h:27
clearArchiveTable
static void clearArchiveTable(void)
Definition: mytharchivehelper.cpp:1512
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:842
main
int main(int argc, char **argv)
Definition: mytharchivehelper.cpp:2565
MythCommandLineParser::ConfigureLogging
int ConfigureLogging(const QString &mask="general", bool progress=false)
Read in logging options and initialize the logging interface.
Definition: mythcommandlineparser.cpp:2870
MythCommandLineParser::LoadArguments
virtual void LoadArguments(void)
Definition: mythcommandlineparser.h:137
MythCoreContext::SaveSetting
void SaveSetting(const QString &key, int newValue)
Definition: mythcorecontext.cpp:885
getBaseName
QString getBaseName(const QString &filename)
Definition: archiveutil.cpp:123
MythAVCopy::Copy
int Copy(AVFrame *To, const MythVideoFrame *From, unsigned char *Buffer, AVPixelFormat Fmt=AV_PIX_FMT_YUV420P)
Initialise AVFrame and copy contents of VideoFrame frame into it, performing any required conversion.
Definition: mythavutil.cpp:267
exitcodes.h
MythCommandLineParser::toInt
int toInt(const QString &key) const
Returns stored QVariant as an integer, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2219
getDBParamters
static int getDBParamters(const QString &outFile)
Definition: mytharchivehelper.cpp:2195
build_compdb.filename
filename
Definition: build_compdb.py:21
mythcommandlineparser.h
build_compdb.options
options
Definition: build_compdb.py:11
mythburn.dbVersion
string dbVersion
Definition: mythburn.py:172
sup2dast
int sup2dast(const char *supfile, const char *ifofile, int delay_ms)
Definition: pxsup2dast.c:904
gContext
MythContext * gContext
This global variable contains the MythContext instance for the application.
Definition: mythcontext.cpp:64
MythContext::Init
bool Init(bool gui=true, bool promptForBackend=false, bool disableAutoDiscovery=false, bool ignoreDB=false)
Definition: mythcontext.cpp:1608
fixFilename
static QString fixFilename(const QString &filename)
Definition: mytharchivehelper.cpp:376
isRemote
static int isRemote(const QString &filename)
Definition: mytharchivehelper.cpp:2221
extractDetailsFromFilename
bool extractDetailsFromFilename(const QString &inFile, QString &chanID, QString &startTime)
Definition: archiveutil.cpp:133
GENERIC_EXIT_NO_MYTHCONTEXT
@ GENERIC_EXIT_NO_MYTHCONTEXT
No MythContext available.
Definition: exitcodes.h:16
uint
unsigned int uint
Definition: freesurround.h:24
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:902
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837