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