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  if (GetMythDownloadManager()->download(sURL, outFile))
859  return true;
860 
861  return false;
862 }
863 
865 //
867 
868 DTC::LiveStreamInfo *Content::AddLiveStream( const QString &sStorageGroup,
869  const QString &sFileName,
870  const QString &sHostName,
871  int nMaxSegments,
872  int nWidth,
873  int nHeight,
874  int nBitrate,
875  int nAudioBitrate,
876  int nSampleRate )
877 {
878  QString sGroup = sStorageGroup;
879 
880  if (sGroup.isEmpty())
881  {
882  LOG(VB_UPNP, LOG_WARNING,
883  "AddLiveStream - StorageGroup missing... using 'Default'");
884  sGroup = "Default";
885  }
886 
887  if (sFileName.isEmpty())
888  {
889  QString sMsg ( "AddLiveStream - FileName missing." );
890 
891  LOG(VB_UPNP, LOG_ERR, sMsg);
892 
893  throw sMsg;
894  }
895 
896  // ------------------------------------------------------------------
897  // Search for the filename
898  // ------------------------------------------------------------------
899 
900  QString sFullFileName;
901  if (sHostName.isEmpty() || sHostName == gCoreContext->GetHostName())
902  {
903  StorageGroup storage( sGroup );
904  sFullFileName = storage.FindFile( sFileName );
905 
906  if (sFullFileName.isEmpty())
907  {
908  LOG(VB_UPNP, LOG_ERR,
909  QString("AddLiveStream - Unable to find %1.").arg(sFileName));
910 
911  return nullptr;
912  }
913  }
914  else
915  {
916  sFullFileName =
917  gCoreContext->GenMythURL(sHostName, 0, sFileName, sStorageGroup);
918  }
919 
920  HTTPLiveStream *hls = new
921  HTTPLiveStream(sFullFileName, nWidth, nHeight, nBitrate, nAudioBitrate,
922  nMaxSegments, 0, 0, nSampleRate);
923 
924  if (!hls)
925  {
926  LOG(VB_UPNP, LOG_ERR,
927  "AddLiveStream - Unable to create HTTPLiveStream.");
928  return nullptr;
929  }
930 
931  DTC::LiveStreamInfo *lsInfo = hls->StartStream();
932 
933  delete hls;
934 
935  return lsInfo;
936 }
937 
939 //
941 
943 {
944  return HTTPLiveStream::RemoveStream(nId);
945 }
946 
948 //
950 
952 {
953  return HTTPLiveStream::StopStream(nId);
954 }
955 
957 //
959 
961 {
962  HTTPLiveStream *hls = new HTTPLiveStream(nId);
963 
964  if (!hls)
965  {
966  LOG( VB_UPNP, LOG_ERR,
967  QString("GetLiveStream - for stream id %1 failed").arg( nId ));
968  return nullptr;
969  }
970 
971  DTC::LiveStreamInfo *hlsInfo = hls->GetLiveStreamInfo();
972  if (!hlsInfo)
973  {
974  LOG( VB_UPNP, LOG_ERR,
975  QString("HLS::GetLiveStreamInfo - for stream id %1 failed")
976  .arg( nId ));
977  return nullptr;
978  }
979 
980  delete hls;
981  return hlsInfo;
982 }
983 
985 //
987 
989 {
990  return HTTPLiveStream::GetLiveStreamInfoList(FileName);
991 }
992 
994 //
996 
998  int nRecordedId,
999  int nChanId,
1000  const QDateTime &recstarttsRaw,
1001  int nMaxSegments,
1002  int nWidth,
1003  int nHeight,
1004  int nBitrate,
1005  int nAudioBitrate,
1006  int nSampleRate )
1007 {
1008  if ((nRecordedId <= 0) &&
1009  (nChanId <= 0 || !recstarttsRaw.isValid()))
1010  throw QString("Recorded ID or Channel ID and StartTime appears invalid.");
1011 
1012  // ------------------------------------------------------------------
1013  // Read Recording From Database
1014  // ------------------------------------------------------------------
1015 
1016  // TODO Should use RecordingInfo
1017  ProgramInfo pginfo;
1018  if (nRecordedId > 0)
1019  pginfo = ProgramInfo(nRecordedId);
1020  else
1021  pginfo = ProgramInfo(nChanId, recstarttsRaw.toUTC());
1022 
1023  if (!pginfo.GetChanID())
1024  {
1025  LOG(VB_UPNP, LOG_ERR,
1026  QString("AddRecordingLiveStream - for %1, %2 failed")
1027  .arg(QString::number(nRecordedId)));
1028  return nullptr;
1029  }
1030 
1031  if (pginfo.GetHostname().toLower() != gCoreContext->GetHostName().toLower())
1032  {
1033  // We only handle requests for local resources
1034 
1035  QString sMsg =
1036  QString("GetRecording: Wrong Host '%1' request from '%2'.")
1037  .arg( gCoreContext->GetHostName())
1038  .arg( pginfo.GetHostname() );
1039 
1040  LOG(VB_UPNP, LOG_ERR, sMsg);
1041 
1042  throw HttpRedirectException( pginfo.GetHostname() );
1043  }
1044 
1045  QString sFileName( GetPlaybackURL(&pginfo) );
1046 
1047  // ----------------------------------------------------------------------
1048  // check to see if the file exists
1049  // ----------------------------------------------------------------------
1050 
1051  if (!QFile::exists( sFileName ))
1052  {
1053  LOG( VB_UPNP, LOG_ERR, QString("AddRecordingLiveStream - for %1, %2 failed")
1054  .arg( nChanId )
1055  .arg( recstarttsRaw.toUTC().toString() ));
1056  return nullptr;
1057  }
1058 
1059  QFileInfo fInfo( sFileName );
1060 
1061  return AddLiveStream( pginfo.GetStorageGroup(), fInfo.fileName(),
1062  pginfo.GetHostname(), nMaxSegments, nWidth,
1063  nHeight, nBitrate, nAudioBitrate, nSampleRate );
1064 }
1065 
1067 //
1069 
1071  int nMaxSegments,
1072  int nWidth,
1073  int nHeight,
1074  int nBitrate,
1075  int nAudioBitrate,
1076  int nSampleRate )
1077 {
1078  if (nId < 0)
1079  throw QString( "Id is invalid" );
1080 
1083 
1084  if (!metadata)
1085  {
1086  LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - no metadata for %1")
1087  .arg( nId ));
1088  return nullptr;
1089  }
1090 
1091  if ( metadata->GetHost().toLower() != gCoreContext->GetHostName().toLower())
1092  {
1093  // We only handle requests for local resources
1094 
1095  QString sMsg =
1096  QString("AddVideoLiveStream: Wrong Host '%1' request from '%2'.")
1097  .arg( gCoreContext->GetHostName())
1098  .arg( metadata->GetHost() );
1099 
1100  LOG(VB_UPNP, LOG_ERR, sMsg);
1101 
1102  throw HttpRedirectException( metadata->GetHost() );
1103  }
1104 
1105  StorageGroup sg("Videos", metadata->GetHost());
1106  QString sFileName = sg.FindFile(metadata->GetFilename());
1107 
1108  // ----------------------------------------------------------------------
1109  // check to see if the file exists
1110  // ----------------------------------------------------------------------
1111 
1112  if (!QFile::exists( sFileName ))
1113  {
1114  LOG( VB_UPNP, LOG_ERR, QString("AddVideoLiveStream - file does not exist."));
1115  return nullptr;
1116  }
1117 
1118  return AddLiveStream( "Videos", metadata->GetFilename(),
1119  metadata->GetHost(), nMaxSegments, nWidth,
1120  nHeight, nBitrate, nAudioBitrate, nSampleRate );
1121 }
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:794
QStringList GetDirList(const QString &StorageGroup) override
Definition: content.cpp:212
void bindValue(const QString &placeholder, const QVariant &val)
Definition: mythdbcon.cpp:875
QString FileHash(QString filename)
QStringList GetDirList(void) const
Definition: storagegroup.h:23
static MusicMetadata * createFromID(int trackid)
DTC::LiveStreamInfo * GetLiveStream(int Id) override
Definition: content.cpp:960
QString GetHash(const QString &StorageGroup, const QString &FileName) override
Definition: content.cpp:798
QString GenMythURL(QString host=QString(), QString port=QString(), QString path=QString(), QString storageGroup=QString())
bool RemoveLiveStream(int Id) override
Definition: content.cpp:942
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:988
QFileInfo GetPreviewImage(int RecordedId, int ChanId, const QDateTime &StartTime, int Width, int Height, int SecsIn, const QString &Format) override
Definition: content.cpp:486
QFileInfo GetMusic(int Id) override
Definition: content.cpp:717
This class creates a preview image of a recording.
QString GetInetRef(void) const
Definition: programinfo.h:431
QString GetStorageGroup(void) const
Definition: programinfo.h:416
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:868
QVariant value(int i) const
Definition: mythdbcon.h:182
DTC::ArtworkInfoList * GetProgramArtworkList(const QString &Inetref, int Season) override
Definition: content.cpp:316
Holds information on recordings and videos.
Definition: programinfo.h:66
QFileInfo GetFile(const QString &StorageGroup, const QString &FileName) override
Definition: content.cpp:55
bool makeFileAccessible(QString filename)
Makes a file accessible to all frontends/backends.
DTC::LiveStreamInfo * StopLiveStream(int Id) override
Definition: content.cpp:951
QString FindNextDirMostFree(void)
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
bool IsLocal(void) const
Definition: programinfo.h:345
QString getAlbumArtFile(void)
static bool RemoveStream(int id)
uint GetSeason(void) const
Definition: programinfo.h:360
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:547
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:1070
QFileInfo GetRecording(int RecordedId, int ChanId, const QDateTime &StartTime) override
Definition: content.cpp:660
void SetOutputSize(const QSize &size)
DTC::LiveStreamInfo * StartStream(void)
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:468
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:819
#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:366
QString GetPlaybackURL(ProgramInfo *pginfo, bool storePath)
QStringList GetFileList(const QString &Path, bool recursive=false)
QString GetHostname(void) const
Definition: programinfo.h:415
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:615
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
QFileInfo GetVideo(int Id) override
Definition: content.cpp:760
ArtworkMap GetArtwork(QString inetref, uint season, bool strict)
QMultiMap< VideoArtworkType, ArtworkInfo > ArtworkMap
QString GetHostName(void)
static VideoMetadataPtr loadOneFromDatabase(uint id)
DTC::ArtworkInfoList * GetRecordingArtworkList(int RecordedId, int ChanId, const QDateTime &StartTime) override
Definition: content.cpp:298
DTC::LiveStreamInfo * AddRecordingLiveStream(int RecordedId, int ChanId, const QDateTime &StartTime, int MaxSegments, int Width, int Height, int Bitrate, int AudioBitrate, int SampleRate) override
Definition: content.cpp:997