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