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