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 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 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  QImage *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 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 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  DTC::ArtworkInfoList *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, 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  PreviewGenerator *previewgen = new PreviewGenerator( &pginfo,
569  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  PreviewGenerator *previewgen = new PreviewGenerator( &pginfo,
640  QString(),
642  previewgen->SetPreviewTimeAsSeconds( nSecsIn );
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  {
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 
725  MSqlQuery query(MSqlQuery::InitCon());
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 
768  MSqlQuery query(MSqlQuery::InitCon());
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 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  gCoreContext->GenMythURL(sHostName, 0, sFileName, sStorageGroup);
915  }
916 
917  HTTPLiveStream *hls = new
918  HTTPLiveStream(sFullFileName, nWidth, nHeight, nBitrate, nAudioBitrate,
919  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  HTTPLiveStream *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  if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower())
1029  {
1030  // We only handle requests for local resources
1031 
1032  QString sMsg =
1033  QString("GetRecording: Wrong Host '%1' request from '%2'.")
1034  .arg( gCoreContext->GetHostName())
1035  .arg( pginfo.GetHostname() );
1036 
1037  LOG(VB_UPNP, LOG_ERR, sMsg);
1038 
1039  throw HttpRedirectException( pginfo.GetHostname() );
1040  }
1041 
1042  QString sFileName( GetPlaybackURL(&pginfo) );
1043 
1044  // ----------------------------------------------------------------------
1045  // check to see if the file exists
1046  // ----------------------------------------------------------------------
1047 
1048  if (!QFile::exists( sFileName ))
1049  {
1050  LOG( VB_UPNP, LOG_ERR, QString("AddRecordingLiveStream - for %1, %2 failed")
1051  .arg( nChanId )
1052  .arg( recstarttsRaw.toUTC().toString() ));
1053  return nullptr;
1054  }
1055 
1056  QFileInfo fInfo( sFileName );
1057 
1058  return AddLiveStream( pginfo.GetStorageGroup(), fInfo.fileName(),
1059  pginfo.GetHostname(), nMaxSegments, nWidth,
1060  nHeight, nBitrate, nAudioBitrate, nSampleRate );
1061 }
1062 
1064 //
1066 
1068  int nMaxSegments,
1069  int nWidth,
1070  int nHeight,
1071  int nBitrate,
1072  int nAudioBitrate,
1073  int nSampleRate )
1074 {
1075  if (nId < 0)
1076  throw QString( "Id is invalid" );
1077 
1080 
1081  if (!metadata)
1082  {
1083  LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - no metadata for %1")
1084  .arg( nId ));
1085  return nullptr;
1086  }
1087 
1088  if ( metadata->GetHost().toLower() != gCoreContext->GetHostName().toLower())
1089  {
1090  // We only handle requests for local resources
1091 
1092  QString sMsg =
1093  QString("AddVideoLiveStream: Wrong Host '%1' request from '%2'.")
1094  .arg( gCoreContext->GetHostName())
1095  .arg( metadata->GetHost() );
1096 
1097  LOG(VB_UPNP, LOG_ERR, sMsg);
1098 
1099  throw HttpRedirectException( metadata->GetHost() );
1100  }
1101 
1102  StorageGroup sg("Videos", metadata->GetHost());
1103  QString sFileName = sg.FindFile(metadata->GetFilename());
1104 
1105  // ----------------------------------------------------------------------
1106  // check to see if the file exists
1107  // ----------------------------------------------------------------------
1108 
1109  if (!QFile::exists( sFileName ))
1110  {
1111  LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - file does not exist."));
1112  return nullptr;
1113  }
1114 
1115  return AddLiveStream( "Videos", metadata->GetFilename(),
1116  metadata->GetHost(), nMaxSegments, nWidth,
1117  nHeight, nBitrate, nAudioBitrate, nSampleRate );
1118 }
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
QStringList GetDirList(const QString &StorageGroup) override
Definition: content.cpp:212
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
QStringList GetDirList(void) const
Definition: storagegroup.h:23
static MusicMetadata * createFromID(int trackid)
DTC::LiveStreamInfo * GetLiveStream(int Id) override
Definition: content.cpp:957
QString GetHash(const QString &StorageGroup, const QString &FileName) override
Definition: content.cpp:798
bool RemoveLiveStream(int Id) override
Definition: content.cpp:939
void FillArtworkInfoList(DTC::ArtworkInfoList *pArtworkInfoList, const QString &sInetref, uint nSeason)
QFileInfo GetRecordingArtwork(const QString &Type, const QString &Inetref, int Season, int Width, int Height) override
Definition: content.cpp:252
void SetPathname(const QString &) const
static DTC::LiveStreamInfoList * GetLiveStreamInfoList(const QString &FileName="")
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
void SetOutputFilename(const QString &)
QStringList GetFileList(const QString &StorageGroup) override
Definition: content.cpp:232
VideoArtworkType
bool isConnected(void)
Only updated once during object creation.
Definition: mythdbcon.h:135
QFileInfo GetAlbumArt(int Id, int Width, int Height) override
Definition: content.cpp:389
bool DownloadFile(const QString &URL, const QString &StorageGroup) override
Definition: content.cpp:831
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
DTC::LiveStreamInfoList * GetLiveStreamList(const QString &FileName) override
Definition: content.cpp:985
QFileInfo GetMusic(int Id) override
Definition: content.cpp:717
This class creates a preview image of a recording.
QFileInfo GetPreviewImage(int RecordedId, int ChanId, const QDateTime &recstarttsRaw, int Width, int Height, int SecsIn, const QString &Format) override
Definition: content.cpp:486
QString GetInetRef(void) const
Definition: programinfo.h:429
QString GetStorageGroup(void) const
Definition: programinfo.h:414
DTC::LiveStreamInfo * GetLiveStreamInfo(DTC::LiveStreamInfo *info=nullptr)
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
QVariant value(int i) const
Definition: mythdbcon.h:198
DTC::ArtworkInfoList * GetProgramArtworkList(const QString &Inetref, int Season) override
Definition: content.cpp:316
Holds information on recordings and videos.
Definition: programinfo.h:66
DTC::ArtworkInfoList * GetRecordingArtworkList(int RecordedId, int ChanId, const QDateTime &recstarttsRaw) override
Definition: content.cpp:298
QFileInfo GetFile(const QString &StorageGroup, const QString &FileName) override
Definition: content.cpp:55
bool makeFileAccessible(const QString &filename)
QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
DTC::LiveStreamInfo * StopLiveStream(int Id) override
Definition: content.cpp:948
QString FindNextDirMostFree(void)
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
bool IsLocal(void) const
Definition: programinfo.h:343
QString getAlbumArtFile(void)
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
bool download(const QString &url, const QString &dest, const bool reload=false)
Downloads a URL to a file in blocking mode.
static bool RemoveStream(int id)
QFileInfo GetRecording(int RecordedId, int ChanId, const QDateTime &recstarttsRaw) override
Definition: content.cpp:660
uint GetSeason(void) const
Definition: programinfo.h:358
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
static DTC::LiveStreamInfo * StopStream(int id)
DTC::LiveStreamInfo * AddVideoLiveStream(int Id, int MaxSegments, int Width, int Height, int Bitrate, int AudioBitrate, int SampleRate) override
Definition: content.cpp:1067
void SetOutputSize(const QSize &size)
DTC::LiveStreamInfo * StartStream(void)
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:461
void SetPreviewTimeAsSeconds(long long seconds_in)
bool SaveAs(QByteArray &data)
QFileInfo GetImageFile(const QString &StorageGroup, const QString &FileName, int Width, int Height) override
Definition: content.cpp:110
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QFileInfo GetVideoArtwork(const QString &Type, int Id, int Width, int Height) override
Definition: content.cpp:330
QString FindFile(const QString &filename)
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:364
QString GetPlaybackURL(ProgramInfo *pginfo, bool storePath)
QStringList GetFileList(const QString &Path, bool recursive=false)
QString GetHostname(void) const
Definition: programinfo.h:413
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
QFileInfo GetVideo(int Id) override
Definition: content.cpp:760
QString FileHash(const QString &filename)
QMultiMap< VideoArtworkType, ArtworkInfo > ArtworkMap
QString GetHostName(void)
static VideoMetadataPtr loadOneFromDatabase(uint id)
ArtworkMap GetArtwork(const QString &inetref, uint season, bool strict)