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-04-22 19:32:04 +02:00
|
|
|
#include <QJsonDocument>
|
2017-05-01 03:22:50 +02:00
|
|
|
#include <QUrlQuery>
|
2015-04-22 19:32:04 +02:00
|
|
|
|
2019-06-10 22:50:15 +02:00
|
|
|
using namespace CppUtilities;
|
2015-04-22 19:32:04 +02:00
|
|
|
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.
|
|
|
|
*/
|
2017-05-01 03:22:50 +02:00
|
|
|
YoutubeDownload::YoutubeDownload(const QUrl &url, QObject *parent)
|
|
|
|
: HttpDownloadWithInfoRequst(url, parent)
|
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:32:04 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new YoutubeDownload for the specified video \a id.
|
|
|
|
*/
|
2017-05-01 03:22:50 +02:00
|
|
|
YoutubeDownload::YoutubeDownload(const QString &id, QObject *parent)
|
2019-08-20 22:28:07 +02:00
|
|
|
: HttpDownloadWithInfoRequst(QUrl(QStringLiteral("https://www.youtube.com/watch?v=%1").arg(id)), parent)
|
2017-05-01 03:22:50 +02:00
|
|
|
{
|
|
|
|
}
|
2015-04-22 19:32:04 +02:00
|
|
|
|
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;
|
2017-05-01 03:22:50 +02:00
|
|
|
if (url.hasQuery()) {
|
2015-04-22 19:32:04 +02:00
|
|
|
videoId = QUrlQuery(url.query(QUrl::FullyDecoded)).queryItemValue("v", QUrl::FullyDecoded);
|
2017-05-01 03:22:50 +02:00
|
|
|
} else if (url.host(QUrl::FullyDecoded).contains(QLatin1String("youtu.be"), Qt::CaseInsensitive)) {
|
2015-04-22 19:32:04 +02:00
|
|
|
videoId = url.path(QUrl::FullyDecoded);
|
|
|
|
videoId.remove(0, 1);
|
|
|
|
}
|
2017-05-01 03:22:50 +02:00
|
|
|
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;
|
2019-08-20 22:28:07 +02:00
|
|
|
return new HttpDownload(QUrl(QStringLiteral("https://www.youtube.com/get_video_info?video_id=%1&asv=3&el=detailpage&hl=en_US").arg(videoId)));
|
2015-04-22 19:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void YoutubeDownload::evalVideoInformation(Download *, QBuffer *videoInfoBuffer)
|
|
|
|
{
|
2017-05-01 03:22:50 +02:00
|
|
|
if (m_itagInfo.isEmpty()) {
|
2019-08-20 22:27:15 +02:00
|
|
|
m_itagInfo = loadJsonObjectFromResource(QStringLiteral(":/jsonobjects/itaginfo"));
|
2015-04-22 19:32:04 +02:00
|
|
|
}
|
|
|
|
QString videoInfo(videoInfoBuffer->readAll());
|
2020-09-04 00:57:42 +02:00
|
|
|
QStringList completeFields = videoInfo.split(QChar('&'), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
2019-12-30 22:27:20 +01:00
|
|
|
for (const QString &completeField : completeFields) {
|
2020-09-04 00:57:42 +02:00
|
|
|
QStringList fieldParts = completeField.split(QChar('='), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
2017-05-01 03:22:50 +02:00
|
|
|
if (fieldParts.count() < 2) {
|
2015-04-22 19:32:04 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
m_fields.insert(QUrl::fromPercentEncoding(fieldParts.at(0).toUtf8()), QUrl::fromPercentEncoding(fieldParts.at(1).toUtf8()));
|
|
|
|
}
|
|
|
|
QString status = m_fields.value(QStringLiteral("status"));
|
2017-05-01 03:22:50 +02:00
|
|
|
if (status == QLatin1String("ok")) {
|
2015-04-22 19:32:04 +02:00
|
|
|
QString title = m_fields.value(QStringLiteral("title"));
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!title.isEmpty()) {
|
2015-04-22 19:32:04 +02:00
|
|
|
setTitle(title.replace(QChar('+'), QChar(' ')));
|
|
|
|
}
|
|
|
|
QString uploader = m_fields.value(QStringLiteral("author"));
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!uploader.isEmpty()) {
|
2015-04-22 19:32:04 +02:00
|
|
|
setUploader(uploader.replace(QChar('+'), QChar(' ')));
|
|
|
|
}
|
|
|
|
bool ok;
|
|
|
|
double duration = m_fields.value(QStringLiteral("length_seconds")).toDouble(&ok);
|
2017-05-01 03:22:50 +02:00
|
|
|
if (ok) {
|
2015-04-22 19:32:04 +02:00
|
|
|
setDuration(TimeSpan::fromSeconds(duration));
|
|
|
|
}
|
|
|
|
QString rating = m_fields.value(QStringLiteral("avg_rating"));
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!rating.isEmpty()) {
|
2015-04-22 19:32:04 +02:00
|
|
|
setRating(rating);
|
|
|
|
}
|
|
|
|
QStringList fmtFieldIds = QStringList() << QStringLiteral("url_encoded_fmt_stream_map") << QStringLiteral("adaptive_fmts");
|
2019-12-30 22:27:20 +01:00
|
|
|
for (const QString &fmtFieldId : fmtFieldIds) {
|
2015-04-22 19:32:04 +02:00
|
|
|
QString fmtField = m_fields.value(fmtFieldId, QString());
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!fmtField.isEmpty()) {
|
2020-09-04 00:57:42 +02:00
|
|
|
QStringList sections = fmtField.split(QChar(','), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
2019-12-30 22:27:20 +01:00
|
|
|
for (const QString §ion : sections) {
|
2020-09-04 00:57:42 +02:00
|
|
|
QStringList fmtParts = section.split(QChar('&'), Qt::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;
|
2019-12-30 22:27:20 +01:00
|
|
|
for (const QString fmtPart : fmtParts) {
|
2020-09-04 00:57:42 +02:00
|
|
|
QStringList fmtSubParts = fmtPart.split(QChar('='), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
2017-05-01 03:22:50 +02:00
|
|
|
if (fmtSubParts.count() >= 2) {
|
2015-04-22 19:32:04 +02:00
|
|
|
QString fieldIdentifier = fmtSubParts.at(0).toLower();
|
2017-05-01 03:22:50 +02:00
|
|
|
if (fieldIdentifier == QLatin1String("url")) {
|
2015-04-22 19:32:04 +02:00
|
|
|
urlPart1 = QUrl::fromPercentEncoding(fmtSubParts.at(1).toUtf8());
|
2017-05-01 03:22:50 +02:00
|
|
|
} else if (fieldIdentifier == QLatin1String("sig")) {
|
2015-04-22 19:32:04 +02:00
|
|
|
urlPart2 = QUrl::fromPercentEncoding(fmtSubParts.at(1).toUtf8());
|
2017-05-01 03:22:50 +02:00
|
|
|
} else if (fieldIdentifier == QLatin1String("itag")) {
|
2015-04-22 19:32:04 +02:00
|
|
|
itag = fmtSubParts.at(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!itag.isEmpty() && !urlPart1.isEmpty()) {
|
|
|
|
if (m_itagInfo.contains(itag)) {
|
2015-04-22 19:32:04 +02:00
|
|
|
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();
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!videoCodec.isEmpty()) {
|
2015-12-18 00:19:27 +01:00
|
|
|
name.append(QChar('/'));
|
|
|
|
name.append(videoCodec);
|
|
|
|
}
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!audioCodec.isEmpty()) {
|
2015-12-18 00:19:27 +01:00
|
|
|
name.append(QChar('/'));
|
|
|
|
name.append(audioCodec);
|
|
|
|
}
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!videoCodec.isEmpty()) {
|
2015-12-18 00:19:27 +01:00
|
|
|
name.append(QStringLiteral(", "));
|
2015-04-22 19:32:04 +02:00
|
|
|
name.append(itagObj.value(QStringLiteral("videoResolution")).toString());
|
|
|
|
}
|
2017-05-01 03:22:50 +02: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();
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!audioBitrate.isEmpty()) {
|
2015-12-18 00:19:27 +01:00
|
|
|
name.append(tr(", %1 kbit/s").arg(audioBitrate));
|
|
|
|
}
|
2015-04-22 19:32:04 +02:00
|
|
|
}
|
2017-05-01 03:22:50 +02: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;
|
2020-09-04 00:57:42 +02:00
|
|
|
url.append(urlPart1.toUtf8());
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!urlPart2.isEmpty()) {
|
2015-04-22 19:32:04 +02:00
|
|
|
url.append("&signature=");
|
2020-09-04 00:57:42 +02:00
|
|
|
url.append(urlPart2.toUtf8());
|
2015-04-22 19:32:04 +02:00
|
|
|
}
|
|
|
|
addDownloadUrl(name, QUrl::fromPercentEncoding(url));
|
|
|
|
m_itags.append(itag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-05-01 03:22:50 +02:00
|
|
|
if (availableOptionCount()) {
|
2015-04-22 19:32:04 +02:00
|
|
|
reportInitiated(true);
|
|
|
|
} else {
|
2017-05-01 03:22:50 +02:00
|
|
|
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."));
|
2015-04-22 19:32:04 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
QString reason = m_fields.value("reason");
|
2017-05-01 03:22:50 +02:00
|
|
|
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."));
|
2015-04-22 19:32:04 +02:00
|
|
|
} else {
|
2017-05-01 03:22:50 +02:00
|
|
|
reportInitiated(false,
|
|
|
|
tr("Failed to retieve the video info. The reason returned by Youtube is: \"%1\".").arg(reason.replace(QChar('+'), QChar(' '))));
|
2015-04-22 19:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2017-05-01 03:22:50 +02:00
|
|
|
while (originalOption != options().at(originalOption).redirectionOf()) {
|
2015-04-22 19:32:04 +02:00
|
|
|
originalOption = options().at(originalOption).redirectionOf();
|
|
|
|
}
|
|
|
|
QString extension;
|
2019-12-30 22:27:20 +01:00
|
|
|
if (originalOption < static_cast<std::size_t>(m_itags.size())) {
|
2015-09-16 17:32:33 +02:00
|
|
|
const auto itag = m_itags.at(originalOption);
|
2017-05-01 03:22:50 +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();
|
2017-05-01 03:22:50 +02:00
|
|
|
if (extension.isEmpty()) {
|
2015-09-16 17:32:33 +02:00
|
|
|
extension = itagObj.value(QStringLiteral("container")).toString().toLower();
|
|
|
|
}
|
2015-04-22 19:32:04 +02:00
|
|
|
}
|
|
|
|
}
|
2017-05-01 03:22:50 +02:00
|
|
|
if (extension.isEmpty()) {
|
2015-04-22 19:32:04 +02:00
|
|
|
extension = QStringLiteral("flv"); // assume flv
|
|
|
|
}
|
|
|
|
extension.insert(0, QStringLiteral("."));
|
2017-05-01 03:22:50 +02:00
|
|
|
if (!filename.endsWith(extension)) {
|
2015-04-22 19:32:04 +02:00
|
|
|
filename.append(extension);
|
|
|
|
}
|
|
|
|
return filename;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString YoutubeDownload::typeName() const
|
|
|
|
{
|
|
|
|
return tr("YouTube");
|
|
|
|
}
|
2019-07-20 20:20:58 +02:00
|
|
|
} // namespace Network
|