videodownloader/network/youtubedownload.cpp

225 lines
9.5 KiB
C++
Raw Normal View History

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
#include <qtutilities/resources/resources.h>
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;
using namespace QtUtilities;
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)
: HttpDownloadWithInfoRequst(QUrl(QStringLiteral("http://www.youtube.com/watch?v=%1").arg(id)), parent)
{
}
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;
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)
{
2017-05-01 03:22:50 +02:00
if (m_itagInfo.isEmpty()) {
// 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"));
2017-05-01 03:22:50 +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);
2017-05-01 03:22:50 +02:00
foreach (QString completeField, completeFields) {
2015-04-22 19:32:04 +02:00
QStringList fieldParts = completeField.split(QChar('='), QString::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");
2017-05-01 03:22:50 +02:00
foreach (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()) {
2015-04-22 19:32:04 +02:00
QStringList sections = fmtField.split(QChar(','), QString::SkipEmptyParts, Qt::CaseSensitive);
2017-05-01 03:22:50 +02:00
foreach (QString section, sections) {
2015-04-22 19:32:04 +02:00
QStringList fmtParts = section.split(QChar('&'), QString::SkipEmptyParts, Qt::CaseSensitive);
QString itag, urlPart1, urlPart2, name;
2015-04-22 19:32:04 +02:00
QJsonObject itagObj;
2017-05-01 03:22:50 +02:00
foreach (QString fmtPart, fmtParts) {
2015-04-22 19:32:04 +02:00
QStringList fmtSubParts = fmtPart.split(QChar('='), QString::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;
url.append(urlPart1);
2017-05-01 03:22:50 +02:00
if (!urlPart2.isEmpty()) {
2015-04-22 19:32:04 +02:00
url.append("&signature=");
url.append(urlPart2);
}
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
{
auto filename = Download::suitableFilename();
2015-04-22 19:32:04 +02:00
// get chosen option, the original option (not the redirection!) is required
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;
2017-05-01 03:22:50 +02:00
if (originalOption < static_cast<size_t>(m_itags.size())) {
const auto itag = m_itags.at(originalOption);
2017-05-01 03:22:50 +02:00
if (m_itagInfo.contains(itag)) {
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()) {
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