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 
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, 0);
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  .arg( 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  if (nSecsIn <= 0)
549  {
550  nSecsIn = -1;
551  sPreviewFileName = QString("%1.png").arg(sFileName);
552  }
553  else
554  {
555  sPreviewFileName = QString("%1.%2.png").arg(sFileName).arg(nSecsIn);
556  }
557 
558  if (!QFile::exists( sPreviewFileName ))
559  {
560  // ------------------------------------------------------------------
561  // Must generate Preview Image, Generate Image and save.
562  // ------------------------------------------------------------------
563  if (!pginfo.IsLocal() && sFileName.startsWith("/"))
564  pginfo.SetPathname(sFileName);
565 
566  if (!pginfo.IsLocal())
567  return QFileInfo();
568 
569  auto *previewgen = new PreviewGenerator( &pginfo, QString(),
571  previewgen->SetPreviewTimeAsSeconds( nSecsIn );
572  previewgen->SetOutputFilename ( sPreviewFileName );
573 
574  bool ok = previewgen->Run();
575 
576  previewgen->deleteLater();
577 
578  if (!ok)
579  return QFileInfo();
580  }
581 
582  bool bDefaultPixmap = (nWidth == 0) && (nHeight == 0);
583 
584  QString sNewFileName;
585 
586  if (bDefaultPixmap)
587  sNewFileName = sPreviewFileName;
588  else
589  {
590  sNewFileName = QString( "%1.%2.%3x%4.%5" )
591  .arg( sFileName )
592  .arg( nSecsIn )
593  .arg( nWidth == 0 ? -1 : nWidth )
594  .arg( nHeight == 0 ? -1 : nHeight )
595  .arg( sImageFormat.toLower() );
596 
597  // ----------------------------------------------------------------------
598  // check to see if scaled preview image is already created and isn't
599  // out of date
600  // ----------------------------------------------------------------------
601  if (QFile::exists( sNewFileName ))
602  {
603  if (QFileInfo(sPreviewFileName).lastModified() <=
604  QFileInfo(sNewFileName).lastModified())
605  return QFileInfo( sNewFileName );
606  }
607 
608  QImage image = QImage(sPreviewFileName);
609 
610  if (image.isNull())
611  return QFileInfo();
612 
613  // We can just re-scale the default (full-size version) to avoid
614  // a preview generator run
615  if ( nWidth <= 0 )
616  image = image.scaledToHeight(nHeight, Qt::SmoothTransformation);
617  else if ( nHeight <= 0 )
618  image = image.scaledToWidth(nWidth, Qt::SmoothTransformation);
619  else
620  image = image.scaled(nWidth, nHeight, Qt::IgnoreAspectRatio,
621  Qt::SmoothTransformation);
622 
623  image.save(sNewFileName, sImageFormat.toUpper().toLocal8Bit());
624 
625  // Let anybody update it
626  bool ret = makeFileAccessible(sNewFileName.toLocal8Bit().constData());
627  if (!ret)
628  {
629  LOG(VB_GENERAL, LOG_ERR, "Unable to change permissions on "
630  "preview image. Backends and frontends "
631  "running under different users will be "
632  "unable to access it");
633  }
634  }
635 
636  if (QFile::exists( sNewFileName ))
637  return QFileInfo( sNewFileName );
638 
639  auto *previewgen = new PreviewGenerator( &pginfo, QString(),
641  previewgen->SetPreviewTimeAsSeconds( nSecsIn );
642  previewgen->SetOutputFilename ( sNewFileName );
643  previewgen->SetOutputSize (QSize(nWidth,nHeight));
644 
645  bool ok = previewgen->Run();
646 
647  previewgen->deleteLater();
648 
649  if (!ok)
650  return QFileInfo();
651 
652  return QFileInfo( sNewFileName );
653 }
654 
656 //
658 
659 QFileInfo Content::GetRecording( int nRecordedId,
660  int nChanId,
661  const QDateTime &recstarttsRaw )
662 {
663  if ((nRecordedId <= 0) &&
664  (nChanId <= 0 || !recstarttsRaw.isValid()))
665  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
666 
667  // ------------------------------------------------------------------
668  // Read Recording From Database
669  // ------------------------------------------------------------------
670 
671  // TODO Should use RecordingInfo
672  ProgramInfo pginfo;
673  if (nRecordedId > 0)
674  pginfo = ProgramInfo(nRecordedId);
675  else
676  pginfo = ProgramInfo(nChanId, recstarttsRaw.toUTC());
677 
678  if (!pginfo.GetChanID())
679  {
680  LOG(VB_UPNP, LOG_ERR, QString("GetRecording - for '%1' failed")
681  .arg(nRecordedId));
682 
683  return QFileInfo();
684  }
685 
686  if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
687  && ! gCoreContext->GetBoolSetting("MasterBackendOverride", false))
688  {
689  // We only handle requests for local resources
690 
691  QString sMsg =
692  QString("GetRecording: Wrong Host '%1' request from '%2'.")
693  .arg( gCoreContext->GetHostName())
694  .arg( pginfo.GetHostname() );
695 
696  LOG(VB_UPNP, LOG_ERR, sMsg);
697 
698  throw HttpRedirectException( pginfo.GetHostname() );
699  }
700 
701  QString sFileName( GetPlaybackURL(&pginfo) );
702 
703  // ----------------------------------------------------------------------
704  // check to see if the file exists
705  // ----------------------------------------------------------------------
706 
707  if (QFile::exists( sFileName ))
708  return QFileInfo( sFileName );
709 
710  return QFileInfo();
711 }
712 
714 //
716 
717 QFileInfo Content::GetMusic( int nId )
718 {
719  QString sFileName;
720 
721  // ----------------------------------------------------------------------
722  // Load Track's FileName
723  // ----------------------------------------------------------------------
724 
726 
727  if (query.isConnected())
728  {
729  query.prepare("SELECT CONCAT_WS('/', music_directories.path, "
730  "music_songs.filename) AS filename FROM music_songs "
731  "LEFT JOIN music_directories ON "
732  "music_songs.directory_id="
733  "music_directories.directory_id "
734  "WHERE music_songs.song_id = :KEY");
735 
736  query.bindValue(":KEY", nId );
737 
738  if (!query.exec())
739  {
740  MythDB::DBError("GetMusic()", query);
741  return QFileInfo();
742  }
743 
744  if (query.next())
745  {
746  sFileName = query.value(0).toString();
747  }
748  }
749 
750  if (sFileName.isEmpty())
751  return QFileInfo();
752 
753  return GetFile( "Music", sFileName );
754 }
755 
757 //
759 
760 QFileInfo Content::GetVideo( int nId )
761 {
762  QString sFileName;
763 
764  // ----------------------------------------------------------------------
765  // Load Track's FileName
766  // ----------------------------------------------------------------------
767 
769 
770  if (query.isConnected())
771  {
772  query.prepare("SELECT filename FROM videometadata WHERE intid = :KEY" );
773  query.bindValue(":KEY", nId );
774 
775  if (!query.exec())
776  {
777  MythDB::DBError("GetVideo()", query);
778  return QFileInfo();
779  }
780 
781  if (query.next())
782  sFileName = query.value(0).toString();
783  }
784 
785  if (sFileName.isEmpty())
786  return QFileInfo();
787 
788  if (!QFile::exists( sFileName ))
789  return GetFile( "Videos", sFileName );
790 
791  return QFileInfo( sFileName );
792 }
793 
795 //
797 
798 QString Content::GetHash( const QString &sStorageGroup,
799  const QString &sFileName )
800 {
801  if ((sFileName.isEmpty()) ||
802  (sFileName.contains("/../")) ||
803  (sFileName.startsWith("../")))
804  {
805  LOG(VB_GENERAL, LOG_ERR,
806  QString("ERROR checking for file, filename '%1' "
807  "fails sanity checks").arg(sFileName));
808  return QString();
809  }
810 
811  QString storageGroup = "Default";
812 
813  if (!sStorageGroup.isEmpty())
814  storageGroup = sStorageGroup;
815 
816  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
817 
818  QString fullname = sgroup.FindFile(sFileName);
819  QString hash = FileHash(fullname);
820 
821  if (hash == "NULL")
822  return QString();
823 
824  return hash;
825 }
826 
828 //
830 
831 bool Content::DownloadFile( const QString &sURL, const QString &sStorageGroup )
832 {
833  QFileInfo finfo(sURL);
834  QString filename = finfo.fileName();
835  StorageGroup sgroup(sStorageGroup, gCoreContext->GetHostName(), false);
836  QString outDir = sgroup.FindNextDirMostFree();
837  QString outFile;
838 
839  if (outDir.isEmpty())
840  {
841  LOG(VB_GENERAL, LOG_ERR,
842  QString("Unable to determine directory "
843  "to write to in %1 write command").arg(sURL));
844  return false;
845  }
846 
847  if ((filename.contains("/../")) ||
848  (filename.startsWith("../")))
849  {
850  LOG(VB_GENERAL, LOG_ERR,
851  QString("ERROR: %1 write filename '%2' does not "
852  "pass sanity checks.") .arg(sURL).arg(filename));
853  return false;
854  }
855 
856  outFile = outDir + "/" + filename;
857 
858  return GetMythDownloadManager()->download(sURL, outFile);
859 }
860 
862 //
864 
865 DTC::LiveStreamInfo *Content::AddLiveStream( const QString &sStorageGroup,
866  const QString &sFileName,
867  const QString &sHostName,
868  int nMaxSegments,
869  int nWidth,
870  int nHeight,
871  int nBitrate,
872  int nAudioBitrate,
873  int nSampleRate )
874 {
875  QString sGroup = sStorageGroup;
876 
877  if (sGroup.isEmpty())
878  {
879  LOG(VB_UPNP, LOG_WARNING,
880  "AddLiveStream - StorageGroup missing... using 'Default'");
881  sGroup = "Default";
882  }
883 
884  if (sFileName.isEmpty())
885  {
886  QString sMsg ( "AddLiveStream - FileName missing." );
887 
888  LOG(VB_UPNP, LOG_ERR, sMsg);
889 
890  throw QString(sMsg);
891  }
892 
893  // ------------------------------------------------------------------
894  // Search for the filename
895  // ------------------------------------------------------------------
896 
897  QString sFullFileName;
898  if (sHostName.isEmpty() || sHostName == gCoreContext->GetHostName())
899  {
900  StorageGroup storage( sGroup );
901  sFullFileName = storage.FindFile( sFileName );
902 
903  if (sFullFileName.isEmpty())
904  {
905  LOG(VB_UPNP, LOG_ERR,
906  QString("AddLiveStream - Unable to find %1.").arg(sFileName));
907 
908  return nullptr;
909  }
910  }
911  else
912  {
913  sFullFileName =
914  MythCoreContext::GenMythURL(sHostName, 0, sFileName, sStorageGroup);
915  }
916 
917  auto *hls = new HTTPLiveStream(sFullFileName, nWidth, nHeight, nBitrate,
918  nAudioBitrate, nMaxSegments, 0, 0, nSampleRate);
919 
920  if (!hls)
921  {
922  LOG(VB_UPNP, LOG_ERR,
923  "AddLiveStream - Unable to create HTTPLiveStream.");
924  return nullptr;
925  }
926 
927  DTC::LiveStreamInfo *lsInfo = hls->StartStream();
928 
929  delete hls;
930 
931  return lsInfo;
932 }
933 
935 //
937 
939 {
940  return HTTPLiveStream::RemoveStream(nId);
941 }
942 
944 //
946 
948 {
949  return HTTPLiveStream::StopStream(nId);
950 }
951 
953 //
955 
957 {
958  auto *hls = new HTTPLiveStream(nId);
959 
960  if (!hls)
961  {
962  LOG( VB_UPNP, LOG_ERR,
963  QString("GetLiveStream - for stream id %1 failed").arg( nId ));
964  return nullptr;
965  }
966 
967  DTC::LiveStreamInfo *hlsInfo = hls->GetLiveStreamInfo();
968  if (!hlsInfo)
969  {
970  LOG( VB_UPNP, LOG_ERR,
971  QString("HLS::GetLiveStreamInfo - for stream id %1 failed")
972  .arg( nId ));
973  return nullptr;
974  }
975 
976  delete hls;
977  return hlsInfo;
978 }
979 
981 //
983 
985 {
986  return HTTPLiveStream::GetLiveStreamInfoList(FileName);
987 }
988 
990 //
992 
994  int nRecordedId,
995  int nChanId,
996  const QDateTime &recstarttsRaw,
997  int nMaxSegments,
998  int nWidth,
999  int nHeight,
1000  int nBitrate,
1001  int nAudioBitrate,
1002  int nSampleRate )
1003 {
1004  if ((nRecordedId <= 0) &&
1005  (nChanId <= 0 || !recstarttsRaw.isValid()))
1006  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
1007 
1008  // ------------------------------------------------------------------
1009  // Read Recording From Database
1010  // ------------------------------------------------------------------
1011 
1012  // TODO Should use RecordingInfo
1013  ProgramInfo pginfo;
1014  if (nRecordedId > 0)
1015  pginfo = ProgramInfo(nRecordedId);
1016  else
1017  pginfo = ProgramInfo(nChanId, recstarttsRaw.toUTC());
1018 
1019  if (!pginfo.GetChanID())
1020  {
1021  LOG(VB_UPNP, LOG_ERR,
1022  QString("AddRecordingLiveStream - for %1, %2 failed")
1023  .arg(QString::number(nRecordedId)));
1024  return nullptr;
1025  }
1026 
1027  bool masterBackendOverride = gCoreContext->GetBoolSetting("MasterBackendOverride", false);
1028 
1029  if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower()
1030  && ! masterBackendOverride)
1031  {
1032  // We only handle requests for local resources
1033 
1034  QString sMsg =
1035  QString("GetRecording: Wrong Host '%1' request from '%2'.")
1036  .arg( gCoreContext->GetHostName())
1037  .arg( pginfo.GetHostname() );
1038 
1039  LOG(VB_UPNP, LOG_ERR, sMsg);
1040 
1041  throw HttpRedirectException( pginfo.GetHostname() );
1042  }
1043 
1044  QString sFileName( GetPlaybackURL(&pginfo) );
1045 
1046  // ----------------------------------------------------------------------
1047  // check to see if the file exists
1048  // ----------------------------------------------------------------------
1049 
1050  if (!QFile::exists( sFileName ))
1051  {
1052  LOG( VB_UPNP, LOG_ERR, QString("AddRecordingLiveStream - for %1, %2 failed")
1053  .arg( nChanId )
1054  .arg( recstarttsRaw.toUTC().toString() ));
1055  return nullptr;
1056  }
1057 
1058  QFileInfo fInfo( sFileName );
1059 
1060  QString hostName;
1061  if (masterBackendOverride)
1062  hostName = gCoreContext->GetHostName();
1063  else
1064  hostName = pginfo.GetHostname();
1065 
1066  return AddLiveStream( pginfo.GetStorageGroup(), fInfo.fileName(),
1067  hostName, nMaxSegments, nWidth,
1068  nHeight, nBitrate, nAudioBitrate, nSampleRate );
1069 }
1070 
1072 //
1074 
1076  int nMaxSegments,
1077  int nWidth,
1078  int nHeight,
1079  int nBitrate,
1080  int nAudioBitrate,
1081  int nSampleRate )
1082 {
1083  if (nId < 0)
1084  throw QString( "Id is invalid" );
1085 
1088 
1089  if (!metadata)
1090  {
1091  LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - no metadata for %1")
1092  .arg( nId ));
1093  return nullptr;
1094  }
1095 
1096  if ( metadata->GetHost().toLower() != gCoreContext->GetHostName().toLower())
1097  {
1098  // We only handle requests for local resources
1099 
1100  QString sMsg =
1101  QString("AddVideoLiveStream: Wrong Host '%1' request from '%2'.")
1102  .arg( gCoreContext->GetHostName())
1103  .arg( metadata->GetHost() );
1104 
1105  LOG(VB_UPNP, LOG_ERR, sMsg);
1106 
1107  throw HttpRedirectException( metadata->GetHost() );
1108  }
1109 
1110  StorageGroup sg("Videos", metadata->GetHost());
1111  QString sFileName = sg.FindFile(metadata->GetFilename());
1112 
1113  // ----------------------------------------------------------------------
1114  // check to see if the file exists
1115  // ----------------------------------------------------------------------
1116 
1117  if (!QFile::exists( sFileName ))
1118  {
1119  LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - file does not exist."));
1120  return nullptr;
1121  }
1122 
1123  return AddLiveStream( "Videos", metadata->GetFilename(),
1124  metadata->GetHost(), nMaxSegments, nWidth,
1125  nHeight, nBitrate, nAudioBitrate, nSampleRate );
1126 }
serviceUtil.h
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:126
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:419
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:865
Content::GetFileList
QStringList GetFileList(const QString &StorageGroup) override
Definition: content.cpp:232
metadataimagehelper.h
DTC::LiveStreamInfo
Definition: liveStreamInfo.h:16
MusicMetadata::createFromID
static MusicMetadata * createFromID(int trackid)
Definition: musicmetadata.cpp:259
RemoteFile::Exists
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:461
simple_ref_ptr
Definition: quicksp.h:25
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:27
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:198
arg
arg(title).arg(filename).arg(doDelete))
GetArtwork
ArtworkMap GetArtwork(const QString &inetref, uint season, bool strict)
Definition: metadataimagehelper.cpp:23
Content::RemoveLiveStream
bool RemoveLiveStream(int Id) override
Definition: content.cpp:938
DTC::LiveStreamInfoList
Definition: liveStreamInfoList.h:15
RemoteFile
Definition: remotefile.h:18
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:81
FillArtworkInfoList
void FillArtworkInfoList(DTC::ArtworkInfoList *pArtworkInfoList, const QString &sInetref, uint nSeason)
Definition: serviceUtil.cpp:329
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:1075
ProgramInfo::GetInetRef
QString GetInetRef(void) const
Definition: programinfo.h:435
Content::GetLiveStream
DTC::LiveStreamInfo * GetLiveStream(int Id) override
Definition: content.cpp:956
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:2328
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:777
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:1262
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:178
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
filename
QString filename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:637
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:135
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:56
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:993
Content::GetHash
QString GetHash(const QString &StorageGroup, const QString &FileName) override
Definition: content.cpp:798
ProgramInfo::GetStorageGroup
QString GetStorageGroup(void) const
Definition: programinfo.h:420
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:923
ProgramInfo::GetChanID
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:370
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:68
mythmiscutil.h
makeFileAccessible
bool makeFileAccessible(const QString &filename)
Definition: mythmiscutil.cpp:437
HTTPLiveStream
Definition: httplivestream.h:23
ProgramInfo::GetSeason
uint GetSeason(void) const
Definition: programinfo.h:364
mythcorecontext.h
StorageGroup::FindNextDirMostFree
QString FindNextDirMostFree(void)
Definition: storagegroup.cpp:671
ProgramInfo::IsLocal
bool IsLocal(void) const
Definition: programinfo.h:349
Content::GetMusic
QFileInfo GetMusic(int Id) override
Definition: content.cpp:717
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:864
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:947
StorageGroup
Definition: storagegroup.h:12
Content::GetVideo
QFileInfo GetVideo(int Id) override
Definition: content.cpp:760
Content::GetRecording
QFileInfo GetRecording(int RecordedId, int ChanId, const QDateTime &recstarttsRaw) override
Definition: content.cpp:659
kArtworkBanner
@ kArtworkBanner
Definition: metadataimagehelper.h:13
ArtworkMap
QMultiMap< VideoArtworkType, ArtworkInfo > ArtworkMap
Definition: metadataimagehelper.h:31
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:855
PreviewGenerator::kLocal
@ kLocal
Definition: previewgenerator.h:42
previewgenerator.h
mythdownloadmanager.h
FileHash
QString FileHash(const QString &filename)
Definition: mythmiscutil.cpp:590
httprequest.h
query
MSqlQuery query(MSqlQuery::InitCon())
kArtworkCoverart
@ kArtworkCoverart
Definition: metadataimagehelper.h:11
DTC::ArtworkInfoList
Definition: artworkInfoList.h:26
Content::DownloadFile
bool DownloadFile(const QString &URL, const QString &StorageGroup) override
Definition: content.cpp:831
musicmetadata.h
Content::GetLiveStreamList
DTC::LiveStreamInfoList * GetLiveStreamList(const QString &FileName) override
Definition: content.cpp:984
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:808