Add stream tagreader
This commit is contained in:
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
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* 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
|
||||
* 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 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 LoadEmbeddedCover(const QString &filename, QByteArray &data) const = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QMutex>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QScopeGuard>
|
||||
|
||||
@@ -37,6 +38,7 @@
|
||||
#include "tagreaderrequest.h"
|
||||
#include "tagreaderismediafilerequest.h"
|
||||
#include "tagreaderreadfilerequest.h"
|
||||
#include "tagreaderreadstreamrequest.h"
|
||||
#include "tagreaderwritefilerequest.h"
|
||||
#include "tagreaderloadcoverdatarequest.h"
|
||||
#include "tagreaderloadcoverimagerequest.h"
|
||||
@@ -45,6 +47,7 @@
|
||||
#include "tagreadersaveratingrequest.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderreadfilereply.h"
|
||||
#include "tagreaderreadstreamreply.h"
|
||||
#include "tagreaderloadcoverdatareply.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)) {
|
||||
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) {
|
||||
|
||||
return tagreader_.WriteFile(filename, song, save_tags_options, save_tag_cover_data);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -29,7 +29,6 @@
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "includes/mutex_protected.h"
|
||||
#include "core/song.h"
|
||||
|
||||
@@ -39,6 +38,7 @@
|
||||
#include "tagreaderresult.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderreadfilereply.h"
|
||||
#include "tagreaderreadstreamreply.h"
|
||||
#include "tagreaderloadcoverdatareply.h"
|
||||
#include "tagreaderloadcoverimagereply.h"
|
||||
#include "savetagsoptions.h"
|
||||
@@ -67,6 +67,11 @@ class TagReaderClient : public QObject {
|
||||
TagReaderResult ReadFileBlocking(const QString &filename, Song *song);
|
||||
[[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());
|
||||
[[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 {
|
||||
|
||||
Q_UNUSED(filename);
|
||||
|
||||
@@ -102,6 +102,11 @@ class TagReaderGME : public TagReaderBase {
|
||||
TagReaderResult IsMediaFile(const QString &filename) 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 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
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "tagreaderresult.h"
|
||||
@@ -38,6 +39,11 @@ class TagReaderReply : public QObject {
|
||||
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_; }
|
||||
|
||||
TagReaderResult result() const { return result_; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,6 +21,7 @@
|
||||
#define TAGREADERREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "tagreaderreply.h"
|
||||
@@ -28,8 +29,10 @@
|
||||
class TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderRequest(const QString &_filename);
|
||||
explicit TagReaderRequest(const QUrl &_url, const QString &_filename);
|
||||
virtual ~TagReaderRequest();
|
||||
QString filename;
|
||||
QUrl url;
|
||||
TagReaderReplyPtr reply;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "tagreadertaglib.h"
|
||||
#include "streamtagreader.h"
|
||||
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
@@ -94,13 +95,14 @@
|
||||
#include <QDateTime>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "includes/scoped_ptr.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/song.h"
|
||||
#include "constants/timeconstants.h"
|
||||
|
||||
#include "albumcovertagdata.h"
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::make_unique;
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
#undef TStringToQString
|
||||
@@ -240,6 +242,7 @@ class FileRefFactory {
|
||||
FileRefFactory() = default;
|
||||
virtual ~FileRefFactory() = default;
|
||||
virtual TagLib::FileRef *GetFileRef(const QString &filename) = 0;
|
||||
virtual TagLib::FileRef *GetFileRef(TagLib::IOStream *iostream) = 0;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(FileRefFactory)
|
||||
@@ -256,6 +259,10 @@ class TagLibFileRefFactory : public FileRefFactory {
|
||||
#endif
|
||||
}
|
||||
|
||||
TagLib::FileRef *GetFileRef(TagLib::IOStream *iostream) override {
|
||||
return new TagLib::FileRef(iostream);
|
||||
}
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(TagLibFileRefFactory)
|
||||
};
|
||||
@@ -270,7 +277,7 @@ TagReaderResult TagReaderTagLib::IsMediaFile(const QString &filename) const {
|
||||
|
||||
qLog(Debug) << "Checking for valid file" << filename;
|
||||
|
||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
return fileref &&
|
||||
!fileref->isNull() &&
|
||||
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()) {
|
||||
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()));
|
||||
song->set_filetype(GuessFileType(&*fileref));
|
||||
|
||||
if (fileref->audioProperties()) {
|
||||
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.
|
||||
if (TagLib::Ogg::XiphComment *vorbis_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||
ParseVorbisComments(vorbis_comment->fieldListMap(), &disc, &compilation, song);
|
||||
TagLib::List<TagLib::FLAC::Picture*> pictures = vorbis_comment->pictureList();
|
||||
if (!pictures.isEmpty()) {
|
||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||
song->set_art_embedded(true);
|
||||
break;
|
||||
if (song->url().isLocalFile()) {
|
||||
TagLib::List<TagLib::FLAC::Picture*> pictures = vorbis_comment->pictureList();
|
||||
if (!pictures.isEmpty()) {
|
||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||
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());
|
||||
if (file_flac->xiphComment()) {
|
||||
ParseVorbisComments(file_flac->xiphComment()->fieldListMap(), &disc, &compilation, song);
|
||||
TagLib::List<TagLib::FLAC::Picture*> pictures = file_flac->pictureList();
|
||||
if (!pictures.isEmpty()) {
|
||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||
song->set_art_embedded(true);
|
||||
break;
|
||||
if (song->url().isLocalFile()) {
|
||||
TagLib::List<TagLib::FLAC::Picture*> pictures = file_flac->pictureList();
|
||||
if (!pictures.isEmpty()) {
|
||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||
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->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;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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.
|
||||
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_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) {
|
||||
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)) {
|
||||
*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
|
||||
if (tag->item(kMP4_CoverArt).isValid()) {
|
||||
if (tag->item(kMP4_CoverArt).isValid() && song->url().isLocalFile()) {
|
||||
song->set_art_embedded(true);
|
||||
}
|
||||
|
||||
@@ -959,7 +1026,7 @@ TagReaderResult TagReaderTagLib::WriteFile(const QString &filename, const Song &
|
||||
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()) {
|
||||
qLog(Error) << "TagLib could not open file" << filename;
|
||||
return TagReaderResult::ErrorCode::FileOpenError;
|
||||
@@ -1310,7 +1377,7 @@ TagReaderResult TagReaderTagLib::LoadEmbeddedCover(const QString &filename, QByt
|
||||
|
||||
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()) {
|
||||
qLog(Error) << "TagLib could not open file" << filename;
|
||||
return TagReaderResult::ErrorCode::FileOpenError;
|
||||
@@ -1532,7 +1599,7 @@ TagReaderResult TagReaderTagLib::SaveEmbeddedCover(const QString &filename, cons
|
||||
return TagReaderResult::ErrorCode::FileDoesNotExist;
|
||||
}
|
||||
|
||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
if (!fileref || fileref->isNull()) {
|
||||
qLog(Error) << "TagLib could not open file" << filename;
|
||||
return TagReaderResult::ErrorCode::FileOpenError;
|
||||
@@ -1672,7 +1739,7 @@ TagReaderResult TagReaderTagLib::SaveSongPlaycount(const QString &filename, cons
|
||||
return TagReaderResult::ErrorCode::FileDoesNotExist;
|
||||
}
|
||||
|
||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
if (!fileref || fileref->isNull()) {
|
||||
qLog(Error) << "TagLib could not open file" << filename;
|
||||
return TagReaderResult::ErrorCode::FileOpenError;
|
||||
@@ -1802,7 +1869,7 @@ TagReaderResult TagReaderTagLib::SaveSongRating(const QString &filename, const f
|
||||
return TagReaderResult::ErrorCode::Success;
|
||||
}
|
||||
|
||||
unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
ScopedPtr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
if (!fileref || fileref->isNull()) {
|
||||
qLog(Error) << "TagLib could not open file" << filename;
|
||||
return TagReaderResult::ErrorCode::FileOpenError;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -40,6 +40,8 @@
|
||||
#include <taglib/mp4tag.h>
|
||||
#include <taglib/asftag.h>
|
||||
|
||||
#include "includes/scoped_ptr.h"
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include "tagreaderbase.h"
|
||||
@@ -66,6 +68,10 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
TagReaderResult IsMediaFile(const QString &filename) 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 LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
||||
@@ -76,6 +82,7 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
|
||||
private:
|
||||
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 ParseVorbisComments(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, Song *song) const;
|
||||
|
||||
Reference in New Issue
Block a user