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