Add stream tagreader
This commit is contained in:
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -80,6 +80,7 @@ jobs:
|
|||||||
qt6-linguist-devel
|
qt6-linguist-devel
|
||||||
gtest
|
gtest
|
||||||
gmock
|
gmock
|
||||||
|
sparsehash-devel
|
||||||
- name: Install kdsingleapplication-qt6-devel
|
- name: Install kdsingleapplication-qt6-devel
|
||||||
if: matrix.opensuse_version == 'tumbleweed'
|
if: matrix.opensuse_version == 'tumbleweed'
|
||||||
run: zypper -n --gpg-auto-import-keys in kdsingleapplication-qt6-devel
|
run: zypper -n --gpg-auto-import-keys in kdsingleapplication-qt6-devel
|
||||||
@@ -193,6 +194,7 @@ jobs:
|
|||||||
kdsingleapplication-qt6-devel
|
kdsingleapplication-qt6-devel
|
||||||
gtest-devel
|
gtest-devel
|
||||||
gmock-devel
|
gmock-devel
|
||||||
|
sparsehash-devel
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -379,6 +381,7 @@ jobs:
|
|||||||
lib64qt6dbus-devel
|
lib64qt6dbus-devel
|
||||||
lib64qt6help-devel
|
lib64qt6help-devel
|
||||||
lib64qt6test-devel
|
lib64qt6test-devel
|
||||||
|
lib64sparsehash-devel
|
||||||
desktop-file-utils
|
desktop-file-utils
|
||||||
appstream-util
|
appstream-util
|
||||||
hicolor-icon-theme
|
hicolor-icon-theme
|
||||||
@@ -466,6 +469,7 @@ jobs:
|
|||||||
libmtp-dev
|
libmtp-dev
|
||||||
libgpod-dev
|
libgpod-dev
|
||||||
libxkbcommon-dev
|
libxkbcommon-dev
|
||||||
|
libsparsehash-dev
|
||||||
qt6-base-dev
|
qt6-base-dev
|
||||||
qt6-base-private-dev
|
qt6-base-private-dev
|
||||||
qt6-base-dev-tools
|
qt6-base-dev-tools
|
||||||
@@ -549,6 +553,7 @@ jobs:
|
|||||||
libmtp-dev
|
libmtp-dev
|
||||||
libgpod-dev
|
libgpod-dev
|
||||||
libxkbcommon-dev
|
libxkbcommon-dev
|
||||||
|
libsparsehash-dev
|
||||||
qt6-base-dev
|
qt6-base-dev
|
||||||
qt6-base-private-dev
|
qt6-base-private-dev
|
||||||
qt6-base-dev-tools
|
qt6-base-dev-tools
|
||||||
@@ -631,6 +636,7 @@ jobs:
|
|||||||
libmtp-dev
|
libmtp-dev
|
||||||
libgpod-dev
|
libgpod-dev
|
||||||
libxkbcommon-dev
|
libxkbcommon-dev
|
||||||
|
libsparsehash-dev
|
||||||
qt6-base-dev
|
qt6-base-dev
|
||||||
qt6-base-private-dev
|
qt6-base-private-dev
|
||||||
qt6-base-dev-tools
|
qt6-base-dev-tools
|
||||||
@@ -687,7 +693,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
mem: 4096
|
mem: 4096
|
||||||
prepare: pkg install -y git cmake pkgconf boost-libs alsa-lib glib qt6-base qt6-tools sqlite gstreamer1 gstreamer1-plugins chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf2 libgpod fftw3 icu kdsingleapplication googletest pulseaudio
|
prepare: pkg install -y git cmake pkgconf boost-libs alsa-lib glib qt6-base qt6-tools sqlite gstreamer1 gstreamer1-plugins chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf2 libgpod fftw3 icu kdsingleapplication googletest pulseaudio sparsehash
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
@@ -712,7 +718,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
mem: 4096
|
mem: 4096
|
||||||
prepare: pkg_add git cmake pkgconf boost glib2 qt6-qtbase qt6-qttools sqlite gstreamer1 gstreamer1-plugins-base chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf libgpod fftw3 icu4c kdsingleapplication pulseaudio
|
prepare: pkg_add git cmake pkgconf boost glib2 qt6-qtbase qt6-qttools sqlite gstreamer1 gstreamer1-plugins-base chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf libgpod fftw3 icu4c kdsingleapplication pulseaudio sparsehash
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
export LDFLAGS="-L/usr/local/lib"
|
export LDFLAGS="-L/usr/local/lib"
|
||||||
|
|||||||
@@ -210,6 +210,8 @@ endif()
|
|||||||
|
|
||||||
find_package(GTest)
|
find_package(GTest)
|
||||||
|
|
||||||
|
pkg_check_modules(LIBSPARSEHASH IMPORTED_TARGET libsparsehash)
|
||||||
|
|
||||||
set(QT_VERSION_MAJOR 6)
|
set(QT_VERSION_MAJOR 6)
|
||||||
set(QT_MIN_VERSION 6.4.0)
|
set(QT_MIN_VERSION 6.4.0)
|
||||||
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR})
|
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR})
|
||||||
@@ -371,6 +373,10 @@ optional_component(QPA_QPLATFORMNATIVEINTERFACE ON "QPA Platform Native Interfac
|
|||||||
DEPENDS "Qt Gui Private" QT_GUI_PRIVATE_FOUND
|
DEPENDS "Qt Gui Private" QT_GUI_PRIVATE_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
|
optional_component(STREAMTAGREADER ON "Stream tagreader"
|
||||||
|
DEPENDS "sparsehash" LIBSPARSEHASH_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
||||||
set(HAVE_CHROMAPRINT ON)
|
set(HAVE_CHROMAPRINT ON)
|
||||||
endif()
|
endif()
|
||||||
@@ -1233,6 +1239,11 @@ optional_source(WIN32
|
|||||||
src/core/windows7thumbbar.h
|
src/core/windows7thumbbar.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
optional_source(HAVE_STREAMTAGREADER
|
||||||
|
SOURCES src/tagreader/streamtagreader.cpp src/tagreader/tagreaderreadstreamrequest.cpp src/tagreader/tagreaderreadstreamreply.cpp
|
||||||
|
HEADERS src/tagreader/tagreaderreadstreamreply.h
|
||||||
|
)
|
||||||
|
|
||||||
if(HAVE_GLOBALSHORTCUTS)
|
if(HAVE_GLOBALSHORTCUTS)
|
||||||
|
|
||||||
optional_source(HAVE_GLOBALSHORTCUTS
|
optional_source(HAVE_GLOBALSHORTCUTS
|
||||||
@@ -1521,6 +1532,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||||||
$<$<BOOL:${HAVE_QPA_QPLATFORMNATIVEINTERFACE}>:Qt${QT_VERSION_MAJOR}::GuiPrivate>
|
$<$<BOOL:${HAVE_QPA_QPLATFORMNATIVEINTERFACE}>:Qt${QT_VERSION_MAJOR}::GuiPrivate>
|
||||||
ICU::uc
|
ICU::uc
|
||||||
ICU::i18n
|
ICU::i18n
|
||||||
|
$<$<BOOL:${HAVE_STREAMTAGREADER}>:PkgConfig::LIBSPARSEHASH>
|
||||||
$<$<BOOL:${HAVE_ALSA}>:ALSA::ALSA>
|
$<$<BOOL:${HAVE_ALSA}>:ALSA::ALSA>
|
||||||
$<$<BOOL:${HAVE_PULSE}>:PkgConfig::LIBPULSE>
|
$<$<BOOL:${HAVE_PULSE}>:PkgConfig::LIBPULSE>
|
||||||
$<$<BOOL:${HAVE_CHROMAPRINT}>:PkgConfig::CHROMAPRINT>
|
$<$<BOOL:${HAVE_CHROMAPRINT}>:PkgConfig::CHROMAPRINT>
|
||||||
|
|||||||
3
debian/control
vendored
3
debian/control
vendored
@@ -29,7 +29,8 @@ Build-Depends: debhelper-compat (= 12),
|
|||||||
libmtp-dev,
|
libmtp-dev,
|
||||||
libchromaprint-dev,
|
libchromaprint-dev,
|
||||||
libfftw3-dev,
|
libfftw3-dev,
|
||||||
libebur128-dev
|
libebur128-dev,
|
||||||
|
libsparsehash-dev
|
||||||
Standards-Version: 4.7.0
|
Standards-Version: 4.7.0
|
||||||
|
|
||||||
Package: strawberry
|
Package: strawberry
|
||||||
|
|||||||
1
dist/unix/strawberry.spec.in
vendored
1
dist/unix/strawberry.spec.in
vendored
@@ -63,6 +63,7 @@ BuildRequires: pkgconfig(libcdio)
|
|||||||
BuildRequires: pkgconfig(libebur128)
|
BuildRequires: pkgconfig(libebur128)
|
||||||
BuildRequires: pkgconfig(libgpod-1.0)
|
BuildRequires: pkgconfig(libgpod-1.0)
|
||||||
BuildRequires: pkgconfig(libmtp)
|
BuildRequires: pkgconfig(libmtp)
|
||||||
|
BuildRequires: pkgconfig(libsparsehash)
|
||||||
BuildRequires: cmake(GTest)
|
BuildRequires: cmake(GTest)
|
||||||
BuildRequires: pkgconfig(gmock)
|
BuildRequires: pkgconfig(gmock)
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#cmakedefine HAVE_GLOBALSHORTCUTS
|
#cmakedefine HAVE_GLOBALSHORTCUTS
|
||||||
#cmakedefine HAVE_X11_GLOBALSHORTCUTS
|
#cmakedefine HAVE_X11_GLOBALSHORTCUTS
|
||||||
#cmakedefine HAVE_KGLOBALACCEL_GLOBALSHORTCUTS
|
#cmakedefine HAVE_KGLOBALACCEL_GLOBALSHORTCUTS
|
||||||
|
#cmakedefine HAVE_STREAMTAGREADER
|
||||||
#cmakedefine HAVE_SUBSONIC
|
#cmakedefine HAVE_SUBSONIC
|
||||||
#cmakedefine HAVE_TIDAL
|
#cmakedefine HAVE_TIDAL
|
||||||
#cmakedefine HAVE_SPOTIFY
|
#cmakedefine HAVE_SPOTIFY
|
||||||
|
|||||||
216
src/tagreader/streamtagreader.cpp
Normal file
216
src/tagreader/streamtagreader.cpp
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QSslError>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "core/networkaccessmanager.h"
|
||||||
|
|
||||||
|
#include "streamtagreader.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr TagLibLengthType kTagLibPrefixCacheBytes = 64UL * 1024UL;
|
||||||
|
constexpr TagLibLengthType kTagLibSuffixCacheBytes = 8UL * 1024UL;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
StreamTagReader::StreamTagReader(const QUrl &url,
|
||||||
|
const QString &filename,
|
||||||
|
const quint64 length,
|
||||||
|
const QString &token_type,
|
||||||
|
const QString &access_token)
|
||||||
|
: url_(url),
|
||||||
|
filename_(filename),
|
||||||
|
encoded_filename_(filename_.toUtf8()),
|
||||||
|
length_(static_cast<TagLibLengthType>(length)),
|
||||||
|
token_type_(token_type),
|
||||||
|
access_token_(access_token),
|
||||||
|
network_(new NetworkAccessManager),
|
||||||
|
cursor_(0),
|
||||||
|
cache_(length),
|
||||||
|
num_requests_(0) {
|
||||||
|
|
||||||
|
network_->setAutoDeleteReplies(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TagLib::FileName StreamTagReader::name() const { return encoded_filename_.data(); }
|
||||||
|
|
||||||
|
TagLib::ByteVector StreamTagReader::readBlock(const TagLibLengthType length) {
|
||||||
|
|
||||||
|
const uint start = static_cast<uint>(cursor_);
|
||||||
|
const uint end = static_cast<uint>(std::min(cursor_ + length - 1, length_ - 1));
|
||||||
|
|
||||||
|
if (end < start) {
|
||||||
|
return TagLib::ByteVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CheckCache(start, end)) {
|
||||||
|
const TagLib::ByteVector cached = GetCache(start, end);
|
||||||
|
cursor_ += static_cast<TagLibLengthType>(cached.size());
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkRequest request(url_);
|
||||||
|
if (!token_type_.isEmpty() && !access_token_.isEmpty()) {
|
||||||
|
request.setRawHeader("Authorization", token_type_.toUtf8() + " " + access_token_.toUtf8());
|
||||||
|
}
|
||||||
|
request.setRawHeader("Range", QStringLiteral("bytes=%1-%2").arg(start).arg(end).toUtf8());
|
||||||
|
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||||
|
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||||
|
|
||||||
|
QNetworkReply *reply = network_->get(request);
|
||||||
|
++num_requests_;
|
||||||
|
|
||||||
|
QEventLoop event_loop;
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &event_loop, &QEventLoop::quit);
|
||||||
|
event_loop.exec();
|
||||||
|
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qLog(Error) << "Unable to get tags from stream for" << url_ << "got error:" << reply->errorString();
|
||||||
|
return TagLib::ByteVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).isValid()) {
|
||||||
|
const int http_status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
if (http_status_code >= 400) {
|
||||||
|
qLog(Error) << "Unable to get tags from stream for" << url_ << "received HTTP code" << http_status_code;
|
||||||
|
return TagLib::ByteVector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray data = reply->readAll();
|
||||||
|
const TagLib::ByteVector bytes(data.data(), static_cast<uint>(data.size()));
|
||||||
|
cursor_ += static_cast<TagLibLengthType>(data.size());
|
||||||
|
|
||||||
|
FillCache(start, bytes);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamTagReader::writeBlock(const TagLib::ByteVector &data) {
|
||||||
|
Q_UNUSED(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamTagReader::insert(const TagLib::ByteVector &data, const TagLibUOffsetType start, const TagLibLengthType replace) {
|
||||||
|
Q_UNUSED(data)
|
||||||
|
Q_UNUSED(start)
|
||||||
|
Q_UNUSED(replace)
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamTagReader::removeBlock(const TagLibUOffsetType start, const TagLibLengthType length) {
|
||||||
|
Q_UNUSED(start)
|
||||||
|
Q_UNUSED(length)
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StreamTagReader::readOnly() const { return true; }
|
||||||
|
|
||||||
|
bool StreamTagReader::isOpen() const { return true; }
|
||||||
|
|
||||||
|
void StreamTagReader::seek(const TagLibOffsetType offset, const TagLib::IOStream::Position position) {
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case TagLib::IOStream::Beginning:
|
||||||
|
cursor_ = offset;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TagLib::IOStream::Current:
|
||||||
|
cursor_ = std::min(cursor_ + static_cast<TagLibLengthType>(offset), length_);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TagLib::IOStream::End:
|
||||||
|
// This should really not have qAbs(), but OGG reading needs it.
|
||||||
|
cursor_ = std::max(static_cast<TagLibLengthType>(0), length_ - qAbs(static_cast<TagLibLengthType>(offset)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamTagReader::clear() { cursor_ = 0; }
|
||||||
|
|
||||||
|
TagLibOffsetType StreamTagReader::tell() const { return static_cast<TagLibOffsetType>(cursor_); }
|
||||||
|
|
||||||
|
TagLibOffsetType StreamTagReader::length() { return static_cast<TagLibOffsetType>(length_); }
|
||||||
|
|
||||||
|
void StreamTagReader::truncate(const TagLibOffsetType length) {
|
||||||
|
Q_UNUSED(length)
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StreamTagReader::CheckCache(const uint start, const uint end) {
|
||||||
|
|
||||||
|
for (uint i = start; i <= end; ++i) {
|
||||||
|
if (!cache_.test(i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamTagReader::FillCache(const uint start, const TagLib::ByteVector &data) {
|
||||||
|
|
||||||
|
for (uint i = 0; i < data.size(); ++i) {
|
||||||
|
cache_.set(start + i, data[static_cast<int>(i)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TagLib::ByteVector StreamTagReader::GetCache(const uint start, const uint end) {
|
||||||
|
|
||||||
|
const uint size = end - start + 1U;
|
||||||
|
TagLib::ByteVector data(size);
|
||||||
|
for (uint i = 0; i < size; ++i) {
|
||||||
|
data[static_cast<int>(i)] = cache_.get(start + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamTagReader::PreCache() {
|
||||||
|
|
||||||
|
// For reading the tags of an MP3, TagLib tends to request:
|
||||||
|
// 1. The first 1024 bytes
|
||||||
|
// 2. Somewhere between the first 2KB and first 60KB
|
||||||
|
// 3. The last KB or two.
|
||||||
|
// 4. Somewhere in the first 64KB again
|
||||||
|
//
|
||||||
|
// OGG Vorbis may read the last 4KB.
|
||||||
|
//
|
||||||
|
// So, if we precache the first 64KB and the last 8KB we should be sorted :-)
|
||||||
|
// Ideally, we would use bytes=0-655364,-8096 but Google Drive does not seem
|
||||||
|
// to support multipart byte ranges yet so we have to make do with two requests.
|
||||||
|
|
||||||
|
seek(0, TagLib::IOStream::Beginning);
|
||||||
|
readBlock(kTagLibPrefixCacheBytes);
|
||||||
|
seek(kTagLibSuffixCacheBytes, TagLib::IOStream::End);
|
||||||
|
readBlock(kTagLibSuffixCacheBytes);
|
||||||
|
clear();
|
||||||
|
|
||||||
|
}
|
||||||
94
src/tagreader/streamtagreader.h
Normal file
94
src/tagreader/streamtagreader.h
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STREAMTAGREADER_H
|
||||||
|
#define STREAMTAGREADER_H
|
||||||
|
|
||||||
|
#include <taglib/tiostream.h>
|
||||||
|
#include <google/sparsetable>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "includes/scoped_ptr.h"
|
||||||
|
#include "core/networkaccessmanager.h"
|
||||||
|
|
||||||
|
#if TAGLIB_MAJOR_VERSION >= 2
|
||||||
|
using TagLibLengthType = size_t;
|
||||||
|
using TagLibUOffsetType = TagLib::offset_t;
|
||||||
|
using TagLibOffsetType = TagLib::offset_t;
|
||||||
|
#else
|
||||||
|
using TagLibLengthType = ulong;
|
||||||
|
using TagLibUOffsetType = ulong;
|
||||||
|
using TagLibOffsetType = long;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class StreamTagReader : public TagLib::IOStream {
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit StreamTagReader(const QUrl &url,
|
||||||
|
const QString &filename,
|
||||||
|
const quint64 length,
|
||||||
|
const QString &token_type,
|
||||||
|
const QString &access_token);
|
||||||
|
|
||||||
|
virtual TagLib::FileName name() const override;
|
||||||
|
virtual TagLib::ByteVector readBlock(const TagLibLengthType length) override;
|
||||||
|
virtual void writeBlock(const TagLib::ByteVector &data) override;
|
||||||
|
virtual void insert(const TagLib::ByteVector &data, const TagLibUOffsetType start, const TagLibLengthType replace) override;
|
||||||
|
virtual void removeBlock(const TagLibUOffsetType start, const TagLibLengthType length) override;
|
||||||
|
virtual bool readOnly() const override;
|
||||||
|
virtual bool isOpen() const override;
|
||||||
|
virtual void seek(const TagLibOffsetType offset, const TagLib::IOStream::Position position) override;
|
||||||
|
virtual void clear() override;
|
||||||
|
virtual TagLibOffsetType tell() const override;
|
||||||
|
virtual TagLibOffsetType length() override;
|
||||||
|
virtual void truncate(const TagLibOffsetType length) override;
|
||||||
|
|
||||||
|
google::sparsetable<char>::size_type cached_bytes() const {
|
||||||
|
return cache_.num_nonempty();
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_requests() const { return num_requests_; }
|
||||||
|
|
||||||
|
void PreCache();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool CheckCache(const uint start, const uint end);
|
||||||
|
void FillCache(const uint start, const TagLib::ByteVector &data);
|
||||||
|
TagLib::ByteVector GetCache(const uint start, const uint end);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QUrl url_;
|
||||||
|
const QString filename_;
|
||||||
|
const QByteArray encoded_filename_;
|
||||||
|
const TagLibLengthType length_;
|
||||||
|
const QString token_type_;
|
||||||
|
const QString access_token_;
|
||||||
|
|
||||||
|
ScopedPtr<NetworkAccessManager> network_;
|
||||||
|
|
||||||
|
TagLibLengthType cursor_;
|
||||||
|
google::sparsetable<char> cache_;
|
||||||
|
int num_requests_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // STREAMTAGREADER_H
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -41,6 +41,10 @@ class TagReaderBase {
|
|||||||
virtual TagReaderResult IsMediaFile(const QString &filename) const = 0;
|
virtual TagReaderResult IsMediaFile(const QString &filename) const = 0;
|
||||||
|
|
||||||
virtual TagReaderResult ReadFile(const QString &filename, Song *song) const = 0;
|
virtual TagReaderResult ReadFile(const QString &filename, Song *song) const = 0;
|
||||||
|
#ifdef HAVE_STREAMTAGREADER
|
||||||
|
virtual TagReaderResult ReadStream(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token, Song *song) const = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
virtual TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const = 0;
|
virtual TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const = 0;
|
||||||
|
|
||||||
virtual TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const = 0;
|
virtual TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const = 0;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QScopeGuard>
|
#include <QScopeGuard>
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
#include "tagreaderrequest.h"
|
#include "tagreaderrequest.h"
|
||||||
#include "tagreaderismediafilerequest.h"
|
#include "tagreaderismediafilerequest.h"
|
||||||
#include "tagreaderreadfilerequest.h"
|
#include "tagreaderreadfilerequest.h"
|
||||||
|
#include "tagreaderreadstreamrequest.h"
|
||||||
#include "tagreaderwritefilerequest.h"
|
#include "tagreaderwritefilerequest.h"
|
||||||
#include "tagreaderloadcoverdatarequest.h"
|
#include "tagreaderloadcoverdatarequest.h"
|
||||||
#include "tagreaderloadcoverimagerequest.h"
|
#include "tagreaderloadcoverimagerequest.h"
|
||||||
@@ -45,6 +47,7 @@
|
|||||||
#include "tagreadersaveratingrequest.h"
|
#include "tagreadersaveratingrequest.h"
|
||||||
#include "tagreaderreply.h"
|
#include "tagreaderreply.h"
|
||||||
#include "tagreaderreadfilereply.h"
|
#include "tagreaderreadfilereply.h"
|
||||||
|
#include "tagreaderreadstreamreply.h"
|
||||||
#include "tagreaderloadcoverdatareply.h"
|
#include "tagreaderloadcoverdatareply.h"
|
||||||
#include "tagreaderloadcoverimagereply.h"
|
#include "tagreaderloadcoverimagereply.h"
|
||||||
|
|
||||||
@@ -174,6 +177,17 @@ void TagReaderClient::ProcessRequest(TagReaderRequestPtr request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef HAVE_STREAMTAGREADER
|
||||||
|
else if (TagReaderReadStreamRequestPtr read_stream_request = dynamic_pointer_cast<TagReaderReadStreamRequest>(request)) {
|
||||||
|
Song song;
|
||||||
|
result = ReadStreamBlocking(read_stream_request->url, read_stream_request->filename, read_stream_request->size, read_stream_request->mtime, read_stream_request->token_type, read_stream_request->access_token, &song);
|
||||||
|
if (result.success()) {
|
||||||
|
if (TagReaderReadStreamReplyPtr read_stream_reply = qSharedPointerDynamicCast<TagReaderReadStreamReply>(reply)) {
|
||||||
|
read_stream_reply->set_song(song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // HAVE_STREAMTAGREADER
|
||||||
else if (TagReaderWriteFileRequestPtr write_file_request = dynamic_pointer_cast<TagReaderWriteFileRequest>(request)) {
|
else if (TagReaderWriteFileRequestPtr write_file_request = dynamic_pointer_cast<TagReaderWriteFileRequest>(request)) {
|
||||||
result = WriteFileBlocking(write_file_request->filename, write_file_request->song, write_file_request->save_tags_options, write_file_request->save_tag_cover_data);
|
result = WriteFileBlocking(write_file_request->filename, write_file_request->song, write_file_request->save_tags_options, write_file_request->save_tag_cover_data);
|
||||||
}
|
}
|
||||||
@@ -257,6 +271,33 @@ TagReaderReadFileReplyPtr TagReaderClient::ReadFileAsync(const QString &filename
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_STREAMTAGREADER
|
||||||
|
TagReaderResult TagReaderClient::ReadStreamBlocking(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token, Song *song) {
|
||||||
|
|
||||||
|
return tagreader_.ReadStream(url, filename, size, mtime, token_type, access_token, song);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TagReaderReadStreamReplyPtr TagReaderClient::ReadStreamAsync(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token) {
|
||||||
|
|
||||||
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
|
|
||||||
|
TagReaderReadStreamReplyPtr reply = TagReaderReply::Create<TagReaderReadStreamReply>(url, filename);
|
||||||
|
|
||||||
|
TagReaderReadStreamRequestPtr request = TagReaderReadStreamRequest::Create(url, filename);
|
||||||
|
request->reply = reply;
|
||||||
|
request->size = size;
|
||||||
|
request->mtime = mtime;
|
||||||
|
request->token_type = token_type;
|
||||||
|
request->access_token = access_token;
|
||||||
|
|
||||||
|
EnqueueRequest(request);
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif // HAVE_STREAMTAGREADER
|
||||||
|
|
||||||
TagReaderResult TagReaderClient::WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) {
|
TagReaderResult TagReaderClient::WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) {
|
||||||
|
|
||||||
return tagreader_.WriteFile(filename, song, save_tags_options, save_tag_cover_data);
|
return tagreader_.WriteFile(filename, song, save_tags_options, save_tag_cover_data);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
|
||||||
#include "includes/mutex_protected.h"
|
#include "includes/mutex_protected.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
@@ -39,6 +38,7 @@
|
|||||||
#include "tagreaderresult.h"
|
#include "tagreaderresult.h"
|
||||||
#include "tagreaderreply.h"
|
#include "tagreaderreply.h"
|
||||||
#include "tagreaderreadfilereply.h"
|
#include "tagreaderreadfilereply.h"
|
||||||
|
#include "tagreaderreadstreamreply.h"
|
||||||
#include "tagreaderloadcoverdatareply.h"
|
#include "tagreaderloadcoverdatareply.h"
|
||||||
#include "tagreaderloadcoverimagereply.h"
|
#include "tagreaderloadcoverimagereply.h"
|
||||||
#include "savetagsoptions.h"
|
#include "savetagsoptions.h"
|
||||||
@@ -67,6 +67,11 @@ class TagReaderClient : public QObject {
|
|||||||
TagReaderResult ReadFileBlocking(const QString &filename, Song *song);
|
TagReaderResult ReadFileBlocking(const QString &filename, Song *song);
|
||||||
[[nodiscard]] TagReaderReadFileReplyPtr ReadFileAsync(const QString &filename);
|
[[nodiscard]] TagReaderReadFileReplyPtr ReadFileAsync(const QString &filename);
|
||||||
|
|
||||||
|
#ifdef HAVE_STREAMTAGREADER
|
||||||
|
TagReaderResult ReadStreamBlocking(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token, Song *song);
|
||||||
|
[[nodiscard]] TagReaderReadStreamReplyPtr ReadStreamAsync(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token);
|
||||||
|
#endif
|
||||||
|
|
||||||
TagReaderResult WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
|
TagReaderResult WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
|
||||||
[[nodiscard]] TagReaderReplyPtr WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
|
[[nodiscard]] TagReaderReplyPtr WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
|
||||||
|
|
||||||
|
|||||||
@@ -301,6 +301,22 @@ TagReaderResult TagReaderGME::ReadFile(const QString &filename, Song *song) cons
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_STREAMTAGREADER
|
||||||
|
TagReaderResult TagReaderGME::ReadStream(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token, Song *song) const {
|
||||||
|
|
||||||
|
Q_UNUSED(url);
|
||||||
|
Q_UNUSED(filename);
|
||||||
|
Q_UNUSED(size);
|
||||||
|
Q_UNUSED(mtime);
|
||||||
|
Q_UNUSED(token_type);
|
||||||
|
Q_UNUSED(access_token);
|
||||||
|
Q_UNUSED(song);
|
||||||
|
|
||||||
|
return TagReaderResult::ErrorCode::Unsupported;
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TagReaderResult TagReaderGME::WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const {
|
TagReaderResult TagReaderGME::WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const {
|
||||||
|
|
||||||
Q_UNUSED(filename);
|
Q_UNUSED(filename);
|
||||||
|
|||||||
@@ -102,6 +102,11 @@ class TagReaderGME : public TagReaderBase {
|
|||||||
TagReaderResult IsMediaFile(const QString &filename) const override;
|
TagReaderResult IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
TagReaderResult ReadFile(const QString &filename, Song *song) const override;
|
TagReaderResult ReadFile(const QString &filename, Song *song) const override;
|
||||||
|
|
||||||
|
#ifdef HAVE_STREAMTAGREADER
|
||||||
|
TagReaderResult ReadStream(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token, Song *song) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const override;
|
TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||||
|
|
||||||
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
||||||
|
|||||||
44
src/tagreader/tagreaderreadstreamreply.cpp
Normal file
44
src/tagreader/tagreaderreadstreamreply.cpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "tagreaderreadstreamreply.h"
|
||||||
|
|
||||||
|
TagReaderReadStreamReply::TagReaderReadStreamReply(const QUrl &_url, const QString &_filename, QObject *parent)
|
||||||
|
: TagReaderReply(_filename, parent), url_(_url) {}
|
||||||
|
|
||||||
|
void TagReaderReadStreamReply::Finish() {
|
||||||
|
|
||||||
|
qLog(Debug) << "Finishing tagreader reply for" << url_;
|
||||||
|
|
||||||
|
finished_ = true;
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, &TagReaderReadStreamReply::EmitFinished, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagReaderReadStreamReply::EmitFinished() {
|
||||||
|
|
||||||
|
Q_EMIT TagReaderReply::Finished(filename_, result_);
|
||||||
|
Q_EMIT TagReaderReadStreamReply::Finished(url_, song_, result_);
|
||||||
|
|
||||||
|
}
|
||||||
55
src/tagreader/tagreaderreadstreamreply.h
Normal file
55
src/tagreader/tagreaderreadstreamreply.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TAGREADERREADSTREAMREPLY_H
|
||||||
|
#define TAGREADERREADSTREAMREPLY_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
#include "tagreaderreply.h"
|
||||||
|
#include "tagreaderresult.h"
|
||||||
|
|
||||||
|
class TagReaderReadStreamReply : public TagReaderReply {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TagReaderReadStreamReply(const QUrl &url, const QString &_filename, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void Finish() override;
|
||||||
|
|
||||||
|
Song song() const { return song_; }
|
||||||
|
void set_song(const Song &song) { song_ = song; }
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void Finished(const QUrl &url, const Song &song, const TagReaderResult &result);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void EmitFinished() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Song song_;
|
||||||
|
QUrl url_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using TagReaderReadStreamReplyPtr = QSharedPointer<TagReaderReadStreamReply>;
|
||||||
|
|
||||||
|
#endif // TAGREADERREADSTREAMREPLY_H
|
||||||
25
src/tagreader/tagreaderreadstreamrequest.cpp
Normal file
25
src/tagreader/tagreaderreadstreamrequest.cpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "tagreaderreadstreamrequest.h"
|
||||||
|
|
||||||
|
TagReaderReadStreamRequest::TagReaderReadStreamRequest(const QUrl &_url, const QString &_filename) : TagReaderRequest(_url, _filename), size(0), mtime(0) {}
|
||||||
43
src/tagreader/tagreaderreadstreamrequest.h
Normal file
43
src/tagreader/tagreaderreadstreamrequest.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TAGREADERREADSTREAMREQUEST_H
|
||||||
|
#define TAGREADERREADSTREAMREQUEST_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "includes/shared_ptr.h"
|
||||||
|
#include "tagreaderrequest.h"
|
||||||
|
|
||||||
|
using std::make_shared;
|
||||||
|
|
||||||
|
class TagReaderReadStreamRequest : public TagReaderRequest {
|
||||||
|
public:
|
||||||
|
explicit TagReaderReadStreamRequest(const QUrl &_url, const QString &_filename);
|
||||||
|
static SharedPtr<TagReaderReadStreamRequest> Create(const QUrl &_url, const QString &_filename) { return make_shared<TagReaderReadStreamRequest>(_url, _filename); }
|
||||||
|
quint64 size;
|
||||||
|
quint64 mtime;
|
||||||
|
QString token_type;
|
||||||
|
QString access_token;
|
||||||
|
};
|
||||||
|
|
||||||
|
using TagReaderReadStreamRequestPtr = SharedPtr<TagReaderReadStreamRequest>;
|
||||||
|
|
||||||
|
#endif // TAGREADERREADSTREAMREQUEST_H
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2024-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2024-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
|
||||||
#include "tagreaderresult.h"
|
#include "tagreaderresult.h"
|
||||||
@@ -38,6 +39,11 @@ class TagReaderReply : public QObject {
|
|||||||
return QSharedPointer<T>(new T(filename));
|
return QSharedPointer<T>(new T(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static QSharedPointer<T> Create(const QUrl &url, const QString &filename) {
|
||||||
|
return QSharedPointer<T>(new T(url, filename));
|
||||||
|
}
|
||||||
|
|
||||||
QString filename() const { return filename_; }
|
QString filename() const { return filename_; }
|
||||||
|
|
||||||
TagReaderResult result() const { return result_; }
|
TagReaderResult result() const { return result_; }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2024-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,8 +27,19 @@ TagReaderRequest::TagReaderRequest(const QString &_filename) : filename(_filenam
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TagReaderRequest::~TagReaderRequest() {
|
TagReaderRequest::TagReaderRequest(const QUrl &_url, const QString &_filename) : filename(_filename), url(_url) {
|
||||||
|
|
||||||
qLog(Debug) << "Tagreader request for" << filename << "deleted";
|
qLog(Debug) << "New tagreader request for" << filename << url;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TagReaderRequest::~TagReaderRequest() {
|
||||||
|
|
||||||
|
if (url.isValid()) {
|
||||||
|
qLog(Debug) << "Tagreader request for" << filename << url << "deleted";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qLog(Debug) << "Tagreader request for" << filename << "deleted";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2024-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
#define TAGREADERREQUEST_H
|
#define TAGREADERREQUEST_H
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
#include "tagreaderreply.h"
|
#include "tagreaderreply.h"
|
||||||
@@ -28,8 +29,10 @@
|
|||||||
class TagReaderRequest {
|
class TagReaderRequest {
|
||||||
public:
|
public:
|
||||||
explicit TagReaderRequest(const QString &_filename);
|
explicit TagReaderRequest(const QString &_filename);
|
||||||
|
explicit TagReaderRequest(const QUrl &_url, const QString &_filename);
|
||||||
virtual ~TagReaderRequest();
|
virtual ~TagReaderRequest();
|
||||||
QString filename;
|
QString filename;
|
||||||
|
QUrl url;
|
||||||
TagReaderReplyPtr reply;
|
TagReaderReplyPtr reply;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2013, David Sansome <me@davidsansome.com>
|
* Copyright 2013, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "tagreadertaglib.h"
|
#include "tagreadertaglib.h"
|
||||||
|
#include "streamtagreader.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -94,13 +95,14 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
|
||||||
|
#include "includes/scoped_ptr.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "constants/timeconstants.h"
|
#include "constants/timeconstants.h"
|
||||||
|
|
||||||
#include "albumcovertagdata.h"
|
#include "albumcovertagdata.h"
|
||||||
|
|
||||||
using std::unique_ptr;
|
using std::make_unique;
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
#undef TStringToQString
|
#undef TStringToQString
|
||||||
@@ -240,6 +242,7 @@ class FileRefFactory {
|
|||||||
FileRefFactory() = default;
|
FileRefFactory() = default;
|
||||||
virtual ~FileRefFactory() = default;
|
virtual ~FileRefFactory() = default;
|
||||||
virtual TagLib::FileRef *GetFileRef(const QString &filename) = 0;
|
virtual TagLib::FileRef *GetFileRef(const QString &filename) = 0;
|
||||||
|
virtual TagLib::FileRef *GetFileRef(TagLib::IOStream *iostream) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_DISABLE_COPY(FileRefFactory)
|
Q_DISABLE_COPY(FileRefFactory)
|
||||||
@@ -256,6 +259,10 @@ class TagLibFileRefFactory : public FileRefFactory {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TagLib::FileRef *GetFileRef(TagLib::IOStream *iostream) override {
|
||||||
|
return new TagLib::FileRef(iostream);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_DISABLE_COPY(TagLibFileRefFactory)
|
Q_DISABLE_COPY(TagLibFileRefFactory)
|
||||||
};
|
};
|
||||||
@@ -270,7 +277,7 @@ TagReaderResult TagReaderTagLib::IsMediaFile(const QString &filename) const {
|
|||||||
|
|
||||||
qLog(Debug) << "Checking for valid file" << filename;
|
qLog(Debug) << "Checking for valid file" << filename;
|
||||||
|
|
||||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
return fileref &&
|
return fileref &&
|
||||||
!fileref->isNull() &&
|
!fileref->isNull() &&
|
||||||
fileref->file() &&
|
fileref->file() &&
|
||||||
@@ -309,41 +316,9 @@ Song::FileType TagReaderTagLib::GuessFileType(TagLib::FileRef *fileref) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TagReaderResult TagReaderTagLib::ReadFile(const QString &filename, Song *song) const {
|
TagReaderResult TagReaderTagLib::Read(SharedPtr<TagLib::FileRef> fileref, Song *song) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) {
|
song->set_filetype(GuessFileType(&*fileref));
|
||||||
return TagReaderResult::ErrorCode::FilenameMissing;
|
|
||||||
}
|
|
||||||
|
|
||||||
qLog(Debug) << "Reading tags from" << filename;
|
|
||||||
|
|
||||||
const QFileInfo fileinfo(filename);
|
|
||||||
if (!fileinfo.exists()) {
|
|
||||||
qLog(Error) << "File" << filename << "does not exist";
|
|
||||||
return TagReaderResult::ErrorCode::FileDoesNotExist;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (song->source() == Song::Source::Unknown) song->set_source(Song::Source::LocalFile);
|
|
||||||
|
|
||||||
const QUrl url = QUrl::fromLocalFile(filename);
|
|
||||||
song->set_basefilename(fileinfo.fileName());
|
|
||||||
song->set_url(url);
|
|
||||||
song->set_filesize(fileinfo.size());
|
|
||||||
song->set_mtime(fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
|
||||||
song->set_ctime(fileinfo.birthTime().isValid() ? std::max(fileinfo.birthTime().toSecsSinceEpoch(), 0LL) : fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
|
||||||
if (song->ctime() <= 0) {
|
|
||||||
song->set_ctime(song->mtime());
|
|
||||||
}
|
|
||||||
song->set_lastseen(QDateTime::currentSecsSinceEpoch());
|
|
||||||
song->set_init_from_file(true);
|
|
||||||
|
|
||||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
|
||||||
if (!fileref || fileref->isNull()) {
|
|
||||||
qLog(Error) << "TagLib could not open file" << filename;
|
|
||||||
return TagReaderResult::ErrorCode::FileOpenError;
|
|
||||||
}
|
|
||||||
|
|
||||||
song->set_filetype(GuessFileType(fileref.get()));
|
|
||||||
|
|
||||||
if (fileref->audioProperties()) {
|
if (fileref->audioProperties()) {
|
||||||
song->set_bitrate(fileref->audioProperties()->bitrate());
|
song->set_bitrate(fileref->audioProperties()->bitrate());
|
||||||
@@ -370,12 +345,14 @@ TagReaderResult TagReaderTagLib::ReadFile(const QString &filename, Song *song) c
|
|||||||
// apart, so we keep specific behavior for some formats by adding another "else if" block below.
|
// apart, so we keep specific behavior for some formats by adding another "else if" block below.
|
||||||
if (TagLib::Ogg::XiphComment *vorbis_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
if (TagLib::Ogg::XiphComment *vorbis_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||||
ParseVorbisComments(vorbis_comment->fieldListMap(), &disc, &compilation, song);
|
ParseVorbisComments(vorbis_comment->fieldListMap(), &disc, &compilation, song);
|
||||||
TagLib::List<TagLib::FLAC::Picture*> pictures = vorbis_comment->pictureList();
|
if (song->url().isLocalFile()) {
|
||||||
if (!pictures.isEmpty()) {
|
TagLib::List<TagLib::FLAC::Picture*> pictures = vorbis_comment->pictureList();
|
||||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
if (!pictures.isEmpty()) {
|
||||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||||
song->set_art_embedded(true);
|
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||||
break;
|
song->set_art_embedded(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,12 +362,14 @@ TagReaderResult TagReaderTagLib::ReadFile(const QString &filename, Song *song) c
|
|||||||
song->set_bitdepth(file_flac->audioProperties()->bitsPerSample());
|
song->set_bitdepth(file_flac->audioProperties()->bitsPerSample());
|
||||||
if (file_flac->xiphComment()) {
|
if (file_flac->xiphComment()) {
|
||||||
ParseVorbisComments(file_flac->xiphComment()->fieldListMap(), &disc, &compilation, song);
|
ParseVorbisComments(file_flac->xiphComment()->fieldListMap(), &disc, &compilation, song);
|
||||||
TagLib::List<TagLib::FLAC::Picture*> pictures = file_flac->pictureList();
|
if (song->url().isLocalFile()) {
|
||||||
if (!pictures.isEmpty()) {
|
TagLib::List<TagLib::FLAC::Picture*> pictures = file_flac->pictureList();
|
||||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
if (!pictures.isEmpty()) {
|
||||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||||
song->set_art_embedded(true);
|
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||||
break;
|
song->set_art_embedded(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,16 +476,104 @@ TagReaderResult TagReaderTagLib::ReadFile(const QString &filename, Song *song) c
|
|||||||
if (song->lastplayed() <= 0) { song->set_lastplayed(-1); }
|
if (song->lastplayed() <= 0) { song->set_lastplayed(-1); }
|
||||||
|
|
||||||
if (song->filetype() == Song::FileType::Unknown) {
|
if (song->filetype() == Song::FileType::Unknown) {
|
||||||
qLog(Error) << "Unknown audio filetype reading" << filename;
|
return TagReaderResult::ErrorCode::Unsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TagReaderResult::ErrorCode::Success;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TagReaderResult TagReaderTagLib::ReadFile(const QString &filename, Song *song) const {
|
||||||
|
|
||||||
|
if (filename.isEmpty()) {
|
||||||
|
return TagReaderResult::ErrorCode::FilenameMissing;
|
||||||
|
}
|
||||||
|
|
||||||
|
qLog(Debug) << "Reading tags from file" << filename;
|
||||||
|
|
||||||
|
const QFileInfo fileinfo(filename);
|
||||||
|
if (!fileinfo.exists()) {
|
||||||
|
qLog(Error) << "File" << filename << "does not exist";
|
||||||
|
return TagReaderResult::ErrorCode::FileDoesNotExist;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (song->source() == Song::Source::Unknown) song->set_source(Song::Source::LocalFile);
|
||||||
|
|
||||||
|
const QUrl url = QUrl::fromLocalFile(filename);
|
||||||
|
song->set_basefilename(fileinfo.fileName());
|
||||||
|
song->set_url(url);
|
||||||
|
song->set_filesize(fileinfo.size());
|
||||||
|
song->set_mtime(fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
||||||
|
song->set_ctime(fileinfo.birthTime().isValid() ? std::max(fileinfo.birthTime().toSecsSinceEpoch(), 0LL) : fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
||||||
|
if (song->ctime() <= 0) {
|
||||||
|
song->set_ctime(song->mtime());
|
||||||
|
}
|
||||||
|
song->set_lastseen(QDateTime::currentSecsSinceEpoch());
|
||||||
|
song->set_init_from_file(true);
|
||||||
|
|
||||||
|
SharedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
|
if (!fileref || fileref->isNull()) {
|
||||||
|
qLog(Error) << "TagLib could not open file" << filename;
|
||||||
|
return TagReaderResult::ErrorCode::FileOpenError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagReaderResult result = Read(fileref, song);
|
||||||
|
if (result.error_code == TagReaderResult::ErrorCode::Unsupported) {
|
||||||
|
qLog(Error) << "Unknown audio filetype reading file" << filename;
|
||||||
return TagReaderResult::ErrorCode::Unsupported;
|
return TagReaderResult::ErrorCode::Unsupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
qLog(Debug) << "Got tags for" << filename;
|
qLog(Debug) << "Got tags for" << filename;
|
||||||
|
|
||||||
return TagReaderResult::ErrorCode::Success;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_STREAMTAGREADER
|
||||||
|
|
||||||
|
TagReaderResult TagReaderTagLib::ReadStream(const QUrl &url,
|
||||||
|
const QString &filename,
|
||||||
|
const quint64 size,
|
||||||
|
const quint64 mtime,
|
||||||
|
const QString &token_type,
|
||||||
|
const QString &access_token,
|
||||||
|
Song *song) const {
|
||||||
|
|
||||||
|
qLog(Debug) << "Loading tags from stream" << url << filename;
|
||||||
|
|
||||||
|
song->set_url(url);
|
||||||
|
song->set_basefilename(QFileInfo(filename).baseName());
|
||||||
|
song->set_filesize(static_cast<qint64>(size));
|
||||||
|
song->set_ctime(static_cast<qint64>(mtime));
|
||||||
|
song->set_mtime(static_cast<qint64>(mtime));
|
||||||
|
|
||||||
|
ScopedPtr<StreamTagReader> stream = make_unique<StreamTagReader>(url, filename, size, token_type, access_token);
|
||||||
|
stream->PreCache();
|
||||||
|
|
||||||
|
if (stream->num_requests() > 2) {
|
||||||
|
qLog(Warning) << "Total requests for file" << filename << stream->num_requests() << stream->cached_bytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(&*stream));
|
||||||
|
if (!fileref || fileref->isNull()) {
|
||||||
|
qLog(Error) << "TagLib could not open stream" << filename << url;
|
||||||
|
return TagReaderResult::ErrorCode::FileOpenError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagReaderResult result = Read(fileref, song);
|
||||||
|
if (result.error_code == TagReaderResult::ErrorCode::Unsupported) {
|
||||||
|
qLog(Error) << "Unknown audio filetype reading stream" << filename << url;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
qLog(Debug) << "Got tags for stream" << filename << url;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HAVE_STREAMTAGREADER
|
||||||
|
|
||||||
void TagReaderTagLib::ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, Song *song) const {
|
void TagReaderTagLib::ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, Song *song) const {
|
||||||
|
|
||||||
TagLib::ID3v2::FrameListMap map = tag->frameListMap();
|
TagLib::ID3v2::FrameListMap map = tag->frameListMap();
|
||||||
@@ -538,7 +605,7 @@ void TagReaderTagLib::ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QSt
|
|||||||
song->set_lyrics(map[kID3v2_UnsychronizedLyrics].front()->toString());
|
song->set_lyrics(map[kID3v2_UnsychronizedLyrics].front()->toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map.contains(kID3v2_CoverArt)) song->set_art_embedded(true);
|
if (map.contains(kID3v2_CoverArt) && song->url().isLocalFile()) song->set_art_embedded(true);
|
||||||
|
|
||||||
// Find a suitable comment tag. For now we ignore iTunNORM comments.
|
// Find a suitable comment tag. For now we ignore iTunNORM comments.
|
||||||
for (uint i = 0; i < map[kID3v2_CommercialFrame].size(); ++i) {
|
for (uint i = 0; i < map[kID3v2_CommercialFrame].size(); ++i) {
|
||||||
@@ -652,7 +719,7 @@ void TagReaderTagLib::ParseVorbisComments(const TagLib::Ogg::FieldListMap &map,
|
|||||||
|
|
||||||
if (map.contains(kVorbisComment_Disc)) *disc = TagLibStringToQString(map[kVorbisComment_Disc].front()).trimmed();
|
if (map.contains(kVorbisComment_Disc)) *disc = TagLibStringToQString(map[kVorbisComment_Disc].front()).trimmed();
|
||||||
if (map.contains(kVorbisComment_Compilation)) *compilation = TagLibStringToQString(map[kVorbisComment_Compilation].front()).trimmed();
|
if (map.contains(kVorbisComment_Compilation)) *compilation = TagLibStringToQString(map[kVorbisComment_Compilation].front()).trimmed();
|
||||||
if (map.contains(kVorbisComment_CoverArt) || map.contains(kVorbisComment_MetadataBlockPicture)) song->set_art_embedded(true);
|
if ((map.contains(kVorbisComment_CoverArt) || map.contains(kVorbisComment_MetadataBlockPicture)) && song->url().isLocalFile()) song->set_art_embedded(true);
|
||||||
|
|
||||||
if (map.contains(kVorbisComment_FMPS_Playcount) && song->playcount() <= 0) {
|
if (map.contains(kVorbisComment_FMPS_Playcount) && song->playcount() <= 0) {
|
||||||
const int playcount = TagLibStringToQString(map[kVorbisComment_FMPS_Playcount].front()).trimmed().toInt();
|
const int playcount = TagLibStringToQString(map[kVorbisComment_FMPS_Playcount].front()).trimmed().toInt();
|
||||||
@@ -689,7 +756,7 @@ void TagReaderTagLib::ParseAPETags(const TagLib::APE::ItemListMap &map, QString
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map.find(kAPE_CoverArt) != map.end()) song->set_art_embedded(true);
|
if (map.find(kAPE_CoverArt) != map.end() && song->url().isLocalFile()) song->set_art_embedded(true);
|
||||||
if (map.contains(kAPE_Compilation)) {
|
if (map.contains(kAPE_Compilation)) {
|
||||||
*compilation = TagLibStringToQString(TagLib::String::number(map[kAPE_Compilation].toString().toInt()));
|
*compilation = TagLibStringToQString(TagLib::String::number(map[kAPE_Compilation].toString().toInt()));
|
||||||
}
|
}
|
||||||
@@ -757,7 +824,7 @@ void TagReaderTagLib::ParseMP4Tags(TagLib::MP4::Tag *tag, QString *disc, QString
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find album cover art
|
// Find album cover art
|
||||||
if (tag->item(kMP4_CoverArt).isValid()) {
|
if (tag->item(kMP4_CoverArt).isValid() && song->url().isLocalFile()) {
|
||||||
song->set_art_embedded(true);
|
song->set_art_embedded(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -959,7 +1026,7 @@ TagReaderResult TagReaderTagLib::WriteFile(const QString &filename, const Song &
|
|||||||
cover = LoadAlbumCoverTagData(filename, save_tag_cover_data);
|
cover = LoadAlbumCoverTagData(filename, save_tag_cover_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
if (!fileref || fileref->isNull()) {
|
if (!fileref || fileref->isNull()) {
|
||||||
qLog(Error) << "TagLib could not open file" << filename;
|
qLog(Error) << "TagLib could not open file" << filename;
|
||||||
return TagReaderResult::ErrorCode::FileOpenError;
|
return TagReaderResult::ErrorCode::FileOpenError;
|
||||||
@@ -1310,7 +1377,7 @@ TagReaderResult TagReaderTagLib::LoadEmbeddedCover(const QString &filename, QByt
|
|||||||
|
|
||||||
qLog(Debug) << "Loading cover from" << filename;
|
qLog(Debug) << "Loading cover from" << filename;
|
||||||
|
|
||||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
if (!fileref || fileref->isNull()) {
|
if (!fileref || fileref->isNull()) {
|
||||||
qLog(Error) << "TagLib could not open file" << filename;
|
qLog(Error) << "TagLib could not open file" << filename;
|
||||||
return TagReaderResult::ErrorCode::FileOpenError;
|
return TagReaderResult::ErrorCode::FileOpenError;
|
||||||
@@ -1532,7 +1599,7 @@ TagReaderResult TagReaderTagLib::SaveEmbeddedCover(const QString &filename, cons
|
|||||||
return TagReaderResult::ErrorCode::FileDoesNotExist;
|
return TagReaderResult::ErrorCode::FileDoesNotExist;
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
if (!fileref || fileref->isNull()) {
|
if (!fileref || fileref->isNull()) {
|
||||||
qLog(Error) << "TagLib could not open file" << filename;
|
qLog(Error) << "TagLib could not open file" << filename;
|
||||||
return TagReaderResult::ErrorCode::FileOpenError;
|
return TagReaderResult::ErrorCode::FileOpenError;
|
||||||
@@ -1672,7 +1739,7 @@ TagReaderResult TagReaderTagLib::SaveSongPlaycount(const QString &filename, cons
|
|||||||
return TagReaderResult::ErrorCode::FileDoesNotExist;
|
return TagReaderResult::ErrorCode::FileDoesNotExist;
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
if (!fileref || fileref->isNull()) {
|
if (!fileref || fileref->isNull()) {
|
||||||
qLog(Error) << "TagLib could not open file" << filename;
|
qLog(Error) << "TagLib could not open file" << filename;
|
||||||
return TagReaderResult::ErrorCode::FileOpenError;
|
return TagReaderResult::ErrorCode::FileOpenError;
|
||||||
@@ -1802,7 +1869,7 @@ TagReaderResult TagReaderTagLib::SaveSongRating(const QString &filename, const f
|
|||||||
return TagReaderResult::ErrorCode::Success;
|
return TagReaderResult::ErrorCode::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
if (!fileref || fileref->isNull()) {
|
if (!fileref || fileref->isNull()) {
|
||||||
qLog(Error) << "TagLib could not open file" << filename;
|
qLog(Error) << "TagLib could not open file" << filename;
|
||||||
return TagReaderResult::ErrorCode::FileOpenError;
|
return TagReaderResult::ErrorCode::FileOpenError;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2013, David Sansome <me@davidsansome.com>
|
* Copyright 2013, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -40,6 +40,8 @@
|
|||||||
#include <taglib/mp4tag.h>
|
#include <taglib/mp4tag.h>
|
||||||
#include <taglib/asftag.h>
|
#include <taglib/asftag.h>
|
||||||
|
|
||||||
|
#include "includes/scoped_ptr.h"
|
||||||
|
#include "includes/shared_ptr.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
@@ -66,6 +68,10 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
TagReaderResult IsMediaFile(const QString &filename) const override;
|
TagReaderResult IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
TagReaderResult ReadFile(const QString &filename, Song *song) const override;
|
TagReaderResult ReadFile(const QString &filename, Song *song) const override;
|
||||||
|
#ifdef HAVE_STREAMTAGREADER
|
||||||
|
TagReaderResult ReadStream(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token, Song *song) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const override;
|
TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||||
|
|
||||||
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
||||||
@@ -76,6 +82,7 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static Song::FileType GuessFileType(TagLib::FileRef *fileref);
|
static Song::FileType GuessFileType(TagLib::FileRef *fileref);
|
||||||
|
TagReaderResult Read(SharedPtr<TagLib::FileRef> fileref, Song *song) const;
|
||||||
|
|
||||||
void ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, Song *song) const;
|
void ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, Song *song) const;
|
||||||
void ParseVorbisComments(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, Song *song) const;
|
void ParseVorbisComments(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, Song *song) const;
|
||||||
|
|||||||
Reference in New Issue
Block a user