@@ -353,6 +353,8 @@ struct Song::Private : public QSharedData {
|
|||||||
std::optional<double> ebur128_integrated_loudness_lufs_;
|
std::optional<double> ebur128_integrated_loudness_lufs_;
|
||||||
std::optional<double> ebur128_loudness_range_lu_;
|
std::optional<double> ebur128_loudness_range_lu_;
|
||||||
|
|
||||||
|
int id3v2_version_; // ID3v2 tag version (3 or 4), 0 if not applicable or unknown
|
||||||
|
|
||||||
bool init_from_file_; // Whether this song was loaded from a file using taglib.
|
bool init_from_file_; // Whether this song was loaded from a file using taglib.
|
||||||
bool suspicious_tags_; // Whether our encoding guesser thinks these tags might be incorrectly encoded.
|
bool suspicious_tags_; // Whether our encoding guesser thinks these tags might be incorrectly encoded.
|
||||||
|
|
||||||
@@ -400,6 +402,8 @@ Song::Private::Private(const Source source)
|
|||||||
rating_(-1),
|
rating_(-1),
|
||||||
bpm_(-1),
|
bpm_(-1),
|
||||||
|
|
||||||
|
id3v2_version_(0),
|
||||||
|
|
||||||
init_from_file_(false),
|
init_from_file_(false),
|
||||||
suspicious_tags_(false)
|
suspicious_tags_(false)
|
||||||
|
|
||||||
@@ -510,6 +514,8 @@ const QString &Song::musicbrainz_work_id() const { return d->musicbrainz_work_id
|
|||||||
std::optional<double> Song::ebur128_integrated_loudness_lufs() const { return d->ebur128_integrated_loudness_lufs_; }
|
std::optional<double> Song::ebur128_integrated_loudness_lufs() const { return d->ebur128_integrated_loudness_lufs_; }
|
||||||
std::optional<double> Song::ebur128_loudness_range_lu() const { return d->ebur128_loudness_range_lu_; }
|
std::optional<double> Song::ebur128_loudness_range_lu() const { return d->ebur128_loudness_range_lu_; }
|
||||||
|
|
||||||
|
int Song::id3v2_version() const { return d->id3v2_version_; }
|
||||||
|
|
||||||
QString *Song::mutable_title() { return &d->title_; }
|
QString *Song::mutable_title() { return &d->title_; }
|
||||||
QString *Song::mutable_album() { return &d->album_; }
|
QString *Song::mutable_album() { return &d->album_; }
|
||||||
QString *Song::mutable_artist() { return &d->artist_; }
|
QString *Song::mutable_artist() { return &d->artist_; }
|
||||||
@@ -624,6 +630,8 @@ void Song::set_musicbrainz_work_id(const QString &v) { d->musicbrainz_work_id_ =
|
|||||||
void Song::set_ebur128_integrated_loudness_lufs(const std::optional<double> v) { d->ebur128_integrated_loudness_lufs_ = v; }
|
void Song::set_ebur128_integrated_loudness_lufs(const std::optional<double> v) { d->ebur128_integrated_loudness_lufs_ = v; }
|
||||||
void Song::set_ebur128_loudness_range_lu(const std::optional<double> v) { d->ebur128_loudness_range_lu_ = v; }
|
void Song::set_ebur128_loudness_range_lu(const std::optional<double> v) { d->ebur128_loudness_range_lu_ = v; }
|
||||||
|
|
||||||
|
void Song::set_id3v2_version(const int v) { d->id3v2_version_ = v; }
|
||||||
|
|
||||||
void Song::set_init_from_file(const bool v) { d->init_from_file_ = v; }
|
void Song::set_init_from_file(const bool v) { d->init_from_file_ = v; }
|
||||||
|
|
||||||
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
|
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
|
||||||
@@ -833,6 +841,10 @@ bool Song::save_embedded_cover_supported(const FileType filetype) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Song::id3v2_tags_supported() const {
|
||||||
|
return d->filetype_ == FileType::MPEG || d->filetype_ == FileType::WAV || d->filetype_ == FileType::AIFF;
|
||||||
|
}
|
||||||
|
|
||||||
int Song::ColumnIndex(const QString &field) {
|
int Song::ColumnIndex(const QString &field) {
|
||||||
|
|
||||||
return static_cast<int>(kRowIdColumns.indexOf(field));
|
return static_cast<int>(kRowIdColumns.indexOf(field));
|
||||||
|
|||||||
@@ -234,6 +234,8 @@ class Song {
|
|||||||
std::optional<double> ebur128_integrated_loudness_lufs() const;
|
std::optional<double> ebur128_integrated_loudness_lufs() const;
|
||||||
std::optional<double> ebur128_loudness_range_lu() const;
|
std::optional<double> ebur128_loudness_range_lu() const;
|
||||||
|
|
||||||
|
int id3v2_version() const;
|
||||||
|
|
||||||
QString *mutable_title();
|
QString *mutable_title();
|
||||||
QString *mutable_album();
|
QString *mutable_album();
|
||||||
QString *mutable_artist();
|
QString *mutable_artist();
|
||||||
@@ -349,6 +351,8 @@ class Song {
|
|||||||
void set_ebur128_integrated_loudness_lufs(const std::optional<double> v);
|
void set_ebur128_integrated_loudness_lufs(const std::optional<double> v);
|
||||||
void set_ebur128_loudness_range_lu(const std::optional<double> v);
|
void set_ebur128_loudness_range_lu(const std::optional<double> v);
|
||||||
|
|
||||||
|
void set_id3v2_version(const int v);
|
||||||
|
|
||||||
void set_init_from_file(const bool v);
|
void set_init_from_file(const bool v);
|
||||||
|
|
||||||
void set_stream_url(const QUrl &v);
|
void set_stream_url(const QUrl &v);
|
||||||
@@ -439,6 +443,8 @@ class Song {
|
|||||||
static bool save_embedded_cover_supported(const FileType filetype);
|
static bool save_embedded_cover_supported(const FileType filetype);
|
||||||
bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
|
bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
|
||||||
|
|
||||||
|
bool id3v2_tags_supported() const;
|
||||||
|
|
||||||
static int ColumnIndex(const QString &field);
|
static int ColumnIndex(const QString &field);
|
||||||
static QString JoinSpec(const QString &table);
|
static QString JoinSpec(const QString &table);
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
#include "utilities/coverutils.h"
|
#include "utilities/coverutils.h"
|
||||||
#include "utilities/coveroptions.h"
|
#include "utilities/coveroptions.h"
|
||||||
#include "tagreader/tagreaderclient.h"
|
#include "tagreader/tagreaderclient.h"
|
||||||
|
#include "tagreader/tagid3v2version.h"
|
||||||
#include "widgets/busyindicator.h"
|
#include "widgets/busyindicator.h"
|
||||||
#include "widgets/lineedit.h"
|
#include "widgets/lineedit.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
@@ -107,6 +108,12 @@ using namespace Qt::Literals::StringLiterals;
|
|||||||
namespace {
|
namespace {
|
||||||
constexpr char kSettingsGroup[] = "EditTagDialog";
|
constexpr char kSettingsGroup[] = "EditTagDialog";
|
||||||
constexpr int kSmallImageSize = 128;
|
constexpr int kSmallImageSize = 128;
|
||||||
|
|
||||||
|
// ID3v2 version constants
|
||||||
|
constexpr int kID3v2_Version_3 = 3;
|
||||||
|
constexpr int kID3v2_Version_4 = 4;
|
||||||
|
constexpr int kComboBoxIndex_ID3v2_3 = 0;
|
||||||
|
constexpr int kComboBoxIndex_ID3v2_4 = 1;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
const char EditTagDialog::kTagsDifferentHintText[] = QT_TR_NOOP("(different across multiple songs)");
|
const char EditTagDialog::kTagsDifferentHintText[] = QT_TR_NOOP("(different across multiple songs)");
|
||||||
@@ -708,6 +715,9 @@ void EditTagDialog::SelectionChanged() {
|
|||||||
bool titlesort_enabled = false;
|
bool titlesort_enabled = false;
|
||||||
bool artistsort_enabled = false;
|
bool artistsort_enabled = false;
|
||||||
bool albumsort_enabled = false;
|
bool albumsort_enabled = false;
|
||||||
|
bool has_id3v2_support = false;
|
||||||
|
int id3v2_version = 0;
|
||||||
|
bool id3v2_version_different = false;
|
||||||
for (const QModelIndex &idx : indexes) {
|
for (const QModelIndex &idx : indexes) {
|
||||||
if (data_.value(idx.row()).cover_action_ == UpdateCoverAction::None) {
|
if (data_.value(idx.row()).cover_action_ == UpdateCoverAction::None) {
|
||||||
data_[idx.row()].cover_result_ = AlbumCoverImageResult();
|
data_[idx.row()].cover_result_ = AlbumCoverImageResult();
|
||||||
@@ -769,6 +779,15 @@ void EditTagDialog::SelectionChanged() {
|
|||||||
if (song.albumsort_supported()) {
|
if (song.albumsort_supported()) {
|
||||||
albumsort_enabled = true;
|
albumsort_enabled = true;
|
||||||
}
|
}
|
||||||
|
if (song.id3v2_tags_supported()) {
|
||||||
|
has_id3v2_support = true;
|
||||||
|
if (id3v2_version == 0) {
|
||||||
|
id3v2_version = song.id3v2_version();
|
||||||
|
}
|
||||||
|
else if (id3v2_version != song.id3v2_version()) {
|
||||||
|
id3v2_version_different = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString summary;
|
QString summary;
|
||||||
@@ -840,6 +859,23 @@ void EditTagDialog::SelectionChanged() {
|
|||||||
ui_->artistsort->setEnabled(artistsort_enabled);
|
ui_->artistsort->setEnabled(artistsort_enabled);
|
||||||
ui_->albumsort->setEnabled(albumsort_enabled);
|
ui_->albumsort->setEnabled(albumsort_enabled);
|
||||||
|
|
||||||
|
ui_->label_id3v2_version->setVisible(has_id3v2_support);
|
||||||
|
ui_->combobox_id3v2_version->setVisible(has_id3v2_support);
|
||||||
|
|
||||||
|
if (has_id3v2_support) {
|
||||||
|
// Set default based on existing version(s)
|
||||||
|
if (id3v2_version_different || id3v2_version == 0) {
|
||||||
|
// Mixed versions or unknown - default to ID3v2.4
|
||||||
|
ui_->combobox_id3v2_version->setCurrentIndex(kComboBoxIndex_ID3v2_4);
|
||||||
|
}
|
||||||
|
else if (id3v2_version == kID3v2_Version_3) {
|
||||||
|
ui_->combobox_id3v2_version->setCurrentIndex(kComboBoxIndex_ID3v2_3);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui_->combobox_id3v2_version->setCurrentIndex(kComboBoxIndex_ID3v2_4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditTagDialog::UpdateUI(const QModelIndexList &indexes) {
|
void EditTagDialog::UpdateUI(const QModelIndexList &indexes) {
|
||||||
@@ -1371,6 +1407,13 @@ void EditTagDialog::SaveData() {
|
|||||||
}
|
}
|
||||||
save_tag_cover_data.cover_data = ref.cover_result_.image_data;
|
save_tag_cover_data.cover_data = ref.cover_result_.image_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine ID3v2 version based on user selection
|
||||||
|
TagID3v2Version tag_id3v2_version = TagID3v2Version::Default;
|
||||||
|
if (ref.current_.filetype() == Song::FileType::MPEG || ref.current_.filetype() == Song::FileType::WAV || ref.current_.filetype() == Song::FileType::AIFF) {
|
||||||
|
tag_id3v2_version = ui_->combobox_id3v2_version->currentIndex() == kComboBoxIndex_ID3v2_3 ? TagID3v2Version::V3 : TagID3v2Version::V4;
|
||||||
|
}
|
||||||
|
|
||||||
TagReaderClient::SaveOptions save_tags_options;
|
TagReaderClient::SaveOptions save_tags_options;
|
||||||
if (save_tags) {
|
if (save_tags) {
|
||||||
save_tags_options |= TagReaderClient::SaveOption::Tags;
|
save_tags_options |= TagReaderClient::SaveOption::Tags;
|
||||||
@@ -1384,7 +1427,7 @@ void EditTagDialog::SaveData() {
|
|||||||
if (save_embedded_cover) {
|
if (save_embedded_cover) {
|
||||||
save_tags_options |= TagReaderClient::SaveOption::Cover;
|
save_tags_options |= TagReaderClient::SaveOption::Cover;
|
||||||
}
|
}
|
||||||
TagReaderReplyPtr reply = tagreader_client_->WriteFileAsync(ref.current_.url().toLocalFile(), ref.current_, save_tags_options, save_tag_cover_data);
|
TagReaderReplyPtr reply = tagreader_client_->WriteFileAsync(ref.current_.url().toLocalFile(), ref.current_, save_tags_options, save_tag_cover_data, tag_id3v2_version);
|
||||||
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
||||||
*connection = QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, ref, connection]() {
|
*connection = QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, ref, connection]() {
|
||||||
SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_);
|
SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_);
|
||||||
|
|||||||
@@ -650,6 +650,47 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="layout_id3v2_version">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_id3v2_version">
|
||||||
|
<property name="text">
|
||||||
|
<string>ID3v2 version:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="combobox_id3v2_version">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>2.3</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>2.4</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="spacer_id3v2_version">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="spacer_albumart_bottom">
|
<spacer name="spacer_albumart_bottom">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
|||||||
29
src/tagreader/tagid3v2version.h
Normal file
29
src/tagreader/tagid3v2version.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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 TAGID3V2VERSION_H
|
||||||
|
#define TAGID3V2VERSION_H
|
||||||
|
|
||||||
|
enum class TagID3v2Version {
|
||||||
|
Default = 0, // Use existing version or library default
|
||||||
|
V3 = 3,
|
||||||
|
V4 = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGID3V2VERSION_H
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "savetagsoptions.h"
|
#include "savetagsoptions.h"
|
||||||
#include "savetagcoverdata.h"
|
#include "savetagcoverdata.h"
|
||||||
#include "albumcovertagdata.h"
|
#include "albumcovertagdata.h"
|
||||||
|
#include "tagid3v2version.h"
|
||||||
|
|
||||||
class TagReaderBase {
|
class TagReaderBase {
|
||||||
public:
|
public:
|
||||||
@@ -45,7 +46,7 @@ class TagReaderBase {
|
|||||||
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;
|
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
|
#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 TagID3v2Version id3v2_version) const = 0;
|
||||||
|
|
||||||
virtual TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const = 0;
|
virtual TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const = 0;
|
||||||
virtual TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const = 0;
|
virtual TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const = 0;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#include "tagreaderreadstreamreply.h"
|
#include "tagreaderreadstreamreply.h"
|
||||||
#include "tagreaderloadcoverdatareply.h"
|
#include "tagreaderloadcoverdatareply.h"
|
||||||
#include "tagreaderloadcoverimagereply.h"
|
#include "tagreaderloadcoverimagereply.h"
|
||||||
|
#include "tagid3v2version.h"
|
||||||
|
|
||||||
using std::dynamic_pointer_cast;
|
using std::dynamic_pointer_cast;
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
@@ -189,7 +190,7 @@ void TagReaderClient::ProcessRequest(TagReaderRequestPtr request) {
|
|||||||
}
|
}
|
||||||
#endif // HAVE_STREAMTAGREADER
|
#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, write_file_request->tag_id3v2_version);
|
||||||
}
|
}
|
||||||
else if (TagReaderLoadCoverDataRequestPtr load_cover_data_request = dynamic_pointer_cast<TagReaderLoadCoverDataRequest>(request)) {
|
else if (TagReaderLoadCoverDataRequestPtr load_cover_data_request = dynamic_pointer_cast<TagReaderLoadCoverDataRequest>(request)) {
|
||||||
QByteArray cover_data;
|
QByteArray cover_data;
|
||||||
@@ -303,13 +304,13 @@ TagReaderReadStreamReplyPtr TagReaderClient::ReadStreamAsync(const QUrl &url, co
|
|||||||
}
|
}
|
||||||
#endif // HAVE_STREAMTAGREADER
|
#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, const TagID3v2Version tag_id3v2_version) {
|
||||||
|
|
||||||
return tagreader_.WriteFile(filename, song, save_tags_options, save_tag_cover_data);
|
return tagreader_.WriteFile(filename, song, save_tags_options, save_tag_cover_data, tag_id3v2_version);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TagReaderReplyPtr TagReaderClient::WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) {
|
TagReaderReplyPtr TagReaderClient::WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data, const TagID3v2Version tag_id3v2_version) {
|
||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() != thread());
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
|
|
||||||
@@ -321,6 +322,7 @@ TagReaderReplyPtr TagReaderClient::WriteFileAsync(const QString &filename, const
|
|||||||
request->song = song;
|
request->song = song;
|
||||||
request->save_tags_options = save_tags_options;
|
request->save_tags_options = save_tags_options;
|
||||||
request->save_tag_cover_data = save_tag_cover_data;
|
request->save_tag_cover_data = save_tag_cover_data;
|
||||||
|
request->tag_id3v2_version = tag_id3v2_version;
|
||||||
|
|
||||||
EnqueueRequest(request);
|
EnqueueRequest(request);
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
#include "tagreaderloadcoverimagereply.h"
|
#include "tagreaderloadcoverimagereply.h"
|
||||||
#include "savetagsoptions.h"
|
#include "savetagsoptions.h"
|
||||||
#include "savetagcoverdata.h"
|
#include "savetagcoverdata.h"
|
||||||
|
#include "tagid3v2version.h"
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
class Song;
|
class Song;
|
||||||
@@ -72,8 +73,8 @@ class TagReaderClient : public QObject {
|
|||||||
[[nodiscard]] TagReaderReadStreamReplyPtr ReadStreamAsync(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token);
|
[[nodiscard]] TagReaderReadStreamReplyPtr ReadStreamAsync(const QUrl &url, const QString &filename, const quint64 size, const quint64 mtime, const QString &token_type, const QString &access_token);
|
||||||
#endif
|
#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(), const TagID3v2Version tag_id3v2_version = TagID3v2Version::Default);
|
||||||
[[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(), const TagID3v2Version tag_id3v2_version = TagID3v2Version::Default);
|
||||||
|
|
||||||
TagReaderResult LoadCoverDataBlocking(const QString &filename, QByteArray &data);
|
TagReaderResult LoadCoverDataBlocking(const QString &filename, QByteArray &data);
|
||||||
TagReaderResult LoadCoverImageBlocking(const QString &filename, QImage &image);
|
TagReaderResult LoadCoverImageBlocking(const QString &filename, QImage &image);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
#include "tagreadertaglib.h"
|
#include "tagreadertaglib.h"
|
||||||
|
#include "tagid3v2version.h"
|
||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
@@ -317,12 +318,13 @@ TagReaderResult TagReaderGME::ReadStream(const QUrl &url, const QString &filenam
|
|||||||
}
|
}
|
||||||
#endif
|
#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 TagID3v2Version id3v2_version) const {
|
||||||
|
|
||||||
Q_UNUSED(filename);
|
Q_UNUSED(filename);
|
||||||
Q_UNUSED(song);
|
Q_UNUSED(song);
|
||||||
Q_UNUSED(save_tags_options);
|
Q_UNUSED(save_tags_options);
|
||||||
Q_UNUSED(save_tag_cover_data);
|
Q_UNUSED(save_tag_cover_data);
|
||||||
|
Q_UNUSED(id3v2_version);
|
||||||
|
|
||||||
return TagReaderResult::ErrorCode::Unsupported;
|
return TagReaderResult::ErrorCode::Unsupported;
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
|
||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
|
#include "tagid3v2version.h"
|
||||||
|
|
||||||
namespace GME {
|
namespace GME {
|
||||||
bool IsSupportedFormat(const QFileInfo &fileinfo);
|
bool IsSupportedFormat(const QFileInfo &fileinfo);
|
||||||
@@ -107,7 +108,7 @@ class TagReaderGME : public TagReaderBase {
|
|||||||
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;
|
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
|
#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 TagID3v2Version id3v2_version) const override;
|
||||||
|
|
||||||
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
||||||
TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const override;
|
TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
#include <taglib/apeproperties.h>
|
#include <taglib/apeproperties.h>
|
||||||
#include <taglib/id3v2tag.h>
|
#include <taglib/id3v2tag.h>
|
||||||
#include <taglib/id3v2frame.h>
|
#include <taglib/id3v2frame.h>
|
||||||
|
#include <taglib/id3v2header.h>
|
||||||
#include <taglib/attachedpictureframe.h>
|
#include <taglib/attachedpictureframe.h>
|
||||||
#include <taglib/textidentificationframe.h>
|
#include <taglib/textidentificationframe.h>
|
||||||
#include <taglib/unsynchronizedlyricsframe.h>
|
#include <taglib/unsynchronizedlyricsframe.h>
|
||||||
@@ -104,6 +105,7 @@
|
|||||||
#include "constants/timeconstants.h"
|
#include "constants/timeconstants.h"
|
||||||
|
|
||||||
#include "albumcovertagdata.h"
|
#include "albumcovertagdata.h"
|
||||||
|
#include "tagid3v2version.h"
|
||||||
|
|
||||||
using std::make_unique;
|
using std::make_unique;
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
@@ -149,6 +151,10 @@ constexpr char kID3v2_MusicBrainz_DiscId[] = "MusicBrainz Disc Id";
|
|||||||
constexpr char kID3v2_MusicBrainz_ReleaseGroupId[] = "MusicBrainz Release Group Id";
|
constexpr char kID3v2_MusicBrainz_ReleaseGroupId[] = "MusicBrainz Release Group Id";
|
||||||
constexpr char kID3v2_MusicBrainz_WorkId[] = "MusicBrainz Work Id";
|
constexpr char kID3v2_MusicBrainz_WorkId[] = "MusicBrainz Work Id";
|
||||||
|
|
||||||
|
// ID3v2 version constants
|
||||||
|
constexpr int kID3v2_Version_3 = 3;
|
||||||
|
constexpr int kID3v2_Version_4 = 4;
|
||||||
|
|
||||||
constexpr char kVorbisComment_AlbumArtist1[] = "ALBUMARTIST";
|
constexpr char kVorbisComment_AlbumArtist1[] = "ALBUMARTIST";
|
||||||
constexpr char kVorbisComment_AlbumArtist2[] = "ALBUM ARTIST";
|
constexpr char kVorbisComment_AlbumArtist2[] = "ALBUM ARTIST";
|
||||||
constexpr char kVorbisComment_AlbumArtistSort[] = "ALBUMARTISTSORT";
|
constexpr char kVorbisComment_AlbumArtistSort[] = "ALBUMARTISTSORT";
|
||||||
@@ -604,8 +610,14 @@ TagReaderResult TagReaderTagLib::ReadStream(const QUrl &url,
|
|||||||
|
|
||||||
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 {
|
||||||
|
|
||||||
|
if (!tag) return;
|
||||||
|
|
||||||
TagLib::ID3v2::FrameListMap map = tag->frameListMap();
|
TagLib::ID3v2::FrameListMap map = tag->frameListMap();
|
||||||
|
|
||||||
|
if (tag->header()) {
|
||||||
|
song->set_id3v2_version(tag->header()->majorVersion());
|
||||||
|
}
|
||||||
|
|
||||||
if (map.contains(kID3v2_Disc)) *disc = TagLibStringToQString(map[kID3v2_Disc].front()->toString()).trimmed();
|
if (map.contains(kID3v2_Disc)) *disc = TagLibStringToQString(map[kID3v2_Disc].front()->toString()).trimmed();
|
||||||
if (map.contains(kID3v2_Composer)) song->set_composer(map[kID3v2_Composer].front()->toString());
|
if (map.contains(kID3v2_Composer)) song->set_composer(map[kID3v2_Composer].front()->toString());
|
||||||
if (map.contains(kID3v2_ComposerSort)) song->set_composersort(map[kID3v2_ComposerSort].front()->toString());
|
if (map.contains(kID3v2_ComposerSort)) song->set_composersort(map[kID3v2_ComposerSort].front()->toString());
|
||||||
@@ -1042,7 +1054,7 @@ void TagReaderTagLib::ParseASFAttribute(const TagLib::ASF::AttributeListMap &att
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TagReaderResult TagReaderTagLib::WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const {
|
TagReaderResult TagReaderTagLib::WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data, const TagID3v2Version tag_id3v2_version) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) {
|
if (filename.isEmpty()) {
|
||||||
return TagReaderResult::ErrorCode::FilenameMissing;
|
return TagReaderResult::ErrorCode::FilenameMissing;
|
||||||
@@ -1265,7 +1277,34 @@ TagReaderResult TagReaderTagLib::WriteFile(const QString &filename, const Song &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool success = fileref->save();
|
// Determine ID3v2 version to use and convert to TagLib type
|
||||||
|
TagLib::ID3v2::Version taglib_id3v2_version = TagLib::ID3v2::v4;
|
||||||
|
if (tag_id3v2_version == TagID3v2Version::V3) {
|
||||||
|
taglib_id3v2_version = TagLib::ID3v2::v3;
|
||||||
|
}
|
||||||
|
else if (tag_id3v2_version == TagID3v2Version::V4) {
|
||||||
|
taglib_id3v2_version = TagLib::ID3v2::v4;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
// For MPEG files, use save with ID3v2 version parameter
|
||||||
|
if (TagLib::MPEG::File *file_mpeg = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
|
||||||
|
success = file_mpeg->save(TagLib::MPEG::File::AllTags, TagLib::File::StripOthers, taglib_id3v2_version);
|
||||||
|
}
|
||||||
|
// For WAV files with ID3v2 tags
|
||||||
|
else if (TagLib::RIFF::WAV::File *file_wav = dynamic_cast<TagLib::RIFF::WAV::File*>(fileref->file())) {
|
||||||
|
success = file_wav->save(TagLib::RIFF::WAV::File::AllTags, TagLib::File::StripOthers, taglib_id3v2_version);
|
||||||
|
}
|
||||||
|
// For AIFF files with ID3v2 tags
|
||||||
|
else if (TagLib::RIFF::AIFF::File *file_aiff = dynamic_cast<TagLib::RIFF::AIFF::File*>(fileref->file())) {
|
||||||
|
success = file_aiff->save(taglib_id3v2_version);
|
||||||
|
}
|
||||||
|
// For all other file types, use default save
|
||||||
|
else {
|
||||||
|
success = fileref->save();
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
if (success) {
|
if (success) {
|
||||||
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
|
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
|
|
||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
#include "savetagcoverdata.h"
|
#include "savetagcoverdata.h"
|
||||||
|
#include "tagid3v2version.h"
|
||||||
|
|
||||||
#undef TStringToQString
|
#undef TStringToQString
|
||||||
#undef QStringToTString
|
#undef QStringToTString
|
||||||
@@ -72,7 +73,7 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
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;
|
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
|
#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 TagID3v2Version tag_id3v2_version) const override;
|
||||||
|
|
||||||
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
||||||
TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const override;
|
TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "tagreaderrequest.h"
|
#include "tagreaderrequest.h"
|
||||||
#include "savetagsoptions.h"
|
#include "savetagsoptions.h"
|
||||||
#include "savetagcoverdata.h"
|
#include "savetagcoverdata.h"
|
||||||
|
#include "tagid3v2version.h"
|
||||||
|
|
||||||
using std::make_shared;
|
using std::make_shared;
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ class TagReaderWriteFileRequest : public TagReaderRequest {
|
|||||||
SaveTagsOptions save_tags_options;
|
SaveTagsOptions save_tags_options;
|
||||||
Song song;
|
Song song;
|
||||||
SaveTagCoverData save_tag_cover_data;
|
SaveTagCoverData save_tag_cover_data;
|
||||||
|
TagID3v2Version tag_id3v2_version;
|
||||||
};
|
};
|
||||||
|
|
||||||
using TagReaderWriteFileRequestPtr = SharedPtr<TagReaderWriteFileRequest>;
|
using TagReaderWriteFileRequestPtr = SharedPtr<TagReaderWriteFileRequest>;
|
||||||
|
|||||||
Reference in New Issue
Block a user