MythTV  master
mytharchivehelper.cpp
Go to the documentation of this file.
1 /* -*- Mode: c++ -*-
2  * vim: set expandtab tabstop=4 shiftwidth=4:
3  *
4  * Original Project
5  * MythTV http://www.mythtv.org
6  *
7  * Copyright (c) 2004, 2005 John Pullan <john@pullan.org>
8  * Copyright (c) 2009, Janne Grunau <janne-mythtv@grunau.be>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
24  *
25  */
26 
27 #include <cstdint>
28 #include <cstdlib>
29 #include <iostream>
30 #include <unistd.h>
31 
32 #include <QtGlobal>
33 #if defined(Q_OS_DARWIN) or defined(__FreeBSD__)
34 #include <sys/param.h>
35 #include <sys/mount.h>
36 #elif defined(__linux__)
37 #include <sys/vfs.h>
38 #endif
39 
40 // Qt headers
41 #include <QApplication>
42 #include <QDir>
43 #include <QDomElement>
44 #include <QFile>
45 #include <QImage>
46 #include <QMutex>
47 #include <QMutexLocker>
48 #include <QRegularExpression>
49 #include <QTextStream>
50 
51 // MythTV headers
52 #include <mythconfig.h>
53 #include <libmyth/mythavframe.h>
54 #include <libmyth/mythcontext.h>
55 #include <libmythbase/exitcodes.h>
59 #include <libmythbase/mythdate.h>
60 #include <libmythbase/mythdb.h>
61 #include <libmythbase/mythdirs.h>
66 #include <libmythbase/mythversion.h>
68 #include <libmythtv/mythavutil.h>
69 
70 extern "C" {
71  #include <libavcodec/avcodec.h>
72  #include <libavformat/avformat.h>
73  #include <libavutil/imgutils.h>
74  #include "external/pxsup2dast.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, 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 const QRegularExpression badChars { 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 
388  MSqlQuery query(MSqlQuery::InitCon());
389  if (query.exec("DESCRIBE " + tableName))
390  {
391  while (query.next())
392  {
393  fieldList.append(query.value(0).toString());
394  }
395  }
396  else
397  {
398  MythDB::DBError("describe table", query);
399  }
400 
401  return fieldList.count();
402 }
403 
404 int NativeArchive::exportRecording(QDomElement &itemNode,
405  const QString &saveDirectory)
406 {
407  QString chanID;
408  QString startTime;
409  QString dbVersion = gCoreContext->GetSetting("DBSchemaVer", "");
410 
411  QString title = fixFilename(itemNode.attribute("title"));
412  QString filename = itemNode.attribute("filename");
413  bool doDelete = (itemNode.attribute("delete", "0") == "0");
414  LOG(VB_JOBQUEUE, LOG_INFO, QString("Archiving %1 (%2), do delete: %3")
415  .arg(title, filename, doDelete ? "true" : "false"));
416 
417  if (title == "" || filename == "")
418  {
419  LOG(VB_JOBQUEUE, LOG_ERR, "Bad title or filename");
420  return 0;
421  }
422 
423  if (!extractDetailsFromFilename(filename, chanID, startTime))
424  {
425  LOG(VB_JOBQUEUE, LOG_ERR,
426  QString("Failed to extract chanID and startTime from '%1'")
427  .arg(filename));
428  return 0;
429  }
430 
431  // create the directory to hold this items files
432  QDir dir(saveDirectory + title);
433  if (!dir.exists())
434  dir.mkpath(saveDirectory + title);
435  if (!dir.exists())
436  LOG(VB_GENERAL, LOG_ERR, "Failed to create savedir: " + ENO);
437 
438  LOG(VB_JOBQUEUE, LOG_INFO, "Creating xml file for " + title);
439  QDomDocument doc("MYTHARCHIVEITEM");
440 
441  QDomElement root = doc.createElement("item");
442  doc.appendChild(root);
443  root.setAttribute("type", "recording");
444  root.setAttribute("databaseversion", dbVersion);
445 
446  QDomElement recorded = doc.createElement("recorded");
447  root.appendChild(recorded);
448 
449  // get details from recorded
450  QStringList fieldList;
451  getFieldList(fieldList, "recorded");
452 
453  MSqlQuery query(MSqlQuery::InitCon());
454  query.prepare("SELECT " + fieldList.join(",")
455  + " FROM recorded"
456  " WHERE chanid = :CHANID and starttime = :STARTTIME;");
457  query.bindValue(":CHANID", chanID);
458  query.bindValue(":STARTTIME", startTime);
459 
460  if (query.exec() && query.next())
461  {
462  QDomElement elem;
463  QDomText text;
464 
465  for (int x = 0; x < fieldList.size(); x++)
466  {
467  elem = doc.createElement(fieldList[x]);
468  text = doc.createTextNode(query.value(x).toString());
469  elem.appendChild(text);
470  recorded.appendChild(elem);
471  }
472 
473  LOG(VB_JOBQUEUE, LOG_INFO, "Created recorded element for " + title);
474  }
475  else
476  {
477  LOG(VB_JOBQUEUE, LOG_INFO, "Failed to get recorded field list");
478  }
479 
480  // add channel details
481  query.prepare("SELECT chanid, channum, callsign, name "
482  "FROM channel WHERE chanid = :CHANID;");
483  query.bindValue(":CHANID", chanID);
484 
485  if (query.exec() && query.next())
486  {
487  QDomElement channel = doc.createElement("channel");
488  channel.setAttribute("chanid", query.value(0).toString());
489  channel.setAttribute("channum", query.value(1).toString());
490  channel.setAttribute("callsign", query.value(2).toString());
491  channel.setAttribute("name", query.value(3).toString());
492  root.appendChild(channel);
493  LOG(VB_JOBQUEUE, LOG_INFO, "Created channel element for " + title);
494  }
495  else
496  {
497  // cannot find the original channel so create a default channel element
498  LOG(VB_JOBQUEUE, LOG_ERR,
499  "Cannot find channel details for chanid " + chanID);
500  QDomElement channel = doc.createElement("channel");
501  channel.setAttribute("chanid", chanID);
502  channel.setAttribute("channum", "unknown");
503  channel.setAttribute("callsign", "unknown");
504  channel.setAttribute("name", "unknown");
505  root.appendChild(channel);
506  LOG(VB_JOBQUEUE, LOG_INFO,
507  "Created a default channel element for " + title);
508  }
509 
510  // add any credits
511  query.prepare("SELECT credits.person, role, people.name "
512  "FROM recordedcredits AS credits "
513  "LEFT JOIN people ON credits.person = people.person "
514  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
515  query.bindValue(":CHANID", chanID);
516  query.bindValue(":STARTTIME", startTime);
517 
518  if (query.exec() && query.size())
519  {
520  QDomElement credits = doc.createElement("credits");
521  while (query.next())
522  {
523  QDomElement credit = doc.createElement("credit");
524  credit.setAttribute("personid", query.value(0).toString());
525  credit.setAttribute("name", query.value(2).toString());
526  credit.setAttribute("role", query.value(1).toString());
527  credits.appendChild(credit);
528  }
529  root.appendChild(credits);
530  LOG(VB_JOBQUEUE, LOG_INFO, "Created credits element for " + title);
531  }
532 
533  // add any rating
534  query.prepare("SELECT `system`, rating FROM recordedrating "
535  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
536  query.bindValue(":CHANID", chanID);
537  query.bindValue(":STARTTIME", startTime);
538 
539  if (query.exec() && query.next())
540  {
541  QDomElement rating = doc.createElement("rating");
542  rating.setAttribute("system", query.value(0).toString());
543  rating.setAttribute("rating", query.value(1).toString());
544  root.appendChild(rating);
545  LOG(VB_JOBQUEUE, LOG_INFO, "Created rating element for " + title);
546  }
547 
548  // add the recordedmarkup table
549  QDomElement recordedmarkup = doc.createElement("recordedmarkup");
550  query.prepare("SELECT chanid, starttime, mark, type, data "
551  "FROM recordedmarkup "
552  "WHERE chanid = :CHANID and starttime = :STARTTIME;");
553  query.bindValue(":CHANID", chanID);
554  query.bindValue(":STARTTIME", startTime);
555  if (query.exec() && query.size())
556  {
557  while (query.next())
558  {
559  QDomElement mark = doc.createElement("mark");
560  mark.setAttribute("mark", query.value(2).toString());
561  mark.setAttribute("type", query.value(3).toString());
562  mark.setAttribute("data", query.value(4).toString());
563  recordedmarkup.appendChild(mark);
564  }
565  root.appendChild(recordedmarkup);
566  LOG(VB_JOBQUEUE, LOG_INFO, "Created recordedmarkup element for " + title);
567  }
568 
569  // add the recordedseek table
570  QDomElement recordedseek = doc.createElement("recordedseek");
571  query.prepare("SELECT chanid, starttime, mark, `offset`, type "
572  "FROM recordedseek "
573  "WHERE chanid = :CHANID and starttime = :STARTTIME;");
574  query.bindValue(":CHANID", chanID);
575  query.bindValue(":STARTTIME", startTime);
576  if (query.exec() && query.size())
577  {
578  while (query.next())
579  {
580  QDomElement mark = doc.createElement("mark");
581  mark.setAttribute("mark", query.value(2).toString());
582  mark.setAttribute("offset", query.value(3).toString());
583  mark.setAttribute("type", query.value(4).toString());
584  recordedseek.appendChild(mark);
585  }
586  root.appendChild(recordedseek);
587  LOG(VB_JOBQUEUE, LOG_INFO,
588  "Created recordedseek element for " + title);
589  }
590 
591  // finally save the xml to the file
592  QString baseName = getBaseName(filename);
593  QString xmlFile = saveDirectory + title + "/" + baseName + ".xml";
594  QFile f(xmlFile);
595  if (!f.open(QIODevice::WriteOnly))
596  {
597  LOG(VB_JOBQUEUE, LOG_ERR,
598  "MythNativeWizard: Failed to open file for writing - " + xmlFile);
599  return 0;
600  }
601 
602  QTextStream t(&f);
603  t << doc.toString(4);
604  f.close();
605 
606  // copy the file
607  LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");
608  bool res = copyFile(filename, saveDirectory + title + "/" + baseName);
609  if (!res)
610  return 0;
611 
612  // copy preview image
613  if (QFile::exists(filename + ".png"))
614  {
615  LOG(VB_JOBQUEUE, LOG_INFO, "Copying preview image");
616  res = copyFile(filename + ".png", saveDirectory
617  + title + "/" + baseName + ".png");
618  if (!res)
619  return 0;
620  }
621 
622  LOG(VB_JOBQUEUE, LOG_INFO, "Item Archived OK");
623 
624  return 1;
625 }
626 
627 int NativeArchive::exportVideo(QDomElement &itemNode,
628  const QString &saveDirectory)
629 {
630  QString dbVersion = gCoreContext->GetSetting("DBSchemaVer", "");
631  int intID = 0;
632  int categoryID = 0;
633  QString coverFile = "";
634 
635  QString title = fixFilename(itemNode.attribute("title"));
636  QString filename = itemNode.attribute("filename");
637  bool doDelete = (itemNode.attribute("delete", "0") == "0");
638  LOG(VB_JOBQUEUE, LOG_INFO, QString("Archiving %1 (%2), do delete: %3")
639  .arg(title, filename, doDelete ? "true" : "false"));
640 
641  if (title == "" || filename == "")
642  {
643  LOG(VB_JOBQUEUE, LOG_ERR, "Bad title or filename");
644  return 0;
645  }
646 
647  // create the directory to hold this items files
648  QDir dir(saveDirectory + title);
649  if (!dir.exists())
650  dir.mkdir(saveDirectory + title);
651 
652  LOG(VB_JOBQUEUE, LOG_INFO, "Creating xml file for " + title);
653  QDomDocument doc("MYTHARCHIVEITEM");
654 
655  QDomElement root = doc.createElement("item");
656  doc.appendChild(root);
657  root.setAttribute("type", "video");
658  root.setAttribute("databaseversion", dbVersion);
659 
660  QDomElement video = doc.createElement("videometadata");
661  root.appendChild(video);
662 
663  // get details from videometadata
664  MSqlQuery query(MSqlQuery::InitCon());
665  query.prepare("SELECT intid, title, director, plot, rating, inetref, "
666  "year, userrating, length, showlevel, filename, coverfile, "
667  "childid, browse, playcommand, category "
668  "FROM videometadata WHERE filename = :FILENAME;");
669  query.bindValue(":FILENAME", filename);
670 
671  if (query.exec() && query.next())
672  {
673  QDomElement elem;
674  QDomText text;
675 
676  elem = doc.createElement("intid");
677  text = doc.createTextNode(query.value(0).toString());
678  intID = query.value(0).toInt();
679  elem.appendChild(text);
680  video.appendChild(elem);
681 
682  elem = doc.createElement("title");
683  text = doc.createTextNode(query.value(1).toString());
684  elem.appendChild(text);
685  video.appendChild(elem);
686 
687  elem = doc.createElement("director");
688  text = doc.createTextNode(query.value(2).toString());
689  elem.appendChild(text);
690  video.appendChild(elem);
691 
692  elem = doc.createElement("plot");
693  text = doc.createTextNode(query.value(3).toString());
694  elem.appendChild(text);
695  video.appendChild(elem);
696 
697  elem = doc.createElement("rating");
698  text = doc.createTextNode(query.value(4).toString());
699  elem.appendChild(text);
700  video.appendChild(elem);
701 
702  elem = doc.createElement("inetref");
703  text = doc.createTextNode(query.value(5).toString());
704  elem.appendChild(text);
705  video.appendChild(elem);
706 
707  elem = doc.createElement("year");
708  text = doc.createTextNode(query.value(6).toString());
709  elem.appendChild(text);
710  video.appendChild(elem);
711 
712  elem = doc.createElement("userrating");
713  text = doc.createTextNode(query.value(7).toString());
714  elem.appendChild(text);
715  video.appendChild(elem);
716 
717  elem = doc.createElement("length");
718  text = doc.createTextNode(query.value(8).toString());
719  elem.appendChild(text);
720  video.appendChild(elem);
721 
722  elem = doc.createElement("showlevel");
723  text = doc.createTextNode(query.value(9).toString());
724  elem.appendChild(text);
725  video.appendChild(elem);
726 
727  // remove the VideoStartupDir part of the filename
728  QString fname = query.value(10).toString();
729  if (fname.startsWith(gCoreContext->GetSetting("VideoStartupDir")))
730  fname = fname.remove(gCoreContext->GetSetting("VideoStartupDir"));
731 
732  elem = doc.createElement("filename");
733  text = doc.createTextNode(fname);
734  elem.appendChild(text);
735  video.appendChild(elem);
736 
737  elem = doc.createElement("coverfile");
738  text = doc.createTextNode(query.value(11).toString());
739  coverFile = query.value(11).toString();
740  elem.appendChild(text);
741  video.appendChild(elem);
742 
743  elem = doc.createElement("childid");
744  text = doc.createTextNode(query.value(12).toString());
745  elem.appendChild(text);
746  video.appendChild(elem);
747 
748  elem = doc.createElement("browse");
749  text = doc.createTextNode(query.value(13).toString());
750  elem.appendChild(text);
751  video.appendChild(elem);
752 
753  elem = doc.createElement("playcommand");
754  text = doc.createTextNode(query.value(14).toString());
755  elem.appendChild(text);
756  video.appendChild(elem);
757 
758  elem = doc.createElement("categoryid");
759  text = doc.createTextNode(query.value(15).toString());
760  categoryID = query.value(15).toInt();
761  elem.appendChild(text);
762  video.appendChild(elem);
763 
764  LOG(VB_JOBQUEUE, LOG_INFO,
765  "Created videometadata element for " + title);
766  }
767 
768  // add category details
769  query.prepare("SELECT intid, category "
770  "FROM videocategory WHERE intid = :INTID;");
771  query.bindValue(":INTID", categoryID);
772 
773  if (query.exec() && query.next())
774  {
775  QDomElement category = doc.createElement("category");
776  category.setAttribute("intid", query.value(0).toString());
777  category.setAttribute("category", query.value(1).toString());
778  root.appendChild(category);
779  LOG(VB_JOBQUEUE, LOG_INFO,
780  "Created videocategory element for " + title);
781  }
782 
783  //add video country details
784  QDomElement countries = doc.createElement("countries");
785  root.appendChild(countries);
786 
787  query.prepare("SELECT intid, country "
788  "FROM videometadatacountry INNER JOIN videocountry "
789  "ON videometadatacountry.idcountry = videocountry.intid "
790  "WHERE idvideo = :INTID;");
791  query.bindValue(":INTID", intID);
792 
793  if (!query.exec())
794  MythDB::DBError("select countries", query);
795 
796  if (query.isActive() && query.size())
797  {
798  while (query.next())
799  {
800  QDomElement country = doc.createElement("country");
801  country.setAttribute("intid", query.value(0).toString());
802  country.setAttribute("country", query.value(1).toString());
803  countries.appendChild(country);
804  }
805  LOG(VB_JOBQUEUE, LOG_INFO, "Created videocountry element for " + title);
806  }
807 
808  // add video genre details
809  QDomElement genres = doc.createElement("genres");
810  root.appendChild(genres);
811 
812  query.prepare("SELECT intid, genre "
813  "FROM videometadatagenre INNER JOIN videogenre "
814  "ON videometadatagenre.idgenre = videogenre.intid "
815  "WHERE idvideo = :INTID;");
816  query.bindValue(":INTID", intID);
817 
818  if (!query.exec())
819  MythDB::DBError("select genres", query);
820 
821  if (query.isActive() && query.size())
822  {
823  while (query.next())
824  {
825  QDomElement genre = doc.createElement("genre");
826  genre.setAttribute("intid", query.value(0).toString());
827  genre.setAttribute("genre", query.value(1).toString());
828  genres.appendChild(genre);
829  }
830  LOG(VB_JOBQUEUE, LOG_INFO, "Created videogenre element for " + title);
831  }
832 
833  // finally save the xml to the file
834  QFileInfo fileInfo(filename);
835  QString xmlFile = saveDirectory + title + "/"
836  + fileInfo.fileName() + ".xml";
837  QFile f(xmlFile);
838  if (!f.open(QIODevice::WriteOnly))
839  {
840  LOG(VB_JOBQUEUE, LOG_INFO,
841  "MythNativeWizard: Failed to open file for writing - " + xmlFile);
842  return 0;
843  }
844 
845  QTextStream t(&f);
846  t << doc.toString(4);
847  f.close();
848 
849  // copy the file
850  LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");
851  bool res = copyFile(filename, saveDirectory + title
852  + "/" + fileInfo.fileName());
853  if (!res)
854  {
855  return 0;
856  }
857 
858  // copy the cover image
859  fileInfo.setFile(coverFile);
860  if (fileInfo.exists())
861  {
862  LOG(VB_JOBQUEUE, LOG_INFO, "Copying cover file");
863  res = copyFile(coverFile, saveDirectory + title
864  + "/" + fileInfo.fileName());
865  if (!res)
866  {
867  return 0;
868  }
869  }
870 
871  LOG(VB_JOBQUEUE, LOG_INFO, "Item Archived OK");
872 
873  return 1;
874 }
875 
876 int NativeArchive::doImportArchive(const QString &xmlFile, int chanID)
877 {
878  // open xml file
879  QDomDocument doc("mydocument");
880  QFile file(xmlFile);
881  if (!file.open(QIODevice::ReadOnly))
882  {
883  LOG(VB_JOBQUEUE, LOG_ERR,
884  "Failed to open file for reading - " + xmlFile);
885  return 1;
886  }
887 
888  if (!doc.setContent(&file))
889  {
890  file.close();
891  LOG(VB_JOBQUEUE, LOG_ERR,
892  "Failed to read from xml file - " + xmlFile);
893  return 1;
894  }
895  file.close();
896 
897  QString docType = doc.doctype().name();
898  QString type;
899  QString dbVersion;
900  QDomNodeList itemNodeList;
901  QDomNode node;
902  QDomElement itemNode;
903 
904  if (docType == "MYTHARCHIVEITEM")
905  {
906  itemNodeList = doc.elementsByTagName("item");
907 
908  if (itemNodeList.count() < 1)
909  {
910  LOG(VB_JOBQUEUE, LOG_ERR,
911  "Couldn't find an 'item' element in XML file");
912  return 1;
913  }
914 
915  node = itemNodeList.item(0);
916  itemNode = node.toElement();
917  type = itemNode.attribute("type");
918  dbVersion = itemNode.attribute("databaseversion");
919 
920  LOG(VB_JOBQUEUE, LOG_INFO,
921  QString("Archive DB version: %1, Local DB version: %2")
922  .arg(dbVersion, gCoreContext->GetSetting("DBSchemaVer")));
923  }
924  else
925  {
926  LOG(VB_JOBQUEUE, LOG_ERR, "Not a native archive xml file - " + xmlFile);
927  return 1;
928  }
929 
930  if (type == "recording")
931  {
932  return importRecording(itemNode, xmlFile, chanID);
933  }
934  if (type == "video")
935  {
936  return importVideo(itemNode, xmlFile);
937  }
938 
939  return 1;
940 }
941 
942 int NativeArchive::importRecording(const QDomElement &itemNode,
943  const QString &xmlFile, int chanID)
944 {
945  LOG(VB_JOBQUEUE, LOG_INFO,
946  QString("Import recording using chanID: %1").arg(chanID));
947  LOG(VB_JOBQUEUE, LOG_INFO,
948  QString("Archived recording xml file: %1").arg(xmlFile));
949 
950  QString videoFile = xmlFile.left(xmlFile.length() - 4);
951  QString basename = videoFile;
952  int pos = videoFile.lastIndexOf('/');
953  if (pos > 0)
954  basename = videoFile.mid(pos + 1);
955 
956  QDomNodeList nodeList = itemNode.elementsByTagName("recorded");
957  if (nodeList.count() < 1)
958  {
959  LOG(VB_JOBQUEUE, LOG_ERR,
960  "Couldn't find a 'recorded' element in XML file");
961  return 1;
962  }
963 
964  QDomNode n = nodeList.item(0);
965  QDomElement recordedNode = n.toElement();
966  QString startTime = findNodeText(recordedNode, "starttime");
967  // check this recording doesn't already exist
968  MSqlQuery query(MSqlQuery::InitCon());
969  query.prepare("SELECT * FROM recorded "
970  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
971  query.bindValue(":CHANID", chanID);
972  query.bindValue(":STARTTIME", startTime);
973  if (query.exec())
974  {
975  if (query.isActive() && query.size())
976  {
977  LOG(VB_JOBQUEUE, LOG_ERR,
978  "This recording appears to already exist!!");
979  return 1;
980  }
981  }
982 
985  basename , "Default");
986 
987  // copy file to recording directory
988  LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file to: " + destFile);
989  if (!copyFile(videoFile, destFile))
990  return 1;
991 
992  // copy any preview image to recording directory
993  if (QFile::exists(videoFile + ".png"))
994  {
995  LOG(VB_JOBQUEUE, LOG_INFO, "Copying preview image file to: " + destFile + ".png");
996  if (!copyFile(videoFile + ".png", destFile + ".png"))
997  return 1;
998  }
999 
1000  // get a list of fields from the xmlFile
1001  QStringList fieldList;
1002  QStringList bindList;
1003  QDomNodeList nodes = recordedNode.childNodes();
1004 
1005  for (int x = 0; x < nodes.count(); x++)
1006  {
1007  QDomNode n2 = nodes.item(x);
1008  QString field = n2.nodeName();
1009  fieldList.append(field);
1010  bindList.append(":" + field.toUpper());
1011  }
1012 
1013  // copy recorded to database
1014  query.prepare("INSERT INTO recorded (" + fieldList.join(",") + ") "
1015  "VALUES (" + bindList.join(",") + ");");
1016  query.bindValue(":CHANID", chanID);
1017  query.bindValue(":STARTTIME", startTime);
1018 
1019  for (int x = 0; x < fieldList.count(); x++)
1020  query.bindValue(bindList.at(x), findNodeText(recordedNode, fieldList.at(x)));
1021 
1022  if (query.exec())
1023  LOG(VB_JOBQUEUE, LOG_INFO, "Inserted recorded details into database");
1024  else
1025  MythDB::DBError("recorded insert", query);
1026 
1027  // copy recordedmarkup to db
1028  nodeList = itemNode.elementsByTagName("recordedmarkup");
1029  if (nodeList.count() < 1)
1030  {
1031  LOG(VB_JOBQUEUE, LOG_WARNING,
1032  "Couldn't find a 'recordedmarkup' element in XML file");
1033  }
1034  else
1035  {
1036  QDomNode n3 = nodeList.item(0);
1037  QDomElement markupNode = n3.toElement();
1038 
1039  nodeList = markupNode.elementsByTagName("mark");
1040  if (nodeList.count() < 1)
1041  {
1042  LOG(VB_JOBQUEUE, LOG_WARNING,
1043  "Couldn't find any 'mark' elements in XML file");
1044  }
1045  else
1046  {
1047  // delete any records for this recordings
1048  query.prepare("DELETE FROM recordedmarkup "
1049  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
1050  query.bindValue(":CHANID", chanID);
1051  query.bindValue(":STARTTIME", startTime);
1052 
1053  if (!query.exec())
1054  MythDB::DBError("recordedmarkup delete", query);
1055 
1056  // add any new records for this recording
1057  for (int x = 0; x < nodeList.count(); x++)
1058  {
1059  QDomNode n4 = nodeList.item(x);
1060  QDomElement e = n4.toElement();
1061  query.prepare("INSERT INTO recordedmarkup (chanid, starttime, "
1062  "mark, type, data)"
1063  "VALUES(:CHANID,:STARTTIME,:MARK,:TYPE,:DATA);");
1064  query.bindValue(":CHANID", chanID);
1065  query.bindValue(":STARTTIME", startTime);
1066  query.bindValue(":MARK", e.attribute("mark"));
1067  query.bindValue(":TYPE", e.attribute("type"));
1068  query.bindValue(":DATA", e.attribute("data"));
1069 
1070  if (!query.exec())
1071  {
1072  MythDB::DBError("recordedmark insert", query);
1073  return 1;
1074  }
1075  }
1076 
1077  LOG(VB_JOBQUEUE, LOG_INFO,
1078  "Inserted recordedmarkup details into database");
1079  }
1080  }
1081 
1082  // copy recordedseek to db
1083  nodeList = itemNode.elementsByTagName("recordedseek");
1084  if (nodeList.count() < 1)
1085  {
1086  LOG(VB_JOBQUEUE, LOG_WARNING,
1087  "Couldn't find a 'recordedseek' element in XML file");
1088  }
1089  else
1090  {
1091  QDomNode n5 = nodeList.item(0);
1092  QDomElement markupNode = n5.toElement();
1093 
1094  nodeList = markupNode.elementsByTagName("mark");
1095  if (nodeList.count() < 1)
1096  {
1097  LOG(VB_JOBQUEUE, LOG_WARNING,
1098  "Couldn't find any 'mark' elements in XML file");
1099  }
1100  else
1101  {
1102  // delete any records for this recordings
1103  query.prepare("DELETE FROM recordedseek "
1104  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
1105  query.bindValue(":CHANID", chanID);
1106  query.bindValue(":STARTTIME", startTime);
1107  query.exec();
1108 
1109  // add the new records for this recording
1110  for (int x = 0; x < nodeList.count(); x++)
1111  {
1112  QDomNode n6 = nodeList.item(x);
1113  QDomElement e = n6.toElement();
1114  query.prepare("INSERT INTO recordedseek (chanid, starttime, "
1115  "mark, `offset`, type)"
1116  "VALUES(:CHANID,:STARTTIME,:MARK,:OFFSET,:TYPE);");
1117  query.bindValue(":CHANID", chanID);
1118  query.bindValue(":STARTTIME", startTime);
1119  query.bindValue(":MARK", e.attribute("mark"));
1120  query.bindValue(":OFFSET", e.attribute("offset"));
1121  query.bindValue(":TYPE", e.attribute("type"));
1122 
1123  if (!query.exec())
1124  {
1125  MythDB::DBError("recordedseek insert", query);
1126  return 1;
1127  }
1128  }
1129 
1130  LOG(VB_JOBQUEUE, LOG_INFO,
1131  "Inserted recordedseek details into database");
1132  }
1133  }
1134 
1135  // FIXME are these needed?
1136  // copy credits to DB
1137  // copy rating to DB
1138 
1139  LOG(VB_JOBQUEUE, LOG_INFO, "Import completed OK");
1140 
1141  return 0;
1142 }
1143 
1144 int NativeArchive::importVideo(const QDomElement &itemNode, const QString &xmlFile)
1145 {
1146  LOG(VB_JOBQUEUE, LOG_INFO, "Importing video");
1147  LOG(VB_JOBQUEUE, LOG_INFO,
1148  QString("Archived video xml file: %1").arg(xmlFile));
1149 
1150  QString videoFile = xmlFile.left(xmlFile.length() - 4);
1151  QFileInfo fileInfo(videoFile);
1152  QString basename = fileInfo.fileName();
1153 
1154  QDomNodeList nodeList = itemNode.elementsByTagName("videometadata");
1155  if (nodeList.count() < 1)
1156  {
1157  LOG(VB_JOBQUEUE, LOG_ERR,
1158  "Couldn't find a 'videometadata' element in XML file");
1159  return 1;
1160  }
1161 
1162  QDomNode n = nodeList.item(0);
1163  QDomElement videoNode = n.toElement();
1164 
1165  // copy file to video directory
1166  QString path = gCoreContext->GetSetting("VideoStartupDir");
1167  QString origFilename = findNodeText(videoNode, "filename");
1168  QStringList dirList = origFilename.split("/", Qt::SkipEmptyParts);
1169  QDir dir;
1170  for (int x = 0; x < dirList.count() - 1; x++)
1171  {
1172  path += "/" + dirList[x];
1173  if (!dir.exists(path))
1174  {
1175  if (!dir.mkdir(path))
1176  {
1177  LOG(VB_JOBQUEUE, LOG_ERR,
1178  QString("Couldn't create directory '%1'").arg(path));
1179  return 1;
1180  }
1181  }
1182  }
1183 
1184  LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");
1185  if (!copyFile(videoFile, path + "/" + basename))
1186  {
1187  return 1;
1188  }
1189 
1190  // copy cover image to Video Artwork dir
1191  QString artworkDir = gCoreContext->GetSetting("VideoArtworkDir");
1192  // get archive path
1193  fileInfo.setFile(videoFile);
1194  QString archivePath = fileInfo.absolutePath();
1195  // get coverfile filename
1196  QString coverFilename = findNodeText(videoNode, "coverfile");
1197  fileInfo.setFile(coverFilename);
1198  coverFilename = fileInfo.fileName();
1199  //check file exists
1200  fileInfo.setFile(archivePath + "/" + coverFilename);
1201  if (fileInfo.exists())
1202  {
1203  LOG(VB_JOBQUEUE, LOG_INFO, "Copying cover file");
1204 
1205  if (!copyFile(archivePath + "/" + coverFilename, artworkDir + "/" + coverFilename))
1206  {
1207  return 1;
1208  }
1209  }
1210  else
1211  {
1212  coverFilename = "No Cover";
1213  }
1214 
1215  // copy videometadata to database
1216  MSqlQuery query(MSqlQuery::InitCon());
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 {
1515  MSqlQuery query(MSqlQuery::InitCon());
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  int res = NativeArchive::doNativeArchive(jobFile);
1532  "MythArchiveLastRunEnd",
1534  gCoreContext->SaveSetting("MythArchiveLastRunStatus",
1535  (res == 0 ? "Success" : "Failed"));
1536 
1537  // clear the archiveitems table if succesful
1538  if (res == 0)
1540 
1541  return res;
1542 }
1543 
1544 static int doImportArchive(const QString &inFile, int chanID)
1545 {
1546  return NativeArchive::doImportArchive(inFile, chanID);
1547 }
1548 
1549 static int grabThumbnail(const QString& inFile, const QString& thumbList, const QString& outFile, int frameCount)
1550 {
1551  // Open recording
1552  LOG(VB_JOBQUEUE, LOG_INFO, QString("grabThumbnail(): Opening '%1'")
1553  .arg(inFile));
1554 
1555  MythCodecMap codecmap;
1556  ArchiveRemoteAVFormatContext inputFC(inFile);
1557  if (!inputFC.isOpen())
1558  {
1559  LOG(VB_JOBQUEUE, LOG_ERR, "grabThumbnail(): Couldn't open input file" +
1560  ENO);
1561  return 1;
1562  }
1563 
1564  // Getting stream information
1565  int ret = avformat_find_stream_info(inputFC, nullptr);
1566  if (ret < 0)
1567  {
1568  LOG(VB_JOBQUEUE, LOG_ERR,
1569  QString("Couldn't get stream info, error #%1").arg(ret));
1570  return 1;
1571  }
1572 
1573  // find the first video stream
1574  int videostream = -1;
1575  int width = 0;
1576  int height = 0;
1577  float fps = NAN;
1578 
1579  for (uint i = 0; i < inputFC->nb_streams; i++)
1580  {
1581  AVStream *st = inputFC->streams[i];
1582  if (inputFC->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
1583  {
1584  videostream = i;
1585  width = st->codecpar->width;
1586  height = st->codecpar->height;
1587  if (st->r_frame_rate.den && st->r_frame_rate.num)
1588  fps = av_q2d(st->r_frame_rate);
1589  else
1590  fps = 1/av_q2d(st->time_base);
1591  break;
1592  }
1593  }
1594 
1595  if (videostream == -1)
1596  {
1597  LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't find a video stream");
1598  return 1;
1599  }
1600 
1601  // get the codec context for the video stream
1602  AVCodecContext *codecCtx = codecmap.GetCodecContext(inputFC->streams[videostream]);
1603 
1604  // get decoder for video stream
1605  const AVCodec * codec = avcodec_find_decoder(codecCtx->codec_id);
1606 
1607  if (codec == nullptr)
1608  {
1609  LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't find codec for video stream");
1610  return 1;
1611  }
1612 
1613  // open codec
1614  if (avcodec_open2(codecCtx, codec, nullptr) < 0)
1615  {
1616  LOG(VB_JOBQUEUE, LOG_ERR, "Couldn't open codec for video stream");
1617  return 1;
1618  }
1619 
1620  // get list of required thumbs
1621  QStringList list = thumbList.split(",", Qt::SkipEmptyParts);
1622  MythAVFrame frame;
1623  if (!frame)
1624  {
1625  return 1;
1626  }
1627  AVPacket pkt;
1628  AVFrame orig;
1629  AVFrame retbuf;
1630  memset(&orig, 0, sizeof(AVFrame));
1631  memset(&retbuf, 0, sizeof(AVFrame));
1632  MythAVCopy copyframe;
1633 
1634  int bufflen = width * height * 4;
1635  auto *outputbuf = new unsigned char[bufflen];
1636 
1637  int frameNo = -1;
1638  int thumbCount = 0;
1639  bool frameFinished = false;
1640 
1641  while (av_read_frame(inputFC, &pkt) >= 0)
1642  {
1643  if (pkt.stream_index == videostream)
1644  {
1645  frameNo++;
1646  if (list[thumbCount].toInt() == (int)(frameNo / fps))
1647  {
1648  thumbCount++;
1649 
1650  avcodec_flush_buffers(codecCtx);
1651  av_frame_unref(frame);
1652  frameFinished = false;
1653  ret = avcodec_receive_frame(codecCtx, frame);
1654  if (ret == 0)
1655  frameFinished = true;
1656  if (ret == 0 || ret == AVERROR(EAGAIN))
1657  avcodec_send_packet(codecCtx, &pkt);
1658  bool keyFrame = (frame->flags & AV_FRAME_FLAG_KEY) != 0;
1659 
1660  while (!frameFinished || !keyFrame)
1661  {
1662  av_packet_unref(&pkt);
1663  int res = av_read_frame(inputFC, &pkt);
1664  if (res < 0)
1665  break;
1666  if (pkt.stream_index == videostream)
1667  {
1668  frameNo++;
1669  av_frame_unref(frame);
1670  ret = avcodec_receive_frame(codecCtx, frame);
1671  if (ret == 0)
1672  frameFinished = true;
1673  if (ret == 0 || ret == AVERROR(EAGAIN))
1674  avcodec_send_packet(codecCtx, &pkt);
1675  keyFrame = (frame->flags & AV_FRAME_FLAG_KEY) != 0;
1676  }
1677  }
1678 
1679  if (frameFinished)
1680  {
1681  // work out what format to save to
1682  QString saveFormat = "JPEG";
1683  if (outFile.right(4) == ".png")
1684  saveFormat = "PNG";
1685 
1686  int count = 0;
1687  while (count < frameCount)
1688  {
1689  QString filename = outFile;
1690  if (filename.contains("%1") && filename.contains("%2"))
1691  filename = filename.arg(thumbCount).arg(count+1);
1692  else if (filename.contains("%1"))
1693  filename = filename.arg(thumbCount);
1694 
1695  av_image_fill_arrays(retbuf.data, retbuf.linesize, outputbuf,
1696  AV_PIX_FMT_RGB32, width, height, IMAGE_ALIGN);
1697 
1698  AVFrame *tmp = frame;
1700 
1701  copyframe.Copy(&retbuf, AV_PIX_FMT_RGB32, tmp,
1702  codecCtx->pix_fmt, width, height);
1703 
1704  QImage img(outputbuf, width, height,
1705  QImage::Format_RGB32);
1706 
1707  if (!img.save(filename, qPrintable(saveFormat)))
1708  {
1709  LOG(VB_GENERAL, LOG_ERR,
1710  QString("grabThumbnail(): Failed to save "
1711  "thumb: '%1'")
1712  .arg(filename));
1713  }
1714 
1715  count++;
1716 
1717  if (count <= frameCount)
1718  {
1719  //grab next frame
1720  frameFinished = false;
1721  while (!frameFinished)
1722  {
1723  int res = av_read_frame(inputFC, &pkt);
1724  if (res < 0)
1725  break;
1726  if (pkt.stream_index == videostream)
1727  {
1728  frameNo++;
1729  ret = avcodec_receive_frame(codecCtx, frame);
1730  if (ret == 0)
1731  frameFinished = true;
1732  if (ret == 0 || ret == AVERROR(EAGAIN))
1733  avcodec_send_packet(codecCtx, &pkt);
1734  }
1735  }
1736  }
1737  }
1738  }
1739 
1740  if (thumbCount >= list.count())
1741  break;
1742  }
1743  }
1744 
1745  av_packet_unref(&pkt);
1746  }
1747 
1748  delete[] outputbuf;
1749 
1750  // close the codec
1751  codecmap.FreeCodecContext(inputFC->streams[videostream]);
1752 
1753  return 0;
1754 }
1755 
1756 static int64_t getFrameCount(AVFormatContext *inputFC, int vid_id)
1757 {
1758  int64_t count = 0;
1759 
1760  LOG(VB_JOBQUEUE, LOG_INFO, "Calculating frame count");
1761 
1762  AVPacket *pkt = av_packet_alloc();
1763  if (pkt == nullptr)
1764  {
1765  LOG(VB_GENERAL, LOG_ERR, "packet allocation failed");
1766  return 0;
1767  }
1768  while (av_read_frame(inputFC, pkt) >= 0)
1769  {
1770  if (pkt->stream_index == vid_id)
1771  {
1772  count++;
1773  }
1774  av_packet_unref(pkt);
1775  }
1776  av_packet_free(&pkt);
1777 
1778  return count;
1779 }
1780 
1781 static int64_t getCutFrames(const QString &filename, int64_t lastFrame)
1782 {
1783  // only wont the filename
1784  QString basename = filename;
1785  int pos = filename.lastIndexOf('/');
1786  if (pos > 0)
1787  basename = filename.mid(pos + 1);
1788 
1789  ProgramInfo *progInfo = getProgramInfoForFile(basename);
1790  if (!progInfo)
1791  return 0;
1792 
1793  if (progInfo->IsVideo())
1794  {
1795  delete progInfo;
1796  return 0;
1797  }
1798 
1799  frm_dir_map_t cutlist;
1800  frm_dir_map_t::iterator it;
1801  uint64_t frames = 0;
1802 
1803  progInfo->QueryCutList(cutlist);
1804 
1805  if (cutlist.empty())
1806  {
1807  delete progInfo;
1808  return 0;
1809  }
1810 
1811  for (it = cutlist.begin(); it != cutlist.end();)
1812  {
1813  uint64_t start = 0;
1814  uint64_t end = 0;
1815 
1816  if (it.value() == MARK_CUT_START)
1817  {
1818  start = it.key();
1819  ++it;
1820  if (it != cutlist.end())
1821  {
1822  end = it.key();
1823  ++it;
1824  }
1825  else
1826  {
1827  end = lastFrame;
1828  }
1829  }
1830  else if (it.value() == MARK_CUT_END)
1831  {
1832  start = 0;
1833  end = it.key();
1834  ++it;
1835  }
1836  else
1837  {
1838  ++it;
1839  continue;
1840  }
1841 
1842  frames += end - start;
1843  }
1844 
1845  delete progInfo;
1846  return frames;
1847 }
1848 
1849 static int64_t getFrameCount(const QString &filename, float fps)
1850 {
1851  // only wont the filename
1852  QString basename = filename;
1853  int pos = filename.lastIndexOf('/');
1854  if (pos > 0)
1855  basename = filename.mid(pos + 1);
1856 
1857  int keyframedist = -1;
1858  frm_pos_map_t posMap;
1859 
1860  ProgramInfo *progInfo = getProgramInfoForFile(basename);
1861  if (!progInfo)
1862  return 0;
1863 
1864  progInfo->QueryPositionMap(posMap, MARK_GOP_BYFRAME);
1865  if (!posMap.empty())
1866  {
1867  keyframedist = 1;
1868  }
1869  else
1870  {
1871  progInfo->QueryPositionMap(posMap, MARK_GOP_START);
1872  if (!posMap.empty())
1873  {
1874  keyframedist = 15;
1875  if (fps < 26 && fps > 24)
1876  keyframedist = 12;
1877  }
1878  else
1879  {
1880  progInfo->QueryPositionMap(posMap, MARK_KEYFRAME);
1881  if (!posMap.empty())
1882  {
1883  // keyframedist should be set in the fileheader so no
1884  // need to try to determine it in this case
1885  delete progInfo;
1886  return 0;
1887  }
1888  }
1889  }
1890 
1891  delete progInfo;
1892  if (posMap.empty())
1893  return 0; // no position map in recording
1894 
1895  frm_pos_map_t::const_iterator it = posMap.cend();
1896  --it;
1897  uint64_t totframes = it.key() * keyframedist;
1898  return totframes;
1899 }
1900 
1901 static int getFileInfo(const QString& inFile, const QString& outFile, int lenMethod)
1902 {
1903  // Open recording
1904  LOG(VB_JOBQUEUE , LOG_INFO, QString("getFileInfo(): Opening '%1'")
1905  .arg(inFile));
1906 
1907  MythCodecMap codecmap;
1908  ArchiveRemoteAVFormatContext inputFC(inFile);
1909  if (!inputFC.isOpen())
1910  {
1911  LOG(VB_JOBQUEUE, LOG_ERR, "getFileInfo(): Couldn't open input file" +
1912  ENO);
1913  return 1;
1914  }
1915 
1916  // Getting stream information
1917  int ret = avformat_find_stream_info(inputFC, nullptr);
1918 
1919  if (ret < 0)
1920  {
1921  LOG(VB_JOBQUEUE, LOG_ERR,
1922  QString("Couldn't get stream info, error #%1").arg(ret));
1923  return 1;
1924  }
1925 
1926  // Dump stream information
1927  av_dump_format(inputFC, 0, qPrintable(inFile), 0);
1928 
1929  QDomDocument doc("FILEINFO");
1930 
1931  QDomElement root = doc.createElement("file");
1932  doc.appendChild(root);
1933  root.setAttribute("type", inputFC->iformat->name);
1934  root.setAttribute("filename", inFile);
1935 
1936  QDomElement streams = doc.createElement("streams");
1937 
1938  root.appendChild(streams);
1939  streams.setAttribute("count", inputFC->nb_streams);
1940  int ffmpegIndex = 0;
1941  uint duration = 0;
1942 
1943  for (uint i = 0; i < inputFC->nb_streams; i++)
1944  {
1945  AVStream *st = inputFC->streams[i];
1946  std::string buf (256,'\0');
1947  AVCodecContext *avctx = codecmap.GetCodecContext(st);
1948  AVCodecParameters *par = st->codecpar;
1949 
1950  if (avctx)
1951  avcodec_string(buf.data(), buf.size(), avctx, static_cast<int>(false));
1952 
1953  switch (st->codecpar->codec_type)
1954  {
1955  case AVMEDIA_TYPE_VIDEO:
1956  {
1957  QStringList param = QString::fromStdString(buf).split(',', Qt::SkipEmptyParts);
1958  QString codec = param[0].remove("Video:", Qt::CaseInsensitive).remove(QChar::Null);
1959  QDomElement stream = doc.createElement("video");
1960  stream.setAttribute("streamindex", i);
1961  stream.setAttribute("ffmpegindex", ffmpegIndex++);
1962  stream.setAttribute("codec", codec.trimmed());
1963  stream.setAttribute("width", par->width);
1964  stream.setAttribute("height", par->height);
1965  stream.setAttribute("bitrate", (qlonglong)par->bit_rate);
1966 
1967  float fps = NAN;
1968  if (st->r_frame_rate.den && st->r_frame_rate.num)
1969  fps = av_q2d(st->r_frame_rate);
1970  else
1971  fps = 1/av_q2d(st->time_base);
1972 
1973  stream.setAttribute("fps", fps);
1974 
1975  if (par->sample_aspect_ratio.den && par->sample_aspect_ratio.num)
1976  {
1977  float aspect_ratio = av_q2d(par->sample_aspect_ratio);
1978  if (QString(inputFC->iformat->name) != "nuv")
1979  aspect_ratio = ((float)par->width
1980  / par->height) * aspect_ratio;
1981 
1982  stream.setAttribute("aspectratio", aspect_ratio);
1983  }
1984  else
1985  {
1986  stream.setAttribute("aspectratio", "N/A");
1987  }
1988 
1989  stream.setAttribute("id", st->id);
1990 
1991  if (st->start_time != (int) AV_NOPTS_VALUE)
1992  {
1993  int secs = st->start_time / AV_TIME_BASE;
1994  int us = st->start_time % AV_TIME_BASE;
1995  stream.setAttribute("start_time", QString("%1.%2")
1996  .arg(secs).arg(av_rescale(us, 1000000, AV_TIME_BASE)));
1997  }
1998  else
1999  {
2000  stream.setAttribute("start_time", 0);
2001  }
2002 
2003  streams.appendChild(stream);
2004 
2005  // TODO: probably should add a better way to choose which
2006  // video stream we use to calc the duration
2007  if (duration == 0)
2008  {
2009  int64_t frameCount = 0;
2010 
2011  switch (lenMethod)
2012  {
2013  case 0:
2014  {
2015  // use duration guess from avformat
2016  if (inputFC->duration != (uint) AV_NOPTS_VALUE)
2017  {
2018  duration = (uint) (inputFC->duration / AV_TIME_BASE);
2019  root.setAttribute("duration", duration);
2020  LOG(VB_JOBQUEUE, LOG_INFO,
2021  QString("duration = %1") .arg(duration));
2022  frameCount = (int64_t)(duration * fps);
2023  }
2024  else
2025  {
2026  root.setAttribute("duration", "N/A");
2027  }
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  {
2066  root.setAttribute("duration", "N/A");
2067  }
2068  break;
2069  }
2070  default:
2071  root.setAttribute("duration", "N/A");
2072  LOG(VB_JOBQUEUE, LOG_ERR,
2073  QString("Unknown lenMethod (%1)")
2074  .arg(lenMethod));
2075  }
2076 
2077  // add duration after all cuts are removed
2078  int64_t cutFrames = getCutFrames(inFile, frameCount);
2079  LOG(VB_JOBQUEUE, LOG_INFO,
2080  QString("cutframes = %1").arg(cutFrames));
2081  int cutduration = (int)(cutFrames / fps);
2082  LOG(VB_JOBQUEUE, LOG_INFO,
2083  QString("cutduration = %1").arg(cutduration));
2084  root.setAttribute("cutduration", duration - cutduration);
2085  }
2086 
2087  break;
2088  }
2089 
2090  case AVMEDIA_TYPE_AUDIO:
2091  {
2092  QStringList param = QString::fromStdString(buf).split(',', Qt::SkipEmptyParts);
2093  QString codec = param[0].remove("Audio:", Qt::CaseInsensitive).remove(QChar::Null);
2094 
2095  QDomElement stream = doc.createElement("audio");
2096  stream.setAttribute("streamindex", i);
2097  stream.setAttribute("ffmpegindex", ffmpegIndex++);
2098 
2099  // change any streams identified as "liba52" to "AC3" which is what
2100  // the mythburn.py script expects to get.
2101  if (codec.trimmed().toLower() == "liba52")
2102  stream.setAttribute("codec", "AC3");
2103  else
2104  stream.setAttribute("codec", codec.trimmed());
2105 
2106  stream.setAttribute("channels", par->ch_layout.nb_channels);
2107 
2108  AVDictionaryEntry *metatag =
2109  av_dict_get(st->metadata, "language", nullptr, 0);
2110  if (metatag)
2111  stream.setAttribute("language", metatag->value);
2112  else
2113  stream.setAttribute("language", "N/A");
2114 
2115  stream.setAttribute("id", st->id);
2116 
2117  stream.setAttribute("samplerate", par->sample_rate);
2118  stream.setAttribute("bitrate", (qlonglong)par->bit_rate);
2119 
2120  if (st->start_time != (int) AV_NOPTS_VALUE)
2121  {
2122  int secs = st->start_time / AV_TIME_BASE;
2123  int us = st->start_time % AV_TIME_BASE;
2124  stream.setAttribute("start_time", QString("%1.%2")
2125  .arg(secs).arg(av_rescale(us, 1000000, AV_TIME_BASE)));
2126  }
2127  else
2128  {
2129  stream.setAttribute("start_time", 0);
2130  }
2131 
2132  streams.appendChild(stream);
2133 
2134  break;
2135  }
2136 
2137  case AVMEDIA_TYPE_SUBTITLE:
2138  {
2139  QStringList param = QString::fromStdString(buf).split(',', Qt::SkipEmptyParts);
2140  QString codec = param[0].remove("Subtitle:", Qt::CaseInsensitive).remove(QChar::Null);
2141 
2142  QDomElement stream = doc.createElement("subtitle");
2143  stream.setAttribute("streamindex", i);
2144  stream.setAttribute("ffmpegindex", ffmpegIndex++);
2145  stream.setAttribute("codec", codec.trimmed());
2146 
2147  AVDictionaryEntry *metatag =
2148  av_dict_get(st->metadata, "language", nullptr, 0);
2149  if (metatag)
2150  stream.setAttribute("language", metatag->value);
2151  else
2152  stream.setAttribute("language", "N/A");
2153 
2154  stream.setAttribute("id", st->id);
2155 
2156  streams.appendChild(stream);
2157 
2158  break;
2159  }
2160 
2161  case AVMEDIA_TYPE_DATA:
2162  {
2163  QDomElement stream = doc.createElement("data");
2164  stream.setAttribute("streamindex", i);
2165  stream.setAttribute("codec", QString::fromStdString(buf).remove(QChar::Null));
2166  streams.appendChild(stream);
2167 
2168  break;
2169  }
2170 
2171  default:
2172  LOG(VB_JOBQUEUE, LOG_ERR,
2173  QString("Skipping unsupported codec %1 on stream %2")
2174  .arg(inputFC->streams[i]->codecpar->codec_type).arg(i));
2175  break;
2176  }
2177  codecmap.FreeCodecContext(st);
2178  }
2179 
2180  // finally save the xml to the file
2181  QFile f(outFile);
2182  if (!f.open(QIODevice::WriteOnly))
2183  {
2184  LOG(VB_JOBQUEUE, LOG_ERR,
2185  "Failed to open file for writing - " + outFile);
2186  return 1;
2187  }
2188 
2189  QTextStream t(&f);
2190  t << doc.toString(4);
2191  f.close();
2192 
2193  return 0;
2194 }
2195 
2196 static int getDBParamters(const QString& outFile)
2197 {
2198  DatabaseParams params = GetMythDB()->GetDatabaseParams();
2199 
2200  // save the db paramters to the file
2201  QFile f(outFile);
2202  if (!f.open(QIODevice::WriteOnly))
2203  {
2204  LOG(VB_GENERAL, LOG_ERR,
2205  QString("MythArchiveHelper: Failed to open file for writing - %1")
2206  .arg(outFile));
2207  return 1;
2208  }
2209 
2210  QTextStream t(&f);
2211  t << params.m_dbHostName << Qt::endl;
2212  t << params.m_dbUserName << Qt::endl;
2213  t << params.m_dbPassword << Qt::endl;
2214  t << params.m_dbName << Qt::endl;
2215  t << gCoreContext->GetHostName() << Qt::endl;
2216  t << GetInstallPrefix() << Qt::endl;
2217  f.close();
2218 
2219  return 0;
2220 }
2221 
2222 static int isRemote(const QString& filename)
2223 {
2224  if (filename.startsWith("myth://"))
2225  return 3;
2226 
2227  // check if the file exists
2228  if (!QFile::exists(filename))
2229  return 0;
2230 
2231 // TODO replace with FileSystemInfo?
2232 #ifdef Q_OS_DARWIN
2233  struct statfs statbuf {};
2234  if ((statfs(qPrintable(filename), &statbuf) == 0) &&
2235  ((!strcmp(statbuf.f_fstypename, "nfs")) || // NFS|FTP
2236  (!strcmp(statbuf.f_fstypename, "afpfs")) || // ApplShr
2237  (!strcmp(statbuf.f_fstypename, "smbfs")))) // SMB
2238  return 2;
2239 #elif defined(__linux__)
2240  struct statfs statbuf {};
2241  if ((statfs(qPrintable(filename), &statbuf) == 0) &&
2242  ((statbuf.f_type == 0x6969) || // NFS
2243  (statbuf.f_type == 0x517B))) // SMB
2244  return 2;
2245 #endif
2246 
2247  return 1;
2248 }
2249 
2251 {
2252  public:
2254  void LoadArguments(void) override; // MythCommandLineParser
2255 };
2256 
2258  MythCommandLineParser("mytharchivehelper")
2260 
2262 {
2263  addHelp();
2264  addVersion();
2265  addLogging();
2266 
2267  add(QStringList{"-t", "--createthumbnail"},
2268  "createthumbnail", false,
2269  "Create one or more thumbnails\n"
2270  "Requires: --infile, --thumblist, --outfile\n"
2271  "Optional: --framecount", "");
2272  add("--infile", "infile", "",
2273  "Input file name\n"
2274  "Used with: --createthumbnail, --getfileinfo, --isremote, "
2275  "--sup2dast, --importarchive", "");
2276  add("--outfile", "outfile", "",
2277  "Output file name\n"
2278  "Used with: --createthumbnail, --getfileinfo, --getdbparameters, "
2279  "--nativearchive\n"
2280  "When used with --createthumbnail: eg 'thumb%1-%2.jpg'\n"
2281  " %1 will be replaced with the no. of the thumb\n"
2282  " %2 will be replaced with the frame no.", "");
2283  add("--thumblist", "thumblist", "",
2284  "Comma-separated list of required thumbs (in seconds)\n"
2285  "Used with: --createthumbnail","");
2286  add("--framecount", "framecount", 1,
2287  "Number of frames to grab (default 1)\n"
2288  "Used with: --createthumbnail", "");
2289 
2290  add(QStringList{"-i", "--getfileinfo"},
2291  "getfileinfo", false,
2292  "Write file info about infile to outfile\n"
2293  "Requires: --infile, --outfile, --method", "");
2294  add("--method", "method", 0,
2295  "Method of file duration calculation\n"
2296  "Used with: --getfileinfo\n"
2297  " 0 = use av_estimate_timings() (quick but not very accurate - "
2298  "default)\n"
2299  " 1 = read all frames (most accurate but slow)\n"
2300  " 2 = use position map in DB (quick, only works for MythTV "
2301  "recordings)", "");
2302 
2303  add(QStringList{"-p", "--getdbparameters"},
2304  "getdbparameters", false,
2305  "Write the mysql database parameters to outfile\n"
2306  "Requires: --outfile", "");
2307 
2308  add(QStringList{"-n", "--nativearchive"},
2309  "nativearchive", false,
2310  "Archive files to a native archive format\n"
2311  "Requires: --outfile", "");
2312 
2313  add(QStringList{"-f", "--importarchive"},
2314  "importarchive", false,
2315  "Import an archived file\n"
2316  "Requires: --infile, --chanid", "");
2317  add("--chanid", "chanid", -1,
2318  "Channel ID to use when inserting records in DB\n"
2319  "Used with: --importarchive", "");
2320 
2321  add(QStringList{"-r", "--isremote"},
2322  "isremote", false,
2323  "Check if infile is on a remote filesystem\n"
2324  "Requires: --infile\n"
2325  "Returns: 0 on error or file not found\n"
2326  " - 1 file is on a local filesystem\n"
2327  " - 2 file is on a remote filesystem", "");
2328 
2329  add(QStringList{"-b", "--burndvd"},
2330  "burndvd", false,
2331  "Burn a created DVD to a blank disc\n"
2332  "Optional: --mediatype, --erasedvdrw, --nativeformat", "");
2333  add("--mediatype", "mediatype", 0,
2334  "Type of media to burn\n"
2335  "Used with: --burndvd\n"
2336  " 0 = single layer DVD (default)\n"
2337  " 1 = dual layer DVD\n"
2338  " 2 = rewritable DVD", "");
2339  add("--erasedvdrw", "erasedvdrw", false,
2340  "Force an erase of DVD-R/W Media\n"
2341  "Used with: --burndvd (optional)", "");
2342  add("--nativeformat", "nativeformat", false,
2343  "Archive is a native archive format\n"
2344  "Used with: --burndvd (optional)", "");
2345 
2346  add(QStringList{"-s", "--sup2dast"},
2347  "sup2dast", false,
2348  "Convert projectX subtitles to DVD subtitles\n"
2349  "Requires: --infile, --ifofile, --delay", "");
2350  add("--ifofile", "ifofile", "",
2351  "Filename of ifo file\n"
2352  "Used with: --sup2dast", "");
2353  add("--delay", "delay", 0,
2354  "Delay in ms to add to subtitles (default 0)\n"
2355  "Used with: --sup2dast", "");
2356 }
2357 
2358 
2359 
2360 static int main_local(int argc, char **argv)
2361 {
2363  if (!cmdline.Parse(argc, argv))
2364  {
2365  cmdline.PrintHelp();
2367  }
2368 
2369  if (cmdline.toBool("showhelp"))
2370  {
2371  cmdline.PrintHelp();
2372  return GENERIC_EXIT_OK;
2373  }
2374 
2375  if (cmdline.toBool("showversion"))
2376  {
2378  return GENERIC_EXIT_OK;
2379  }
2380 
2381  QCoreApplication a(argc, argv);
2382  QCoreApplication::setApplicationName("mytharchivehelper");
2383 
2384  // by default we only output our messages
2385  QString mask("jobqueue");
2386  int retval = cmdline.ConfigureLogging(mask);
2387  if (retval != GENERIC_EXIT_OK)
2388  return retval;
2389 
2391  // Don't listen to console input
2392  close(0);
2393 
2394  MythContext context {MYTH_BINARY_VERSION};
2395  if (!context.Init(false))
2396  {
2397  LOG(VB_GENERAL, LOG_ERR, "Failed to init MythContext, exiting.");
2399  }
2400 
2401  int res = 0;
2402  bool bGrabThumbnail = cmdline.toBool("createthumbnail");
2403  bool bGetDBParameters = cmdline.toBool("getdbparameters");
2404  bool bNativeArchive = cmdline.toBool("nativearchive");
2405  bool bImportArchive = cmdline.toBool("importarchive");
2406  bool bGetFileInfo = cmdline.toBool("getfileinfo");
2407  bool bIsRemote = cmdline.toBool("isremote");
2408  bool bDoBurn = cmdline.toBool("burndvd");
2409  bool bEraseDVDRW = cmdline.toBool("erasedvdrw");
2410  bool bNativeFormat = cmdline.toBool("nativeformat");;
2411  bool bSup2Dast = cmdline.toBool("sup2dast");
2412 
2413  QString thumbList = cmdline.toString("thumblist");
2414  QString inFile = cmdline.toString("infile");
2415  QString outFile = cmdline.toString("outfile");
2416  QString ifoFile = cmdline.toString("ifofile");
2417 
2418  int mediaType = cmdline.toUInt("mediatype");
2419  int lenMethod = cmdline.toUInt("method");
2420  int chanID = cmdline.toInt("chanid");
2421  int frameCount = cmdline.toUInt("framecount");
2422  int delay = cmdline.toUInt("delay");
2423 
2424  // Check command line arguments
2425  if (bGrabThumbnail)
2426  {
2427  if (inFile.isEmpty())
2428  {
2429  LOG(VB_GENERAL, LOG_ERR, "Missing --infile in -t/--grabthumbnail "
2430  "option");
2432  }
2433 
2434  if (thumbList.isEmpty())
2435  {
2436  LOG(VB_GENERAL, LOG_ERR, "Missing --thumblist in -t/--grabthumbnail"
2437  " option");
2439  }
2440 
2441  if (outFile.isEmpty())
2442  {
2443  LOG(VB_GENERAL, LOG_ERR, "Missing --outfile in -t/--grabthumbnail "
2444  "option");
2446  }
2447  }
2448 
2449  if (bGetDBParameters)
2450  {
2451  if (outFile.isEmpty())
2452  {
2453  LOG(VB_GENERAL, LOG_ERR, "Missing argument to -p/--getdbparameters "
2454  "option");
2456  }
2457  }
2458 
2459  if (bIsRemote)
2460  {
2461  if (inFile.isEmpty())
2462  {
2463  LOG(VB_GENERAL, LOG_ERR,
2464  "Missing argument to -r/--isremote option");
2466  }
2467  }
2468 
2469  if (bDoBurn)
2470  {
2471  if (mediaType < 0 || mediaType > 2)
2472  {
2473  LOG(VB_GENERAL, LOG_ERR, QString("Invalid mediatype given: %1")
2474  .arg(mediaType));
2476  }
2477  }
2478 
2479  if (bNativeArchive)
2480  {
2481  if (outFile.isEmpty())
2482  {
2483  LOG(VB_GENERAL, LOG_ERR, "Missing argument to -n/--nativearchive "
2484  "option");
2486  }
2487  }
2488 
2489  if (bImportArchive)
2490  {
2491  if (inFile.isEmpty())
2492  {
2493  LOG(VB_GENERAL, LOG_ERR, "Missing --infile argument to "
2494  "-f/--importarchive option");
2496  }
2497  }
2498 
2499  if (bGetFileInfo)
2500  {
2501  if (inFile.isEmpty())
2502  {
2503  LOG(VB_GENERAL, LOG_ERR, "Missing --infile in -i/--getfileinfo "
2504  "option");
2506  }
2507 
2508  if (outFile.isEmpty())
2509  {
2510  LOG(VB_GENERAL, LOG_ERR, "Missing --outfile in -i/--getfileinfo "
2511  "option");
2513  }
2514  }
2515 
2516  if (bSup2Dast)
2517  {
2518  if (inFile.isEmpty())
2519  {
2520  LOG(VB_GENERAL, LOG_ERR,
2521  "Missing --infile in -s/--sup2dast option");
2523  }
2524 
2525  if (ifoFile.isEmpty())
2526  {
2527  LOG(VB_GENERAL, LOG_ERR,
2528  "Missing --ifofile in -s/--sup2dast option");
2530  }
2531  }
2532 
2533  if (bGrabThumbnail)
2534  res = grabThumbnail(inFile, thumbList, outFile, frameCount);
2535  else if (bGetDBParameters)
2536  res = getDBParamters(outFile);
2537  else if (bNativeArchive)
2538  res = doNativeArchive(outFile);
2539  else if (bImportArchive)
2540  res = doImportArchive(inFile, chanID);
2541  else if (bGetFileInfo)
2542  res = getFileInfo(inFile, outFile, lenMethod);
2543  else if (bIsRemote)
2544  res = isRemote(inFile);
2545  else if (bDoBurn)
2546  res = doBurnDVD(mediaType, bEraseDVDRW, bNativeFormat);
2547  else if (bSup2Dast)
2548  {
2549  QByteArray inFileBA = inFile.toLocal8Bit();
2550  QByteArray ifoFileBA = ifoFile.toLocal8Bit();
2551  res = sup2dast(inFileBA.constData(), ifoFileBA.constData(), delay);
2552  }
2553  else
2554  {
2555  cmdline.PrintHelp();
2556  }
2557 
2558  exit(res);
2559 }
2560 
2561 int main(int argc, char **argv)
2562 {
2563  int result = main_local(argc, argv);
2564  logStop();
2565  return result;
2566 }
MSqlQuery::isActive
bool isActive(void) const
Definition: mythdbcon.h:215
NativeArchive::exportVideo
static int exportVideo(QDomElement &itemNode, const QString &saveDirectory)
Definition: mytharchivehelper.cpp:627
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:127
ArchiveRemoteAVFormatContext::isOpen
bool isOpen() const
Definition: remoteavformatcontext.h:103
badChars
static const QRegularExpression badChars
Definition: mytharchivehelper.cpp:375
pxsup2dast.h
MythDate::toString
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:214
AD_FILE
@ AD_FILE
Definition: archiveutil.h:21
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
MythCoreContext::GetMasterHostName
QString GetMasterHostName(void)
Definition: mythcorecontext.cpp:813
DatabaseParams::m_dbHostName
QString m_dbHostName
database server
Definition: mythdbparams.h:21
MARK_KEYFRAME
@ MARK_KEYFRAME
Definition: programtypes.h:61
MythCommandLineParser
Parent class for defining application command line parsers.
Definition: mythcommandlineparser.h:116
mythdb.h
ProgramInfo::QueryPositionMap
void QueryPositionMap(frm_pos_map_t &posMap, MarkTypes type) const
Definition: programinfo.cpp:3794
MythCodecMap::GetCodecContext
AVCodecContext * GetCodecContext(const AVStream *Stream, const AVCodec *Codec=nullptr, bool NullCodec=false)
Definition: mythavutil.cpp:288
MARK_GOP_START
@ MARK_GOP_START
Definition: programtypes.h:60
NativeArchive::NativeArchive
NativeArchive(void)
Definition: mytharchivehelper.cpp:100
cmdline
MythCommFlagCommandLineParser cmdline
Definition: mythcommflag.cpp:63
DatabaseParams
Structure containing the basic Database parameters.
Definition: mythdbparams.h:10
NativeArchive
Definition: mytharchivehelper.cpp:81
MythArchiveHelperCommandLineParser
Definition: mytharchivehelper.cpp:2250
MythContext
Startup context for MythTV.
Definition: mythcontext.h:19
MythAVFrame
MythAVFrame little utility class that act as a safe way to allocate an AVFrame which can then be allo...
Definition: mythavframe.h:26
MythAVUtil::DeinterlaceAVFrame
static void DeinterlaceAVFrame(AVFrame *Frame)
Deinterlace an AVFrame.
Definition: mythavutil.cpp:135
mythcoreutil.h
AD_DVD_RW
@ AD_DVD_RW
Definition: archiveutil.h:20
xbmcvfs.exists
bool exists(str path)
Definition: xbmcvfs.py:51
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:204
MythCommandLineParser::addVersion
void addVersion(void)
Canned argument definition for –version.
Definition: mythcommandlineparser.cpp:2558
frm_dir_map_t
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:117
NativeArchive::doImportArchive
static int doImportArchive(const QString &xmlFile, int chanID)
Definition: mytharchivehelper.cpp:876
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
NativeArchive::doNativeArchive
static int doNativeArchive(const QString &jobFile)
Definition: mytharchivehelper.cpp:235
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
GetMythDB
MythDB * GetMythDB(void)
Definition: mythdb.cpp:51
build_compdb.file
file
Definition: build_compdb.py:55
mythavframe.h
burnISOImage
static int burnISOImage(int mediaType, bool bEraseDVDRW, bool nativeFormat)
Definition: mytharchivehelper.cpp:161
mythdirs.h
myth_system
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
Definition: mythsystemlegacy.cpp:506
getTempDirectory
QString getTempDirectory(bool showError)
Definition: archiveutil.cpp:46
doBurnDVD
static int doBurnDVD(int mediaType, bool bEraseDVDRW, bool nativeFormat)
Definition: mytharchivehelper.cpp:219
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
close
#define close
Definition: compat.h:43
tmp
static guint32 * tmp
Definition: goom_core.cpp:26
MythCommandLineParser::addLogging
void addLogging(const QString &defaultVerbosity="general", LogLevel_t defaultLogLevel=LOG_INFO)
Canned argument definition for all logging options, including –verbose, –logpath, –quiet,...
Definition: mythcommandlineparser.cpp:2662
mythsystemlegacy.h
MythArchiveHelperCommandLineParser::MythArchiveHelperCommandLineParser
MythArchiveHelperCommandLineParser()
Definition: mytharchivehelper.cpp:2257
MythRemoveDirectory
bool MythRemoveDirectory(QDir &aDir)
Definition: mythmiscutil.cpp:759
getFrameCount
static int64_t getFrameCount(AVFormatContext *inputFC, int vid_id)
Definition: mytharchivehelper.cpp:1756
mythpluginexport.h
GetInstallPrefix
QString GetInstallPrefix(void)
Definition: mythdirs.cpp:259
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:1558
MythCommandLineParser::add
CommandLineArg * add(const QString &arg, const QString &name, bool def, QString help, QString longhelp)
Definition: mythcommandlineparser.cpp:1776
NativeArchive::copyFile
static bool copyFile(const QString &source, const QString &destination)
Definition: mytharchivehelper.cpp:122
mythdate.h
programinfo.h
GENERIC_EXIT_INVALID_CMDLINE
@ GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:18
mythlogging.h
MythCoreContext::GenMythURL
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
Definition: mythcorecontext.cpp:766
hardwareprofile.i18n.t
t
Definition: i18n.py:36
statfs
int statfs(const char *path, struct statfs *buffer)
Definition: compat.h:105
GENERIC_EXIT_OK
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
getCutFrames
static int64_t getCutFrames(const QString &filename, int64_t lastFrame)
Definition: mytharchivehelper.cpp:1781
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
hardwareprofile.scan.rating
def rating(profile, smoonURL, gate)
Definition: scan.py:36
NativeArchive::~NativeArchive
~NativeArchive(void)
Definition: mytharchivehelper.cpp:114
ProgramInfo::QueryCutList
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
Definition: programinfo.cpp:3485
MARK_CUT_START
@ MARK_CUT_START
Definition: programtypes.h:55
MythCommandLineParser::PrintVersion
static void PrintVersion(void)
Print application version information.
Definition: mythcommandlineparser.cpp:1386
MythCodecMap::FreeCodecContext
void FreeCodecContext(const AVStream *Stream)
Definition: mythavutil.cpp:333
MythCommandLineParser::toUInt
uint toUInt(const QString &key) const
Returns stored QVariant as an unsigned integer, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2252
MythCommandLineParser::PrintHelp
void PrintHelp(void) const
Print command line option help.
Definition: mythcommandlineparser.cpp:1402
DatabaseParams::m_dbPassword
QString m_dbPassword
DB password.
Definition: mythdbparams.h:25
ArchiveRemoteAVFormatContext
Definition: remoteavformatcontext.h:15
DatabaseParams::m_dbName
QString m_dbName
database name
Definition: mythdbparams.h:26
MythCodecMap
Definition: mythavutil.h:26
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:57
NativeArchive::findNodeText
static QString findNodeText(const QDomElement &elem, const QString &nodeName)
Definition: mytharchivehelper.cpp:1470
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:918
getProgramInfoForFile
ProgramInfo * getProgramInfoForFile(const QString &inFile)
Definition: archiveutil.cpp:163
logStop
void logStop(void)
Entry point for stopping logging for an application.
Definition: logging.cpp:685
MARK_GOP_BYFRAME
@ MARK_GOP_BYFRAME
Definition: programtypes.h:63
frm_pos_map_t
QMap< long long, long long > frm_pos_map_t
Frame # -> File offset map.
Definition: programtypes.h:44
grabThumbnail
static int grabThumbnail(const QString &inFile, const QString &thumbList, const QString &outFile, int frameCount)
Definition: mytharchivehelper.cpp:1549
NativeArchive::importRecording
static int importRecording(const QDomElement &itemNode, const QString &xmlFile, int chanID)
Definition: mytharchivehelper.cpp:942
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:986
NativeArchive::getFieldList
static int getFieldList(QStringList &fieldList, const QString &tableName)
Definition: mytharchivehelper.cpp:384
getFileInfo
static int getFileInfo(const QString &inFile, const QString &outFile, int lenMethod)
Definition: mytharchivehelper.cpp:1901
doNativeArchive
static int doNativeArchive(const QString &jobFile)
Definition: mytharchivehelper.cpp:1522
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:67
mythmiscutil.h
MythCommandLineParser::toString
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
Definition: mythcommandlineparser.cpp:2360
MARK_CUT_END
@ MARK_CUT_END
Definition: programtypes.h:54
mythcorecontext.h
NativeArchive::exportRecording
static int exportRecording(QDomElement &itemNode, const QString &saveDirectory)
Definition: mytharchivehelper.cpp:404
MythCommandLineParser::addHelp
void addHelp(void)
Canned argument definition for –help.
Definition: mythcommandlineparser.cpp:2545
MythCommandLineParser::toBool
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
Definition: mythcommandlineparser.cpp:2203
statfs
Definition: compat.h:93
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
DatabaseParams::m_dbUserName
QString m_dbUserName
DB user name.
Definition: mythdbparams.h:24
MPLUGIN_PUBLIC
#define MPLUGIN_PUBLIC
Definition: mythpluginexport.h:9
ProgramInfo::IsVideo
bool IsVideo(void) const
Definition: programinfo.h:490
mythavutil.h
NativeArchive::importVideo
static int importVideo(const QDomElement &itemNode, const QString &xmlFile)
Definition: mytharchivehelper.cpp:1144
mythcontext.h
main_local
static int main_local(int argc, char **argv)
Definition: mytharchivehelper.cpp:2360
MythArchiveHelperCommandLineParser::LoadArguments
void LoadArguments(void) override
Definition: mytharchivehelper.cpp:2261
MythAVCopy
Definition: mythavutil.h:43
doImportArchive
static int doImportArchive(const QString &inFile, int chanID)
Definition: mytharchivehelper.cpp:1544
createISOImage
static bool createISOImage(QString &sourceDirectory)
Definition: mytharchivehelper.cpp:137
MythDate::kDatabase
@ kDatabase
Default UTC, database format.
Definition: mythdate.h:27
clearArchiveTable
static void clearArchiveTable(void)
Definition: mytharchivehelper.cpp:1513
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:844
main
int main(int argc, char **argv)
Definition: mytharchivehelper.cpp:2561
MythCommandLineParser::ConfigureLogging
int ConfigureLogging(const QString &mask="general", bool progress=false)
Read in logging options and initialize the logging interface.
Definition: mythcommandlineparser.cpp:2876
MythCommandLineParser::LoadArguments
virtual void LoadArguments(void)
Definition: mythcommandlineparser.h:137
MythCoreContext::SaveSetting
void SaveSetting(const QString &key, int newValue)
Definition: mythcorecontext.cpp:887
getBaseName
QString getBaseName(const QString &filename)
Definition: archiveutil.cpp:123
MythAVCopy::Copy
int Copy(AVFrame *To, const MythVideoFrame *From, unsigned char *Buffer, AVPixelFormat Fmt=AV_PIX_FMT_YUV420P)
Initialise AVFrame and copy contents of VideoFrame frame into it, performing any required conversion.
Definition: mythavutil.cpp:267
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:2225
getDBParamters
static int getDBParamters(const QString &outFile)
Definition: mytharchivehelper.cpp:2196
build_compdb.filename
filename
Definition: build_compdb.py:21
mythcommandlineparser.h
build_compdb.options
options
Definition: build_compdb.py:11
mythburn.dbVersion
string dbVersion
Definition: mythburn.py:171
sup2dast
int sup2dast(const char *supfile, const char *ifofile, int delay_ms)
Definition: pxsup2dast.c:904
fixFilename
static QString fixFilename(const QString &filename)
Definition: mytharchivehelper.cpp:377
isRemote
static int isRemote(const QString &filename)
Definition: mytharchivehelper.cpp:2222
extractDetailsFromFilename
bool extractDetailsFromFilename(const QString &inFile, QString &chanID, QString &startTime)
Definition: archiveutil.cpp:133
GENERIC_EXIT_NO_MYTHCONTEXT
@ GENERIC_EXIT_NO_MYTHCONTEXT
No MythContext available.
Definition: exitcodes.h:16
uint
unsigned int uint
Definition: freesurround.h:24
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:904
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:837