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