mirror of
https://github.com/taglib/taglib.git
synced 2026-06-13 17:59:24 -04:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d86716194 | ||
|
|
eb749ac55b | ||
|
|
148cc9a921 | ||
|
|
88d6b18b4f | ||
|
|
d61a333f27 | ||
|
|
e73517d058 | ||
|
|
6563ceaafa | ||
|
|
c57431e903 | ||
|
|
42dcfec86b |
35
.github/workflows/build.yml
vendored
35
.github/workflows/build.yml
vendored
@@ -9,10 +9,14 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest, windows-2025]
|
||||
include:
|
||||
- os: windows-latest
|
||||
cmake_extra_args: '-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"'
|
||||
- os: windows-2025
|
||||
cmake_extra_args: '-G "MinGW Makefiles" -DCPPUNIT_INCLUDE_DIR="$env:GITHUB_WORKSPACE/pkg/usr/local/include" -DCPPUNIT_LIBRARIES="$($env:GITHUB_WORKSPACE -replace "\\", "/")/pkg/usr/local/lib/libcppunit.dll.a" -DCPPUNIT_INSTALLED_VERSION="1.15.1"'
|
||||
# The windows-2025 runner is used for MinGW.
|
||||
name: ${{ matrix.os == 'windows-2025' && 'mingw' || matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -32,6 +36,24 @@ jobs:
|
||||
run: vcpkg install cppunit utfcpp zlib --triplet x64-windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- name: Set up MinGW
|
||||
shell: bash
|
||||
run: |
|
||||
# Fetch utf8cpp, build cppunit
|
||||
git submodule update --init
|
||||
# Fails with fatal error: cppunit/config-auto.h: No such file or directory
|
||||
# vcpkg install cppunit utfcpp zlib --triplet x64-mingw-dynamic
|
||||
# Probably not working with CMake and MinGW, so we have to build it ourselves.
|
||||
curl -sL "https://dev-www.libreoffice.org/src/cppunit-1.15.1.tar.gz" | tar xz
|
||||
mkdir -p build_cppunit
|
||||
(cd build_cppunit && MAKE=mingw32-make ../cppunit-1.15.1/configure \
|
||||
--enable-shared=yes --enable-static=no \
|
||||
--disable-dependency-tracking --disable-doxygen)
|
||||
find build_cppunit -name Makefile -exec sed -i 's/\($[({]SHELL[)}]\)/"\1"/' {} \;
|
||||
sed -i 's/^\(SUBDIRS.*\) examples\(.*\)$/\1\2/' build_cppunit/Makefile
|
||||
(cd build_cppunit && mingw32-make -j$(nproc) install DESTDIR=$(cd .. && pwd)/pkg)
|
||||
if: matrix.os == 'windows-2025'
|
||||
|
||||
- name: Configure
|
||||
run: >
|
||||
cmake -B${{github.workspace}}/build
|
||||
@@ -41,12 +63,12 @@ jobs:
|
||||
${{ matrix.cmake_extra_args }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{github.workspace}}/build
|
||||
run: ctest -C ${{env.BUILD_TYPE}} -V --no-tests=error
|
||||
if: matrix.os != 'windows-latest'
|
||||
if: matrix.os != 'windows-latest' && matrix.os != 'windows-2025'
|
||||
|
||||
- name: Test Windows
|
||||
working-directory: ${{github.workspace}}/build
|
||||
@@ -57,3 +79,10 @@ jobs:
|
||||
$env:Path += ";$env:VCPKG_INSTALLATION_ROOT\packages\zlib_x64-windows\bin"
|
||||
ctest -C ${{env.BUILD_TYPE}} -V --no-tests=error
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- name: Test MinGW
|
||||
working-directory: ${{github.workspace}}/build
|
||||
run: |
|
||||
$env:Path += ";$PWD/taglib;$PWD/bindings/c;${{github.workspace}}/pkg/usr/local/bin"
|
||||
ctest -C ${{env.BUILD_TYPE}} -V --no-tests=error
|
||||
if: matrix.os == 'windows-2025'
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
TagLib 2.1.1 (June 30, 2025)
|
||||
============================
|
||||
|
||||
* Map ID3v2.3 IPLS frames to both ID3v2.4 TIPL and TMCL to have a consistent
|
||||
behavior when using MusicBrainz tags with the property map interface.
|
||||
* Fix missing include for `wchar_t` when using C bindings with MinGW.
|
||||
|
||||
TagLib 2.1 (May 31, 2025)
|
||||
=========================
|
||||
|
||||
|
||||
@@ -19,9 +19,6 @@ if(APPLE)
|
||||
set(FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "Directory to install frameworks to.")
|
||||
endif()
|
||||
endif()
|
||||
if(NOT BUILD_SHARED_LIBS)
|
||||
add_definitions(-DTAGLIB_STATIC)
|
||||
endif()
|
||||
option(ENABLE_STATIC_RUNTIME "Visual Studio, link with runtime statically" OFF)
|
||||
|
||||
option(ENABLE_CCACHE "Use ccache when building libtag" OFF)
|
||||
@@ -96,7 +93,7 @@ endif()
|
||||
# Patch version: increase it for bug fix releases.
|
||||
set(TAGLIB_SOVERSION_MAJOR 2)
|
||||
set(TAGLIB_SOVERSION_MINOR 1)
|
||||
set(TAGLIB_SOVERSION_PATCH 0)
|
||||
set(TAGLIB_SOVERSION_PATCH 1)
|
||||
|
||||
include(ConfigureChecks.cmake)
|
||||
|
||||
|
||||
@@ -112,6 +112,9 @@ set_target_properties(tag_c PROPERTIES
|
||||
DEFINE_SYMBOL MAKE_TAGLIB_C_LIB
|
||||
INSTALL_NAME_DIR ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
)
|
||||
if(NOT BUILD_SHARED_LIBS)
|
||||
target_compile_definitions(tag_c PUBLIC TAGLIB_STATIC)
|
||||
endif()
|
||||
|
||||
if(TAGLIB_INSTALL_SUFFIX)
|
||||
if(BUILD_SHARED_LIBS)
|
||||
|
||||
@@ -43,6 +43,7 @@ extern "C" {
|
||||
#define TAGLIB_C_EXPORT
|
||||
#endif
|
||||
|
||||
#include <wchar.h>
|
||||
#ifdef _MSC_VER
|
||||
/* minwindef.h contains typedef int BOOL */
|
||||
#include <windows.h>
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "tag_c.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tag_c.h"
|
||||
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
@@ -461,6 +461,9 @@ set_target_properties(tag PROPERTIES
|
||||
INTERFACE_LINK_LIBRARIES "${ZLIB_INTERFACE_LINK_LIBRARIES}"
|
||||
PUBLIC_HEADER "${tag_HDRS}"
|
||||
)
|
||||
if(NOT BUILD_SHARED_LIBS)
|
||||
target_compile_definitions(tag PUBLIC TAGLIB_STATIC)
|
||||
endif()
|
||||
if(VISIBILITY_HIDDEN)
|
||||
set_target_properties(tag PROPERTIES C_VISIBILITY_PRESET hidden)
|
||||
set_target_properties(tag PROPERTIES CXX_VISIBILITY_PRESET hidden)
|
||||
|
||||
@@ -139,7 +139,7 @@ void APE::Properties::read(File *file, offset_t streamLength)
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
const auto length = static_cast<double>(d->sampleFrames) * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -302,16 +302,16 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
|
||||
|
||||
if(kind == 0) {
|
||||
data = renderString(name, true) +
|
||||
ByteVector::fromShort(static_cast<int>(d->type), false) +
|
||||
ByteVector::fromShort(data.size(), false) +
|
||||
ByteVector::fromShort(static_cast<short>(d->type), false) +
|
||||
ByteVector::fromShort(static_cast<short>(data.size()), false) +
|
||||
data;
|
||||
}
|
||||
else {
|
||||
ByteVector nameData = renderString(name);
|
||||
data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) +
|
||||
ByteVector::fromShort(d->stream, false) +
|
||||
ByteVector::fromShort(nameData.size(), false) +
|
||||
ByteVector::fromShort(static_cast<int>(d->type), false) +
|
||||
data = ByteVector::fromShort(static_cast<short>(kind == 2 ? d->language : 0), false) +
|
||||
ByteVector::fromShort(static_cast<short>(d->stream), false) +
|
||||
ByteVector::fromShort(static_cast<short>(nameData.size()), false) +
|
||||
ByteVector::fromShort(static_cast<short>(d->type), false) +
|
||||
ByteVector::fromUInt(data.size(), false) +
|
||||
nameData +
|
||||
data;
|
||||
|
||||
@@ -221,7 +221,8 @@ void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, long l
|
||||
|
||||
const long long duration = data.toLongLong(40, false);
|
||||
const long long preroll = data.toLongLong(56, false);
|
||||
file->d->properties->setLengthInMilliseconds(static_cast<int>(duration / 10000.0 - preroll + 0.5));
|
||||
file->d->properties->setLengthInMilliseconds(
|
||||
static_cast<int>(static_cast<double>(duration) / 10000.0 - static_cast<double>(preroll) + 0.5));
|
||||
}
|
||||
|
||||
ByteVector ASF::File::FilePrivate::StreamPropertiesObject::guid() const
|
||||
@@ -271,11 +272,11 @@ ByteVector ASF::File::FilePrivate::ContentDescriptionObject::render(ASF::File *f
|
||||
const ByteVector v4 = renderString(file->d->tag->comment());
|
||||
const ByteVector v5 = renderString(file->d->tag->rating());
|
||||
data.clear();
|
||||
data.append(ByteVector::fromShort(v1.size(), false));
|
||||
data.append(ByteVector::fromShort(v2.size(), false));
|
||||
data.append(ByteVector::fromShort(v3.size(), false));
|
||||
data.append(ByteVector::fromShort(v4.size(), false));
|
||||
data.append(ByteVector::fromShort(v5.size(), false));
|
||||
data.append(ByteVector::fromShort(static_cast<short>(v1.size()), false));
|
||||
data.append(ByteVector::fromShort(static_cast<short>(v2.size()), false));
|
||||
data.append(ByteVector::fromShort(static_cast<short>(v3.size()), false));
|
||||
data.append(ByteVector::fromShort(static_cast<short>(v4.size()), false));
|
||||
data.append(ByteVector::fromShort(static_cast<short>(v5.size()), false));
|
||||
data.append(v1);
|
||||
data.append(v2);
|
||||
data.append(v3);
|
||||
@@ -302,7 +303,7 @@ void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *
|
||||
ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF::File *file)
|
||||
{
|
||||
data.clear();
|
||||
data.append(ByteVector::fromShort(attributeData.size(), false));
|
||||
data.append(ByteVector::fromShort(static_cast<short>(attributeData.size()), false));
|
||||
data.append(attributeData.toByteVector(""));
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
@@ -325,7 +326,7 @@ void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, long long /*
|
||||
ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file)
|
||||
{
|
||||
data.clear();
|
||||
data.append(ByteVector::fromShort(attributeData.size(), false));
|
||||
data.append(ByteVector::fromShort(static_cast<short>(attributeData.size()), false));
|
||||
data.append(attributeData.toByteVector(""));
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
@@ -348,7 +349,7 @@ void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, long
|
||||
ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file)
|
||||
{
|
||||
data.clear();
|
||||
data.append(ByteVector::fromShort(attributeData.size(), false));
|
||||
data.append(ByteVector::fromShort(static_cast<short>(attributeData.size()), false));
|
||||
data.append(attributeData.toByteVector(""));
|
||||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace TagLib
|
||||
{
|
||||
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false);
|
||||
if(includeLength) {
|
||||
data = ByteVector::fromShort(data.size(), false) + data;
|
||||
data = ByteVector::fromShort(static_cast<short>(data.size()), false) + data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ DSDIFF::Properties::Properties(unsigned int sampleRate,
|
||||
d->sampleRate = sampleRate;
|
||||
d->bitrate = bitrate;
|
||||
d->length = d->sampleRate > 0
|
||||
? static_cast<int>(d->sampleCount * 1000.0 / d->sampleRate + 0.5)
|
||||
? static_cast<int>(static_cast<double>(d->sampleCount) * 1000.0 / d->sampleRate + 0.5)
|
||||
: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ bool DSF::File::isSupported(IOStream *stream)
|
||||
return id.startsWith("DSD ");
|
||||
}
|
||||
|
||||
DSF::File::File(FileName file, bool readProperties,
|
||||
DSF::File::File(FileName file, bool,
|
||||
AudioProperties::ReadStyle propertiesStyle,
|
||||
ID3v2::FrameFactory *frameFactory) :
|
||||
TagLib::File(file),
|
||||
@@ -69,7 +69,7 @@ DSF::File::File(FileName file, bool readProperties,
|
||||
read(propertiesStyle);
|
||||
}
|
||||
|
||||
DSF::File::File(IOStream *stream, bool readProperties,
|
||||
DSF::File::File(IOStream *stream, bool,
|
||||
AudioProperties::ReadStyle propertiesStyle,
|
||||
ID3v2::FrameFactory *frameFactory) :
|
||||
TagLib::File(stream),
|
||||
|
||||
@@ -129,6 +129,6 @@ void DSF::Properties::read(const ByteVector &data)
|
||||
d->bitrate = static_cast<unsigned int>(
|
||||
d->samplingFrequency * d->bitsPerSample * d->channelNum / 1000.0 + 0.5);
|
||||
d->length = d->samplingFrequency > 0
|
||||
? static_cast<unsigned int>(d->sampleCount * 1000.0 / d->samplingFrequency + 0.5)
|
||||
? static_cast<unsigned int>(static_cast<double>(d->sampleCount) * 1000.0 / d->samplingFrequency + 0.5)
|
||||
: 0;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ public:
|
||||
return !file || !file->isValid();
|
||||
}
|
||||
|
||||
bool isNullWithDebugMessage(const String &methodName) const
|
||||
bool isNullWithDebugMessage([[maybe_unused]] const String &methodName) const
|
||||
{
|
||||
if(isNull()) {
|
||||
debug("FileRef::" + methodName + "() - Called without a valid file.");
|
||||
|
||||
@@ -274,7 +274,7 @@ bool FLAC::File::save()
|
||||
it = d->blocks.erase(it);
|
||||
continue;
|
||||
}
|
||||
blockHeader[0] = (*it)->code();
|
||||
blockHeader[0] = static_cast<char>((*it)->code());
|
||||
data.append(blockHeader);
|
||||
data.append(blockData);
|
||||
++it;
|
||||
|
||||
@@ -134,7 +134,7 @@ void FLAC::Properties::read(const ByteVector &data, offset_t streamLength)
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
const auto length = static_cast<double>(d->sampleFrames) * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
|
||||
}
|
||||
|
||||
if(data.size() >= pos + 16)
|
||||
|
||||
@@ -167,7 +167,7 @@ bool IT::File::save()
|
||||
if(messageOffset + messageLength >= fileSize) {
|
||||
// append new message
|
||||
seek(54);
|
||||
writeU16L(message.size());
|
||||
writeU16L(static_cast<unsigned short>(message.size()));
|
||||
writeU32L(messageOffset);
|
||||
seek(messageOffset);
|
||||
writeBlock(message);
|
||||
|
||||
@@ -400,7 +400,7 @@ Map<ByteVector, String> ItemFactory::namePropertyMap() const
|
||||
}
|
||||
|
||||
MP4::AtomDataList ItemFactory::parseData2(
|
||||
const MP4::Atom *atom, const ByteVector &data, int expectedFlags,
|
||||
const MP4::Atom *, const ByteVector &data, int expectedFlags,
|
||||
bool freeForm)
|
||||
{
|
||||
AtomDataList result;
|
||||
@@ -663,7 +663,7 @@ ByteVector ItemFactory::renderInt(
|
||||
const ByteVector &name, const MP4::Item &item)
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector::fromShort(item.toInt()));
|
||||
data.append(ByteVector::fromShort(static_cast<short>(item.toInt())));
|
||||
return renderData(name, TypeInteger, data);
|
||||
}
|
||||
|
||||
@@ -703,8 +703,8 @@ ByteVector ItemFactory::renderIntPair(
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(2, '\0') +
|
||||
ByteVector::fromShort(item.toIntPair().first) +
|
||||
ByteVector::fromShort(item.toIntPair().second) +
|
||||
ByteVector::fromShort(static_cast<short>(item.toIntPair().first)) +
|
||||
ByteVector::fromShort(static_cast<short>(item.toIntPair().second)) +
|
||||
ByteVector(2, '\0'));
|
||||
return renderData(name, TypeImplicit, data);
|
||||
}
|
||||
@@ -714,8 +714,8 @@ ByteVector ItemFactory::renderIntPairNoTrailing(
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(2, '\0') +
|
||||
ByteVector::fromShort(item.toIntPair().first) +
|
||||
ByteVector::fromShort(item.toIntPair().second));
|
||||
ByteVector::fromShort(static_cast<short>(item.toIntPair().first)) +
|
||||
ByteVector::fromShort(static_cast<short>(item.toIntPair().second)));
|
||||
return renderData(name, TypeImplicit, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ MP4::Properties::read(File *file, const Atoms *atoms)
|
||||
}
|
||||
}
|
||||
if(unit > 0 && length > 0)
|
||||
d->length = static_cast<int>(length * 1000.0 / unit + 0.5);
|
||||
d->length = static_cast<int>(static_cast<double>(length) * 1000.0 / static_cast<double>(unit) + 0.5);
|
||||
|
||||
MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd");
|
||||
if(!atom) {
|
||||
|
||||
@@ -229,7 +229,7 @@ void MPC::Properties::readSV8(File *file, offset_t streamLength)
|
||||
frameCount > 0 && d->sampleRate > 0) {
|
||||
const auto length = static_cast<double>(frameCount) * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
else if (packetType == "RG") {
|
||||
@@ -331,6 +331,6 @@ void MPC::Properties::readSV7(const ByteVector &data, offset_t streamLength)
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
|
||||
if(d->bitrate == 0)
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ void ID3v1::Tag::setComment(const String &s)
|
||||
|
||||
void ID3v1::Tag::setGenre(const String &s)
|
||||
{
|
||||
d->genre = ID3v1::genreIndex(s);
|
||||
d->genre = static_cast<unsigned char>(ID3v1::genreIndex(s));
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setYear(unsigned int i)
|
||||
@@ -186,7 +186,7 @@ void ID3v1::Tag::setYear(unsigned int i)
|
||||
|
||||
void ID3v1::Tag::setTrack(unsigned int i)
|
||||
{
|
||||
d->track = i < 256 ? i : 0;
|
||||
d->track = static_cast<unsigned char>(i < 256 ? i : 0);
|
||||
}
|
||||
|
||||
unsigned int ID3v1::Tag::genreNumber() const
|
||||
@@ -196,7 +196,7 @@ unsigned int ID3v1::Tag::genreNumber() const
|
||||
|
||||
void ID3v1::Tag::setGenreNumber(unsigned int i)
|
||||
{
|
||||
d->genre = i < 256 ? i : 255;
|
||||
d->genre = static_cast<unsigned char>(i < 256 ? i : 255);
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setStringHandler(const StringHandler *handler)
|
||||
|
||||
@@ -315,7 +315,7 @@ PropertyMap TextIdentificationFrame::makeTIPLProperties() const
|
||||
const StringList l = fieldList();
|
||||
for(auto it = l.begin(); it != l.end(); ++it) {
|
||||
auto found = std::find_if(involvedPeople.begin(), involvedPeople.end(),
|
||||
[=](const auto &person) { return *it == person.first; });
|
||||
[=](const auto &person) { return it->upper() == person.first; });
|
||||
if(found != involvedPeople.end()) {
|
||||
map.insert(found->second, (++it)->split(","));
|
||||
}
|
||||
|
||||
@@ -357,6 +357,49 @@ void FrameFactory::rebuildAggregateFrames(ID3v2::Tag *tag) const
|
||||
}
|
||||
}
|
||||
}
|
||||
if(tag->header()->majorVersion() < 4 &&
|
||||
tag->frameList("TIPL").size() == 1 &&
|
||||
tag->frameList("TMCL").size() == 0)
|
||||
{
|
||||
// FrameFactory::updateFrame() has mapped IPLS (ID3v2.3)/ IPL (ID3v2.2)
|
||||
// to TIPL (ID3v2.4). However, the musicians should be rather in TMCL.
|
||||
// Move all involvement/involvee pairs which are not supported by the
|
||||
// TIPL property map interface to a TMCL frame.
|
||||
if(auto tipl =
|
||||
dynamic_cast<TextIdentificationFrame *>(tag->frameList("TIPL").front())) {
|
||||
if(StringList tiplValues = tipl->toStringList(); tiplValues.size() % 2 == 0) {
|
||||
static StringList tiplKeys;
|
||||
if(tiplKeys.isEmpty()) {
|
||||
for(const auto &kv : TextIdentificationFrame::involvedPeopleMap()) {
|
||||
tiplKeys.append(kv.second);
|
||||
}
|
||||
}
|
||||
StringList tmclValues;
|
||||
for(auto it = tiplValues.begin(); it != tiplValues.end();) {
|
||||
const String involvement = *it;
|
||||
if(!tiplKeys.contains(involvement.upper())) {
|
||||
tmclValues.append(involvement);
|
||||
it = tiplValues.erase(it);
|
||||
tmclValues.append(*it);
|
||||
it = tiplValues.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if(!tmclValues.isEmpty()) {
|
||||
auto tmcl = new TextIdentificationFrame("TMCL");
|
||||
tmcl->setText(tmclValues);
|
||||
tag->addFrame(tmcl);
|
||||
if(!tiplValues.isEmpty()) {
|
||||
tipl->setText(tiplValues);
|
||||
} else {
|
||||
tag->removeFrame(tipl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String::Type FrameFactory::defaultTextEncoding() const
|
||||
|
||||
@@ -240,7 +240,7 @@ void MPEG::Properties::read(File *file, ReadStyle readStyle)
|
||||
const Header lastHeader(file, lastFrameOffset, false);
|
||||
if(const offset_t streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength();
|
||||
streamLength > 0)
|
||||
d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5);
|
||||
d->length = static_cast<int>(static_cast<double>(streamLength) * 8.0 / d->bitrate + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ void Opus::Properties::read(File *file)
|
||||
fileLengthWithoutOverhead -= file->packet(i).size();
|
||||
}
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(fileLengthWithoutOverhead * 8.0 / length + 0.5);
|
||||
d->bitrate = static_cast<int>(static_cast<double>(fileLengthWithoutOverhead) * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -163,7 +163,7 @@ void Speex::Properties::read(File *file)
|
||||
fileLengthWithoutOverhead -= file->packet(i).size();
|
||||
}
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(fileLengthWithoutOverhead * 8.0 / length + 0.5);
|
||||
d->bitrate = static_cast<int>(static_cast<double>(fileLengthWithoutOverhead) * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -166,7 +166,7 @@ void Vorbis::Properties::read(File *file)
|
||||
fileLengthWithoutOverhead -= file->packet(i).size();
|
||||
}
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(fileLengthWithoutOverhead * 8.0 / length + 0.5);
|
||||
d->bitrate = static_cast<int>(static_cast<double>(fileLengthWithoutOverhead) * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -196,7 +196,7 @@ bool Shorten::File::isSupported(IOStream *stream)
|
||||
return id.startsWith("ajkg");
|
||||
}
|
||||
|
||||
Shorten::File::File(FileName file, bool readProperties,
|
||||
Shorten::File::File(FileName file, bool,
|
||||
AudioProperties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(file),
|
||||
d(std::make_unique<FilePrivate>())
|
||||
@@ -205,7 +205,7 @@ Shorten::File::File(FileName file, bool readProperties,
|
||||
read(propertiesStyle);
|
||||
}
|
||||
|
||||
Shorten::File::File(IOStream *stream, bool readProperties,
|
||||
Shorten::File::File(IOStream *stream, bool,
|
||||
AudioProperties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(stream),
|
||||
d(std::make_unique<FilePrivate>())
|
||||
|
||||
@@ -65,7 +65,7 @@ Shorten::Properties::Properties(const PropertyValues *values, ReadStyle style) :
|
||||
|
||||
d->bitrate = static_cast<int>(d->sampleRate * d->bitsPerSample * d->channelCount / 1000.0 + 0.5);
|
||||
if(d->sampleRate > 0)
|
||||
d->length = static_cast<int>(d->sampleFrames * 1000.0 / d->sampleRate + 0.5);
|
||||
d->length = static_cast<int>(static_cast<double>(d->sampleFrames) * 1000.0 / d->sampleRate + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
#define TAGLIB_MAJOR_VERSION 2
|
||||
#define TAGLIB_MINOR_VERSION 1
|
||||
#define TAGLIB_PATCH_VERSION 0
|
||||
#define TAGLIB_PATCH_VERSION 1
|
||||
|
||||
#if (defined(_MSC_VER) && _MSC_VER >= 1600)
|
||||
#define TAGLIB_CONSTRUCT_BITSET(x) static_cast<unsigned long long>(x)
|
||||
|
||||
@@ -120,7 +120,8 @@ T toNumber(const ByteVector &v, size_t offset, size_t length, bool mostSignifica
|
||||
T sum = 0;
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
const size_t shift = (mostSignificantByteFirst ? length - 1 - i : i) * 8;
|
||||
sum |= static_cast<T>(static_cast<unsigned char>(v[static_cast<int>(offset + i)])) << shift;
|
||||
sum |= static_cast<T>(
|
||||
static_cast<T>(static_cast<unsigned char>(v[static_cast<int>(offset + i)])) << shift);
|
||||
}
|
||||
|
||||
return sum;
|
||||
@@ -236,7 +237,7 @@ long double toFloat80(const ByteVector &v, size_t offset)
|
||||
debug("toFloat80() - can't handle the infinity or NaN. Returning 0.");
|
||||
return 0.0;
|
||||
}
|
||||
val = ::ldexp(static_cast<long double>(fraction), exponent - 16383 - 63);
|
||||
val = ::ldexpl(static_cast<long double>(fraction), exponent - 16383 - 63);
|
||||
}
|
||||
|
||||
if(negative)
|
||||
|
||||
@@ -145,7 +145,7 @@ void TrueAudio::Properties::read(const ByteVector &data, offset_t streamLength)
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
const auto length = static_cast<double>(d->sampleFrames) * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ void WavPack::Properties::read(File *file, offset_t streamLength)
|
||||
if(d->version < MIN_STREAM_VERS || d->version > MAX_STREAM_VERS)
|
||||
break;
|
||||
|
||||
d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - ((flags & SHIFT_MASK) >> SHIFT_LSB);
|
||||
d->bitsPerSample = static_cast<int>(((flags & BYTES_STORED) + 1) * 8 - ((flags & SHIFT_MASK) >> SHIFT_LSB));
|
||||
d->sampleRate = static_cast<int>(smplRate);
|
||||
d->lossless = !(flags & HYBRID_FLAG);
|
||||
d->sampleFrames = smplFrames;
|
||||
@@ -308,7 +308,7 @@ void WavPack::Properties::read(File *file, offset_t streamLength)
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
const auto length = static_cast<double>(d->sampleFrames) * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -560,7 +560,7 @@ public:
|
||||
// reverse
|
||||
{
|
||||
for(int i = 0; i < 256; i++){
|
||||
all[i]=static_cast<unsigned char>(255)-i;
|
||||
all[i]=static_cast<unsigned char>(255 - i);
|
||||
}
|
||||
ByteVector b64 = all.toBase64();
|
||||
ByteVector original = ByteVector::fromBase64(b64);
|
||||
|
||||
@@ -1035,7 +1035,7 @@ public:
|
||||
tf->setText(StringList().append("Guitar").append("Artist 1").append("Drums").append("Artist 2"));
|
||||
foo.ID3v2Tag()->addFrame(tf);
|
||||
tf = new ID3v2::TextIdentificationFrame("TIPL", String::Latin1);
|
||||
tf->setText(StringList().append("Producer").append("Artist 3").append("Mastering").append("Artist 4"));
|
||||
tf->setText(StringList().append("Producer").append("Artist 3").append("Engineer").append("Artist 4"));
|
||||
foo.ID3v2Tag()->addFrame(tf);
|
||||
tf = new ID3v2::TextIdentificationFrame("TCON", String::Latin1);
|
||||
tf->setText(StringList().append("51").append("Noise").append("Power Noise"));
|
||||
@@ -1062,15 +1062,18 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(String("2012-04-17T12:01"), tf->fieldList().front());
|
||||
tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TIPL").front());
|
||||
CPPUNIT_ASSERT(tf);
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(8), tf->fieldList().size());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(4), tf->fieldList().size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Producer"), tf->fieldList()[0]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Artist 3"), tf->fieldList()[1]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Engineer"), tf->fieldList()[2]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Artist 4"), tf->fieldList()[3]);
|
||||
tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TMCL").front());
|
||||
CPPUNIT_ASSERT(tf);
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(4), tf->fieldList().size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Guitar"), tf->fieldList()[0]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Artist 1"), tf->fieldList()[1]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Drums"), tf->fieldList()[2]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Artist 2"), tf->fieldList()[3]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Producer"), tf->fieldList()[4]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Artist 3"), tf->fieldList()[5]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Mastering"), tf->fieldList()[6]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Artist 4"), tf->fieldList()[7]);
|
||||
tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TCON").front());
|
||||
CPPUNIT_ASSERT(tf);
|
||||
CPPUNIT_ASSERT_EQUAL(3U, tf->fieldList().size());
|
||||
@@ -1090,7 +1093,7 @@ public:
|
||||
}
|
||||
{
|
||||
const ByteVector expectedId3v23Data(
|
||||
"ID3" "\x03\x00\x00\x00\x00\x09\x49"
|
||||
"ID3" "\x03\x00\x00\x00\x00\x09\x48"
|
||||
"TSOA" "\x00\x00\x00\x01\x00\x00\x00"
|
||||
"TSOT" "\x00\x00\x00\x01\x00\x00\x00"
|
||||
"TSOP" "\x00\x00\x00\x01\x00\x00\x00"
|
||||
@@ -1098,10 +1101,10 @@ public:
|
||||
"TYER" "\x00\x00\x00\x05\x00\x00\x00" "2012"
|
||||
"TDAT" "\x00\x00\x00\x05\x00\x00\x00" "1704"
|
||||
"TIME" "\x00\x00\x00\x05\x00\x00\x00" "1201"
|
||||
"IPLS" "\x00\x00\x00\x44\x00\x00\x00" "Guitar" "\x00"
|
||||
"IPLS" "\x00\x00\x00\x43\x00\x00\x00" "Guitar" "\x00"
|
||||
"Artist 1" "\x00" "Drums" "\x00" "Artist 2" "\x00" "Producer" "\x00"
|
||||
"Artist 3" "\x00" "Mastering" "\x00" "Artist 4"
|
||||
"TCON" "\x00\x00\x00\x14\x00\x00\x00" "(51)(39)Power Noise", 211);
|
||||
"Artist 3" "\x00" "Engineer" "\x00" "Artist 4"
|
||||
"TCON" "\x00\x00\x00\x14\x00\x00\x00" "(51)(39)Power Noise", 210);
|
||||
const ByteVector actualId3v23Data =
|
||||
PlainFile(newname.c_str()).readBlock(expectedId3v23Data.size());
|
||||
CPPUNIT_ASSERT_EQUAL(expectedId3v23Data, actualId3v23Data);
|
||||
@@ -1265,6 +1268,38 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<ID3v2::UserTextIdentificationFrame *>(nullptr), ID3v2::UserTextIdentificationFrame::find(&tag, "non existing"));
|
||||
CPPUNIT_ASSERT_EQUAL(frame10, ID3v2::CommentsFrame::findByDescription(&tag, "iTunNORM"));
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<ID3v2::CommentsFrame *>(nullptr), ID3v2::CommentsFrame::findByDescription(&tag, "non existing"));
|
||||
|
||||
// Check if the allowed TIPL keys are correctly mapped to properties.
|
||||
// These are the keys used by MusicBrainz: arranger, engineer, producer, DJ-mix, mix.
|
||||
// See https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html.
|
||||
// MusicBrainz Picard uses lowercase keys whereas TagLib used uppercase keys.
|
||||
auto frame11 = new ID3v2::TextIdentificationFrame("TIPL");
|
||||
StringList tiplData;
|
||||
tiplData.append("arranger");
|
||||
tiplData.append("an arranger");
|
||||
tiplData.append("ENGINEER");
|
||||
tiplData.append("an engineer");
|
||||
tiplData.append("producer");
|
||||
tiplData.append("a producer");
|
||||
tiplData.append("DJ-mix");
|
||||
tiplData.append("a DJ mixer");
|
||||
tiplData.append("mix");
|
||||
tiplData.append("a mixer");
|
||||
frame11->setText(tiplData);
|
||||
tag.addFrame(frame11);
|
||||
|
||||
properties = tag.properties();
|
||||
CPPUNIT_ASSERT_EQUAL(0u, properties.unsupportedData().size());
|
||||
CPPUNIT_ASSERT(properties.contains("ARRANGER"));
|
||||
CPPUNIT_ASSERT(properties.contains("ENGINEER"));
|
||||
CPPUNIT_ASSERT(properties.contains("PRODUCER"));
|
||||
CPPUNIT_ASSERT(properties.contains("DJMIXER"));
|
||||
CPPUNIT_ASSERT(properties.contains("MIXER"));
|
||||
CPPUNIT_ASSERT_EQUAL(String("an arranger"), properties["ARRANGER"].front());
|
||||
CPPUNIT_ASSERT_EQUAL(String("an engineer"), properties["ENGINEER"].front());
|
||||
CPPUNIT_ASSERT_EQUAL(String("a producer"), properties["PRODUCER"].front());
|
||||
CPPUNIT_ASSERT_EQUAL(String("a DJ mixer"), properties["DJMIXER"].front());
|
||||
CPPUNIT_ASSERT_EQUAL(String("a mixer"), properties["MIXER"].front());
|
||||
}
|
||||
|
||||
void testPropertiesMovement()
|
||||
|
||||
Reference in New Issue
Block a user