MythTV  master
v2content.cpp
Go to the documentation of this file.
1 // Program Name: content.cpp
3 // Created : Mar. 7, 2011
4 //
5 // Copyright (c) 2011 David Blain <dblain@mythtv.org>
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 //
25 
26 // C++
27 #include <cmath>
28 
29 // Qt
30 #include <QDir>
31 #include <QImage>
32 #include <QImageWriter>
33 
34 // MythTV
35 #include "libmythbase/compat.h"
38 #include "libmythbase/mythdate.h"
42 #include "libmythbase/remotefile.h"
49 
50 // MythBackend
51 #include "v2content.h"
52 #include "v2serviceUtil.h"
53 
54 // Qt6 has made the QFileInfo::QFileInfo(QString) constructor
55 // explicit, which means that it is no longer possible to use an
56 // initializer list to construct a QFileInfo. Disable that clang-tidy
57 // check for this file so it can still be run on the rest of the file
58 // in the project.
59 //
60 // NOLINTBEGIN(modernize-return-braced-init-list)
61 
62 // This will be initialised in a thread safe manner on first use
64  (CONTENT_HANDLE, V2Content::staticMetaObject, &V2Content::RegisterCustomTypes))
65 
67 {
68  qRegisterMetaType<V2ArtworkInfoList*>("V2ArtworkInfoList");
69  qRegisterMetaType<V2ArtworkInfo*>("V2ArtworkInfo");
70  // qRegisterMetaType<V2LiveStreamInfo*>("V2LiveStreamInfo");
71  // qRegisterMetaType<V2LiveStreamInfoList*>("V2LiveStreamInfoList");
72 }
73 
75 
77 //
79 
80 QFileInfo V2Content::GetFile( const QString &sStorageGroup,
81  const QString &sFileName )
82 {
83  QString sGroup = sStorageGroup;
84 
85  if (sGroup.isEmpty())
86  {
87  LOG(VB_UPNP, LOG_WARNING,
88  "GetFile - StorageGroup missing... using 'Default'");
89  sGroup = "Default";
90  }
91 
92  if (sFileName.isEmpty())
93  {
94  QString sMsg ( "GetFile - FileName missing." );
95 
96  //LOG(VB_UPNP, LOG_ERR, sMsg);
97 
98  throw QString(sMsg);
99  }
100 
101  // ------------------------------------------------------------------
102  // Search for the filename
103  // ------------------------------------------------------------------
104 
105  StorageGroup storage( sGroup );
106  QString sFullFileName = storage.FindFile( sFileName );
107 
108  if (sFullFileName.isEmpty())
109  {
110  LOG(VB_UPNP, LOG_ERR,
111  QString("GetFile - Unable to find %1.").arg(sFileName));
112 
113  return {};
114  }
115 
116  // ----------------------------------------------------------------------
117  // check to see if the file (still) exists
118  // ----------------------------------------------------------------------
119 
120  if (QFile::exists( sFullFileName ))
121  {
122  return QFileInfo( sFullFileName );
123  }
124 
125  LOG(VB_UPNP, LOG_ERR,
126  QString("GetFile - File Does not exist %1.").arg(sFullFileName));
127 
128  return {};
129 }
130 
132 //
134 
135 QFileInfo V2Content::GetImageFile( const QString &sStorageGroup,
136  const QString &sFileName,
137  int nWidth,
138  int nHeight)
139 {
140  QString sGroup = sStorageGroup;
141 
142  if (sGroup.isEmpty())
143  {
144  LOG(VB_UPNP, LOG_WARNING,
145  "GetImageFile - StorageGroup missing... using 'Default'");
146  sGroup = "Default";
147  }
148 
149  if (sFileName.isEmpty())
150  {
151  QString sMsg ( "GetImageFile - FileName missing." );
152 
153  //LOG(VB_UPNP, LOG_WARNING, sMsg);
154 
155  throw QString(sMsg);
156  }
157 
158  // ------------------------------------------------------------------
159  // Search for the filename
160  // ------------------------------------------------------------------
161 
162  StorageGroup storage( sGroup );
163  QString sFullFileName = storage.FindFile( sFileName );
164 
165  if (sFullFileName.isEmpty())
166  {
167  LOG(VB_UPNP, LOG_WARNING,
168  QString("GetImageFile - Unable to find %1.").arg(sFileName));
169 
170  return {};
171  }
172 
173  // ----------------------------------------------------------------------
174  // check to see if the file (still) exists
175  // ----------------------------------------------------------------------
176 
177  if (!QFile::exists( sFullFileName ))
178  {
179  LOG(VB_UPNP, LOG_WARNING,
180  QString("GetImageFile - File Does not exist %1.").arg(sFullFileName));
181  return {};
182  }
183 
184  // ----------------------------------------------------------------------
185  // If no scaling is required return the file info
186  // ----------------------------------------------------------------------
187  if ((nWidth == 0) && (nHeight == 0))
188  return QFileInfo( sFullFileName );
189 
190  // ----------------------------------------------------------------------
191  // Create a filename for the scaled copy
192  // ----------------------------------------------------------------------
193  QString sNewFileName = QString( "%1.%2x%3.jpg" )
194  .arg( sFullFileName )
195  .arg( nWidth )
196  .arg( nHeight );
197 
198  // ----------------------------------------------------------------------
199  // check to see if image is already created.
200  // ----------------------------------------------------------------------
201 
202  if (QFile::exists( sNewFileName ))
203  return QFileInfo( sNewFileName );
204 
205  // ----------------------------------------------------------------------
206  // Must generate Generate Image and save.
207  // ----------------------------------------------------------------------
208 
209  auto *pImage = new QImage( sFullFileName );
210 
211  if (!pImage || pImage->isNull())
212  return {};
213 
214  float fAspect = (float)(pImage->width()) / pImage->height();
215 
216  if ( nWidth == 0 )
217  nWidth = (int)std::rint(nHeight * fAspect);
218 
219  if ( nHeight == 0 )
220  nHeight = (int)std::rint(nWidth / fAspect);
221 
222  QImage img = pImage->scaled( nWidth, nHeight, Qt::KeepAspectRatio,
223  Qt::SmoothTransformation);
224 
225  QByteArray fname = sNewFileName.toLatin1();
226  img.save( fname.constData(), "JPG", 60 );
227 
228  delete pImage;
229 
230  return QFileInfo( sNewFileName );
231 }
232 
234 //
236 
237 QStringList V2Content::GetDirList( const QString &sStorageGroup )
238 {
239 
240  if (sStorageGroup.isEmpty())
241  {
242  QString sMsg( "GetDirList - StorageGroup missing.");
243  LOG(VB_UPNP, LOG_ERR, sMsg);
244 
245  throw QString(sMsg);
246  }
247 
248  StorageGroup sgroup(sStorageGroup);
249 
250  return sgroup.GetDirList("", true);
251 }
252 
254 //
256 
257 QStringList V2Content::GetFileList( const QString &sStorageGroup )
258 {
259 
260  if (sStorageGroup.isEmpty())
261  {
262  QString sMsg( "GetFileList - StorageGroup missing.");
263  LOG(VB_UPNP, LOG_ERR, sMsg);
264 
265  throw QString(sMsg);
266  }
267 
268  StorageGroup sgroup(sStorageGroup);
269 
270  return sgroup.GetFileList("", true);
271 }
272 
274 //
276 
277 QFileInfo V2Content::GetRecordingArtwork ( const QString &sType,
278  const QString &sInetref,
279  int nSeason,
280  int nWidth,
281  int nHeight)
282 {
283  ArtworkMap map = GetArtwork(sInetref, nSeason);
284 
285  if (map.isEmpty())
286  return {};
287 
289  QString sgroup;
290 
291  if (sType.toLower() == "coverart")
292  {
293  sgroup = "Coverart";
295  }
296  else if (sType.toLower() == "fanart")
297  {
298  sgroup = "Fanart";
300  }
301  else if (sType.toLower() == "banner")
302  {
303  sgroup = "Banners";
305  }
306 
307  if (!map.contains(type))
308  return {};
309 
310  QUrl url(map.value(type).url);
311  QString sFileName = url.path();
312 
313  if (sFileName.isEmpty())
314  return {};
315 
316  return GetImageFile( sgroup, sFileName, nWidth, nHeight);
317 }
318 
320 //
322 
324  int chanid,
325  const QDateTime &StartTime)
326 {
327  if ((RecordedId <= 0) &&
328  (chanid <= 0 || !StartTime.isValid()))
329  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
330 
331  // TODO Should use RecordingInfo
332  ProgramInfo pginfo;
333  if (RecordedId > 0)
334  pginfo = ProgramInfo(RecordedId);
335  else
336  pginfo = ProgramInfo(chanid, StartTime.toUTC());
337 
338  return GetProgramArtworkList(pginfo.GetInetRef(), pginfo.GetSeason());
339 }
340 
342  int nSeason )
343 {
344  auto *pArtwork = new V2ArtworkInfoList();
345 
346  V2FillArtworkInfoList (pArtwork, sInetref, nSeason);
347 
348  return pArtwork;
349 }
351 //
353 
354 // NOTE: If you rename this, you must also update upnpcdsvideo.cpp
355 QFileInfo V2Content::GetVideoArtwork( const QString &sType,
356  int nId, int nWidth, int nHeight )
357 {
358  LOG(VB_UPNP, LOG_INFO, QString("GetVideoArtwork ID = %1").arg(nId));
359 
360  QString sgroup = "Coverart";
361  QString column = "coverfile";
362 
363  if (sType.toLower() == "coverart")
364  {
365  sgroup = "Coverart";
366  column = "coverfile";
367  }
368  else if (sType.toLower() == "fanart")
369  {
370  sgroup = "Fanart";
371  column = "fanart";
372  }
373  else if (sType.toLower() == "banner")
374  {
375  sgroup = "Banners";
376  column = "banner";
377  }
378  else if (sType.toLower() == "screenshot")
379  {
380  sgroup = "Screenshots";
381  column = "screenshot";
382  }
383 
384  // ----------------------------------------------------------------------
385  // Read Video artwork file path from database
386  // ----------------------------------------------------------------------
387 
388  MSqlQuery query(MSqlQuery::InitCon());
389 
390  QString querystr = QString("SELECT %1 FROM videometadata WHERE "
391  "intid = :ITEMID").arg(column);
392 
393  query.prepare(querystr);
394  query.bindValue(":ITEMID", nId);
395 
396  if (!query.exec())
397  MythDB::DBError("GetVideoArtwork ", query);
398 
399  if (!query.next())
400  return {};
401 
402  QString sFileName = query.value(0).toString();
403 
404  if (sFileName.isEmpty())
405  return {};
406 
407  return GetImageFile( sgroup, sFileName, nWidth, nHeight );
408 }
409 
411 //
413 
414 QFileInfo V2Content::GetAlbumArt( int nTrackId, int nWidth, int nHeight )
415 {
416  // ----------------------------------------------------------------------
417  // Read AlbumArt file path from database
418  // ----------------------------------------------------------------------
419 
420  MusicMetadata *metadata = MusicMetadata::createFromID(nTrackId);
421 
422  if (!metadata)
423  return {};
424 
425  QString sFullFileName = metadata->getAlbumArtFile();
426  LOG(VB_GENERAL, LOG_DEBUG, QString("GetAlbumArt: %1").arg(sFullFileName));
427 
428  delete metadata;
429 
430  if (!RemoteFile::Exists(sFullFileName))
431  return {};
432 
433  QString sNewFileName = QString( "/tmp/%1.%2x%3.jpg" )
434  .arg( QFileInfo(sFullFileName).fileName() )
435  .arg( nWidth )
436  .arg( nHeight );
437 
438  // ----------------------------------------------------------------------
439  // check to see if albumart image is already created.
440  // ----------------------------------------------------------------------
441 
442  if (QFile::exists( sNewFileName ))
443  return QFileInfo( sNewFileName );
444 
445  // ----------------------------------------------------------------------
446  // Must generate Albumart Image, Generate Image and save.
447  // ----------------------------------------------------------------------
448 
449 
450  QImage img;
451  if (sFullFileName.startsWith("myth://"))
452  {
453  RemoteFile rf(sFullFileName, false, false, 0s);
454  QByteArray data;
455  rf.SaveAs(data);
456 
457  img.loadFromData(data);
458  }
459  else
460  img.load(sFullFileName);
461 
462  if (img.isNull())
463  return {};
464 
465  // We don't need to scale if no height and width were specified
466  // but still need to save as jpg if it's in another format
467  if ((nWidth == 0) && (nHeight == 0))
468  {
469  if (!sFullFileName.startsWith("myth://"))
470  {
471  QFileInfo fi(sFullFileName);
472  if (fi.suffix().toLower() == "jpg")
473  return fi;
474  }
475  }
476  else if (nWidth > img.width() && nHeight > img.height())
477  {
478  // Requested dimensions are larger than the source image, so instead of
479  // scaling up which will produce horrible results return the fullsize
480  // image and the user can scale further if they want instead
481  // NOTE: If this behaviour is changed, for example making it optional,
482  // then upnp code will need changing to compensate
483  }
484  else
485  {
486  float fAspect = (float)(img.width()) / img.height();
487 
488  if ( nWidth == 0 || nWidth > img.width() )
489  nWidth = (int)std::rint(nHeight * fAspect);
490 
491  if ( nHeight == 0 || nHeight > img.height() )
492  nHeight = (int)std::rint(nWidth / fAspect);
493 
494  img = img.scaled( nWidth, nHeight, Qt::KeepAspectRatio,
495  Qt::SmoothTransformation);
496  }
497 
498  QString fname = sNewFileName.toLatin1().constData();
499  // Use JPG not PNG for compatibility with the most uPnP devices and
500  // faster loading (smaller file to send over network)
501  if (!img.save( fname, "JPG" ))
502  return {};
503 
504  return QFileInfo( sNewFileName );
505 }
506 
508 //
510 
511 QFileInfo V2Content::GetPreviewImage( int nRecordedId,
512  int nChanId,
513  const QDateTime &StartTime,
514  int nWidth,
515  int nHeight,
516  int nSecsIn,
517  const QString &sFormat )
518 {
519  if ((nRecordedId <= 0) &&
520  (nChanId <= 0 || !StartTime.isValid()))
521  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
522 
523  if (!sFormat.isEmpty()
524  && !QImageWriter::supportedImageFormats().contains(sFormat.toLower().toLocal8Bit()))
525  {
526  throw QString("GetPreviewImage: Specified 'Format' is not supported.");
527  }
528 
529  // ----------------------------------------------------------------------
530  // Read Recording From Database
531  // ----------------------------------------------------------------------
532 
533  // TODO Should use RecordingInfo
534  ProgramInfo pginfo;
535  if (nRecordedId > 0)
536  pginfo = ProgramInfo(nRecordedId);
537  else
538  pginfo = ProgramInfo(nChanId, StartTime.toUTC());
539 
540  if (!pginfo.GetChanID())
541  {
542  LOG(VB_GENERAL, LOG_ERR,
543  QString("GetPreviewImage: No recording for '%1'")
544  .arg(nRecordedId));
545  return {};
546  }
547 
548  if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
549  && ! gCoreContext->GetBoolSetting("MasterBackendOverride", false))
550  {
551  QString sMsg =
552  QString("GetPreviewImage: Wrong Host '%1' request from '%2'")
553  .arg( gCoreContext->GetHostName(),
554  pginfo.GetHostname() );
555 
556  LOG(VB_UPNP, LOG_ERR, sMsg);
557 
558  throw V2HttpRedirectException( pginfo.GetHostname() );
559  }
560 
561  QString sImageFormat = sFormat;
562  if (sImageFormat.isEmpty())
563  sImageFormat = "PNG";
564 
565  QString sFileName = GetPlaybackURL(&pginfo);
566 
567  // ----------------------------------------------------------------------
568  // check to see if default preview image is already created.
569  // ----------------------------------------------------------------------
570 
571  QString sPreviewFileName;
572 
573  auto nSecs = std::chrono::seconds(nSecsIn);
574  if (nSecs <= 0s)
575  {
576  nSecs = -1s;
577  sPreviewFileName = QString("%1.png").arg(sFileName);
578  }
579  else
580  {
581  sPreviewFileName = QString("%1.%2.png").arg(sFileName).arg(nSecsIn);
582  }
583 
584  if (!QFile::exists( sPreviewFileName ))
585  {
586  // ------------------------------------------------------------------
587  // Must generate Preview Image, Generate Image and save.
588  // ------------------------------------------------------------------
589  if (!pginfo.IsLocal() && sFileName.startsWith("/"))
590  pginfo.SetPathname(sFileName);
591 
592  if (!pginfo.IsLocal())
593  return {};
594 
595  auto *previewgen = new PreviewGenerator( &pginfo, QString(),
597  previewgen->SetPreviewTimeAsSeconds( nSecs );
598  previewgen->SetOutputFilename ( sPreviewFileName );
599 
600  bool ok = previewgen->Run();
601 
602  previewgen->deleteLater();
603 
604  if (!ok)
605  return {};
606  }
607 
608  bool bDefaultPixmap = (nWidth == 0) && (nHeight == 0);
609 
610  QString sNewFileName;
611 
612  if (bDefaultPixmap)
613  sNewFileName = sPreviewFileName;
614  else
615  {
616  sNewFileName = QString( "%1.%2.%3x%4.%5" )
617  .arg( sFileName )
618  .arg( nSecsIn )
619  .arg( nWidth == 0 ? -1 : nWidth )
620  .arg( nHeight == 0 ? -1 : nHeight )
621  .arg( sImageFormat.toLower() );
622 
623  // ----------------------------------------------------------------------
624  // check to see if scaled preview image is already created and isn't
625  // out of date
626  // ----------------------------------------------------------------------
627  if (QFile::exists( sNewFileName ))
628  {
629  if (QFileInfo(sPreviewFileName).lastModified() <=
630  QFileInfo(sNewFileName).lastModified())
631  return QFileInfo( sNewFileName );
632  }
633 
634  QImage image = QImage(sPreviewFileName);
635 
636  if (image.isNull())
637  return {};
638 
639  // We can just re-scale the default (full-size version) to avoid
640  // a preview generator run
641  if ( nWidth <= 0 )
642  image = image.scaledToHeight(nHeight, Qt::SmoothTransformation);
643  else if ( nHeight <= 0 )
644  image = image.scaledToWidth(nWidth, Qt::SmoothTransformation);
645  else
646  image = image.scaled(nWidth, nHeight, Qt::IgnoreAspectRatio,
647  Qt::SmoothTransformation);
648 
649  image.save(sNewFileName, sImageFormat.toUpper().toLocal8Bit());
650 
651  // Let anybody update it
652  bool ret = makeFileAccessible(sNewFileName.toLocal8Bit().constData());
653  if (!ret)
654  {
655  LOG(VB_GENERAL, LOG_ERR, "Unable to change permissions on "
656  "preview image. Backends and frontends "
657  "running under different users will be "
658  "unable to access it");
659  }
660  }
661 
662  if (QFile::exists( sNewFileName ))
663  return QFileInfo( sNewFileName );
664 
665  auto *previewgen = new PreviewGenerator( &pginfo, QString(),
667  previewgen->SetPreviewTimeAsSeconds( nSecs );
668  previewgen->SetOutputFilename ( sNewFileName );
669  previewgen->SetOutputSize (QSize(nWidth,nHeight));
670 
671  bool ok = previewgen->Run();
672 
673  previewgen->deleteLater();
674 
675  if (!ok)
676  return {};
677 
678  return QFileInfo( sNewFileName );
679 }
680 
682 //
684 
685 QFileInfo V2Content::GetRecording( int nRecordedId,
686  int nChanId,
687  const QDateTime &StartTime )
688 {
689  if ((nRecordedId <= 0) &&
690  (nChanId <= 0 || !StartTime.isValid()))
691  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
692 
693  // ------------------------------------------------------------------
694  // Read Recording From Database
695  // ------------------------------------------------------------------
696 
697  // TODO Should use RecordingInfo
698  ProgramInfo pginfo;
699  if (nRecordedId > 0)
700  pginfo = ProgramInfo(nRecordedId);
701  else
702  pginfo = ProgramInfo(nChanId, StartTime.toUTC());
703 
704  if (!pginfo.GetChanID())
705  {
706  LOG(VB_UPNP, LOG_ERR, QString("GetRecording - for '%1' failed")
707  .arg(nRecordedId));
708 
709  return {};
710  }
711 
712  if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
713  && ! gCoreContext->GetBoolSetting("MasterBackendOverride", false))
714  {
715  // We only handle requests for local resources
716 
717  QString sMsg =
718  QString("GetRecording: Wrong Host '%1' request from '%2'.")
719  .arg( gCoreContext->GetHostName(),
720  pginfo.GetHostname() );
721 
722  LOG(VB_UPNP, LOG_ERR, sMsg);
723 
724  throw V2HttpRedirectException( pginfo.GetHostname() );
725  }
726 
727  QString sFileName( GetPlaybackURL(&pginfo) );
728 
729  // ----------------------------------------------------------------------
730  // check to see if the file exists
731  // ----------------------------------------------------------------------
732 
733  if (QFile::exists( sFileName ))
734  return QFileInfo( sFileName );
735 
736  return {};
737 }
738 
740 //
742 
743 QFileInfo V2Content::GetMusic( int nId )
744 {
745  QString sFileName;
746 
747  // ----------------------------------------------------------------------
748  // Load Track's FileName
749  // ----------------------------------------------------------------------
750 
751  MSqlQuery query(MSqlQuery::InitCon());
752 
753  if (query.isConnected())
754  {
755  query.prepare("SELECT CONCAT_WS('/', music_directories.path, "
756  "music_songs.filename) AS filename FROM music_songs "
757  "LEFT JOIN music_directories ON "
758  "music_songs.directory_id="
759  "music_directories.directory_id "
760  "WHERE music_songs.song_id = :KEY");
761 
762  query.bindValue(":KEY", nId );
763 
764  if (!query.exec())
765  {
766  MythDB::DBError("GetMusic()", query);
767  return {};
768  }
769 
770  if (query.next())
771  {
772  sFileName = query.value(0).toString();
773  }
774  }
775 
776  if (sFileName.isEmpty())
777  return {};
778 
779  return GetFile( "Music", sFileName );
780 }
781 
783 //
785 
786 QFileInfo V2Content::GetVideo( int nId )
787 {
788  QString sFileName;
789 
790  // ----------------------------------------------------------------------
791  // Load Track's FileName
792  // ----------------------------------------------------------------------
793 
794  MSqlQuery query(MSqlQuery::InitCon());
795 
796  if (query.isConnected())
797  {
798  query.prepare("SELECT filename FROM videometadata WHERE intid = :KEY" );
799  query.bindValue(":KEY", nId );
800 
801  if (!query.exec())
802  {
803  MythDB::DBError("GetVideo()", query);
804  return {};
805  }
806 
807  if (query.next())
808  sFileName = query.value(0).toString();
809  }
810 
811  if (sFileName.isEmpty())
812  return {};
813 
814  if (!QFile::exists( sFileName ))
815  return GetFile( "Videos", sFileName );
816 
817  return QFileInfo( sFileName );
818 }
819 
821 //
823 
824 QString V2Content::GetHash( const QString &sStorageGroup,
825  const QString &sFileName )
826 {
827  if ((sFileName.isEmpty()) ||
828  (sFileName.contains("/../")) ||
829  (sFileName.startsWith("../")))
830  {
831  LOG(VB_GENERAL, LOG_ERR,
832  QString("ERROR checking for file, filename '%1' "
833  "fails sanity checks").arg(sFileName));
834  return {};
835  }
836 
837  QString storageGroup = "Default";
838 
839  if (!sStorageGroup.isEmpty())
840  storageGroup = sStorageGroup;
841 
842  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
843 
844  QString fullname = sgroup.FindFile(sFileName);
845  QString hash = FileHash(fullname);
846 
847  if (hash == "NULL")
848  return {};
849 
850  return hash;
851 }
852 
854 //
856 
857 bool V2Content::DownloadFile( const QString &sURL, const QString &sStorageGroup )
858 {
859  QFileInfo finfo(sURL);
860  QString filename = finfo.fileName();
861  StorageGroup sgroup(sStorageGroup, gCoreContext->GetHostName(), false);
862  QString outDir = sgroup.FindNextDirMostFree();
863  QString outFile;
864 
865  if (outDir.isEmpty())
866  {
867  LOG(VB_GENERAL, LOG_ERR,
868  QString("Unable to determine directory "
869  "to write to in %1 write command").arg(sURL));
870  return false;
871  }
872 
873  if ((filename.contains("/../")) ||
874  (filename.startsWith("../")))
875  {
876  LOG(VB_GENERAL, LOG_ERR,
877  QString("ERROR: %1 write filename '%2' does not "
878  "pass sanity checks.").arg(sURL, filename));
879  return false;
880  }
881 
882  outFile = outDir + "/" + filename;
883 
884  return GetMythDownloadManager()->download(sURL, outFile);
885 }
886 
887 
888 // V2LiveStreamInfo *V2Content::AddLiveStream( const QString &sStorageGroup,
889 // const QString &sFileName,
890 // const QString &sHostName,
891 // int nMaxSegments,
892 // int nWidth,
893 // int nHeight,
894 // int nBitrate,
895 // int nAudioBitrate,
896 // int nSampleRate )
897 // {
898 // QString sGroup = sStorageGroup;
899 
900 // if (sGroup.isEmpty())
901 // {
902 // LOG(VB_UPNP, LOG_WARNING,
903 // "AddLiveStream - StorageGroup missing... using 'Default'");
904 // sGroup = "Default";
905 // }
906 
907 // if (sFileName.isEmpty())
908 // {
909 // QString sMsg ( "AddLiveStream - FileName missing." );
910 
911 // LOG(VB_UPNP, LOG_ERR, sMsg);
912 
913 // throw QString(sMsg);
914 // }
915 
916 // // ------------------------------------------------------------------
917 // // Search for the filename
918 // // ------------------------------------------------------------------
919 
920 // QString sFullFileName;
921 // if (sHostName.isEmpty() || sHostName == gCoreContext->GetHostName())
922 // {
923 // StorageGroup storage( sGroup );
924 // sFullFileName = storage.FindFile( sFileName );
925 
926 // if (sFullFileName.isEmpty())
927 // {
928 // LOG(VB_UPNP, LOG_ERR,
929 // QString("AddLiveStream - Unable to find %1.").arg(sFileName));
930 
931 // return nullptr;
932 // }
933 // }
934 // else
935 // {
936 // sFullFileName =
937 // MythCoreContext::GenMythURL(sHostName, 0, sFileName, sStorageGroup);
938 // }
939 
940 // auto *hls = new HTTPLiveStream(sFullFileName, nWidth, nHeight, nBitrate,
941 // nAudioBitrate, nMaxSegments, 0, 0, nSampleRate);
942 
943 // if (!hls)
944 // {
945 // LOG(VB_UPNP, LOG_ERR,
946 // "AddLiveStream - Unable to create HTTPLiveStream.");
947 // return nullptr;
948 // }
949 
950 // V2LiveStreamInfo *lsInfo = hls->StartStream();
951 
952 // delete hls;
953 
954 // return lsInfo;
955 // }
956 
957 // /////////////////////////////////////////////////////////////////////////////
958 // //
959 // /////////////////////////////////////////////////////////////////////////////
960 
961 // bool V2Content::RemoveLiveStream( int nId )
962 // {
963 // return HTTPLiveStream::RemoveStream(nId);
964 // }
965 
966 // /////////////////////////////////////////////////////////////////////////////
967 // //
968 // /////////////////////////////////////////////////////////////////////////////
969 
970 // V2LiveStreamInfo *V2Content::StopLiveStream( int nId )
971 // {
972 // return HTTPLiveStream::StopStream(nId);
973 // }
974 
975 // /////////////////////////////////////////////////////////////////////////////
976 // //
977 // /////////////////////////////////////////////////////////////////////////////
978 
979 // V2LiveStreamInfo *V2Content::GetLiveStream( int nId )
980 // {
981 // auto *hls = new HTTPLiveStream(nId);
982 
983 // if (!hls)
984 // {
985 // LOG( VB_UPNP, LOG_ERR,
986 // QString("GetLiveStream - for stream id %1 failed").arg( nId ));
987 // return nullptr;
988 // }
989 
990 // V2LiveStreamInfo *hlsInfo = hls->GetLiveStreamInfo();
991 // if (!hlsInfo)
992 // {
993 // LOG( VB_UPNP, LOG_ERR,
994 // QString("HLS::GetLiveStreamInfo - for stream id %1 failed")
995 // .arg( nId ));
996 // return nullptr;
997 // }
998 
999 // delete hls;
1000 // return hlsInfo;
1001 // }
1002 
1003 // /////////////////////////////////////////////////////////////////////////////
1004 // //
1005 // /////////////////////////////////////////////////////////////////////////////
1006 
1007 // V2LiveStreamInfoList *V2Content::GetLiveStreamList( const QString &FileName )
1008 // {
1009 // return HTTPLiveStream::GetLiveStreamInfoList(FileName);
1010 // }
1011 
1012 // /////////////////////////////////////////////////////////////////////////////
1013 // //
1014 // /////////////////////////////////////////////////////////////////////////////
1015 
1016 // V2LiveStreamInfo *V2Content::AddRecordingLiveStream(
1017 // int nRecordedId,
1018 // int nChanId,
1019 // const QDateTime &StartTime,
1020 // int nMaxSegments,
1021 // int nWidth,
1022 // int nHeight,
1023 // int nBitrate,
1024 // int nAudioBitrate,
1025 // int nSampleRate )
1026 // {
1027 // if ((nRecordedId <= 0) &&
1028 // (nChanId <= 0 || !StartTime.isValid()))
1029 // throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
1030 
1031 // // ------------------------------------------------------------------
1032 // // Read Recording From Database
1033 // // ------------------------------------------------------------------
1034 
1035 // // TODO Should use RecordingInfo
1036 // ProgramInfo pginfo;
1037 // if (nRecordedId > 0)
1038 // pginfo = ProgramInfo(nRecordedId);
1039 // else
1040 // pginfo = ProgramInfo(nChanId, StartTime.toUTC());
1041 
1042 // if (!pginfo.GetChanID())
1043 // {
1044 // LOG(VB_UPNP, LOG_ERR,
1045 // QString("AddRecordingLiveStream - for %1, %2 failed")
1046 // .arg(QString::number(nRecordedId)));
1047 // return nullptr;
1048 // }
1049 
1050 // bool masterBackendOverride = gCoreContext->GetBoolSetting("MasterBackendOverride", false);
1051 
1052 // if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
1053 // && ! masterBackendOverride)
1054 // {
1055 // // We only handle requests for local resources
1056 
1057 // QString sMsg =
1058 // QString("GetRecording: Wrong Host '%1' request from '%2'.")
1059 // .arg( gCoreContext->GetHostName(),
1060 // pginfo.GetHostname() );
1061 
1062 // LOG(VB_UPNP, LOG_ERR, sMsg);
1063 
1064 // throw V2HttpRedirectException( pginfo.GetHostname() );
1065 // }
1066 
1067 // QString sFileName( GetPlaybackURL(&pginfo) );
1068 
1069 // // ----------------------------------------------------------------------
1070 // // check to see if the file exists
1071 // // ----------------------------------------------------------------------
1072 
1073 // if (!QFile::exists( sFileName ))
1074 // {
1075 // LOG( VB_UPNP, LOG_ERR, QString("AddRecordingLiveStream - for %1, %2 failed")
1076 // .arg( nChanId )
1077 // .arg( StartTime.toUTC().toString() ));
1078 // return nullptr;
1079 // }
1080 
1081 // QFileInfo fInfo( sFileName );
1082 
1083 // QString hostName;
1084 // if (masterBackendOverride)
1085 // hostName = gCoreContext->GetHostName();
1086 // else
1087 // hostName = pginfo.GetHostname();
1088 
1089 // return AddLiveStream( pginfo.GetStorageGroup(), fInfo.fileName(),
1090 // hostName, nMaxSegments, nWidth,
1091 // nHeight, nBitrate, nAudioBitrate, nSampleRate );
1092 // }
1093 
1094 // /////////////////////////////////////////////////////////////////////////////
1095 // //
1096 // /////////////////////////////////////////////////////////////////////////////
1097 
1098 // V2LiveStreamInfo *V2Content::AddVideoLiveStream( int nId,
1099 // int nMaxSegments,
1100 // int nWidth,
1101 // int nHeight,
1102 // int nBitrate,
1103 // int nAudioBitrate,
1104 // int nSampleRate )
1105 // {
1106 // if (nId < 0)
1107 // throw QString( "Id is invalid" );
1108 
1109 // VideoMetadataListManager::VideoMetadataPtr metadata =
1110 // VideoMetadataListManager::loadOneFromDatabase(nId);
1111 
1112 // if (!metadata)
1113 // {
1114 // LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - no metadata for %1")
1115 // .arg( nId ));
1116 // return nullptr;
1117 // }
1118 
1119 // if ( metadata->GetHost().toLower() != gCoreContext->GetHostName().toLower())
1120 // {
1121 // // We only handle requests for local resources
1122 
1123 // QString sMsg =
1124 // QString("AddVideoLiveStream: Wrong Host '%1' request from '%2'.")
1125 // .arg( gCoreContext->GetHostName(),
1126 // metadata->GetHost() );
1127 
1128 // LOG(VB_UPNP, LOG_ERR, sMsg);
1129 
1130 // throw V2HttpRedirectException( metadata->GetHost() );
1131 // }
1132 
1133 // StorageGroup sg("Videos", metadata->GetHost());
1134 // QString sFileName = sg.FindFile(metadata->GetFilename());
1135 
1136 // // ----------------------------------------------------------------------
1137 // // check to see if the file exists
1138 // // ----------------------------------------------------------------------
1139 
1140 // if (!QFile::exists( sFileName ))
1141 // {
1142 // LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - file does not exist."));
1143 // return nullptr;
1144 // }
1145 
1146 // return AddLiveStream( "Videos", metadata->GetFilename(),
1147 // metadata->GetHost(), nMaxSegments, nWidth,
1148 // nHeight, nBitrate, nAudioBitrate, nSampleRate );
1149 // }
1150 
1151 // NOLINTEND(modernize-return-braced-init-list)
V2Content::GetHash
static QString GetHash(const QString &StorageGroup, const QString &FileName)
Definition: v2content.cpp:824
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
VideoArtworkType
VideoArtworkType
Definition: metadataimagehelper.h:10
RemoteFile::SaveAs
bool SaveAs(QByteArray &data)
Definition: remotefile.cpp:1173
ProgramInfo::GetHostname
QString GetHostname(void) const
Definition: programinfo.h:421
V2HttpRedirectException
Definition: mythhttpservice.h:40
CONTENT_HANDLE
#define CONTENT_HANDLE
Definition: v2content.h:35
metadataimagehelper.h
V2ArtworkInfoList
Definition: v2artworkInfoList.h:22
Q_GLOBAL_STATIC_WITH_ARGS
Q_GLOBAL_STATIC_WITH_ARGS(MythHTTPMetaService, s_service,(CONTENT_HANDLE, V2Content::staticMetaObject, &V2Content::RegisterCustomTypes)) void V2Content
Definition: v2content.cpp:63
MusicMetadata::createFromID
static MusicMetadata * createFromID(int trackid)
Definition: musicmetadata.cpp:255
V2Content::GetProgramArtworkList
static V2ArtworkInfoList * GetProgramArtworkList(const QString &Inetref, int Season)
Definition: v2content.cpp:341
RemoteFile::Exists
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:454
StorageGroup::FindFile
QString FindFile(const QString &filename)
Definition: storagegroup.cpp:597
PreviewGenerator
This class creates a preview image of a recording.
Definition: previewgenerator.h:27
v2content.h
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:204
GetArtwork
ArtworkMap GetArtwork(const QString &inetref, uint season, bool strict)
Definition: metadataimagehelper.cpp:23
V2Content::GetFile
static QFileInfo GetFile(const QString &StorageGroup, const QString &FileName)
Definition: v2content.cpp:80
mythhttpmetaservice.h
RemoteFile
Definition: remotefile.h:17
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MusicMetadata
Definition: musicmetadata.h:80
V2Content::GetAlbumArt
static QFileInfo GetAlbumArt(int Id, int Width, int Height)
Definition: v2content.cpp:414
V2Content::GetImageFile
static QFileInfo GetImageFile(const QString &StorageGroup, const QString &FileName, int Width, int Height)
Definition: v2content.cpp:135
ProgramInfo::GetInetRef
QString GetInetRef(void) const
Definition: programinfo.h:437
fileserverutil.h
mythdate.h
ProgramInfo::SetPathname
void SetPathname(const QString &pn)
Definition: programinfo.cpp:2438
V2Content::GetMusic
static QFileInfo GetMusic(int Id)
Definition: v2content.cpp:743
StorageGroup::GetFileList
QStringList GetFileList(const QString &Path, bool recursive=false)
Definition: storagegroup.cpp:271
programinfo.h
V2Content::V2Content
V2Content()
Definition: v2content.cpp:74
v2serviceUtil.h
remotefile.h
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
compat.h
V2Content::GetRecording
static QFileInfo GetRecording(int RecordedId, int ChanId, const QDateTime &StartTime)
Definition: v2content.cpp:685
MusicMetadata::getAlbumArtFile
QString getAlbumArtFile(void)
Definition: musicmetadata.cpp:1258
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
GetPlaybackURL
QString GetPlaybackURL(ProgramInfo *pginfo, bool storePath)
Definition: fileserverutil.cpp:46
kArtworkFanart
@ kArtworkFanart
Definition: metadataimagehelper.h:12
V2Content::GetRecordingArtworkList
static V2ArtworkInfoList * GetRecordingArtworkList(int RecordedId, int ChanId, const QDateTime &StartTime)
Definition: v2content.cpp:323
V2Content::GetVideoArtwork
static QFileInfo GetVideoArtwork(const QString &Type, int Id, int Width, int Height)
Definition: v2content.cpp:355
MythDownloadManager::download
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
Definition: mythdownloadmanager.cpp:430
storagegroup.h
StorageGroup::GetDirList
QStringList GetDirList(void) const
Definition: storagegroup.h:23
MSqlQuery::isConnected
bool isConnected(void) const
Only updated once during object creation.
Definition: mythdbcon.h:137
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
V2Content::GetDirList
static QStringList GetDirList(const QString &StorageGroup)
Definition: v2content.cpp:237
MythHTTPService
Definition: mythhttpservice.h:19
videometadatalistmanager.h
MythCoreContext::GetBoolSetting
bool GetBoolSetting(const QString &key, bool defaultval=false)
Definition: mythcorecontext.cpp:906
V2Content::DownloadFile
static bool DownloadFile(const QString &URL, const QString &StorageGroup)
Definition: v2content.cpp:857
ProgramInfo::GetChanID
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:372
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:67
mythmiscutil.h
makeFileAccessible
bool makeFileAccessible(const QString &filename)
Definition: mythmiscutil.cpp:395
ProgramInfo::GetSeason
uint GetSeason(void) const
Definition: programinfo.h:366
mythcorecontext.h
StorageGroup::FindNextDirMostFree
QString FindNextDirMostFree(void)
Definition: storagegroup.cpp:666
ProgramInfo::IsLocal
bool IsLocal(void) const
Definition: programinfo.h:351
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
V2Content::GetVideo
static QFileInfo GetVideo(int Id)
Definition: v2content.cpp:786
StorageGroup
Definition: storagegroup.h:11
kArtworkBanner
@ kArtworkBanner
Definition: metadataimagehelper.h:13
ArtworkMap
QMultiMap< VideoArtworkType, ArtworkInfo > ArtworkMap
Definition: metadataimagehelper.h:31
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:838
PreviewGenerator::kLocal
@ kLocal
Definition: previewgenerator.h:43
previewgenerator.h
mythdownloadmanager.h
build_compdb.filename
filename
Definition: build_compdb.py:21
FileHash
QString FileHash(const QString &filename)
Definition: mythmiscutil.cpp:548
V2Content::GetRecordingArtwork
static QFileInfo GetRecordingArtwork(const QString &Type, const QString &Inetref, int Season, int Width, int Height)
Definition: v2content.cpp:277
V2FillArtworkInfoList
void V2FillArtworkInfoList(V2ArtworkInfoList *pArtworkInfoList, const QString &sInetref, uint nSeason)
Definition: v2serviceUtil.cpp:318
kArtworkCoverart
@ kArtworkCoverart
Definition: metadataimagehelper.h:11
musicmetadata.h
MythHTTPMetaService
Definition: mythhttpmetaservice.h:10
V2Content::GetFileList
static QStringList GetFileList(const QString &StorageGroup)
Definition: v2content.cpp:257
GetMythDownloadManager
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
Definition: mythdownloadmanager.cpp:145
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
V2Content::GetPreviewImage
static QFileInfo GetPreviewImage(int RecordedId, int ChanId, const QDateTime &StartTime, int Width, int Height, int SecsIn, const QString &Format)
Definition: v2content.cpp:511
V2Content::RegisterCustomTypes
static void RegisterCustomTypes()