MythTV  master
mythplugins/mytharchive/mytharchivehelper/main.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 <mythconfig.h>
34 #if CONFIG_DARWIN or defined(__FreeBSD__)
35 #include <sys/param.h>
36 #include <sys/mount.h>
37 #elif __linux__
38 #include <sys/vfs.h>
39 #endif
40 
41 // Qt headers
42 #include <QApplication>
43 #include <QFile>
44 #include <QDir>
45 #include <QDomElement>
46 #include <QImage>
47 #include <QMutex>
48 #include <QMutexLocker>
49 #include <QTextStream>
50 
51 // MythTV headers
52 #include <mythcommandlineparser.h>
53 #include <mythmiscutil.h>
54 #include <mythcoreutil.h>
55 #include <mythcontext.h>
56 #include <mythversion.h>
57 #include <exitcodes.h>
58 #include <mythdb.h>
59 #include <programinfo.h>
60 #include <mythdirs.h>
61 #include <mythconfig.h>
62 #include <mythsystemlegacy.h>
63 #include <mythdate.h>
64 #include <mythlogging.h>
65 #include <mythavutil.h>
66 
67 extern "C" {
68 #include <libavcodec/avcodec.h>
69 #include <libavformat/avformat.h>
70 #include "external/pxsup2dast.h"
71 #include "libavutil/imgutils.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).arg(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 QRegExp badChars = QRegExp(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 
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")
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 
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
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")
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
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;");
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).arg(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
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");
1203  coverFilename = fileInfo.fileName();
1204  //check file exists
1206  if (fileInfo.exists())
1207  {
1208  LOG(VB_JOBQUEUE, LOG_INFO, "Copying cover file");
1209 
1211  {
1212  return 1;
1213  }
1214  }
1215  else
1216  coverFilename = "No Cover";
1217 
1218  // copy videometadata to database
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 {
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  RemoteAVFormatContext 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  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  AVPacket pkt;
1768  int64_t count = 0;
1769 
1770  LOG(VB_JOBQUEUE, LOG_INFO, "Calculating frame count");
1771 
1772  av_init_packet(&pkt);
1773 
1774  while (av_read_frame(inputFC, &pkt) >= 0)
1775  {
1776  if (pkt.stream_index == vid_id)
1777  {
1778  count++;
1779  }
1780  av_packet_unref(&pkt);
1781  }
1782 
1783  return count;
1784 }
1785 
1786 static int64_t getCutFrames(const QString &filename, int64_t lastFrame)
1787 {
1788  // only wont the filename
1789  QString basename = filename;
1790  int pos = filename.lastIndexOf('/');
1791  if (pos > 0)
1792  basename = filename.mid(pos + 1);
1793 
1794  ProgramInfo *progInfo = getProgramInfoForFile(basename);
1795  if (!progInfo)
1796  return 0;
1797 
1798  if (progInfo->IsVideo())
1799  {
1800  delete progInfo;
1801  return 0;
1802  }
1803 
1804  frm_dir_map_t cutlist;
1805  frm_dir_map_t::iterator it;
1806  uint64_t frames = 0;
1807 
1808  progInfo->QueryCutList(cutlist);
1809 
1810  if (cutlist.empty())
1811  {
1812  delete progInfo;
1813  return 0;
1814  }
1815 
1816  for (it = cutlist.begin(); it != cutlist.end();)
1817  {
1818  uint64_t start = 0;
1819  uint64_t end = 0;
1820 
1821  if (it.value() == MARK_CUT_START)
1822  {
1823  start = it.key();
1824  ++it;
1825  if (it != cutlist.end())
1826  {
1827  end = it.key();
1828  ++it;
1829  }
1830  else
1831  end = lastFrame;
1832  }
1833  else if (it.value() == MARK_CUT_END)
1834  {
1835  start = 0;
1836  end = it.key();
1837  ++it;
1838  }
1839  else
1840  {
1841  ++it;
1842  continue;
1843  }
1844 
1845  frames += end - start;
1846  }
1847 
1848  delete progInfo;
1849  return frames;
1850 }
1851 
1852 static int64_t getFrameCount(const QString &filename, float fps)
1853 {
1854  // only wont the filename
1855  QString basename = filename;
1856  int pos = filename.lastIndexOf('/');
1857  if (pos > 0)
1858  basename = filename.mid(pos + 1);
1859 
1860  int keyframedist = -1;
1861  frm_pos_map_t posMap;
1862 
1863  ProgramInfo *progInfo = getProgramInfoForFile(basename);
1864  if (!progInfo)
1865  return 0;
1866 
1867  progInfo->QueryPositionMap(posMap, MARK_GOP_BYFRAME);
1868  if (!posMap.empty())
1869  {
1870  keyframedist = 1;
1871  }
1872  else
1873  {
1874  progInfo->QueryPositionMap(posMap, MARK_GOP_START);
1875  if (!posMap.empty())
1876  {
1877  keyframedist = 15;
1878  if (fps < 26 && fps > 24)
1879  keyframedist = 12;
1880  }
1881  else
1882  {
1883  progInfo->QueryPositionMap(posMap, MARK_KEYFRAME);
1884  if (!posMap.empty())
1885  {
1886  // keyframedist should be set in the fileheader so no
1887  // need to try to determine it in this case
1888  delete progInfo;
1889  return 0;
1890  }
1891  }
1892  }
1893 
1894  delete progInfo;
1895  if (posMap.empty())
1896  return 0; // no position map in recording
1897 
1898  frm_pos_map_t::const_iterator it = posMap.cend();
1899  --it;
1900  uint64_t totframes = it.key() * keyframedist;
1901  return totframes;
1902 }
1903 
1904 static int getFileInfo(const QString& inFile, const QString& outFile, int lenMethod)
1905 {
1906  // Open recording
1907  LOG(VB_JOBQUEUE , LOG_INFO, QString("getFileInfo(): Opening '%1'")
1908  .arg(inFile));
1909 
1910  MythCodecMap codecmap;
1911  RemoteAVFormatContext inputFC(inFile);
1912  if (!inputFC.isOpen())
1913  {
1914  LOG(VB_JOBQUEUE, LOG_ERR, "getFileInfo(): Couldn't open input file" +
1915  ENO);
1916  return 1;
1917  }
1918 
1919  // Getting stream information
1920  int ret = avformat_find_stream_info(inputFC, nullptr);
1921 
1922  if (ret < 0)
1923  {
1924  LOG(VB_JOBQUEUE, LOG_ERR,
1925  QString("Couldn't get stream info, error #%1").arg(ret));
1926  return 1;
1927  }
1928 
1929  // Dump stream information
1930  av_dump_format(inputFC, 0, qPrintable(inFile), 0);
1931 
1932  QDomDocument doc("FILEINFO");
1933 
1934  QDomElement root = doc.createElement("file");
1935  doc.appendChild(root);
1936  root.setAttribute("type", inputFC->iformat->name);
1937  root.setAttribute("filename", inFile);
1938 
1939  QDomElement streams = doc.createElement("streams");
1940 
1941  root.appendChild(streams);
1942  streams.setAttribute("count", inputFC->nb_streams);
1943  int ffmpegIndex = 0;
1944  uint duration = 0;
1945 
1946  for (uint i = 0; i < inputFC->nb_streams; i++)
1947  {
1948  AVStream *st = inputFC->streams[i];
1949  std::string buf (256,'\0');
1950  AVCodecContext *avctx = codecmap.GetCodecContext(st);
1951  AVCodecParameters *par = st->codecpar;
1952 
1953  if (avctx)
1954  avcodec_string(buf.data(), buf.size(), avctx, static_cast<int>(false));
1955 
1956  switch (st->codecpar->codec_type)
1957  {
1958  case AVMEDIA_TYPE_VIDEO:
1959  {
1960 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1961  QStringList param = QString::fromStdString(buf).split(',', QString::SkipEmptyParts);
1962 #else
1963  QStringList param = QString::fromStdString(buf).split(',', Qt::SkipEmptyParts);
1964 #endif
1965  QString codec = param[0].remove("Video:", Qt::CaseInsensitive);
1966  QDomElement stream = doc.createElement("video");
1967  stream.setAttribute("streamindex", i);
1968  stream.setAttribute("ffmpegindex", ffmpegIndex++);
1969  stream.setAttribute("codec", codec.trimmed());
1970  stream.setAttribute("width", par->width);
1971  stream.setAttribute("height", par->height);
1972  stream.setAttribute("bitrate", (qlonglong)par->bit_rate);
1973 
1974  float fps = NAN;
1975  if (st->r_frame_rate.den && st->r_frame_rate.num)
1976  fps = av_q2d(st->r_frame_rate);
1977  else
1978  fps = 1/av_q2d(st->time_base);
1979 
1980  stream.setAttribute("fps", fps);
1981 
1982  if (par->sample_aspect_ratio.den && par->sample_aspect_ratio.num)
1983  {
1984  float aspect_ratio = av_q2d(par->sample_aspect_ratio);
1985  if (QString(inputFC->iformat->name) != "nuv")
1986  aspect_ratio = ((float)par->width
1987  / par->height) * aspect_ratio;
1988 
1989  stream.setAttribute("aspectratio", aspect_ratio);
1990  }
1991  else
1992  stream.setAttribute("aspectratio", "N/A");
1993 
1994  stream.setAttribute("id", st->id);
1995 
1996  if (st->start_time != (int) AV_NOPTS_VALUE)
1997  {
1998  int secs = st->start_time / AV_TIME_BASE;
1999  int us = st->start_time % AV_TIME_BASE;
2000  stream.setAttribute("start_time", QString("%1.%2")
2001  .arg(secs).arg(av_rescale(us, 1000000, AV_TIME_BASE)));
2002  }
2003  else
2004  stream.setAttribute("start_time", 0);
2005 
2006  streams.appendChild(stream);
2007 
2008  // TODO: probably should add a better way to choose which
2009  // video stream we use to calc the duration
2010  if (duration == 0)
2011  {
2012  int64_t frameCount = 0;
2013 
2014  switch (lenMethod)
2015  {
2016  case 0:
2017  {
2018  // use duration guess from avformat
2019  if (inputFC->duration != (uint) AV_NOPTS_VALUE)
2020  {
2021  duration = (uint) (inputFC->duration / AV_TIME_BASE);
2022  root.setAttribute("duration", duration);
2023  LOG(VB_JOBQUEUE, LOG_INFO,
2024  QString("duration = %1") .arg(duration));
2025  frameCount = (int64_t)(duration * fps);
2026  }
2027  else
2028  root.setAttribute("duration", "N/A");
2029  break;
2030  }
2031  case 1:
2032  {
2033  // calc duration of the file by counting the video frames
2034  frameCount = getFrameCount(inputFC, i);
2035  LOG(VB_JOBQUEUE, LOG_INFO,
2036  QString("frames = %1").arg(frameCount));
2037  duration = (uint)(frameCount / fps);
2038  LOG(VB_JOBQUEUE, LOG_INFO,
2039  QString("duration = %1").arg(duration));
2040  root.setAttribute("duration", duration);
2041  break;
2042  }
2043  case 2:
2044  {
2045  // use info from pos map in db
2046  // (only useful if the file is a myth recording)
2047  frameCount = getFrameCount(inFile, fps);
2048  if (frameCount)
2049  {
2050  LOG(VB_JOBQUEUE, LOG_INFO,
2051  QString("frames = %1").arg(frameCount));
2052  duration = (uint)(frameCount / fps);
2053  LOG(VB_JOBQUEUE, LOG_INFO,
2054  QString("duration = %1").arg(duration));
2055  root.setAttribute("duration", duration);
2056  }
2057  else if (inputFC->duration != (uint) AV_NOPTS_VALUE)
2058  {
2059  duration = (uint) (inputFC->duration / AV_TIME_BASE);
2060  root.setAttribute("duration", duration);
2061  LOG(VB_JOBQUEUE, LOG_INFO,
2062  QString("duration = %1").arg(duration));
2063  frameCount = (int64_t)(duration * fps);
2064  }
2065  else
2066  root.setAttribute("duration", "N/A");
2067  break;
2068  }
2069  default:
2070  root.setAttribute("duration", "N/A");
2071  LOG(VB_JOBQUEUE, LOG_ERR,
2072  QString("Unknown lenMethod (%1)")
2073  .arg(lenMethod));
2074  }
2075 
2076  // add duration after all cuts are removed
2077  int64_t cutFrames = getCutFrames(inFile, frameCount);
2078  LOG(VB_JOBQUEUE, LOG_INFO,
2079  QString("cutframes = %1").arg(cutFrames));
2080  int cutduration = (int)(cutFrames / fps);
2081  LOG(VB_JOBQUEUE, LOG_INFO,
2082  QString("cutduration = %1").arg(cutduration));
2083  root.setAttribute("cutduration", duration - cutduration);
2084  }
2085 
2086  break;
2087  }
2088 
2089  case AVMEDIA_TYPE_AUDIO:
2090  {
2091 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
2092  QStringList param = QString::fromStdString(buf).split(',', QString::SkipEmptyParts);
2093 #else
2094  QStringList param = QString::fromStdString(buf).split(',', Qt::SkipEmptyParts);
2095 #endif
2096  QString codec = param[0].remove("Audio:", Qt::CaseInsensitive);
2097 
2098  QDomElement stream = doc.createElement("audio");
2099  stream.setAttribute("streamindex", i);
2100  stream.setAttribute("ffmpegindex", ffmpegIndex++);
2101 
2102  // change any streams identified as "liba52" to "AC3" which is what
2103  // the mythburn.py script expects to get.
2104  if (codec.trimmed().toLower() == "liba52")
2105  stream.setAttribute("codec", "AC3");
2106  else
2107  stream.setAttribute("codec", codec.trimmed());
2108 
2109  stream.setAttribute("channels", par->channels);
2110 
2111  AVDictionaryEntry *metatag =
2112  av_dict_get(st->metadata, "language", nullptr, 0);
2113  if (metatag)
2114  stream.setAttribute("language", metatag->value);
2115  else
2116  stream.setAttribute("language", "N/A");
2117 
2118  stream.setAttribute("id", st->id);
2119 
2120  stream.setAttribute("samplerate", par->sample_rate);
2121  stream.setAttribute("bitrate", (qlonglong)par->bit_rate);
2122 
2123  if (st->start_time != (int) AV_NOPTS_VALUE)
2124  {
2125  int secs = st->start_time / AV_TIME_BASE;
2126  int us = st->start_time % AV_TIME_BASE;
2127  stream.setAttribute("start_time", QString("%1.%2")
2128  .arg(secs).arg(av_rescale(us, 1000000, AV_TIME_BASE)));
2129  }
2130  else
2131  stream.setAttribute("start_time", 0);
2132 
2133  streams.appendChild(stream);
2134 
2135  break;
2136  }
2137 
2138  case AVMEDIA_TYPE_SUBTITLE:
2139  {
2140 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
2141  QStringList param = QString::fromStdString(buf).split(',', QString::SkipEmptyParts);
2142 #else
2143  QStringList param = QString::fromStdString(buf).split(',', Qt::SkipEmptyParts);
2144 #endif
2145  QString codec = param[0].remove("Subtitle:", Qt::CaseInsensitive);
2146 
2147  QDomElement stream = doc.createElement("subtitle");
2148  stream.setAttribute("streamindex", i);
2149  stream.setAttribute("ffmpegindex", ffmpegIndex++);
2150  stream.setAttribute("codec", codec.trimmed());
2151 
2152  AVDictionaryEntry *metatag =
2153  av_dict_get(st->metadata, "language", nullptr, 0);
2154  if (metatag)
2155  stream.setAttribute("language", metatag->value);
2156  else
2157  stream.setAttribute("language", "N/A");
2158 
2159  stream.setAttribute("id", st->id);
2160 
2161  streams.appendChild(stream);
2162 
2163  break;
2164  }
2165 
2166  case AVMEDIA_TYPE_DATA:
2167  {
2168  QDomElement stream = doc.createElement("data");
2169  stream.setAttribute("streamindex", i);
2170  stream.setAttribute("codec", QString::fromStdString(buf));
2171  streams.appendChild(stream);
2172 
2173  break;
2174  }
2175 
2176  default:
2177  LOG(VB_JOBQUEUE, LOG_ERR,
2178  QString("Skipping unsupported codec %1 on stream %2")
2179  .arg(inputFC->streams[i]->codecpar->codec_type).arg(i));
2180  break;
2181  }
2182  codecmap.FreeCodecContext(st);
2183  }
2184 
2185  // finally save the xml to the file
2186  QFile f(outFile);
2187  if (!f.open(QIODevice::WriteOnly))
2188  {
2189  LOG(VB_JOBQUEUE, LOG_ERR,
2190  "Failed to open file for writing - " + outFile);
2191  return 1;
2192  }
2193 
2194  QTextStream t(&f);
2195  t << doc.toString(4);
2196  f.close();
2197 
2198  return 0;
2199 }
2200 
2201 static int getDBParamters(const QString& outFile)
2202 {
2204 
2205  // save the db paramters to the file
2206  QFile f(outFile);
2207  if (!f.open(QIODevice::WriteOnly))
2208  {
2209  LOG(VB_GENERAL, LOG_ERR,
2210  QString("MythArchiveHelper: Failed to open file for writing - %1")
2211  .arg(outFile));
2212  return 1;
2213  }
2214 
2215  QTextStream t(&f);
2216  t << params.m_dbHostName << QT_ENDL;
2217  t << params.m_dbUserName << QT_ENDL;
2218  t << params.m_dbPassword << QT_ENDL;
2219  t << params.m_dbName << QT_ENDL;
2220  t << gCoreContext->GetHostName() << QT_ENDL;
2221  t << GetInstallPrefix() << QT_ENDL;
2222  f.close();
2223 
2224  return 0;
2225 }
2226 
2227 static int isRemote(const QString& filename)
2228 {
2229  if (filename.startsWith("myth://"))
2230  return 3;
2231 
2232  // check if the file exists
2233  if (!QFile::exists(filename))
2234  return 0;
2235 
2236 #if CONFIG_DARWIN
2237  struct statfs statbuf {};
2238  if ((statfs(qPrintable(filename), &statbuf) == 0) &&
2239  ((!strcmp(statbuf.f_fstypename, "nfs")) || // NFS|FTP
2240  (!strcmp(statbuf.f_fstypename, "afpfs")) || // ApplShr
2241  (!strcmp(statbuf.f_fstypename, "smbfs")))) // SMB
2242  return 2;
2243 #elif __linux__
2244  struct statfs statbuf {};
2245  if ((statfs(qPrintable(filename), &statbuf) == 0) &&
2246  ((statbuf.f_type == 0x6969) || // NFS
2247  (statbuf.f_type == 0x517B))) // SMB
2248  return 2;
2249 #endif
2250 
2251  return 1;
2252 }
2253 
2255 {
2256  public:
2258  void LoadArguments(void) override; // MythCommandLineParser
2259 };
2260 
2262  MythCommandLineParser("mytharchivehelper")
2264 
2266 {
2267  addHelp();
2268  addVersion();
2269  addLogging();
2270 
2271  add(QStringList{"-t", "--createthumbnail"},
2272  "createthumbnail", false,
2273  "Create one or more thumbnails\n"
2274  "Requires: --infile, --thumblist, --outfile\n"
2275  "Optional: --framecount", "");
2276  add("--infile", "infile", "",
2277  "Input file name\n"
2278  "Used with: --createthumbnail, --getfileinfo, --isremote, "
2279  "--sup2dast, --importarchive", "");
2280  add("--outfile", "outfile", "",
2281  "Output file name\n"
2282  "Used with: --createthumbnail, --getfileinfo, --getdbparameters, "
2283  "--nativearchive\n"
2284  "When used with --createthumbnail: eg 'thumb%1-%2.jpg'\n"
2285  " %1 will be replaced with the no. of the thumb\n"
2286  " %2 will be replaced with the frame no.", "");
2287  add("--thumblist", "thumblist", "",
2288  "Comma-separated list of required thumbs (in seconds)\n"
2289  "Used with: --createthumbnail","");
2290  add("--framecount", "framecount", 1,
2291  "Number of frames to grab (default 1)\n"
2292  "Used with: --createthumbnail", "");
2293 
2294  add(QStringList{"-i", "--getfileinfo"},
2295  "getfileinfo", false,
2296  "Write file info about infile to outfile\n"
2297  "Requires: --infile, --outfile, --method", "");
2298  add("--method", "method", 0,
2299  "Method of file duration calculation\n"
2300  "Used with: --getfileinfo\n"
2301  " 0 = use av_estimate_timings() (quick but not very accurate - "
2302  "default)\n"
2303  " 1 = read all frames (most accurate but slow)\n"
2304  " 2 = use position map in DB (quick, only works for MythTV "
2305  "recordings)", "");
2306 
2307  add(QStringList{"-p", "--getdbparameters"},
2308  "getdbparameters", false,
2309  "Write the mysql database parameters to outfile\n"
2310  "Requires: --outfile", "");
2311 
2312  add(QStringList{"-n", "--nativearchive"},
2313  "nativearchive", false,
2314  "Archive files to a native archive format\n"
2315  "Requires: --outfile", "");
2316 
2317  add(QStringList{"-f", "--importarchive"},
2318  "importarchive", false,
2319  "Import an archived file\n"
2320  "Requires: --infile, --chanid", "");
2321  add("--chanid", "chanid", -1,
2322  "Channel ID to use when inserting records in DB\n"
2323  "Used with: --importarchive", "");
2324 
2325  add(QStringList{"-r", "--isremote"},
2326  "isremote", false,
2327  "Check if infile is on a remote filesystem\n"
2328  "Requires: --infile\n"
2329  "Returns: 0 on error or file not found\n"
2330  " - 1 file is on a local filesystem\n"
2331  " - 2 file is on a remote filesystem", "");
2332 
2333  add(QStringList{"-b", "--burndvd"},
2334  "burndvd", false,
2335  "Burn a created DVD to a blank disc\n"
2336  "Optional: --mediatype, --erasedvdrw, --nativeformat", "");
2337  add("--mediatype", "mediatype", 0,
2338  "Type of media to burn\n"
2339  "Used with: --burndvd\n"
2340  " 0 = single layer DVD (default)\n"
2341  " 1 = dual layer DVD\n"
2342  " 2 = rewritable DVD", "");
2343  add("--erasedvdrw", "erasedvdrw", false,
2344  "Force an erase of DVD-R/W Media\n"
2345  "Used with: --burndvd (optional)", "");
2346  add("--nativeformat", "nativeformat", false,
2347  "Archive is a native archive format\n"
2348  "Used with: --burndvd (optional)", "");
2349 
2350  add(QStringList{"-s", "--sup2dast"},
2351  "sup2dast", false,
2352  "Convert projectX subtitles to DVD subtitles\n"
2353  "Requires: --infile, --ifofile, --delay", "");
2354  add("--ifofile", "ifofile", "",
2355  "Filename of ifo file\n"
2356  "Used with: --sup2dast", "");
2357  add("--delay", "delay", 0,
2358  "Delay in ms to add to subtitles (default 0)\n"
2359  "Used with: --sup2dast", "");
2360 }
2361 
2362 
2363 
2364 int main(int argc, char **argv)
2365 {
2367  if (!cmdline.Parse(argc, argv))
2368  {
2369  cmdline.PrintHelp();
2371  }
2372 
2373  if (cmdline.toBool("showhelp"))
2374  {
2375  cmdline.PrintHelp();
2376  return GENERIC_EXIT_OK;
2377  }
2378 
2379  if (cmdline.toBool("showversion"))
2380  {
2382  return GENERIC_EXIT_OK;
2383  }
2384 
2385  QCoreApplication a(argc, argv);
2386  QCoreApplication::setApplicationName("mytharchivehelper");
2387 
2388  // by default we only output our messages
2389  int retval = GENERIC_EXIT_OK;
2390  QString mask("jobqueue");
2391  if ((retval = cmdline.ConfigureLogging(mask)) != GENERIC_EXIT_OK)
2392  return retval;
2393 
2395  // Don't listen to console input
2396  close(0);
2397 
2399  if (!gContext->Init(false))
2400  {
2401  LOG(VB_GENERAL, LOG_ERR, "Failed to init MythContext, exiting.");
2402  delete gContext;
2403  gContext = nullptr;
2405  }
2406 
2407  int res = 0;
2408  bool bGrabThumbnail = cmdline.toBool("createthumbnail");
2409  bool bGetDBParameters = cmdline.toBool("getdbparameters");
2410  bool bNativeArchive = cmdline.toBool("nativearchive");
2411  bool bImportArchive = cmdline.toBool("importarchive");
2412  bool bGetFileInfo = cmdline.toBool("getfileinfo");
2413  bool bIsRemote = cmdline.toBool("isremote");
2414  bool bDoBurn = cmdline.toBool("burndvd");
2415  bool bEraseDVDRW = cmdline.toBool("erasedvdrw");
2416  bool bNativeFormat = cmdline.toBool("nativeformat");;
2417  bool bSup2Dast = cmdline.toBool("sup2dast");
2418 
2419  QString thumbList = cmdline.toString("thumblist");
2420  QString inFile = cmdline.toString("infile");
2421  QString outFile = cmdline.toString("outfile");
2422  QString ifoFile = cmdline.toString("ifofile");
2423 
2424  int mediaType = cmdline.toUInt("mediatype");
2425  int lenMethod = cmdline.toUInt("method");
2426  int chanID = cmdline.toInt("chanid");
2427  int frameCount = cmdline.toUInt("framecount");
2428  int delay = cmdline.toUInt("delay");
2429 
2430  // Check command line arguments
2431  if (bGrabThumbnail)
2432  {
2433  if (inFile.isEmpty())
2434  {
2435  LOG(VB_GENERAL, LOG_ERR, "Missing --infile in -t/--grabthumbnail "
2436  "option");
2438  }
2439 
2440  if (thumbList.isEmpty())
2441  {
2442  LOG(VB_GENERAL, LOG_ERR, "Missing --thumblist in -t/--grabthumbnail"
2443  " option");
2445  }
2446 
2447  if (outFile.isEmpty())
2448  {
2449  LOG(VB_GENERAL, LOG_ERR, "Missing --outfile in -t/--grabthumbnail "
2450  "option");
2452  }
2453  }
2454 
2455  if (bGetDBParameters)
2456  {
2457  if (outFile.isEmpty())
2458  {
2459  LOG(VB_GENERAL, LOG_ERR, "Missing argument to -p/--getdbparameters "
2460  "option");
2462  }
2463  }
2464 
2465  if (bIsRemote)
2466  {
2467  if (inFile.isEmpty())
2468  {
2469  LOG(VB_GENERAL, LOG_ERR,
2470  "Missing argument to -r/--isremote option");
2472  }
2473  }
2474 
2475  if (bDoBurn)
2476  {
2477  if (mediaType < 0 || mediaType > 2)
2478  {
2479  LOG(VB_GENERAL, LOG_ERR, QString("Invalid mediatype given: %1")
2480  .arg(mediaType));
2482  }
2483  }
2484 
2485  if (bNativeArchive)
2486  {
2487  if (outFile.isEmpty())
2488  {
2489  LOG(VB_GENERAL, LOG_ERR, "Missing argument to -n/--nativearchive "
2490  "option");
2492  }
2493  }
2494 
2495  if (bImportArchive)
2496  {
2497  if (inFile.isEmpty())
2498  {
2499  LOG(VB_GENERAL, LOG_ERR, "Missing --infile argument to "
2500  "-f/--importarchive option");
2502  }
2503  }
2504 
2505  if (bGetFileInfo)
2506  {
2507  if (inFile.isEmpty())
2508  {
2509  LOG(VB_GENERAL, LOG_ERR, "Missing --infile in -i/--getfileinfo "
2510  "option");
2512  }
2513 
2514  if (outFile.isEmpty())
2515  {
2516  LOG(VB_GENERAL, LOG_ERR, "Missing --outfile in -i/--getfileinfo "
2517  "option");
2519  }
2520  }
2521 
2522  if (bSup2Dast)
2523  {
2524  if (inFile.isEmpty())
2525  {
2526  LOG(VB_GENERAL, LOG_ERR,
2527  "Missing --infile in -s/--sup2dast option");
2529  }
2530 
2531  if (ifoFile.isEmpty())
2532  {
2533  LOG(VB_GENERAL, LOG_ERR,
2534  "Missing --ifofile in -s/--sup2dast option");
2536  }
2537  }
2538 
2539  if (bGrabThumbnail)
2540  res = grabThumbnail(inFile, thumbList, outFile, frameCount);
2541  else if (bGetDBParameters)
2542  res = getDBParamters(outFile);
2543  else if (bNativeArchive)
2544  res = doNativeArchive(outFile);
2545  else if (bImportArchive)
2546  res = doImportArchive(inFile, chanID);
2547  else if (bGetFileInfo)
2548  res = getFileInfo(inFile, outFile, lenMethod);
2549  else if (bIsRemote)
2550  res = isRemote(inFile);
2551  else if (bDoBurn)
2552  res = doBurnDVD(mediaType, bEraseDVDRW, bNativeFormat);
2553  else if (bSup2Dast)
2554  {
2555  QByteArray inFileBA = inFile.toLocal8Bit();
2556  QByteArray ifoFileBA = ifoFile.toLocal8Bit();
2557  res = sup2dast(inFileBA.constData(), ifoFileBA.constData(), delay);
2558  }
2559  else
2560  cmdline.PrintHelp();
2561 
2562  delete gContext;
2563  gContext = nullptr;
2564 
2565  exit(res);
2566 }
2567 
2568 
MSqlQuery::isActive
bool isActive(void) const
Definition: mythdbcon.h:204
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:126
e
QDomElement e
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1420
doNativeArchive
static int doNativeArchive(const QString &jobFile)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1525
RemoteAVFormatContext::isOpen
bool isOpen() const
Definition: mytharchive/mytharchive/remoteavformatcontext.h:104
channel
QDomElement channel
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:501
MPUBLIC
#define MPUBLIC
Definition: mythexp.h:10
pxsup2dast.h
NativeArchive::doImportArchive
static int doImportArchive(const QString &xmlFile, int chanID)
MARK_KEYFRAME
@ MARK_KEYFRAME
Definition: programtypes.h:62
MythDate::toString
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:80
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:203
fileInfo
QFileInfo fileInfo(filename)
doDelete
bool doDelete
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:638
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:72
myth_system
uint myth_system(const QString &command, uint flags, uint timeout)
Definition: mythsystemlegacy.cpp:501
MythCoreContext::GetMasterHostName
QString GetMasterHostName(void)
Definition: mythcorecontext.cpp:828
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:21
root
QDomElement root
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:656
nodeList
nodeList
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1271
xmlFile
QString xmlFile
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:594
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:3592
MythCodecMap::GetCodecContext
AVCodecContext * GetCodecContext(const AVStream *Stream, const AVCodec *Codec=nullptr, bool NullCodec=false)
Definition: mythavutil.cpp:241
NativeArchive::NativeArchive
NativeArchive(void)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:103
fixFilename
META_PUBLIC QString fixFilename(const QString &filename)
remove any bad filename characters
intid
int intid
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1254
doc
QDomDocument doc("MYTHARCHIVEITEM")
artworkDir
QString artworkDir
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1196
MythContext::GetDatabaseParams
DatabaseParams GetDatabaseParams(void)
Definition: mythcontext.cpp:1677
getCutFrames
static int64_t getCutFrames(const QString &filename, int64_t lastFrame)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1786
burnISOImage
static int burnISOImage(int mediaType, bool bEraseDVDRW, bool nativeFormat)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:164
clearArchiveTable
static void clearArchiveTable(void)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1516
title
QString title
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:636
DatabaseParams
Structure containing the basic Database parameters.
Definition: mythdbparams.h:10
NativeArchive
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:85
MythArchiveHelperCommandLineParser
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:2255
MARK_CUT_END
@ MARK_CUT_END
Definition: programtypes.h:55
MythContext
Startup context for MythTV.
Definition: mythcontext.h:44
MythAVFrame
MythAVFrame little utility class that act as a safe way to allocate an AVFrame which can then be allo...
Definition: mythaverror.h:53
MythAVUtil::DeinterlaceAVFrame
static void DeinterlaceAVFrame(AVFrame *Frame)
Deinterlace an AVFrame.
Definition: mythavutil.cpp:125
mythcoreutil.h
intID
int intID
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:632
recordedmarkup
QDomElement recordedmarkup
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:550
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:198
MythCommandLineParser::addVersion
void addVersion(void)
Canned argument definition for –version.
Definition: mythcommandlineparser.cpp:2288
arg
arg(title).arg(filename).arg(doDelete))
frm_dir_map_t
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:82
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
NativeArchive::doNativeArchive
static int doNativeArchive(const QString &jobFile)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:238
coverFilename
QString coverFilename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1201
category
QString category
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1422
build_compdb.file
file
Definition: build_compdb.py:55
mythdirs.h
GENERIC_EXIT_INVALID_CMDLINE
#define GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:15
getTempDirectory
QString getTempDirectory(bool showError)
Definition: archiveutil.cpp:68
genresNode
QDomElement genresNode
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1279
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
video
QDomElement video
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:661
close
#define close
Definition: compat.h:17
tmp
static guint32 * tmp
Definition: goom_core.cpp:31
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:2385
mythversion.h
mythsystemlegacy.h
MythArchiveHelperCommandLineParser::MythArchiveHelperCommandLineParser
MythArchiveHelperCommandLineParser()
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:2261
MythRemoveDirectory
bool MythRemoveDirectory(QDir &aDir)
Definition: mythmiscutil.cpp:808
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:1435
NativeArchive::copyFile
static bool copyFile(const QString &source, const QString &destination)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:125
baseName
QString baseName
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:593
mythdate.h
countriesNode
QDomElement countriesNode
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1349
programinfo.h
RemoteAVFormatContext
Definition: mytharchive/mytharchive/remoteavformatcontext.h:17
mythlogging.h
MythCoreContext::GenMythURL
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
Definition: mythcorecontext.cpp:781
doImportArchive
static int doImportArchive(const QString &inFile, int chanID)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1548
doBurnDVD
static int doBurnDVD(int mediaType, bool bEraseDVDRW, bool nativeFormat)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:222
AD_DVD_RW
@ AD_DVD_RW
Definition: archiveutil.h:20
badChars
static QRegExp badChars
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:378
hardwareprofile.i18n.t
t
Definition: i18n.py:36
MARK_GOP_START
@ MARK_GOP_START
Definition: programtypes.h:61
statfs
int statfs(const char *path, struct statfs *buffer)
Definition: compat.h:179
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
mark
Definition: lang.cpp:21
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:178
hardwareprofile.scan.rating
def rating(profile, smoonURL, gate)
Definition: scan.py:39
NativeArchive::~NativeArchive
~NativeArchive(void)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:117
ProgramInfo::QueryCutList
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
Definition: programinfo.cpp:3294
createISOImage
static bool createISOImage(QString &sourceDirectory)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:140
grabThumbnail
static int grabThumbnail(const QString &inFile, const QString &thumbList, const QString &outFile, int frameCount)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1554
MythCommandLineParser::PrintVersion
static void PrintVersion(void)
Print application version information.
Definition: mythcommandlineparser.cpp:1263
filename
QString filename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:637
MythCodecMap::FreeCodecContext
void FreeCodecContext(const AVStream *Stream)
Definition: mythavutil.cpp:289
MythCommandLineParser::toUInt
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
Definition: mythcommandlineparser.cpp:1990
f
QTextStream t & f
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:603
MythCommandLineParser::PrintHelp
void PrintHelp(void) const
Print command line option help.
Definition: mythcommandlineparser.cpp:1279
coverFile
QString coverFile
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:634
DatabaseParams::m_dbPassword
QString m_dbPassword
DB password.
Definition: mythdbparams.h:25
DatabaseParams::m_dbName
QString m_dbName
database name
Definition: mythdbparams.h:26
origFilename
QString origFilename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1168
MythCodecMap
Definition: mythavutil.h:24
uint
unsigned int uint
Definition: compat.h:141
recordedseek
QDomElement recordedseek
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:571
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
QT_ENDL
#define QT_ENDL
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:81
NativeArchive::importRecording
static int importRecording(const QDomElement &itemNode, const QString &xmlFile, int chanID)
NativeArchive::findNodeText
static QString findNodeText(const QDomElement &elem, const QString &nodeName)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1473
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:933
getProgramInfoForFile
ProgramInfo * getProgramInfoForFile(const QString &inFile)
Definition: archiveutil.cpp:185
frm_pos_map_t
QMap< long long, long long > frm_pos_map_t
Frame # -> File offset map.
Definition: programtypes.h:46
getFrameCount
static int64_t getFrameCount(AVFormatContext *inputFC, int vid_id)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1765
NativeArchive::getFieldList
static int getFieldList(QStringList &fieldList, const QString &tableName)
NativeArchive::importVideo
static int importVideo(const QDomElement &itemNode, const QString &xmlFile)
getDBParamters
static int getDBParamters(const QString &outFile)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:2201
MARK_CUT_START
@ MARK_CUT_START
Definition: programtypes.h:56
MYTH_BINARY_VERSION
#define MYTH_BINARY_VERSION
Update this whenever the plug-in ABI changes.
Definition: mythversion.h:15
main
int main(int argc, char **argv)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:2364
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:1001
isRemote
static int isRemote(const QString &filename)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:2227
cmdline
MythCommFlagCommandLineParser cmdline
Definition: mythtv/programs/mythcommflag/main.cpp:76
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:68
NativeArchive::exportVideo
static int exportVideo(QDomElement &itemNode, const QString &saveDirectory)
mythmiscutil.h
MythCommandLineParser::add
CommandLineArg * add(const QString &arg, const QString &name, bool def, QString help, QString longhelp)
Definition: mythcommandlineparser.h:145
countries
QDomElement countries
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:785
MythCommandLineParser::toString
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2098
NativeArchive::exportRecording
static int exportRecording(QDomElement &itemNode, const QString &saveDirectory)
MythCommandLineParser::addHelp
void addHelp(void)
Canned argument definition for –help.
Definition: mythcommandlineparser.cpp:2275
MythCommandLineParser::toBool
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
Definition: mythcommandlineparser.cpp:1941
dir
QDir dir
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1174
MARK_GOP_BYFRAME
@ MARK_GOP_BYFRAME
Definition: programtypes.h:64
archivePath
QString archivePath
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1199
statfs
Definition: compat.h:167
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
DatabaseParams::m_dbUserName
QString m_dbUserName
DB user name.
Definition: mythdbparams.h:24
ProgramInfo::IsVideo
bool IsVideo(void) const
Definition: programinfo.h:482
mythavutil.h
mythcontext.h
categoryID
int categoryID
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:633
GENERIC_EXIT_NO_MYTHCONTEXT
#define GENERIC_EXIT_NO_MYTHCONTEXT
No MythContext available.
Definition: exitcodes.h:13
dirList
QStringList dirList
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1172
MythArchiveHelperCommandLineParser::LoadArguments
void LoadArguments(void) override
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:2265
MythAVCopy
Definition: mythavutil.h:41
AD_FILE
@ AD_FILE
Definition: archiveutil.h:21
MythDate::kDatabase
@ kDatabase
Default UTC, database format.
Definition: mythdate.h:24
LOG
LOG(VB_JOBQUEUE, LOG_INFO, "Created a default channel element for "+title)
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:859
MythCommandLineParser::ConfigureLogging
int ConfigureLogging(const QString &mask="general", bool progress=false)
Read in logging options and initialize the logging interface.
Definition: mythcommandlineparser.cpp:2585
MythCommandLineParser::LoadArguments
virtual void LoadArguments(void)
Definition: mythcommandlineparser.h:134
MythCoreContext::SaveSetting
void SaveSetting(const QString &key, int newValue)
Definition: mythcorecontext.cpp:902
getBaseName
QString getBaseName(const QString &filename)
Definition: archiveutil.cpp:145
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:220
getFileInfo
static int getFileInfo(const QString &inFile, const QString &outFile, int lenMethod)
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1904
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:1963
mythcommandlineparser.h
query
MSqlQuery query(MSqlQuery::InitCon())
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:876
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:1564
extractDetailsFromFilename
bool extractDetailsFromFilename(const QString &inFile, QString &chanID, QString &startTime)
Definition: archiveutil.cpp:155
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:919
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
genres
QDomElement genres
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:810