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