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