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