Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
Martchus | 3e243750a0 | |
Martchus | ec0dda2677 | |
Martchus | 55deda90ff | |
Martchus | 7db4e0bc56 | |
Martchus | e4c5b23546 | |
Martchus | 5e42ba07ca | |
Martchus | 884bed480d | |
Martchus | 8d09b4e308 |
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# meta data
|
||||
project(videodownloader)
|
||||
|
@ -11,7 +11,7 @@ set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
|
|||
set(META_APP_DESCRIPTION "Simple video downloader with Qt GUI and backends for multiple platforms, e.g. YouTube and Vimeo")
|
||||
set(META_VERSION_MAJOR 1)
|
||||
set(META_VERSION_MINOR 3)
|
||||
set(META_VERSION_PATCH 6)
|
||||
set(META_VERSION_PATCH 7)
|
||||
|
||||
# add project files
|
||||
set(HEADER_FILES
|
||||
|
@ -146,7 +146,7 @@ find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
|
|||
use_cpp_utilities()
|
||||
|
||||
# find qtutilities
|
||||
find_package(qtutilities${CONFIGURATION_PACKAGE_SUFFIX} 6.0.0 REQUIRED)
|
||||
find_package(qtutilities${CONFIGURATION_PACKAGE_SUFFIX} 6.3.0 REQUIRED)
|
||||
use_qt_utilities()
|
||||
|
||||
# add Qt modules which can currently not be detected automatically
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
Files: resources/icons/*
|
||||
resources/icons/*
|
||||
|
||||
This subset of The Breeze Icon Theme is
|
||||
|
||||
Copyright (C) 2014 Uri Herrera <uri_herrera@nitrux.in> and others
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Clarification:
|
||||
|
||||
The GNU Lesser General Public License or LGPL is written for
|
||||
software libraries in the first place. We expressly want the LGPL to
|
||||
be valid for this artwork library too.
|
||||
|
||||
KDE Breeze theme icons is a special kind of software library, it is an
|
||||
artwork library, it's elements can be used in a Graphical User Interface, or
|
||||
GUI.
|
||||
|
||||
Source code, for this library means:
|
||||
- where they exist, SVG;
|
||||
- otherwise, if applicable, the multi-layered formats xcf or psd, or
|
||||
otherwise png.
|
||||
|
||||
The LGPL in some sections obliges you to make the files carry
|
||||
notices. With images this is in some cases impossible or hardly useful.
|
||||
|
||||
With this library a notice is placed at a prominent place in the directory
|
||||
containing the elements. You may follow this practice.
|
||||
|
||||
The exception in section 5 of the GNU Lesser General Public License covers
|
||||
the use of elements of this art library in a GUI.
|
||||
|
||||
https://vdesign.kde.org/
|
||||
|
||||
-----
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
16
README.md
|
@ -1,12 +1,12 @@
|
|||
# Video Downloader
|
||||
A video downloader with Qt 5 GUI and backends for multiple platforms, e.g. YouTube and
|
||||
A video downloader with Qt GUI and backends for multiple platforms, e.g. YouTube and
|
||||
Vimeo.
|
||||
|
||||
---
|
||||
|
||||
**Note**: This project is not maintained anymore so any provider-specific code might be
|
||||
outdated and not work anymore. I keep the project around as an example for doing HTTP
|
||||
downloads with Qt 5 showing the progress and speed in a list view.
|
||||
downloads with Qt showing the progress and speed in a list view.
|
||||
|
||||
---
|
||||
|
||||
|
@ -23,6 +23,14 @@ by YouTube which currently need to be downloaded separately and then muxed toget
|
|||
|
||||
## Build instructions
|
||||
The video downloader depends on c++utilities and qtutilities and is built in the same
|
||||
way as these libaries.
|
||||
way as these libraries.
|
||||
|
||||
The following Qt 5 modules are requried: core gui widgets network
|
||||
The following Qt modules are required: core gui widgets network
|
||||
|
||||
## Copyright notice and license
|
||||
Copyright © 2015-2022 Marius Kittler
|
||||
|
||||
All code is licensed under [GPL-2-or-later](LICENSE).
|
||||
|
||||
## Attribution for 3rd party content
|
||||
All icons found in this repository are taken from the [KDE/Breeze](https://invent.kde.org/frameworks/breeze-icons) project.
|
||||
|
|
|
@ -99,7 +99,7 @@ void download(int argc, char *argv[], const ArgumentOccurrence &, const Argument
|
|||
}
|
||||
++specifiedDownloads;
|
||||
}
|
||||
// check whether downloads could be instantiated, print appropiate error messages if not
|
||||
// check whether downloads could be instantiated, print appropriate error messages if not
|
||||
if (!specifiedDownloads) {
|
||||
cout << "No downloads have been specified." << endl;
|
||||
} else if (downloads.isEmpty()) {
|
||||
|
|
|
@ -59,7 +59,7 @@ void DownloadInteraction::downloadRequiresOutputDevice(Download *download, size_
|
|||
&& (TargetPage::targetDirectory().isEmpty() || QDir(TargetPage::targetDirectory()).exists()) // and the default directory exists or is empty
|
||||
&& !fileName.isEmpty()) { // and the file name is not empty
|
||||
download->provideOutputDevice(optionIndex, new QFile(TargetPage::targetDirectory() % QChar('/') % fileName), true);
|
||||
} else { // aks the user for the target path otherwise
|
||||
} else { // ask the user for the target path otherwise
|
||||
QFileDialog *dlg = new QFileDialog(m_parentWidget);
|
||||
#ifndef Q_OS_WIN
|
||||
// the native dialog can only be shown modal under Windows
|
||||
|
@ -227,7 +227,7 @@ void DownloadInteraction::downloadHasSslErrors(Download *download, size_t option
|
|||
}
|
||||
auto *dlg = new QMessageBox(m_parentWidget);
|
||||
dlg->setModal(false);
|
||||
dlg->setWindowTitle(tr("SSL errors occured") % QStringLiteral(" - ") % QCoreApplication::applicationName());
|
||||
dlg->setWindowTitle(tr("SSL errors occurred") % QStringLiteral(" - ") % QCoreApplication::applicationName());
|
||||
dlg->setTextFormat(Qt::RichText);
|
||||
dlg->setText(tr("The download <i>%1</i> has SSL errors.").arg(downloadName));
|
||||
dlg->setInformativeText(tr("Do you want to ignore the SSL errors for this download?"));
|
||||
|
|
|
@ -195,8 +195,12 @@ MainWindow::~MainWindow()
|
|||
void MainWindow::showAboutDialog()
|
||||
{
|
||||
if (!m_aboutDlg) {
|
||||
m_aboutDlg = new AboutDialog(this, QStringLiteral(APP_URL),
|
||||
tr("Simple video downloader with Qt GUI and backends for multiple platforms, e.g. YouTube and Vimeo."),
|
||||
m_aboutDlg = new AboutDialog(this, QString(),
|
||||
QStringLiteral("<p>Developed by " APP_AUTHOR
|
||||
"<br>Program icon and fallback icons from <a href=\"https://invent.kde.org/frameworks/breeze-icons\">KDE/Breeze</a> "
|
||||
"project (copyright © 2014 Uri Herrera <uri_herrera@nitrux.in> and others, see the according "
|
||||
"<a href=\"" APP_URL "/blob/master/LICENSE.LESSER\">LGPL-3.0 license</a>)</p>"),
|
||||
QString(), QString(), tr("Simple video downloader with Qt GUI and backends for multiple platforms, e.g. YouTube and Vimeo."),
|
||||
QImage(QStringLiteral(":/icons/hicolor/128x128/apps/videodownloader.png")));
|
||||
}
|
||||
if (m_aboutDlg->isHidden()) {
|
||||
|
@ -339,7 +343,7 @@ void MainWindow::startOrStopSelectedDownloads()
|
|||
download->init();
|
||||
break;
|
||||
case DownloadStatus::Initiating:
|
||||
// when the download is retireving initial information it can be stopped
|
||||
// when the download is retrieving initial information it can be stopped
|
||||
download->stop();
|
||||
break;
|
||||
case DownloadStatus::Downloading:
|
||||
|
@ -474,7 +478,7 @@ void MainWindow::updateStartStopControls()
|
|||
int toInterrupt = 0;
|
||||
int noCommand = 0;
|
||||
int downloadLinksAvailable = 0;
|
||||
int removeable = 0;
|
||||
int removable = 0;
|
||||
int withTargetPath = 0;
|
||||
bool downloadsSelected = !selectedDownloads.isEmpty();
|
||||
if (downloadsSelected) {
|
||||
|
@ -482,7 +486,7 @@ void MainWindow::updateStartStopControls()
|
|||
switch (download->status()) {
|
||||
case DownloadStatus::None:
|
||||
++toInit;
|
||||
++removeable;
|
||||
++removable;
|
||||
break;
|
||||
case DownloadStatus::Initiating:
|
||||
++toStop;
|
||||
|
@ -497,7 +501,7 @@ void MainWindow::updateStartStopControls()
|
|||
break;
|
||||
case DownloadStatus::Ready:
|
||||
++downloadLinksAvailable;
|
||||
++removeable;
|
||||
++removable;
|
||||
if (download->supportsRange() && download->range().currentOffset() > 0) {
|
||||
++toResume;
|
||||
} else {
|
||||
|
@ -511,10 +515,10 @@ void MainWindow::updateStartStopControls()
|
|||
} else {
|
||||
++toInit;
|
||||
}
|
||||
++removeable;
|
||||
++removable;
|
||||
break;
|
||||
case DownloadStatus::Interrupted:
|
||||
++removeable;
|
||||
++removable;
|
||||
++downloadLinksAvailable;
|
||||
if (download->supportsRange()) {
|
||||
++toResume;
|
||||
|
@ -523,7 +527,7 @@ void MainWindow::updateStartStopControls()
|
|||
case DownloadStatus::Finished:
|
||||
++downloadLinksAvailable;
|
||||
++toRestart;
|
||||
++removeable;
|
||||
++removable;
|
||||
break;
|
||||
case DownloadStatus::Interrupting:
|
||||
case DownloadStatus::Aborting:
|
||||
|
@ -610,12 +614,12 @@ void MainWindow::updateStartStopControls()
|
|||
if (m_ui->toolBar->minimumWidth() < m_ui->toolBar->width()) {
|
||||
m_ui->toolBar->setMinimumWidth(m_ui->toolBar->width());
|
||||
}
|
||||
if (removeable == 1) {
|
||||
if (removable == 1) {
|
||||
m_ui->actionRemove_selected_downloads_from_list->setText(tr("Remove selected download from list"));
|
||||
} else if (removeable > 1) {
|
||||
m_ui->actionRemove_selected_downloads_from_list->setText(tr("Remove selected downloads (%1) from list").arg(removeable));
|
||||
} else if (removable > 1) {
|
||||
m_ui->actionRemove_selected_downloads_from_list->setText(tr("Remove selected downloads (%1) from list").arg(removable));
|
||||
}
|
||||
m_ui->actionRemove_selected_downloads_from_list->setEnabled(removeable > 0);
|
||||
m_ui->actionRemove_selected_downloads_from_list->setEnabled(removable > 0);
|
||||
if (downloadLinksAvailable > 0) {
|
||||
if (downloadLinksAvailable == 1) {
|
||||
m_ui->actionCopy_download_url->setText(tr("Copy download url"));
|
||||
|
@ -668,9 +672,9 @@ void MainWindow::clipboardDataChanged()
|
|||
{
|
||||
if (!m_internalClipboardChange && m_superviseClipboardToolButton->isChecked()) {
|
||||
QString data = QApplication::clipboard()->text();
|
||||
QStringList lines = data.split(QChar('\n'), QString::SkipEmptyParts);
|
||||
QStringList lines = data.split(QChar('\n'), Qt::SkipEmptyParts);
|
||||
for (const QString &line : lines) {
|
||||
if (Download *download = Download::fromUrl(line)) {
|
||||
if (Download *const download = Download::fromUrl(line)) {
|
||||
addDownload(download);
|
||||
if (m_trayIcon && m_trayIcon->isVisible()) {
|
||||
m_trayIcon->showMessage(windowTitle(), tr("The download \"%1\" has been added.").arg(data));
|
||||
|
@ -710,7 +714,7 @@ void MainWindow::setDownloadRange()
|
|||
} else if (!downloads.size()) {
|
||||
QMessageBox::warning(this, windowTitle(), tr("There is no download selected."));
|
||||
} else {
|
||||
QMessageBox::warning(this, windowTitle(), tr("You can only set the range of a singe download at once."));
|
||||
QMessageBox::warning(this, windowTitle(), tr("You can only set the range of a single download at once."));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -726,7 +730,7 @@ void MainWindow::setTargetPath()
|
|||
} else if (!downloads.size()) {
|
||||
QMessageBox::warning(this, windowTitle(), tr("There is no download selected."));
|
||||
} else {
|
||||
QMessageBox::warning(this, windowTitle(), tr("You can only set the target of a singe download at once."));
|
||||
QMessageBox::warning(this, windowTitle(), tr("You can only set the target of a single download at once."));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -895,11 +899,7 @@ void MainWindow::setupTrayIcon()
|
|||
{
|
||||
#ifdef CONFIG_USE_TRAY_ICON
|
||||
if (!m_trayIcon) {
|
||||
#ifdef OS_WIN32
|
||||
m_trayIcon = new QSystemTrayIcon(QIcon(QStringLiteral(":/icons/hicolor/16x16/misc/trayicon.ico")));
|
||||
#else
|
||||
m_trayIcon = new QSystemTrayIcon(QIcon(QStringLiteral(":/icons/hicolor/128x128/apps/videodownloader.png")));
|
||||
#endif
|
||||
connect(m_trayIcon, &QSystemTrayIcon::activated, this,
|
||||
static_cast<void (MainWindow::*)(QSystemTrayIcon::ActivationReason)>(&MainWindow::trayIconActivated));
|
||||
}
|
||||
|
|
|
@ -256,7 +256,7 @@ QWidget *ProxyPage::setupWidget()
|
|||
|
||||
void ProxyPage::updateProxy()
|
||||
{
|
||||
QStringList parts = ui()->hostNameLineEdit->text().split(":", QString::SkipEmptyParts);
|
||||
QStringList parts = ui()->hostNameLineEdit->text().split(":", Qt::SkipEmptyParts);
|
||||
if (parts.count() == 2) {
|
||||
bool ok;
|
||||
int port = parts.at(1).toInt(&ok);
|
||||
|
@ -406,13 +406,13 @@ SettingsDialog::SettingsDialog(QWidget *parent)
|
|||
category->setDisplayName(tr("General"));
|
||||
category->assignPages({ new TargetPage(this), new UiPage() });
|
||||
category->setIcon(
|
||||
QIcon::fromTheme(QStringLiteral("preferences-other"), QIcon(QStringLiteral(":/icons/hicolor/32x32/categories/preferences-general.png"))));
|
||||
QIcon::fromTheme(QStringLiteral("preferences-other"), QIcon(QStringLiteral(":/icons/hicolor/32x32/categories/preferences-other.svg"))));
|
||||
categories << category;
|
||||
|
||||
category = new OptionCategory(this);
|
||||
category->setDisplayName(tr("Network"));
|
||||
category->setIcon(QIcon::fromTheme(
|
||||
QStringLiteral("preferences-system-network"), QIcon(QStringLiteral(":/icons/hicolor/32x32/categories/preferences-network.png"))));
|
||||
QStringLiteral("preferences-system-network"), QIcon(QStringLiteral(":/icons/hicolor/32x32/categories/applications-internet.svg"))));
|
||||
category->assignPages({ new ProxyPage(), new UserAgentPage(), new MiscPage(), new StatsPage() });
|
||||
categories << category;
|
||||
|
||||
|
@ -422,7 +422,7 @@ SettingsDialog::SettingsDialog(QWidget *parent)
|
|||
|
||||
setMinimumSize(800, 450);
|
||||
setWindowIcon(
|
||||
QIcon::fromTheme(QStringLiteral("preferences-other"), QIcon(QStringLiteral(":/icons/hicolor/32x32/categories/preferences-general.png"))));
|
||||
QIcon::fromTheme(QStringLiteral("preferences-other"), QIcon(QStringLiteral(":/icons/hicolor/32x32/categories/preferences-other.svg"))));
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
|
|
|
@ -52,7 +52,7 @@ namespace Network {
|
|||
* - reportNewDataToBeWritten(): Reports that there is new data to be written available.
|
||||
* - reportRedirectionAvailable(): Reports that there is a redirection available.
|
||||
* - reportAuthenticationRequired(): Reports that authentication credentials are required.
|
||||
* - reportSslErrors(): Reports that one or more SSL errors occured.
|
||||
* - reportSslErrors(): Reports that one or more SSL errors occurred.
|
||||
* - reportDownloadComplete(): Reports that the download is complete.
|
||||
* - reportFinalDownloadStatus(): Reports the final download status.
|
||||
* - addDownloadUrl(): Makes a download URL under the specified option name available. Meant to be called during initialization.
|
||||
|
@ -350,7 +350,7 @@ void Download::interrupt()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new donwload with the specified \a url.
|
||||
* \brief Constructs a new download with the specified \a url.
|
||||
*/
|
||||
Download::Download(const QUrl &url, QObject *parent)
|
||||
: QObject(parent)
|
||||
|
@ -429,11 +429,11 @@ void Download::init()
|
|||
/*!
|
||||
* \brief Reports the initialization status.
|
||||
*
|
||||
* Needs to be called when subclassing after the initialzation ended.
|
||||
* Needs to be called when subclassing after the initialization ended.
|
||||
*
|
||||
* \param success Specifies whether the initialization succeeded.
|
||||
* \param reasonIfNot Specifies the reason if the initialization failed; ignored otherwise.
|
||||
* \param networkError Specifies if and what kind of network error occured.
|
||||
* \param networkError Specifies if and what kind of network error occurred.
|
||||
*/
|
||||
void Download::reportInitiated(bool success, const QString &reasonIfNot, const QNetworkReply::NetworkError &networkError)
|
||||
{
|
||||
|
@ -612,7 +612,7 @@ void Download::finalizeOutputDevice(size_t optionIndex)
|
|||
* \param optionIndex Specifies the concerning option.
|
||||
* \param success Specifies whether the download was successful.
|
||||
* \param statusDescription Specifies a status description.
|
||||
* \param networkError Specifies if or what kind of network error occured.
|
||||
* \param networkError Specifies if or what kind of network error occurred.
|
||||
*/
|
||||
void Download::reportFinalDownloadStatus(size_t optionIndex, bool success, const QString &statusDescription, QNetworkReply::NetworkError networkError)
|
||||
{
|
||||
|
@ -802,7 +802,7 @@ void Download::reportRedirectionAvailable(size_t originalOptionIndex)
|
|||
optionData.m_downloadComplete = false;
|
||||
setStatus(DownloadStatus::Downloading);
|
||||
} else {
|
||||
reportFinalDownloadStatus(originalOptionIndex, false, tr("Follwing redirection failed."));
|
||||
reportFinalDownloadStatus(originalOptionIndex, false, tr("Following redirection failed."));
|
||||
}
|
||||
break;
|
||||
case PermissionStatus::Refused:
|
||||
|
@ -833,14 +833,14 @@ void Download::reportAuthenticationRequired(size_t optionIndex, const QString &r
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Reports that SSL errors occured.
|
||||
* \brief Reports that SSL errors occurred.
|
||||
*
|
||||
* \param optionIndex Specifies the concerning option index. Use a negative value when the
|
||||
* errors occured during initialization.
|
||||
* errors occurred during initialization.
|
||||
* \param reply Specifies the concerning reply.
|
||||
* \param sslErrors Specifies which SSL errors occured.
|
||||
* \param sslErrors Specifies which SSL errors occurred.
|
||||
*
|
||||
* Needs to be called when subclassing if SSL errors occured.
|
||||
* Needs to be called when subclassing if SSL errors occurred.
|
||||
*/
|
||||
void Download::reportSslErrors(size_t optionIndex, QNetworkReply *reply, const QList<QSslError> &sslErrors)
|
||||
{
|
||||
|
@ -940,7 +940,7 @@ void Download::changeDownloadUrl(size_t optionIndex, const QUrl &value)
|
|||
* \brief Provides an output device.
|
||||
* \param optionIndex Specifies the index of the option the output device is provided for.
|
||||
* \param device Specifies the output device.
|
||||
* \param giveOwnership Specifies whether the ownership is transfered to download.
|
||||
* \param giveOwnership Specifies whether the ownership is transferred to download.
|
||||
*
|
||||
* Use this method to provide an output device after the outputDeviceRequired() signal has
|
||||
* been emitted.
|
||||
|
|
|
@ -538,7 +538,7 @@ inline int Download::positionInCollection() const
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the status update interval in miliseconds.
|
||||
* \brief Returns the status update interval in milliseconds.
|
||||
*/
|
||||
inline int Download::progressUpdateInterval() const
|
||||
{
|
||||
|
@ -547,7 +547,7 @@ inline int Download::progressUpdateInterval() const
|
|||
|
||||
/*!
|
||||
* \brief Sets the status interval.
|
||||
* \param value Specifies the status inverval in miliseconds.
|
||||
* \param value Specifies the status interval in milliseconds.
|
||||
*/
|
||||
inline void Download::setProgressUpdateInterval(int value)
|
||||
{
|
||||
|
@ -674,7 +674,7 @@ inline void Download::setTitle(const QString &value)
|
|||
* does nothing if a title has been set previously using the setTitle() method.
|
||||
* This might be useful since title information which is only derived from a
|
||||
* filename should generally not overwrite a title which has been obtained previously
|
||||
* from a better souce (for example the video meta data returned by YouTube in
|
||||
* from a better source (for example the video meta data returned by YouTube in
|
||||
* case of a YouTube download).
|
||||
*
|
||||
* \sa setTitle()
|
||||
|
@ -763,7 +763,7 @@ inline void Download::setCollectionName(const QString &value)
|
|||
* \brief Returns an indication whether the download is initiated.
|
||||
*
|
||||
* \remarks A download which has been failed might be initiated or not - depending
|
||||
* whether the failure occured during initialization or during the acutal download.
|
||||
* whether the failure occurred during initialization or during the actual download.
|
||||
*/
|
||||
inline bool Download::isInitiated() const
|
||||
{
|
||||
|
@ -878,7 +878,7 @@ inline CppUtilities::TimeSpan Download::shiftRemainingTime()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the number of bytes received since the downlod has been started.
|
||||
* \brief Returns the number of bytes received since the download has been started.
|
||||
*/
|
||||
inline qint64 Download::bytesReceived() const
|
||||
{
|
||||
|
@ -917,7 +917,7 @@ inline qint64 Download::newBytesToReceive()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the number of miliseconds since the last progress update.
|
||||
* \brief Returns the number of milliseconds since the last progress update.
|
||||
*/
|
||||
inline int Download::lastProgressUpdate() const
|
||||
{
|
||||
|
@ -958,9 +958,9 @@ inline void Download::setStatusInfo(const QString &value)
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns what kind of network error occured.
|
||||
* \brief Returns what kind of network error occurred.
|
||||
*
|
||||
* If no network error occured QNetworkReply::NoError is returned.
|
||||
* If no network error occurred QNetworkReply::NoError is returned.
|
||||
*/
|
||||
inline QNetworkReply::NetworkError Download::networkError() const
|
||||
{
|
||||
|
|
|
@ -50,7 +50,7 @@ void FileNukeDownload::evalVideoInformation(Download *, QBuffer *videoInfoBuffer
|
|||
query.addQueryItem(QStringLiteral("fname"), fname);
|
||||
query.addQueryItem(QStringLiteral("referer"), QString());
|
||||
query.addQueryItem(QStringLiteral("method_free"), QStringLiteral("Free"));
|
||||
m_postData.append(query.toString(QUrl::FullyEncoded));
|
||||
m_postData.append(query.toString(QUrl::FullyEncoded).toUtf8());
|
||||
++m_currentStep;
|
||||
doInit();
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ void FileNukeDownload::evalVideoInformation(Download *, QBuffer *videoInfoBuffer
|
|||
pos = videoInfo.indexOf(QLatin1String(",'"), pos + 1);
|
||||
if (pos > 0) {
|
||||
substring(videoInfo, str, pos, QStringLiteral("'"), QStringLiteral("'"));
|
||||
QStringList parts = str.split(QChar('|'), QString::KeepEmptyParts);
|
||||
QStringList parts = str.split(QChar('|'), Qt::KeepEmptyParts);
|
||||
|
||||
if (parts.count() >= 21) {
|
||||
addDownloadUrl(tr("H.264/AAC/MP4"),
|
||||
|
|
|
@ -47,7 +47,7 @@ protected:
|
|||
* \brief Specifies possible return values of the evalResults() method.
|
||||
*/
|
||||
enum class ParsingResult {
|
||||
Error, /**< Indicates that an error occured. reasonForFail might hold an error message. */
|
||||
Error, /**< Indicates that an error occurred. reasonForFail might hold an error message. */
|
||||
Success, /**< Indicates that the results could be parsed correctly. */
|
||||
AnotherRequestRequired /**< Indicates that the results could be parsed correctly but there are still results to be fetched. */
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include "../../application/utils.h"
|
||||
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
|
||||
using namespace CppUtilities;
|
||||
using namespace Application;
|
||||
|
@ -33,64 +33,61 @@ Download *LinkFinder::createRequest(QString &)
|
|||
DownloadFinder::ParsingResult LinkFinder::parseResults(const QByteArray &data, QString &)
|
||||
{
|
||||
QString html(data);
|
||||
QRegExp titlePattern(QStringLiteral("<title>(.+)</title>"), Qt::CaseInsensitive);
|
||||
QRegExp linkPattern(QStringLiteral("<a([^>]+)>(.+)</a>"), Qt::CaseInsensitive);
|
||||
QRegExp commentPattern(QStringLiteral("<!--(.+)-->"), Qt::CaseInsensitive);
|
||||
QRegExp hrefPattern1(QStringLiteral("\\s*href\\s*=\\s*['](.+)['>]"), Qt::CaseInsensitive);
|
||||
QRegExp hrefPattern2(QStringLiteral("\\s*href\\s*=\\s*[\"](.+)[\">]"), Qt::CaseInsensitive);
|
||||
titlePattern.setMinimal(true);
|
||||
linkPattern.setMinimal(true);
|
||||
commentPattern.setMinimal(true);
|
||||
hrefPattern1.setMinimal(true);
|
||||
hrefPattern2.setMinimal(true);
|
||||
static const QRegularExpression titlePattern(
|
||||
QStringLiteral("<title>(.+)</title>"), QRegularExpression::CaseInsensitiveOption | QRegularExpression::InvertedGreedinessOption);
|
||||
static const QRegularExpression linkPattern(
|
||||
QStringLiteral("<a([^>]+)>(.+)</a>"), QRegularExpression::CaseInsensitiveOption | QRegularExpression::InvertedGreedinessOption);
|
||||
static const QRegularExpression commentPattern(
|
||||
QStringLiteral("<!--(.+)-->"), QRegularExpression::CaseInsensitiveOption | QRegularExpression::InvertedGreedinessOption);
|
||||
static const QRegularExpression hrefPattern1(
|
||||
QStringLiteral("\\s*href\\s*=\\s*['](.+)['>]"), QRegularExpression::CaseInsensitiveOption | QRegularExpression::InvertedGreedinessOption);
|
||||
static const QRegularExpression hrefPattern2(
|
||||
QStringLiteral("\\s*href\\s*=\\s*[\"](.+)[\">]"), QRegularExpression::CaseInsensitiveOption | QRegularExpression::InvertedGreedinessOption);
|
||||
QString pageTitle;
|
||||
if (titlePattern.indexIn(html) >= 0 && titlePattern.captureCount() >= 1) {
|
||||
pageTitle = titlePattern.cap(1);
|
||||
const auto titleMatch = titlePattern.match(html);
|
||||
if (titleMatch.hasMatch()) {
|
||||
pageTitle = titleMatch.captured(1);
|
||||
replaceHtmlEntities(pageTitle);
|
||||
}
|
||||
int overallIndex = 0;
|
||||
int commentIndex = commentPattern.indexIn(html, overallIndex);
|
||||
int linkIndex = 0;
|
||||
while (((linkIndex = linkPattern.indexIn(html, overallIndex)) >= 0)) {
|
||||
if (commentIndex >= 0 && commentIndex < linkIndex) {
|
||||
auto commentMatch = commentPattern.match(html, 0);
|
||||
decltype(commentMatch.capturedEnd()) overallIndex = 0;
|
||||
for (auto linkMatch = linkPattern.match(html, overallIndex); linkMatch.hasMatch(); linkMatch = linkPattern.match(html, overallIndex)) {
|
||||
if (commentMatch.capturedStart() >= 0 && commentMatch.capturedStart() < linkMatch.capturedStart()) {
|
||||
// skip comment
|
||||
overallIndex = commentIndex + commentPattern.matchedLength();
|
||||
commentIndex = commentPattern.indexIn(html, overallIndex);
|
||||
} else if (linkIndex >= 0) {
|
||||
// read actual link
|
||||
if (linkPattern.captureCount() >= 2) {
|
||||
QString title(linkPattern.cap(2));
|
||||
QString href(linkPattern.cap(1));
|
||||
QString urlStr;
|
||||
if (hrefPattern1.indexIn(href) >= 0 && hrefPattern1.captureCount() >= 1) {
|
||||
urlStr = hrefPattern1.cap(1);
|
||||
} else if (hrefPattern2.indexIn(href) >= 0 && hrefPattern2.captureCount() >= 1) {
|
||||
urlStr = hrefPattern2.cap(1);
|
||||
}
|
||||
if (!urlStr.isEmpty()) {
|
||||
replaceHtmlEntities(title);
|
||||
replaceHtmlEntities(urlStr);
|
||||
// resolve relative URLs
|
||||
QUrl url(urlStr);
|
||||
if (url.isRelative()) {
|
||||
url = m_url.resolved(url);
|
||||
}
|
||||
// avoid duplicate results
|
||||
if (Download *duplicateDownload = downloadByInitialUrl(url)) {
|
||||
if (!title.isEmpty() && duplicateDownload->title().isEmpty()) {
|
||||
duplicateDownload->provideMetaData(title);
|
||||
}
|
||||
} else if (Download *result = Download::fromUrl(url)) {
|
||||
result->provideMetaData(title, QString(), TimeSpan(), pageTitle, results().size());
|
||||
reportResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
overallIndex = linkIndex + linkPattern.matchedLength();
|
||||
} else {
|
||||
// no more links
|
||||
overallIndex = commentMatch.capturedEnd();
|
||||
commentMatch = commentPattern.match(html, overallIndex);
|
||||
break;
|
||||
}
|
||||
// read actual link
|
||||
QString title = linkMatch.captured(2), href = linkMatch.captured(1), urlStr;
|
||||
const auto hrefMatch1 = hrefPattern1.match(href);
|
||||
if (hrefMatch1.hasMatch()) {
|
||||
urlStr = hrefMatch1.captured(1);
|
||||
} else {
|
||||
const auto hrefMatch2 = hrefPattern2.match(href);
|
||||
if (hrefMatch2.hasMatch()) {
|
||||
urlStr = hrefMatch2.captured(1);
|
||||
}
|
||||
}
|
||||
if (!urlStr.isEmpty()) {
|
||||
replaceHtmlEntities(title);
|
||||
replaceHtmlEntities(urlStr);
|
||||
// resolve relative URLs
|
||||
QUrl url(urlStr);
|
||||
if (url.isRelative()) {
|
||||
url = m_url.resolved(url);
|
||||
}
|
||||
// avoid duplicate results
|
||||
if (Download *const duplicateDownload = downloadByInitialUrl(url)) {
|
||||
if (!title.isEmpty() && duplicateDownload->title().isEmpty()) {
|
||||
duplicateDownload->provideMetaData(title);
|
||||
}
|
||||
} else if (Download *result = Download::fromUrl(url)) {
|
||||
result->provideMetaData(title, QString(), TimeSpan(), pageTitle, results().size());
|
||||
reportResult(result);
|
||||
}
|
||||
}
|
||||
overallIndex = linkMatch.capturedEnd();
|
||||
}
|
||||
return DownloadFinder::ParsingResult::Success;
|
||||
}
|
||||
|
|
|
@ -304,7 +304,7 @@ bool GroovesharkDownload::loadAuthenticationInformationFromFile(const QString &p
|
|||
}
|
||||
clientVal = fileObj.value(QStringLiteral("referer"));
|
||||
if (clientVal.isString()) {
|
||||
m_referer.append(clientVal.toString());
|
||||
m_referer.append(clientVal.toString().toUtf8());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -316,18 +316,18 @@ bool GroovesharkDownload::loadAuthenticationInformationFromFile(const QString &p
|
|||
QJsonValue GroovesharkDownload::generateTokenHash(QString method, int mode)
|
||||
{
|
||||
QByteArray toHash;
|
||||
toHash.append(method);
|
||||
toHash.append(method.toUtf8());
|
||||
toHash.append(':');
|
||||
toHash.append(m_token);
|
||||
toHash.append(m_token.toUtf8());
|
||||
switch (mode) {
|
||||
case 1:
|
||||
toHash.append(m_htmlRandomizer);
|
||||
toHash.append(m_htmlRandomizer.toUtf8());
|
||||
break;
|
||||
default:
|
||||
toHash.append(m_jsRandomizer);
|
||||
toHash.append(m_jsRandomizer.toUtf8());
|
||||
break;
|
||||
}
|
||||
toHash.append(m_anyRandomizer);
|
||||
toHash.append(m_anyRandomizer.toUtf8());
|
||||
QString res;
|
||||
res.append(m_anyRandomizer);
|
||||
res.append(QString(QCryptographicHash::hash(toHash, QCryptographicHash::Sha1).toHex()).toLower());
|
||||
|
@ -340,7 +340,7 @@ QJsonValue GroovesharkDownload::generateTokenHash(QString method, int mode)
|
|||
QJsonValue GroovesharkDownload::generateSecretKey()
|
||||
{
|
||||
QByteArray toHash;
|
||||
toHash.append(m_sessionId.toString());
|
||||
toHash.append(m_sessionId.toString().toUtf8());
|
||||
QString secretKey(QCryptographicHash::hash(toHash, QCryptographicHash::Md5).toHex());
|
||||
return QJsonValue(secretKey);
|
||||
}
|
||||
|
@ -364,7 +364,7 @@ HttpDownload *GroovesharkDownload::createJsonPostRequest(const QString &method,
|
|||
HttpDownload *download = new HttpDownload(QUrl(url));
|
||||
download->setMethod(HttpDownloadMethod::Post);
|
||||
download->setPostData(postData);
|
||||
download->setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
download->setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QLatin1String("application/json")));
|
||||
download->setHeader("Refer", m_referer);
|
||||
download->setHeader("Accept", m_accept);
|
||||
//download->setHeader("Connection", "keep-alive");
|
||||
|
@ -379,12 +379,12 @@ void GroovesharkDownload::setupFinalRequest()
|
|||
setMethod(HttpDownloadMethod::Post);
|
||||
switch (m_requestType) {
|
||||
case GroovesharkRequestType::SongStream: {
|
||||
setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QLatin1String("application/x-www-form-urlencoded")));
|
||||
setHeader("Accept", m_accept);
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("streamKey", m_streamKey);
|
||||
QByteArray postData;
|
||||
postData.append(query.toString(QUrl::FullyEncoded));
|
||||
postData.append(query.toString(QUrl::FullyEncoded).toUtf8());
|
||||
setPostData(postData);
|
||||
addDownloadUrl(tr("MPEG-1 Layer 3"), QUrl(QStringLiteral("http://%1/stream.php").arg(m_streamHost)));
|
||||
break;
|
||||
|
|
|
@ -78,13 +78,13 @@ void HttpDownload::startRequest(size_t optionIndex)
|
|||
QByteArray rangeVal;
|
||||
rangeVal.append("bytes=");
|
||||
if (currentOffset > 0) {
|
||||
rangeVal.append(QString::number(currentOffset));
|
||||
rangeVal.append(QString::number(currentOffset).toUtf8());
|
||||
} else {
|
||||
rangeVal.append('0');
|
||||
}
|
||||
rangeVal.append('-');
|
||||
if (endOffset > 0) {
|
||||
rangeVal.append(QString::number(endOffset));
|
||||
rangeVal.append(QString::number(endOffset).toUtf8());
|
||||
}
|
||||
m_request.setRawHeader("Range", rangeVal);
|
||||
} else {
|
||||
|
@ -145,13 +145,13 @@ void HttpDownload::checkStatusAndClear(size_t optionIndex)
|
|||
// wrong to report a failed download here?
|
||||
reportFinalDownloadStatus(optionIndex, false, reasonForFail, error);
|
||||
} else {
|
||||
// some other error occured
|
||||
// some other error occurred
|
||||
reply->deleteLater();
|
||||
m_replies.removeAll(reply);
|
||||
reportFinalDownloadStatus(optionIndex, false, reasonForFail, error);
|
||||
}
|
||||
} else {
|
||||
// no error occured
|
||||
// no error occurred
|
||||
// check if there's a redirection
|
||||
QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
reply->deleteLater();
|
||||
|
|
|
@ -65,7 +65,7 @@ void HttpDownloadWithInfoRequst::infoRequestChangedStatus(Download *download)
|
|||
{
|
||||
switch (download->status()) {
|
||||
case DownloadStatus::Failed:
|
||||
reportInitiated(false, tr("Couldn't retieve the video information. %1").arg(statusInfo()));
|
||||
reportInitiated(false, tr("Couldn't retrieve the video information. %1").arg(statusInfo()));
|
||||
break;
|
||||
case DownloadStatus::Ready:
|
||||
if (m_infoDownload->isValidOptionChosen()) {
|
||||
|
|
|
@ -72,7 +72,7 @@ void SockshareDownload::evalVideoInformation(Download *, QBuffer *videoInfoBuffe
|
|||
QUrlQuery query;
|
||||
query.addQueryItem(QStringLiteral("hash"), str);
|
||||
query.addQueryItem(QStringLiteral("confirm"), QStringLiteral("Continue as Free User"));
|
||||
m_postData.append(query.toString(QUrl::FullyEncoded));
|
||||
m_postData.append(query.toString(QUrl::FullyEncoded).toUtf8());
|
||||
m_currentStep++;
|
||||
doInit();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "./vimeodownload.h"
|
||||
|
||||
#include <qtutilities/misc/compat.h>
|
||||
|
||||
#include <c++utilities/chrono/timespan.h>
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
@ -33,19 +35,11 @@ VimeoDownload::VimeoDownload(const QString &id, QObject *parent)
|
|||
|
||||
Download *VimeoDownload::infoRequestDownload(bool &success, QString &reasonForFail)
|
||||
{
|
||||
const auto pathParts = initialUrl()
|
||||
.path(QUrl::FullyDecoded)
|
||||
.
|
||||
#if QT_VERSION >= 0x050400
|
||||
splitRef
|
||||
#else
|
||||
split
|
||||
#endif
|
||||
(QChar('/'), QString::SkipEmptyParts);
|
||||
const auto pathParts = QtUtilities::splitRef(initialUrl().path(QUrl::FullyDecoded), QChar('/'), Qt::SkipEmptyParts);
|
||||
if (pathParts.size() < 2) {
|
||||
const auto &id = pathParts.back();
|
||||
bool isInt;
|
||||
id.toULongLong(&isInt);
|
||||
static_cast<void>(id.toULongLong(&isInt));
|
||||
if (isInt) {
|
||||
setId(id
|
||||
#if QT_VERSION >= 0x050400
|
||||
|
|
|
@ -62,9 +62,9 @@ void YoutubeDownload::evalVideoInformation(Download *, QBuffer *videoInfoBuffer)
|
|||
m_itagInfo = loadJsonObjectFromResource(QStringLiteral(":/jsonobjects/itaginfo"));
|
||||
}
|
||||
QString videoInfo(videoInfoBuffer->readAll());
|
||||
QStringList completeFields = videoInfo.split(QChar('&'), QString::SkipEmptyParts, Qt::CaseSensitive);
|
||||
QStringList completeFields = videoInfo.split(QChar('&'), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
||||
for (const QString &completeField : completeFields) {
|
||||
QStringList fieldParts = completeField.split(QChar('='), QString::SkipEmptyParts, Qt::CaseSensitive);
|
||||
QStringList fieldParts = completeField.split(QChar('='), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
||||
if (fieldParts.count() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
@ -93,13 +93,13 @@ void YoutubeDownload::evalVideoInformation(Download *, QBuffer *videoInfoBuffer)
|
|||
for (const QString &fmtFieldId : fmtFieldIds) {
|
||||
QString fmtField = m_fields.value(fmtFieldId, QString());
|
||||
if (!fmtField.isEmpty()) {
|
||||
QStringList sections = fmtField.split(QChar(','), QString::SkipEmptyParts, Qt::CaseSensitive);
|
||||
QStringList sections = fmtField.split(QChar(','), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
||||
for (const QString §ion : sections) {
|
||||
QStringList fmtParts = section.split(QChar('&'), QString::SkipEmptyParts, Qt::CaseSensitive);
|
||||
QStringList fmtParts = section.split(QChar('&'), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
||||
QString itag, urlPart1, urlPart2, name;
|
||||
QJsonObject itagObj;
|
||||
for (const QString fmtPart : fmtParts) {
|
||||
QStringList fmtSubParts = fmtPart.split(QChar('='), QString::SkipEmptyParts, Qt::CaseSensitive);
|
||||
QStringList fmtSubParts = fmtPart.split(QChar('='), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
||||
if (fmtSubParts.count() >= 2) {
|
||||
QString fieldIdentifier = fmtSubParts.at(0).toLower();
|
||||
if (fieldIdentifier == QLatin1String("url")) {
|
||||
|
@ -146,10 +146,10 @@ void YoutubeDownload::evalVideoInformation(Download *, QBuffer *videoInfoBuffer)
|
|||
name = itag;
|
||||
}
|
||||
QByteArray url;
|
||||
url.append(urlPart1);
|
||||
url.append(urlPart1.toUtf8());
|
||||
if (!urlPart2.isEmpty()) {
|
||||
url.append("&signature=");
|
||||
url.append(urlPart2);
|
||||
url.append(urlPart2.toUtf8());
|
||||
}
|
||||
addDownloadUrl(name, QUrl::fromPercentEncoding(url));
|
||||
m_itags.append(itag);
|
||||
|
@ -167,10 +167,10 @@ void YoutubeDownload::evalVideoInformation(Download *, QBuffer *videoInfoBuffer)
|
|||
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."));
|
||||
tr("Failed to retrieve 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(' '))));
|
||||
tr("Failed to retrieve the video info. The reason returned by Youtube is: \"%1\".").arg(reason.replace(QChar('+'), QChar(' '))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>icons/hicolor/128x128/apps/videodownloader.png</file>
|
||||
<file>icons/hicolor/32x32/categories/preferences-general.png</file>
|
||||
<file>icons/hicolor/32x32/categories/preferences-network.png</file>
|
||||
<file>icons/hicolor/32x32/categories/applications-internet.svg</file>
|
||||
<file>icons/hicolor/32x32/categories/preferences-other.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 3.3 KiB |
|
@ -0,0 +1 @@
|
|||
<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="2" x2="2" y1="30" y2="2"><stop offset="0" stop-color="#197cf1"/><stop offset="1" stop-color="#20bcfa"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="23" x2="30" xlink:href="#a" y1="13" y2="20"/><linearGradient id="a"><stop offset="0" stop-color="#292c2f"/><stop offset="1" stop-opacity="0"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="6" x2="26" xlink:href="#a" y1="6" y2="26"/><linearGradient id="e" gradientUnits="userSpaceOnUse" x1="2" x2="2" y1="30" y2="2"><stop offset="0" stop-color="#54d883"/><stop offset="1" stop-color="#abf9c7"/></linearGradient><rect fill="url(#b)" height="28" rx="14" width="28" x="2" y="2"/><path d="M16 2A13.965 13.965 0 0 0 2.078 14.53l.543.07L6 17l7.188 4.133 1.47 8.799A13.97 13.97 0 0 0 30 16c0-7.756-6.244-14-14-14z" fill="url(#c)" fill-rule="evenodd" opacity=".2"/><path d="M29.73 13.27L25 18l3.758 3.758a13.984 13.984 0 0 0 .972-8.488z" fill="url(#d)" fill-rule="evenodd" opacity=".2"/><path d="M16 2a14 14 0 0 0-13.059 9H3l3-1 1 1 1 1v-2l2-1V8l2-1 2-2v1l1-1-1-1h2v1l2-1-1-1v-.95A14 14 0 0 0 16 2zm10.375 4.625L26 7v1h1.469a14 14 0 0 0-1.094-1.375zm1.316 1.684L27 9l-2 2-1 1-1 3v1l1 1 1 1 2 1 2.791-.688A14 14 0 0 0 30 16a14 14 0 0 0-2.309-7.691zM4 13l-1 1h-.838c-.05.34-.088.681-.113 1.023v.002L4 16l1 1 2 1 1 1-1 1v2l1 2 2 2v2.629A14 14 0 0 0 16 30c.17-.003.34-.009.51-.018L17 29v-1l2-1 1-1v-2l1-1v-1l-1-1-3-1-1-1-1-1h-1l-1-1-3-1-2 1H6v-2H5v-2z" fill="url(#e)"/><path d="M29.979 15.414A14 14 0 0 1 16 29 14 14 0 0 1 2.021 15.586 14 14 0 0 0 2 16a14 14 0 0 0 14 14 14 14 0 0 0 14-14 14 14 0 0 0-.021-.586z" fill="#292c2f" opacity=".2"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||
<defs id="defs5455">
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4303" id="linearGradient4238" y1="545.79797" y2="517.79797" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0 1 -0.000006 0)"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4159" id="linearGradient4370" y1="30" y2="1.999974" x2="0" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4303">
|
||||
<stop style="stop-color:#c6cdd1" id="stop4305"/>
|
||||
<stop offset="1" style="stop-color:#e0e5e7" id="stop4307"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4159">
|
||||
<stop style="stop-color:#2a2c2f" id="stop4161"/>
|
||||
<stop offset="1" style="stop-color:#424649" id="stop4163"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4227" id="linearGradient4198" y1="9" x1="9.00001" y2="22.999973" x2="23" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4227">
|
||||
<stop style="stop-color:#292c2f" id="stop4229"/>
|
||||
<stop offset="1" style="stop-opacity:0" id="stop4231"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<metadata id="metadata5458"/>
|
||||
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
|
||||
<rect width="27.999989" x="386.57144" y="517.79797" rx="13.999994" height="28" style="fill:url(#linearGradient4238)" id="rect4175"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:url(#linearGradient4198);opacity:0.2;fill-rule:evenodd" id="path4175" d="m 9.4003906,15 -0.099609,2 0.9999998,1 10.970703,10.970703 c 3.4891,-1.412467 6.267011,-4.183045 7.6875,-7.667969 L 22.706,14.95 22.191,14.6 19.400391,15 19.318359,16.662109 17.706,14.95 17.191,14.6 14.400391,15 14.318359,16.662109 12.706,14.95 12.191,14.6 Z" transform="matrix(1 0 0 1 384.57143 515.798)"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:url(#linearGradient4370)" id="rect4176" d="m 16,14 c -1.108,0 -2,0.892 -2,2 0,1.108 0.892,2 2,2 1.108,0 2,-0.892 2,-2 0,-1.108 -0.892,-2 -2,-2 z m -5,0 c -1.108,0 -2,0.892 -2,2 0,1.108 0.892,2 2,2 1.108,0 2,-0.892 2,-2 0,-1.108 -0.892,-2 -2,-2 z m 10,0 c -1.108,0 -2,0.892 -2,2 0,1.108 0.892,2 2,2 1.108,0 2,-0.892 2,-2 0,-1.108 -0.892,-2 -2,-2 z" transform="matrix(1 0 0 1 384.57143 515.798)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -1 +1,18 @@
|
|||
<?xml version="1.0" ?><svg height="24" version="1.1" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><g transform="translate(0 -1028.4)"><path d="m2 20v1c0 1.105 0.8954 2 2 2h2 12 2c1.105 0 2-0.895 2-2v-1h-20z" fill="#c0392b" transform="translate(0 1028.4)"/><g fill="#e74c3c"><rect height="6" width="12" x="6" y="1044.4"/><rect height="7" width="12" x="6" y="1029.4"/><rect height="10" width="12" x="6" y="1035.4"/><rect height="21" transform="translate(0 1028.4)" width="12" x="6" y="1"/></g><path d="m5 22 0.3397-20.196 17.32 10.392z" fill="#ecf0f1" transform="matrix(.28312 0 0 .29709 8.5844 1036.8)"/><path d="m4 1029.4c-1.1046 0-2 0.9-2 2v3 11 3c0 1.1 0.8954 2 2 2h2v-3-15-3h-2zm-0.5 2h1c0.2761 0 0.5 0.2 0.5 0.5v1c0 0.2-0.2239 0.5-0.5 0.5h-1c-0.2761 0-0.5-0.3-0.5-0.5v-1c0-0.3 0.2239-0.5 0.5-0.5zm0 3h1c0.2761 0 0.5 0.2 0.5 0.5v1c0 0.2-0.2239 0.5-0.5 0.5h-1c-0.2761 0-0.5-0.3-0.5-0.5v-1c0-0.3 0.2239-0.5 0.5-0.5zm0 3h1c0.2761 0 0.5 0.2 0.5 0.5v1c0 0.2-0.2239 0.5-0.5 0.5h-1c-0.2761 0-0.5-0.3-0.5-0.5v-1c0-0.3 0.2239-0.5 0.5-0.5zm0 3h1c0.2761 0 0.5 0.2 0.5 0.5v1c0 0.2-0.2239 0.5-0.5 0.5h-1c-0.2761 0-0.5-0.3-0.5-0.5v-1c0-0.3 0.2239-0.5 0.5-0.5zm0 3h1c0.2761 0 0.5 0.2 0.5 0.5v1c0 0.2-0.2239 0.5-0.5 0.5h-1c-0.2761 0-0.5-0.3-0.5-0.5v-1c0-0.3 0.2239-0.5 0.5-0.5zm0 3h1c0.2761 0 0.5 0.2 0.5 0.5v1c0 0.2-0.2239 0.5-0.5 0.5h-1c-0.2761 0-0.5-0.3-0.5-0.5v-1c0-0.3 0.2239-0.5 0.5-0.5z" fill="#c0392b"/><path d="m20 1029.4c1.105 0 2 0.9 2 2v3 11 3c0 1.1-0.895 2-2 2h-2v-3-15-3h2zm0.5 2h-1c-0.276 0-0.5 0.2-0.5 0.5v1c0 0.2 0.224 0.5 0.5 0.5h1c0.276 0 0.5-0.3 0.5-0.5v-1c0-0.3-0.224-0.5-0.5-0.5zm0 3h-1c-0.276 0-0.5 0.2-0.5 0.5v1c0 0.2 0.224 0.5 0.5 0.5h1c0.276 0 0.5-0.3 0.5-0.5v-1c0-0.3-0.224-0.5-0.5-0.5zm0 3h-1c-0.276 0-0.5 0.2-0.5 0.5v1c0 0.2 0.224 0.5 0.5 0.5h1c0.276 0 0.5-0.3 0.5-0.5v-1c0-0.3-0.224-0.5-0.5-0.5zm0 3h-1c-0.276 0-0.5 0.2-0.5 0.5v1c0 0.2 0.224 0.5 0.5 0.5h1c0.276 0 0.5-0.3 0.5-0.5v-1c0-0.3-0.224-0.5-0.5-0.5zm0 3h-1c-0.276 0-0.5 0.2-0.5 0.5v1c0 0.2 0.224 0.5 0.5 0.5h1c0.276 0 0.5-0.3 0.5-0.5v-1c0-0.3-0.224-0.5-0.5-0.5zm0 3h-1c-0.276 0-0.5 0.2-0.5 0.5v1c0 0.2 0.224 0.5 0.5 0.5h1c0.276 0 0.5-0.3 0.5-0.5v-1c0-0.3-0.224-0.5-0.5-0.5z" fill="#c0392b"/></g></svg>
|
||||
<svg width="32" xmlns="http://www.w3.org/2000/svg" height="32">
|
||||
<defs>
|
||||
<linearGradient id="a" y1="394.54" y2="325.98" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="matrix(.48276 0 0 .40835 384.64 384.68)">
|
||||
<stop stop-color="#ffffff" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000" transform="translate(-384.57-515.8)">
|
||||
<path fill="#db3333" d="m414.57 545.8h-28v-22h28v7z"/>
|
||||
<rect x="386.57" y="544.8" fill-opacity=".294" width="28" fill="#2e3132" height="1"/>
|
||||
<g fill="#ffffff">
|
||||
<rect x="386.57" y="523.8" fill-opacity=".41" width="28" height="1"/>
|
||||
<path opacity=".75" d="m387.57 525.8v2h2v-2zm24 0v2h2v-2zm-24 4v2h2v-2zm24 0v2h2v-2zm-24 4v2h2v-2zm24 0v2h2v-2zm-24 4v2h2v-2zm24 0v2h2v-2zm-24 4v2h2v-2zm24 0v2h2v-2z"/>
|
||||
<path opacity=".75" d="m405.55 527.8v2.821c-2.88.049-3.588 2.796-3.588 2.796h2.172v2.821h-3.368c-2.026 6.111-6.688 5.567-6.688 5.567v-2.821c0 0 2.958.537 4.467-4.849 1.887-6.737 7-6.334 7-6.334"/>
|
||||
</g>
|
||||
<path fill="url(#a)" d="m386.57 523.8v22h28v-22z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -1 +0,0 @@
|
|||
<?xml version="1.0" ?><svg height="24" version="1.1" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><g transform="translate(0 -1028.4)"><g fill="#2980b9"><path d="m12 1039.4c-3.0105 0-5.2407 0.1-6.7256 0.2-0.4788 0.1-0.9101 0.3-1.2672 0.6-0.357 0.3-0.6012 0.7-0.7148 1.1-0.211 0.9-0.2924 2.2-0.2924 4 0 1.9 0.0814 3.2 0.2924 4.1 0.1055 0.5 0.3578 0.8 0.7148 1.2 0.3652 0.3 0.764 0.5 1.2347 0.5 1.493 0.2 3.7476 0.3 6.7581 0.3 3.01 0 5.265-0.1 6.758-0.3 0.471 0 0.91-0.2 1.267-0.5 0.357-0.4 0.569-0.7 0.683-1.2 0.211-0.9 0.292-2.2 0.292-4.1 0-1.8-0.081-3.1-0.292-4-0.106-0.4-0.318-0.8-0.683-1.1-0.357-0.3-0.796-0.5-1.267-0.6-1.493-0.1-3.748-0.2-6.758-0.2z" fill="#c0392b"/><path d="m12 1038.4c-3.0105 0-5.2407 0.1-6.7256 0.2-0.4788 0.1-0.9101 0.3-1.2672 0.6-0.357 0.3-0.6012 0.7-0.7148 1.1-0.211 0.9-0.2924 2.2-0.2924 4 0 1.9 0.0814 3.2 0.2924 4.1 0.1055 0.5 0.3578 0.8 0.7148 1.2 0.3652 0.3 0.764 0.5 1.2347 0.5 1.493 0.2 3.7476 0.3 6.7581 0.3 3.01 0 5.265-0.1 6.758-0.3 0.471 0 0.91-0.2 1.267-0.5 0.357-0.4 0.569-0.7 0.683-1.2 0.211-0.9 0.292-2.2 0.292-4.1 0-1.8-0.081-3.1-0.292-4-0.106-0.4-0.318-0.8-0.683-1.1-0.357-0.3-0.796-0.5-1.267-0.6-1.493-0.1-3.748-0.2-6.758-0.2z" fill="#e74c3c"/><path d="m6.5 1l1.5 5v3h1v-3l1.5-5h-1l-1 3.3438-1-3.3438h-1zm6 2c-0.828 0-1.5 0.6716-1.5 1.5v3c0 0.8284 0.672 1.5 1.5 1.5s1.5-0.6716 1.5-1.5v-3c0-0.8284-0.672-1.5-1.5-1.5zm2.5 0v0.3438 1.6562 2.75c0 0.3821 0.036 0.664 0.094 0.8438 0.093 0.277 0.325 0.4063 0.656 0.4062 0.359 0.0001 0.869-0.2232 1.25-0.6875v0.25 0.4375h1v-6h-1v4.5 0.0938c-0.216 0.322-0.587 0.5-0.781 0.5-0.13 0-0.197-0.0852-0.219-0.25-0.007-0.015 0-0.1591 0-0.4063v-2.4375-1.6562-0.3438h-1zm-2.5 1c0.276 0 0.5 0.2239 0.5 0.5v3c0 0.2761-0.224 0.5-0.5 0.5s-0.5-0.2239-0.5-0.5v-3c0-0.2761 0.224-0.5 0.5-0.5z" fill="#202020" transform="translate(0 1028.4)"/><g fill="#ecf0f1"><rect height="1" width="4" x="4.5" y="1040.4"/><rect height="7" width="1" x="6" y="1041.4"/><path d="m8 1042.4v0.3 1.7 2.7c0 0.4 0.0362 0.7 0.0938 0.9 0.0934 0.2 0.3256 0.4 0.6562 0.4 0.3594 0 0.8691-0.3 1.25-0.7v0.2 0.5h1v-6h-1v4.5 0.1c-0.2156 0.3-0.5872 0.5-0.7812 0.5-0.1294 0-0.1972-0.1-0.2188-0.3-0.0072 0 0-0.2 0-0.4v-2.4-1.7-0.3h-1z"/><path d="m12 12v2 6h1v-0.438-0.25c0.381 0.465 0.891 0.688 1.25 0.688 0.331 0 0.563-0.129 0.656-0.406 0.058-0.18 0.094-0.462 0.094-0.844v-1.75-1.75c0-0.382-0.036-0.664-0.094-0.844-0.093-0.277-0.325-0.406-0.656-0.406-0.359 0-0.869 0.223-1.25 0.688v-0.688-2h-1zm1.781 2.906c0.13 0 0.197 0.085 0.219 0.25 0.007 0.015 0 0.159 0 0.406v1.438 1.438c0 0.247 0.007 0.391 0 0.406-0.022 0.165-0.089 0.25-0.219 0.25-0.194 0-0.565-0.178-0.781-0.5v-0.094-3.094c0.216-0.322 0.587-0.5 0.781-0.5z" transform="translate(0 1028.4)"/><path d="m17.5 14c-0.828 0-1.5 0.672-1.5 1.5v3c0 0.828 0.672 1.5 1.5 1.5s1.5-0.672 1.5-1.5v-0.5h-1v0.5c0 0.276-0.224 0.5-0.5 0.5s-0.5-0.224-0.5-0.5v-0.5-1h1 1v-1.5c0-0.828-0.672-1.5-1.5-1.5zm0 1c0.276 0 0.5 0.224 0.5 0.5v0.5h-1v-0.5c0-0.276 0.224-0.5 0.5-0.5z" transform="translate(0 1028.4)"/></g></g></g></svg>
|
Before Width: | Height: | Size: 3.1 KiB |