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