mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Fix and improve Streamlink code
Move streamlink code to its own file Fixes #275 Untested on linux, but should work decently there as well.
This commit is contained in:
parent
6c56e9cc82
commit
41fbcc738b
6 changed files with 237 additions and 98 deletions
|
@ -175,7 +175,8 @@ SOURCES += \
|
|||
src/widgets/window.cpp \
|
||||
src/providers/irc/ircaccount.cpp \
|
||||
src/providers/irc/ircserver.cpp \
|
||||
src/providers/irc/ircchannel2.cpp
|
||||
src/providers/irc/ircchannel2.cpp \
|
||||
src/util/streamlink.cpp
|
||||
|
||||
HEADERS += \
|
||||
src/precompiled_header.hpp \
|
||||
|
@ -287,7 +288,8 @@ HEADERS += \
|
|||
src/providers/irc/abstractircserver.hpp \
|
||||
src/providers/irc/ircaccount.hpp \
|
||||
src/providers/irc/ircserver.hpp \
|
||||
src/providers/irc/ircchannel2.hpp
|
||||
src/providers/irc/ircchannel2.hpp \
|
||||
src/util/streamlink.hpp
|
||||
|
||||
RESOURCES += \
|
||||
resources/resources.qrc
|
||||
|
|
182
src/util/streamlink.cpp
Normal file
182
src/util/streamlink.cpp
Normal file
|
@ -0,0 +1,182 @@
|
|||
#include "util/streamlink.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
#include "widgets/qualitypopup.hpp"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace chatterino {
|
||||
namespace streamlink {
|
||||
|
||||
namespace {
|
||||
|
||||
const char *GetBinaryName()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return "streamlink.exe";
|
||||
#else
|
||||
return "streamlink";
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *GetDefaultBinaryPath()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return "C:\\Program Files (x86)\\Streamlink\\bin\\streamlink.exe";
|
||||
#else
|
||||
return "/usr/bin/streamlink";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CheckStreamlinkPath(const QString &path)
|
||||
{
|
||||
QFileInfo fileinfo(path);
|
||||
|
||||
if (!fileinfo.exists()) {
|
||||
return false;
|
||||
// throw Exception(fS("Streamlink path ({}) is invalid, file does not exist", path));
|
||||
}
|
||||
|
||||
if (fileinfo.isDir() || !fileinfo.isExecutable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Make streamlink binary finder smarter
|
||||
QString GetStreamlinkBinaryPath()
|
||||
{
|
||||
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
|
||||
|
||||
QString settingPath = settings.streamlinkPath;
|
||||
|
||||
QStringList paths;
|
||||
paths << settingPath;
|
||||
paths << GetDefaultBinaryPath();
|
||||
#ifdef _WIN32
|
||||
paths << settingPath + "\\" + GetBinaryName();
|
||||
paths << settingPath + "\\bin\\" + GetBinaryName();
|
||||
#else
|
||||
paths << "/usr/local/bin/streamlink";
|
||||
paths << "/bin/streamlink";
|
||||
#endif
|
||||
|
||||
for (const auto &path : paths) {
|
||||
if (CheckStreamlinkPath(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
throw Exception("Unable to find streamlink binary. Install streamlink or set the binary path "
|
||||
"in the settings dialog.");
|
||||
}
|
||||
|
||||
void GetStreamQualities(const QString &channelURL, std::function<void(QStringList)> cb)
|
||||
{
|
||||
QString path = GetStreamlinkBinaryPath();
|
||||
|
||||
// XXX: Memory leak
|
||||
QProcess *p = new QProcess();
|
||||
|
||||
QObject::connect(p, static_cast<void (QProcess::*)(int)>(&QProcess::finished), [=](int) {
|
||||
QString lastLine = QString(p->readAllStandardOutput());
|
||||
lastLine = lastLine.trimmed().split('\n').last().trimmed();
|
||||
if (lastLine.startsWith("Available streams: ")) {
|
||||
QStringList options;
|
||||
QStringList split = lastLine.right(lastLine.length() - 19).split(", ");
|
||||
|
||||
for (int i = split.length() - 1; i >= 0; i--) {
|
||||
QString option = split.at(i);
|
||||
if (option.endsWith(" (worst)")) {
|
||||
options << option.left(option.length() - 8);
|
||||
} else if (option.endsWith(" (best)")) {
|
||||
options << option.left(option.length() - 7);
|
||||
} else {
|
||||
options << option;
|
||||
}
|
||||
}
|
||||
|
||||
cb(options);
|
||||
}
|
||||
});
|
||||
|
||||
p->start(path, {channelURL, "--default-stream=KKona"});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void OpenStreamlink(const QString &channelURL, const QString &quality, QStringList extraArguments)
|
||||
{
|
||||
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
|
||||
|
||||
QString path = GetStreamlinkBinaryPath();
|
||||
|
||||
QStringList arguments;
|
||||
|
||||
QString additionalOptions = settings.streamlinkOpts.getValue();
|
||||
if (!additionalOptions.isEmpty()) {
|
||||
arguments << settings.streamlinkOpts;
|
||||
}
|
||||
|
||||
arguments.append(extraArguments);
|
||||
|
||||
arguments << channelURL;
|
||||
|
||||
if (!quality.isEmpty()) {
|
||||
arguments << quality;
|
||||
}
|
||||
|
||||
QProcess::startDetached(path, arguments);
|
||||
}
|
||||
|
||||
void Start(const QString &channel)
|
||||
{
|
||||
QString channelURL = "twitch.tv/" + channel;
|
||||
|
||||
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
|
||||
|
||||
QString preferredQuality = settings.preferredQuality;
|
||||
preferredQuality = preferredQuality.toLower();
|
||||
|
||||
if (preferredQuality == "choose") {
|
||||
GetStreamQualities(channelURL, [=](QStringList qualityOptions) {
|
||||
widgets::QualityPopup::showDialog(channel, qualityOptions);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList args;
|
||||
|
||||
// Quality converted from Chatterino format to Streamlink format
|
||||
QString quality;
|
||||
// Streamlink qualities to exclude
|
||||
QString exclude;
|
||||
|
||||
if (preferredQuality == "high") {
|
||||
exclude = ">720p30";
|
||||
quality = "high,best";
|
||||
} else if (preferredQuality == "medium") {
|
||||
exclude = ">540p30";
|
||||
quality = "medium,best";
|
||||
} else if (preferredQuality == "low") {
|
||||
exclude = ">360p30";
|
||||
quality = "low,best";
|
||||
} else if (preferredQuality == "audio only") {
|
||||
quality = "audio,audio_only";
|
||||
} else {
|
||||
quality = "best";
|
||||
}
|
||||
if (!exclude.isEmpty()) {
|
||||
args << "--stream-sorting-excludes" << exclude;
|
||||
}
|
||||
|
||||
OpenStreamlink(channelURL, quality, args);
|
||||
}
|
||||
|
||||
} // namespace streamlink
|
||||
} // namespace chatterino
|
27
src/util/streamlink.hpp
Normal file
27
src/util/streamlink.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace chatterino {
|
||||
namespace streamlink {
|
||||
|
||||
class Exception : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
// Open streamlink for given channel, quality and extra arguments
|
||||
// the "Additional arguments" are fetched and added at the beginning of the streamlink call
|
||||
void OpenStreamlink(const QString &channelURL, const QString &quality,
|
||||
QStringList extraArguments = QStringList());
|
||||
|
||||
// Start opening streamlink for the given channel, reading settings like quality from settings
|
||||
// and opening a quality dialog if the quality is "Choose"
|
||||
void Start(const QString &channel);
|
||||
|
||||
} // namespace streamlink
|
||||
} // namespace chatterino
|
|
@ -1,14 +1,12 @@
|
|||
#include "qualitypopup.hpp"
|
||||
|
||||
#include <QProcess>
|
||||
#include "debug/log.hpp"
|
||||
#include "util/streamlink.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace widgets {
|
||||
|
||||
QualityPopup::QualityPopup(const QString &channel, const QString &path, QStringList options)
|
||||
: BaseWindow()
|
||||
, channel(channel)
|
||||
, path(path)
|
||||
QualityPopup::QualityPopup(const QString &_channelName, QStringList options)
|
||||
: channelName(_channelName)
|
||||
{
|
||||
this->ui.okButton.setText("OK");
|
||||
this->ui.cancelButton.setText("Cancel");
|
||||
|
@ -21,9 +19,7 @@ QualityPopup::QualityPopup(const QString &channel, const QString &path, QStringL
|
|||
this->ui.buttonBox.addButton(&this->ui.okButton, QDialogButtonBox::ButtonRole::AcceptRole);
|
||||
this->ui.buttonBox.addButton(&this->ui.cancelButton, QDialogButtonBox::ButtonRole::RejectRole);
|
||||
|
||||
for (int i = 0; i < options.length(); ++i) {
|
||||
this->ui.selector.addItem(options.at(i));
|
||||
}
|
||||
this->ui.selector.addItems(options);
|
||||
|
||||
this->ui.vbox.addWidget(&this->ui.selector);
|
||||
this->ui.vbox.addWidget(&this->ui.buttonBox);
|
||||
|
@ -31,9 +27,9 @@ QualityPopup::QualityPopup(const QString &channel, const QString &path, QStringL
|
|||
this->setLayout(&this->ui.vbox);
|
||||
}
|
||||
|
||||
void QualityPopup::showDialog(const QString &channel, const QString &path, QStringList options)
|
||||
void QualityPopup::showDialog(const QString &channelName, QStringList options)
|
||||
{
|
||||
QualityPopup *instance = new QualityPopup(channel, path, options);
|
||||
QualityPopup *instance = new QualityPopup(channelName, options);
|
||||
|
||||
instance->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
|
@ -45,9 +41,16 @@ void QualityPopup::showDialog(const QString &channel, const QString &path, QStri
|
|||
|
||||
void QualityPopup::okButtonClicked()
|
||||
{
|
||||
QString channelURL = "twitch.tv/" + this->channelName;
|
||||
|
||||
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
|
||||
QProcess::startDetached(this->path, {"twitch.tv/" + this->channel,
|
||||
this->ui.selector.currentText(), settings.streamlinkOpts});
|
||||
|
||||
try {
|
||||
streamlink::OpenStreamlink(channelURL, this->ui.selector.currentText());
|
||||
} catch (const streamlink::Exception &ex) {
|
||||
debug::Log("Exception caught trying to open streamlink: {}", ex.what());
|
||||
}
|
||||
|
||||
this->close();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ namespace widgets {
|
|||
class QualityPopup : public BaseWindow
|
||||
{
|
||||
public:
|
||||
QualityPopup(const QString &channel, const QString &path, QStringList options);
|
||||
static void showDialog(const QString &channel, const QString &path, QStringList options);
|
||||
QualityPopup(const QString &_channelName, QStringList options);
|
||||
static void showDialog(const QString &_channelName, QStringList options);
|
||||
|
||||
private:
|
||||
struct {
|
||||
|
@ -29,8 +29,7 @@ private:
|
|||
QPushButton cancelButton;
|
||||
} ui;
|
||||
|
||||
QString channel;
|
||||
QString path;
|
||||
QString channelName;
|
||||
|
||||
void okButtonClicked();
|
||||
void cancelButtonClicked();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "singletons/settingsmanager.hpp"
|
||||
#include "singletons/thememanager.hpp"
|
||||
#include "singletons/windowmanager.hpp"
|
||||
#include "util/streamlink.hpp"
|
||||
#include "util/urlfetch.hpp"
|
||||
#include "widgets/helper/searchpopup.hpp"
|
||||
#include "widgets/helper/shortcut.hpp"
|
||||
|
@ -21,13 +22,11 @@
|
|||
#include <QDesktopServices>
|
||||
#include <QDockWidget>
|
||||
#include <QDrag>
|
||||
#include <QFileInfo>
|
||||
#include <QFont>
|
||||
#include <QFontDatabase>
|
||||
#include <QListWidget>
|
||||
#include <QMimeData>
|
||||
#include <QPainter>
|
||||
#include <QProcess>
|
||||
#include <QShortcut>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
@ -364,83 +363,10 @@ void Split::doOpenPopupPlayer()
|
|||
|
||||
void Split::doOpenStreamlink()
|
||||
{
|
||||
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
|
||||
QString preferredQuality = settings.preferredQuality;
|
||||
preferredQuality = preferredQuality.toLower();
|
||||
// TODO(Confuseh): Default streamlink paths
|
||||
QString path = settings.streamlinkPath;
|
||||
QString channel = this->channelName.getValue();
|
||||
QFileInfo fileinfo = QFileInfo(path);
|
||||
|
||||
if (path.isEmpty()) {
|
||||
debug::Log("[Split:doOpenStreamlink] No streamlink path selected in Settings");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileinfo.exists()) {
|
||||
debug::Log("[Split:doOpenStreamlink] Streamlink path ({}) is invalid, file does not exist",
|
||||
path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileinfo.isDir() || !fileinfo.isExecutable()) {
|
||||
debug::Log("[Split:doOpenStreamlink] Streamlink path ({}) is invalid, it needs to point to "
|
||||
"the streamlink executable",
|
||||
path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (preferredQuality != "choose") {
|
||||
QStringList args = {"twitch.tv/" + channel};
|
||||
QString quality = "";
|
||||
QString exclude = "";
|
||||
if (preferredQuality == "high") {
|
||||
exclude = ">720p30";
|
||||
quality = "high,best";
|
||||
} else if (preferredQuality == "medium") {
|
||||
exclude = ">540p30";
|
||||
quality = "medium,best";
|
||||
} else if (preferredQuality == "low") {
|
||||
exclude = ">360p30";
|
||||
quality = "low,best";
|
||||
} else if (preferredQuality == "audio only") {
|
||||
quality = "audio,audio_only";
|
||||
} else {
|
||||
quality = "best";
|
||||
}
|
||||
if (quality != "")
|
||||
args << quality;
|
||||
if (exclude != "")
|
||||
args << "--stream-sorting-excludes" << exclude;
|
||||
args << settings.streamlinkOpts;
|
||||
QProcess::startDetached(path, args);
|
||||
} else {
|
||||
QProcess *p = new QProcess();
|
||||
// my god that signal though
|
||||
QObject::connect(p, static_cast<void (QProcess::*)(int)>(&QProcess::finished), this,
|
||||
[path, channel, p](int) {
|
||||
QString lastLine = QString(p->readAllStandardOutput());
|
||||
lastLine = lastLine.trimmed().split('\n').last().trimmed();
|
||||
if (lastLine.startsWith("Available streams: ")) {
|
||||
QStringList options;
|
||||
QStringList split =
|
||||
lastLine.right(lastLine.length() - 19).split(", ");
|
||||
|
||||
for (int i = split.length() - 1; i >= 0; i--) {
|
||||
QString option = split.at(i);
|
||||
if (option.endsWith(" (worst)")) {
|
||||
options << option.left(option.length() - 8);
|
||||
} else if (option.endsWith(" (best)")) {
|
||||
options << option.left(option.length() - 7);
|
||||
} else {
|
||||
options << option;
|
||||
}
|
||||
}
|
||||
|
||||
QualityPopup::showDialog(channel, path, options);
|
||||
}
|
||||
});
|
||||
p->start(path, {"twitch.tv/" + channel, "--default-stream=KKona"});
|
||||
try {
|
||||
streamlink::Start(this->channelName.getValue());
|
||||
} catch (const streamlink::Exception &ex) {
|
||||
debug::Log("Error in doOpenStreamlink: {}", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue