2015-09-08 17:05:59 +02:00
# include "./youtubedownload.h"
2015-04-22 19:32:04 +02:00
# include "../application/utils.h"
2015-12-08 08:41:06 +01:00
# include "resources/config.h"
2015-12-05 22:56:32 +01:00
2015-09-16 17:32:33 +02:00
# include <qtutilities/resources/resources.h>
2015-04-22 19:32:04 +02:00
# include <QUrlQuery>
# include <QJsonDocument>
using namespace ChronoUtilities ;
using namespace Application ;
namespace Network {
QJsonObject YoutubeDownload : : m_itagInfo = QJsonObject ( ) ;
/*!
* \ class YoutubeDownload
* \ brief Download implementation for YouTube videos .
*/
/*!
* \ brief Constructs a new YoutubeDownload for the specified \ a url .
*/
YoutubeDownload : : YoutubeDownload ( const QUrl & url , QObject * parent ) :
HttpDownloadWithInfoRequst ( url , parent )
{ }
/*!
* \ brief Constructs a new YoutubeDownload for the specified video \ a id .
*/
YoutubeDownload : : YoutubeDownload ( const QString & id , QObject * parent ) :
HttpDownloadWithInfoRequst ( QUrl ( QStringLiteral ( " http://www.youtube.com/watch?v=%1 " ) . arg ( id ) ) , parent )
{ }
2015-10-14 23:08:57 +02:00
Download * YoutubeDownload : : infoRequestDownload ( bool & success , QString & reasonForFail )
2015-04-22 19:32:04 +02:00
{
const QUrl & url = initialUrl ( ) ;
QString videoId ;
if ( url . hasQuery ( ) ) {
videoId = QUrlQuery ( url . query ( QUrl : : FullyDecoded ) ) . queryItemValue ( " v " , QUrl : : FullyDecoded ) ;
} else if ( url . host ( QUrl : : FullyDecoded ) . contains ( QLatin1String ( " youtu.be " ) , Qt : : CaseInsensitive ) ) {
videoId = url . path ( QUrl : : FullyDecoded ) ;
videoId . remove ( 0 , 1 ) ;
}
if ( videoId . isEmpty ( ) ) {
2015-10-14 23:08:57 +02:00
success = false ;
2015-04-22 19:32:04 +02:00
reasonForFail = tr ( " The video ID couldn't be identified. " ) ;
return nullptr ;
} else {
setId ( videoId ) ;
2015-10-14 23:08:57 +02:00
success = true ;
2015-04-22 19:32:04 +02:00
return new HttpDownload ( QUrl ( QStringLiteral ( " http://www.youtube.com/get_video_info?video_id=%1&asv=3&el=detailpage&hl=en_US " ) . arg ( videoId ) ) ) ;
}
}
void YoutubeDownload : : evalVideoInformation ( Download * , QBuffer * videoInfoBuffer )
{
if ( m_itagInfo . isEmpty ( ) ) {
2015-09-16 17:32:33 +02:00
// allow an external config file to be used instead of built-in values
2015-12-05 22:56:32 +01:00
QString path = ConfigFile : : locateConfigFile ( QStringLiteral ( PROJECT_NAME ) , QStringLiteral ( " json/itaginfo.json " ) ) ;
2015-09-16 17:32:33 +02:00
if ( path . isEmpty ( ) ) {
path = QStringLiteral ( " :/jsonobjects/itaginfo " ) ;
}
m_itagInfo = loadJsonObjectFromResource ( path ) ;
2015-04-22 19:32:04 +02:00
}
QString videoInfo ( videoInfoBuffer - > readAll ( ) ) ;
QStringList completeFields = videoInfo . split ( QChar ( ' & ' ) , QString : : SkipEmptyParts , Qt : : CaseSensitive ) ;
foreach ( QString completeField , completeFields ) {
QStringList fieldParts = completeField . split ( QChar ( ' = ' ) , QString : : SkipEmptyParts , Qt : : CaseSensitive ) ;
if ( fieldParts . count ( ) < 2 ) {
continue ;
}
m_fields . insert ( QUrl : : fromPercentEncoding ( fieldParts . at ( 0 ) . toUtf8 ( ) ) , QUrl : : fromPercentEncoding ( fieldParts . at ( 1 ) . toUtf8 ( ) ) ) ;
}
QString status = m_fields . value ( QStringLiteral ( " status " ) ) ;
if ( status = = QLatin1String ( " ok " ) ) {
QString title = m_fields . value ( QStringLiteral ( " title " ) ) ;
if ( ! title . isEmpty ( ) ) {
setTitle ( title . replace ( QChar ( ' + ' ) , QChar ( ' ' ) ) ) ;
}
QString uploader = m_fields . value ( QStringLiteral ( " author " ) ) ;
if ( ! uploader . isEmpty ( ) ) {
setUploader ( uploader . replace ( QChar ( ' + ' ) , QChar ( ' ' ) ) ) ;
}
bool ok ;
double duration = m_fields . value ( QStringLiteral ( " length_seconds " ) ) . toDouble ( & ok ) ;
if ( ok ) {
setDuration ( TimeSpan : : fromSeconds ( duration ) ) ;
}
QString rating = m_fields . value ( QStringLiteral ( " avg_rating " ) ) ;
if ( ! rating . isEmpty ( ) ) {
setRating ( rating ) ;
}
QStringList fmtFieldIds = QStringList ( ) < < QStringLiteral ( " url_encoded_fmt_stream_map " ) < < QStringLiteral ( " adaptive_fmts " ) ;
foreach ( const QString & fmtFieldId , fmtFieldIds ) {
QString fmtField = m_fields . value ( fmtFieldId , QString ( ) ) ;
if ( ! fmtField . isEmpty ( ) ) {
QStringList sections = fmtField . split ( QChar ( ' , ' ) , QString : : SkipEmptyParts , Qt : : CaseSensitive ) ;
foreach ( QString section , sections ) {
QStringList fmtParts = section . split ( QChar ( ' & ' ) , QString : : SkipEmptyParts , Qt : : CaseSensitive ) ;
2015-10-14 23:18:41 +02:00
QString itag , urlPart1 , urlPart2 , name ;
2015-04-22 19:32:04 +02:00
QJsonObject itagObj ;
foreach ( QString fmtPart , fmtParts ) {
QStringList fmtSubParts = fmtPart . split ( QChar ( ' = ' ) , QString : : SkipEmptyParts , Qt : : CaseSensitive ) ;
if ( fmtSubParts . count ( ) > = 2 ) {
QString fieldIdentifier = fmtSubParts . at ( 0 ) . toLower ( ) ;
if ( fieldIdentifier = = QLatin1String ( " url " ) ) {
urlPart1 = QUrl : : fromPercentEncoding ( fmtSubParts . at ( 1 ) . toUtf8 ( ) ) ;
} else if ( fieldIdentifier = = QLatin1String ( " sig " ) ) {
urlPart2 = QUrl : : fromPercentEncoding ( fmtSubParts . at ( 1 ) . toUtf8 ( ) ) ;
} else if ( fieldIdentifier = = QLatin1String ( " itag " ) ) {
itag = fmtSubParts . at ( 1 ) ;
}
}
}
if ( ! itag . isEmpty ( ) & & ! urlPart1 . isEmpty ( ) ) {
if ( m_itagInfo . contains ( itag ) ) {
itagObj = m_itagInfo . value ( itag ) . toObject ( ) ;
name . append ( itagObj . value ( QStringLiteral ( " container " ) ) . toString ( ) ) ;
2015-12-18 00:19:27 +01:00
const QString videoCodec = itagObj . value ( QStringLiteral ( " videoCodec " ) ) . toString ( ) ;
const QString audioCodec = itagObj . value ( QStringLiteral ( " audioCodec " ) ) . toString ( ) ;
if ( ! videoCodec . isEmpty ( ) ) {
name . append ( QChar ( ' / ' ) ) ;
name . append ( videoCodec ) ;
}
if ( ! audioCodec . isEmpty ( ) ) {
name . append ( QChar ( ' / ' ) ) ;
name . append ( audioCodec ) ;
}
if ( ! videoCodec . isEmpty ( ) ) {
name . append ( QStringLiteral ( " , " ) ) ;
2015-04-22 19:32:04 +02:00
name . append ( itagObj . value ( QStringLiteral ( " videoResolution " ) ) . toString ( ) ) ;
}
2015-12-18 00:19:27 +01:00
if ( videoCodec . isEmpty ( ) ) {
2015-04-22 19:32:04 +02:00
name . append ( tr ( " , no video " ) ) ;
2015-12-18 00:19:27 +01:00
const QString audioBitrate = itagObj . value ( QStringLiteral ( " audioBitrate " ) ) . toString ( ) ;
if ( ! audioBitrate . isEmpty ( ) ) {
name . append ( tr ( " , %1 kbit/s " ) . arg ( audioBitrate ) ) ;
}
2015-04-22 19:32:04 +02:00
}
2015-12-18 00:19:27 +01:00
if ( audioCodec . isEmpty ( ) ) {
2015-04-22 19:32:04 +02:00
name . append ( tr ( " , no audio " ) ) ;
}
name . append ( QStringLiteral ( " ( " ) ) ;
name . append ( itag ) ;
name . append ( QStringLiteral ( " ) " ) ) ;
} else {
name = itag ;
}
QByteArray url ;
url . append ( urlPart1 ) ;
if ( ! urlPart2 . isEmpty ( ) ) {
url . append ( " &signature= " ) ;
url . append ( urlPart2 ) ;
}
addDownloadUrl ( name , QUrl : : fromPercentEncoding ( url ) ) ;
m_itags . append ( itag ) ;
}
}
}
}
2015-10-14 23:18:41 +02:00
if ( availableOptionCount ( ) ) {
2015-04-22 19:32:04 +02:00
reportInitiated ( true ) ;
} else {
reportInitiated ( false , tr ( " Couldn't pharse the video info. The status of the video info is ok, but it seems like YouTube changed something in their API. " ) ) ;
}
} else {
QString reason = m_fields . value ( " reason " ) ;
if ( reason . isEmpty ( ) ) {
reportInitiated ( false , tr ( " Failed to retieve the video info. The reason couldn't be identified. It seems like YouTube changed something in their API. " ) ) ;
} else {
reportInitiated ( false , tr ( " Failed to retieve the video info. The reason returned by Youtube is: \" %1 \" . " ) . arg ( reason . replace ( QChar ( ' + ' ) , QChar ( ' ' ) ) ) ) ;
}
}
}
QString YoutubeDownload : : videoInfo ( QString field , const QString & defaultValue )
{
return m_fields . value ( field , defaultValue ) ;
}
QString YoutubeDownload : : suitableFilename ( ) const
{
2015-09-16 17:32:33 +02:00
auto filename = Download : : suitableFilename ( ) ;
2015-04-22 19:32:04 +02:00
// get chosen option, the original option (not the redirection!) is required
2015-09-16 17:32:33 +02:00
auto originalOption = chosenOption ( ) ;
2015-04-22 19:32:04 +02:00
while ( originalOption ! = options ( ) . at ( originalOption ) . redirectionOf ( ) ) {
originalOption = options ( ) . at ( originalOption ) . redirectionOf ( ) ;
}
QString extension ;
if ( originalOption < static_cast < size_t > ( m_itags . size ( ) ) ) {
2015-09-16 17:32:33 +02:00
const auto itag = m_itags . at ( originalOption ) ;
2015-04-22 19:32:04 +02:00
if ( m_itagInfo . contains ( itag ) ) {
2015-09-16 17:32:33 +02:00
const auto itagObj = m_itagInfo . value ( itag ) . toObject ( ) ;
extension = itagObj . value ( QStringLiteral ( " ext " ) ) . toString ( ) ;
if ( extension . isEmpty ( ) ) {
extension = itagObj . value ( QStringLiteral ( " container " ) ) . toString ( ) . toLower ( ) ;
}
2015-04-22 19:32:04 +02:00
}
}
if ( extension . isEmpty ( ) ) {
extension = QStringLiteral ( " flv " ) ; // assume flv
}
extension . insert ( 0 , QStringLiteral ( " . " ) ) ;
if ( ! filename . endsWith ( extension ) ) {
filename . append ( extension ) ;
}
return filename ;
}
QString YoutubeDownload : : typeName ( ) const
{
return tr ( " YouTube " ) ;
}
}