mirror of
https://github.com/taglib/taglib.git
synced 2026-06-07 06:50:32 -04:00
Compare commits
70 Commits
v1.12-beta
...
v1.12-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c14571647 | ||
|
|
f4b476a620 | ||
|
|
f8d78a61f7 | ||
|
|
cf6c68bafc | ||
|
|
f7c24930cd | ||
|
|
310c3bc043 | ||
|
|
e6c03c6de8 | ||
|
|
2c29fbeabb | ||
|
|
4828a3b925 | ||
|
|
794a2a0b2b | ||
|
|
f32d27973f | ||
|
|
f74b166435 | ||
|
|
c28dc78060 | ||
|
|
89fb62cfb1 | ||
|
|
59a2994f4e | ||
|
|
f3bdd416da | ||
|
|
39f86d02e7 | ||
|
|
1a2e1d08ac | ||
|
|
3f3b48353c | ||
|
|
54f5c66b65 | ||
|
|
3749dd7b75 | ||
|
|
d602ae483e | ||
|
|
5374cb1ac4 | ||
|
|
8a461ccedc | ||
|
|
17c2220588 | ||
|
|
271c004b30 | ||
|
|
25d9bd1814 | ||
|
|
b0bb7f8c0f | ||
|
|
4d3ab73d2e | ||
|
|
ae867cbd8c | ||
|
|
563fbaf82a | ||
|
|
e5ad041e42 | ||
|
|
9ca7b0c33a | ||
|
|
a00b3499b4 | ||
|
|
d84e86da9c | ||
|
|
741ed4e4bf | ||
|
|
e4813f4996 | ||
|
|
1332d44ff6 | ||
|
|
3d71ea1ad2 | ||
|
|
02c8995273 | ||
|
|
7e92af6e8b | ||
|
|
7ec1127f3e | ||
|
|
30d839538d | ||
|
|
8f6b6ac055 | ||
|
|
a5f11697f7 | ||
|
|
1721354627 | ||
|
|
ac7e5303a6 | ||
|
|
8ba246cdbe | ||
|
|
6656528f18 | ||
|
|
bad2cea122 | ||
|
|
61d5bcfd5b | ||
|
|
2dd6931eee | ||
|
|
91b00b17b2 | ||
|
|
e116824380 | ||
|
|
2db13ad8cf | ||
|
|
cd767738fc | ||
|
|
43bc11541d | ||
|
|
6806dc4cf2 | ||
|
|
ec9c49b964 | ||
|
|
872cafb0b9 | ||
|
|
47342f6974 | ||
|
|
5763d042a6 | ||
|
|
cd9e6b7502 | ||
|
|
54508df30b | ||
|
|
dcf0331eb1 | ||
|
|
96155f35fa | ||
|
|
ebd3373d6d | ||
|
|
f3ecfa11bb | ||
|
|
7082b9f66b | ||
|
|
2d90d938fe |
@@ -12,6 +12,9 @@ compiler:
|
||||
- gcc
|
||||
- clang
|
||||
|
||||
arch:
|
||||
- ppc64le
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
|
||||
@@ -90,9 +90,9 @@ endif()
|
||||
# 2. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0.
|
||||
# 3. If any interfaces have been added since the last public release, then increment age.
|
||||
# 4. If any interfaces have been removed since the last public release, then set age to 0.
|
||||
set(TAGLIB_SOVERSION_CURRENT 18)
|
||||
set(TAGLIB_SOVERSION_CURRENT 19)
|
||||
set(TAGLIB_SOVERSION_REVISION 0)
|
||||
set(TAGLIB_SOVERSION_AGE 17)
|
||||
set(TAGLIB_SOVERSION_AGE 18)
|
||||
|
||||
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
|
||||
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")
|
||||
|
||||
@@ -37,17 +37,6 @@ endif()
|
||||
# Determine which kind of atomic operations your compiler supports.
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <atomic>
|
||||
int main() {
|
||||
std::atomic_int x(1);
|
||||
++x;
|
||||
--x;
|
||||
return 0;
|
||||
}
|
||||
" HAVE_STD_ATOMIC)
|
||||
|
||||
if(NOT HAVE_STD_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
int main() {
|
||||
volatile int x;
|
||||
__sync_add_and_fetch(&x, 1);
|
||||
@@ -56,8 +45,8 @@ if(NOT HAVE_STD_ATOMIC)
|
||||
}
|
||||
" HAVE_GCC_ATOMIC)
|
||||
|
||||
if(NOT HAVE_GCC_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
if(NOT HAVE_GCC_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
#include <libkern/OSAtomic.h>
|
||||
int main() {
|
||||
volatile int32_t x;
|
||||
@@ -67,8 +56,8 @@ if(NOT HAVE_STD_ATOMIC)
|
||||
}
|
||||
" HAVE_MAC_ATOMIC)
|
||||
|
||||
if(NOT HAVE_MAC_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
if(NOT HAVE_MAC_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
#include <windows.h>
|
||||
int main() {
|
||||
volatile LONG x;
|
||||
@@ -78,8 +67,8 @@ if(NOT HAVE_STD_ATOMIC)
|
||||
}
|
||||
" HAVE_WIN_ATOMIC)
|
||||
|
||||
if(NOT HAVE_WIN_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
if(NOT HAVE_WIN_ATOMIC)
|
||||
check_cxx_source_compiles("
|
||||
#include <ia64intrin.h>
|
||||
int main() {
|
||||
volatile int x;
|
||||
@@ -88,7 +77,6 @@ if(NOT HAVE_STD_ATOMIC)
|
||||
return 0;
|
||||
}
|
||||
" HAVE_IA64_ATOMIC)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
14
NEWS
14
NEWS
@@ -1,24 +1,36 @@
|
||||
============================
|
||||
|
||||
* Added support for DSF and DSDIFF files.
|
||||
* Added support for WinRT.
|
||||
* Added support for Linux on POWER.
|
||||
* Added support for classical music tags of iTunes 12.5.
|
||||
* Added support for file descriptor to FileStream.
|
||||
* Added support for 'cmID', 'purl', 'egid' MP4 atoms.
|
||||
* Added support for 'GRP1' ID3v2 frame.
|
||||
* Added support for extensible WAV subformat.
|
||||
* Enabled FileRef to detect file types based on the stream content.
|
||||
* Dropped support for Windows 9x and NT 4.0 or older.
|
||||
* Check for mandatory header objects in ASF files.
|
||||
* More tolerant handling of RIFF padding, WAV files, broken MPEG streams.
|
||||
* Improved calculation of Ogg, Opus, Speex, WAV, MP4 bitrates.
|
||||
* Improved Windows compatibility by storing FLAC picture after comments.
|
||||
* Fixed numerical genres in ID3v2.3.0 'TCON' frames.
|
||||
* Fixed consistency of API removing MP4 items when empty values are set.
|
||||
* Fixed consistency of API preferring COMM frames with no description.
|
||||
* Fixed OOB read on invalid Ogg FLAC files (CVE-2018-11439).
|
||||
* Fixed handling of empty MPEG files.
|
||||
* Fixed parsing MP4 mdhd timescale.
|
||||
* Fixed reading MP4 atoms with zero length.
|
||||
* Fixed reading FLAC files with zero-sized seektables.
|
||||
* Fixed handling of lowercase field names in Vorbis Comments.
|
||||
* Fixed handling of 'rate' atoms in MP4 files.
|
||||
* Fixed handling of invalid UTF-8 sequences.
|
||||
* Fixed possible file corruptions when saving Ogg files.
|
||||
* Fixed handling of non-audio blocks, sampling rates, DSD audio in WavPack files.
|
||||
* TableOfContentsFrame::toString() improved.
|
||||
* UserTextIdentificationFrame::toString() improved.
|
||||
* Marked FileRef::create() deprecated.
|
||||
* Marked MPEG::File::save() with boolean parameters deprecated,
|
||||
provide overloads with enum parameters.
|
||||
* Several smaller bug fixes and performance improvements.
|
||||
|
||||
TagLib 1.11.1 (Oct 24, 2016)
|
||||
|
||||
@@ -10,7 +10,7 @@ TagLib is a library for reading and editing the metadata of several
|
||||
popular audio formats. Currently it supports both ID3v1 and [ID3v2][]
|
||||
for MP3 files, [Ogg Vorbis][] comments and ID3 tags
|
||||
in [FLAC][], MPC, Speex, WavPack, TrueAudio, WAV, AIFF, MP4, APE,
|
||||
DSF, DFF, and ASF files.
|
||||
and ASF files.
|
||||
|
||||
TagLib is distributed under the [GNU Lesser General Public License][]
|
||||
(LGPL) and [Mozilla Public License][] (MPL). Essentially that means that
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#cmakedefine HAVE_OPENBSD_BYTESWAP 1
|
||||
|
||||
/* Defined if your compiler supports some atomic operations */
|
||||
#cmakedefine HAVE_STD_ATOMIC 1
|
||||
#cmakedefine HAVE_GCC_ATOMIC 1
|
||||
#cmakedefine HAVE_MAC_ATOMIC 1
|
||||
#cmakedefine HAVE_WIN_ATOMIC 1
|
||||
|
||||
@@ -5,6 +5,7 @@ include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v1
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2/frames
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../bindings/c/
|
||||
)
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <id3v2tag.h>
|
||||
#include <id3v2frame.h>
|
||||
#include <id3v2header.h>
|
||||
#include <commentsframe.h>
|
||||
|
||||
#include <id3v1tag.h>
|
||||
|
||||
@@ -65,8 +66,15 @@ int main(int argc, char *argv[])
|
||||
<< endl;
|
||||
|
||||
ID3v2::FrameList::ConstIterator it = id3v2tag->frameList().begin();
|
||||
for(; it != id3v2tag->frameList().end(); it++)
|
||||
cout << (*it)->frameID() << " - \"" << (*it)->toString() << "\"" << endl;
|
||||
for(; it != id3v2tag->frameList().end(); it++) {
|
||||
cout << (*it)->frameID();
|
||||
|
||||
if(ID3v2::CommentsFrame *comment = dynamic_cast<ID3v2::CommentsFrame *>(*it))
|
||||
if(!comment->description().isEmpty())
|
||||
cout << " [" << comment->description() << "]";
|
||||
|
||||
cout << " - \"" << (*it)->toString() << "\"" << endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
cout << "file does not have a valid id3v2 tag" << endl;
|
||||
|
||||
@@ -16,8 +16,8 @@ EOH
|
||||
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=@CMAKE_INSTALL_PREFIX@
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
libdir=@LIB_INSTALL_DIR@
|
||||
includedir=@INCLUDE_INSTALL_DIR@
|
||||
|
||||
flags=""
|
||||
|
||||
@@ -32,7 +32,7 @@ do
|
||||
flags="$flags -L$libdir -ltag @ZLIB_LIBRARIES_FLAGS@"
|
||||
;;
|
||||
--cflags)
|
||||
flags="$flags -I$includedir/taglib"
|
||||
flags="$flags -I$includedir -I$includedir/taglib"
|
||||
;;
|
||||
--version)
|
||||
echo @TAGLIB_LIB_VERSION_STRING@
|
||||
|
||||
@@ -28,7 +28,7 @@ goto theend
|
||||
* It would be preferable if the top level CMakeLists.txt provided the library name during config. ??
|
||||
:doit
|
||||
if /i "%1#" == "--libs#" echo -L${LIB_INSTALL_DIR} -llibtag
|
||||
if /i "%1#" == "--cflags#" echo -I${INCLUDE_INSTALL_DIR}/taglib
|
||||
if /i "%1#" == "--cflags#" echo -I${INCLUDE_INSTALL_DIR} -I${INCLUDE_INSTALL_DIR}/taglib
|
||||
if /i "%1#" == "--version#" echo ${TAGLIB_LIB_VERSION_STRING}
|
||||
if /i "%1#" == "--prefix#" echo ${CMAKE_INSTALL_PREFIX}
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@ Description: Audio meta-data library
|
||||
Requires:
|
||||
Version: @TAGLIB_LIB_VERSION_STRING@
|
||||
Libs: -L${libdir} -ltag @ZLIB_LIBRARIES_FLAGS@
|
||||
Cflags: -I${includedir}/taglib
|
||||
Cflags: -I${includedir} -I${includedir}/taglib
|
||||
|
||||
@@ -24,8 +24,6 @@ include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/s3m
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/it
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/xm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/dsf
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/dsdiff
|
||||
${taglib_SOURCE_DIR}/3rdparty
|
||||
)
|
||||
|
||||
@@ -337,6 +335,7 @@ set(tag_LIB_SRCS
|
||||
)
|
||||
|
||||
add_library(tag ${tag_LIB_SRCS} ${tag_HDRS})
|
||||
set_property(TARGET tag PROPERTY CXX_STANDARD 98)
|
||||
|
||||
if(HAVE_ZLIB AND NOT HAVE_ZLIB_SOURCE)
|
||||
target_link_libraries(tag ${ZLIB_LIBRARIES})
|
||||
|
||||
@@ -49,8 +49,8 @@ APE Tag Version 2.000 (with header, recommended):
|
||||
|
||||
APE tag items should be sorted ascending by size. When streaming, parts of the
|
||||
APE tag may be dropped to reduce the danger of drop outs between tracks. This
|
||||
is not required, but is strongly recommended. It would be desirable for the i
|
||||
tems to be sorted by importance / size, but this is not feasible. This
|
||||
is not required, but is strongly recommended. It would be desirable for the
|
||||
items to be sorted by importance / size, but this is not feasible. This
|
||||
convention should only be broken when adding less important small items and it
|
||||
is not desirable to rewrite the entire tag. An APE tag at the end of a file
|
||||
(the recommended location) must have at least a footer; an APE tag at the
|
||||
|
||||
@@ -215,7 +215,9 @@ namespace
|
||||
{"DATE", "YEAR" },
|
||||
{"ALBUMARTIST", "ALBUM ARTIST"},
|
||||
{"DISCNUMBER", "DISC" },
|
||||
{"REMIXER", "MIXARTIST" }};
|
||||
{"REMIXER", "MIXARTIST" },
|
||||
{"RELEASESTATUS", "MUSICBRAINZ_ALBUMSTATUS" },
|
||||
{"RELEASETYPE", "MUSICBRAINZ_ALBUMTYPE" }};
|
||||
const size_t keyConversionsSize = sizeof(keyConversions) / sizeof(keyConversions[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace
|
||||
{ "WM/AlbumTitle", "ALBUM" },
|
||||
{ "WM/AlbumArtist", "ALBUMARTIST" },
|
||||
{ "WM/Composer", "COMPOSER" },
|
||||
{ "WM/Writer", "WRITER" },
|
||||
{ "WM/Writer", "LYRICIST" },
|
||||
{ "WM/Conductor", "CONDUCTOR" },
|
||||
{ "WM/ModifiedBy", "REMIXER" },
|
||||
{ "WM/Year", "DATE" },
|
||||
@@ -243,11 +243,17 @@ namespace
|
||||
{ "WM/TitleSortOrder", "TITLESORT" },
|
||||
{ "WM/Script", "SCRIPT" },
|
||||
{ "WM/Language", "LANGUAGE" },
|
||||
{ "WM/ARTISTS", "ARTISTS" },
|
||||
{ "ASIN", "ASIN" },
|
||||
{ "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" },
|
||||
{ "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "MusicBrainz/Album Release Country", "RELEASECOUNTRY" },
|
||||
{ "MusicBrainz/Album Status", "RELEASESTATUS" },
|
||||
{ "MusicBrainz/Album Type", "RELEASETYPE" },
|
||||
{ "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "MusicBrainz/Release Track Id", "MUSICBRAINZ_RELEASETRACKID" },
|
||||
{ "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "MusicIP/PUID", "MUSICIP_PUID" },
|
||||
{ "Acoustid/Id", "ACOUSTID_ID" },
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <tfile.h>
|
||||
#include <tfilestream.h>
|
||||
#include <tstring.h>
|
||||
@@ -65,6 +67,8 @@ namespace
|
||||
File *detectByResolvers(FileName fileName, bool readAudioProperties,
|
||||
AudioProperties::ReadStyle audioPropertiesStyle)
|
||||
{
|
||||
if(::strlen(fileName) == 0)
|
||||
return 0;
|
||||
ResolverList::ConstIterator it = fileTypeResolvers.begin();
|
||||
for(; it != fileTypeResolvers.end(); ++it) {
|
||||
File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
@@ -362,6 +366,7 @@ StringList FileRef::defaultFileExtensions()
|
||||
l.append("ogg");
|
||||
l.append("flac");
|
||||
l.append("oga");
|
||||
l.append("opus");
|
||||
l.append("mp3");
|
||||
l.append("mpc");
|
||||
l.append("wv");
|
||||
@@ -378,6 +383,8 @@ StringList FileRef::defaultFileExtensions()
|
||||
l.append("asf");
|
||||
l.append("aif");
|
||||
l.append("aiff");
|
||||
l.append("afc");
|
||||
l.append("aifc");
|
||||
l.append("wav");
|
||||
l.append("ape");
|
||||
l.append("mod");
|
||||
@@ -460,7 +467,11 @@ void FileRef::parse(FileName fileName, bool readAudioProperties,
|
||||
void FileRef::parse(IOStream *stream, bool readAudioProperties,
|
||||
AudioProperties::ReadStyle audioPropertiesStyle)
|
||||
{
|
||||
// User-defined resolvers won't work with a stream.
|
||||
// Try user-defined resolvers.
|
||||
|
||||
d->file = detectByResolvers(stream->name(), readAudioProperties, audioPropertiesStyle);
|
||||
if(d->file)
|
||||
return;
|
||||
|
||||
// Try to resolve file types based on the file extension.
|
||||
|
||||
|
||||
@@ -187,16 +187,24 @@ bool FLAC::File::save()
|
||||
|
||||
// Replace metadata blocks
|
||||
|
||||
for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
|
||||
MetadataBlock *commentBlock =
|
||||
new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData);
|
||||
for(BlockIterator it = d->blocks.begin(); it != d->blocks.end();) {
|
||||
if((*it)->code() == MetadataBlock::VorbisComment) {
|
||||
// Set the new Vorbis Comment block
|
||||
// Remove the old Vorbis Comment block
|
||||
delete *it;
|
||||
d->blocks.erase(it);
|
||||
break;
|
||||
it = d->blocks.erase(it);
|
||||
continue;
|
||||
}
|
||||
if(commentBlock && (*it)->code() == MetadataBlock::Picture) {
|
||||
// Set the new Vorbis Comment block before the first picture block
|
||||
d->blocks.insert(it, commentBlock);
|
||||
commentBlock = 0;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
d->blocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData));
|
||||
if(commentBlock)
|
||||
d->blocks.append(commentBlock);
|
||||
|
||||
// Render data for the metadata blocks
|
||||
|
||||
|
||||
@@ -31,6 +31,27 @@
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
namespace
|
||||
{
|
||||
// Calculate the total bytes used by audio data, used to calculate the bitrate
|
||||
long long calculateMdatLength(const MP4::AtomList &list)
|
||||
{
|
||||
long long totalLength = 0;
|
||||
for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) {
|
||||
long length = (*it)->length;
|
||||
if(length == 0)
|
||||
return 0; // for safety, see checkValid() in mp4file.cpp
|
||||
|
||||
if((*it)->name == "mdat")
|
||||
totalLength += length;
|
||||
|
||||
totalLength += calculateMdatLength((*it)->children);
|
||||
}
|
||||
|
||||
return totalLength;
|
||||
}
|
||||
}
|
||||
|
||||
class MP4::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
@@ -213,7 +234,14 @@ MP4::Properties::read(File *file, Atoms *atoms)
|
||||
pos += 3;
|
||||
}
|
||||
pos += 10;
|
||||
d->bitrate = static_cast<int>((data.toUInt(pos) + 500) / 1000.0 + 0.5);
|
||||
const unsigned int bitrateValue = data.toUInt(pos);
|
||||
if(bitrateValue != 0 || d->length <= 0) {
|
||||
d->bitrate = static_cast<int>((bitrateValue + 500) / 1000.0 + 0.5);
|
||||
}
|
||||
else {
|
||||
d->bitrate = static_cast<int>(
|
||||
(calculateMdatLength(atoms->atoms) * 8) / d->length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,6 +252,13 @@ MP4::Properties::read(File *file, Atoms *atoms)
|
||||
d->channels = data.at(73);
|
||||
d->bitrate = static_cast<int>(data.toUInt(80U) / 1000.0 + 0.5);
|
||||
d->sampleRate = data.toUInt(84U);
|
||||
|
||||
if(d->bitrate == 0 && d->length > 0) {
|
||||
// There are files which do not contain a nominal bitrate, e.g. those
|
||||
// generated by refalac64.exe. Calculate the bitrate from the audio
|
||||
// data size (mdat atoms) and the duration.
|
||||
d->bitrate = (calculateMdatLength(atoms->atoms) * 8) / d->length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ MP4::Tag::parseCovr(const MP4::Atom *atom)
|
||||
const int length = static_cast<int>(data.toUInt(pos));
|
||||
if(length < 12) {
|
||||
debug("MP4: Too short atom");
|
||||
break;;
|
||||
break;
|
||||
}
|
||||
|
||||
const ByteVector name = data.mid(pos + 4, 4);
|
||||
@@ -775,31 +775,41 @@ MP4::Tag::track() const
|
||||
void
|
||||
MP4::Tag::setTitle(const String &value)
|
||||
{
|
||||
d->items["\251nam"] = StringList(value);
|
||||
setTextItem("\251nam", value);
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::setArtist(const String &value)
|
||||
{
|
||||
d->items["\251ART"] = StringList(value);
|
||||
setTextItem("\251ART", value);
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::setAlbum(const String &value)
|
||||
{
|
||||
d->items["\251alb"] = StringList(value);
|
||||
setTextItem("\251alb", value);
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::setComment(const String &value)
|
||||
{
|
||||
d->items["\251cmt"] = StringList(value);
|
||||
setTextItem("\251cmt", value);
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::setGenre(const String &value)
|
||||
{
|
||||
d->items["\251gen"] = StringList(value);
|
||||
setTextItem("\251gen", value);
|
||||
}
|
||||
|
||||
void
|
||||
MP4::Tag::setTextItem(const String &key, const String &value)
|
||||
{
|
||||
if (!value.isEmpty()) {
|
||||
d->items[key] = StringList(value);
|
||||
} else {
|
||||
d->items.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -885,6 +895,17 @@ namespace
|
||||
{ "soco", "COMPOSERSORT" },
|
||||
{ "sosn", "SHOWSORT" },
|
||||
{ "shwm", "SHOWWORKMOVEMENT" },
|
||||
{ "pgap", "GAPLESSPLAYBACK" },
|
||||
{ "pcst", "PODCAST" },
|
||||
{ "catg", "PODCASTCATEGORY" },
|
||||
{ "desc", "PODCASTDESC" },
|
||||
{ "egid", "PODCASTID" },
|
||||
{ "purl", "PODCASTURL" },
|
||||
{ "tves", "TVEPISODE" },
|
||||
{ "tven", "TVEPISODEID" },
|
||||
{ "tvnn", "TVNETWORK" },
|
||||
{ "tvsn", "TVSEASON" },
|
||||
{ "tvsh", "TVSHOW" },
|
||||
{ "\251wrk", "WORK" },
|
||||
{ "\251mvn", "MOVEMENTNAME" },
|
||||
{ "\251mvi", "MOVEMENTNUMBER" },
|
||||
@@ -894,7 +915,13 @@ namespace
|
||||
{ "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Release Track Id", "MUSICBRAINZ_RELEASETRACKID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Album Release Country", "RELEASECOUNTRY" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Album Status", "RELEASESTATUS" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Album Type", "RELEASETYPE" },
|
||||
{ "----:com.apple.iTunes:ARTISTS", "ARTISTS" },
|
||||
{ "----:com.apple.iTunes:originaldate", "ORIGINALDATE" },
|
||||
{ "----:com.apple.iTunes:ASIN", "ASIN" },
|
||||
{ "----:com.apple.iTunes:LABEL", "LABEL" },
|
||||
{ "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
|
||||
@@ -942,10 +969,12 @@ PropertyMap MP4::Tag::properties() const
|
||||
}
|
||||
props[key] = value;
|
||||
}
|
||||
else if(key == "BPM" || key == "MOVEMENTNUMBER" || key == "MOVEMENTCOUNT") {
|
||||
else if(key == "BPM" || key == "MOVEMENTNUMBER" || key == "MOVEMENTCOUNT" ||
|
||||
key == "TVEPISODE" || key == "TVSEASON") {
|
||||
props[key] = String::number(it->second.toInt());
|
||||
}
|
||||
else if(key == "COMPILATION" || key == "SHOWWORKMOVEMENT") {
|
||||
else if(key == "COMPILATION" || key == "SHOWWORKMOVEMENT" ||
|
||||
key == "GAPLESSPLAYBACK" || key == "PODCAST") {
|
||||
props[key] = String::number(it->second.toBool());
|
||||
}
|
||||
else {
|
||||
@@ -997,11 +1026,15 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
|
||||
d->items[name] = MP4::Item(first, second);
|
||||
}
|
||||
}
|
||||
else if((it->first == "BPM" || it->first == "MOVEMENTNUMBER" || it->first == "MOVEMENTCOUNT") && !it->second.isEmpty()) {
|
||||
else if((it->first == "BPM" || it->first == "MOVEMENTNUMBER" ||
|
||||
it->first == "MOVEMENTCOUNT" || it->first == "TVEPISODE" ||
|
||||
it->first == "TVSEASON") && !it->second.isEmpty()) {
|
||||
int value = it->second.front().toInt();
|
||||
d->items[name] = MP4::Item(value);
|
||||
}
|
||||
else if((it->first == "COMPILATION" || it->first == "SHOWWORKMOVEMENT") && !it->second.isEmpty()) {
|
||||
else if((it->first == "COMPILATION" || it->first == "SHOWWORKMOVEMENT" ||
|
||||
it->first == "GAPLESSPLAYBACK" || it->first == "PODCAST") &&
|
||||
!it->second.isEmpty()) {
|
||||
bool value = (it->second.front().toInt() != 0);
|
||||
d->items[name] = MP4::Item(value);
|
||||
}
|
||||
|
||||
@@ -106,6 +106,13 @@ namespace TagLib {
|
||||
void removeUnsupportedProperties(const StringList& properties);
|
||||
PropertyMap setProperties(const PropertyMap &properties);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Sets the value of \a key to \a value, overwriting any previous value.
|
||||
* If \a value is empty, the item is removed.
|
||||
*/
|
||||
void setTextItem(const String &key, const String &value);
|
||||
|
||||
private:
|
||||
AtomDataList parseData2(const Atom *atom, int expectedFlags = -1,
|
||||
bool freeForm = false);
|
||||
|
||||
@@ -49,18 +49,17 @@ public:
|
||||
albumGain(0),
|
||||
albumPeak(0) {}
|
||||
|
||||
int version;
|
||||
int length;
|
||||
int bitrate;
|
||||
int sampleRate;
|
||||
int channels;
|
||||
int version;
|
||||
int length;
|
||||
int bitrate;
|
||||
int sampleRate;
|
||||
int channels;
|
||||
unsigned int totalFrames;
|
||||
unsigned int sampleFrames;
|
||||
unsigned int trackGain;
|
||||
unsigned int trackPeak;
|
||||
unsigned int albumGain;
|
||||
unsigned int albumPeak;
|
||||
String flags;
|
||||
int trackGain;
|
||||
int trackPeak;
|
||||
int albumGain;
|
||||
int albumPeak;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -312,9 +311,9 @@ void MPC::Properties::readSV7(const ByteVector &data, long streamLength)
|
||||
const unsigned int gapless = data.toUInt(5, false);
|
||||
|
||||
d->trackGain = data.toShort(14, false);
|
||||
d->trackPeak = data.toShort(12, false);
|
||||
d->trackPeak = data.toUShort(12, false);
|
||||
d->albumGain = data.toShort(18, false);
|
||||
d->albumPeak = data.toShort(16, false);
|
||||
d->albumPeak = data.toUShort(16, false);
|
||||
|
||||
// convert gain info
|
||||
if(d->trackGain != 0) {
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace
|
||||
L"Ambient",
|
||||
L"Trip-Hop",
|
||||
L"Vocal",
|
||||
L"Jazz+Funk",
|
||||
L"Jazz-Funk",
|
||||
L"Fusion",
|
||||
L"Trance",
|
||||
L"Classical",
|
||||
@@ -111,16 +111,16 @@ namespace
|
||||
L"Rock & Roll",
|
||||
L"Hard Rock",
|
||||
L"Folk",
|
||||
L"Folk/Rock",
|
||||
L"Folk Rock",
|
||||
L"National Folk",
|
||||
L"Swing",
|
||||
L"Fusion",
|
||||
L"Bebob",
|
||||
L"Fast Fusion",
|
||||
L"Bebop",
|
||||
L"Latin",
|
||||
L"Revival",
|
||||
L"Celtic",
|
||||
L"Bluegrass",
|
||||
L"Avantgarde",
|
||||
L"Avant-garde",
|
||||
L"Gothic Rock",
|
||||
L"Progressive Rock",
|
||||
L"Psychedelic Rock",
|
||||
@@ -155,15 +155,15 @@ namespace
|
||||
L"Drum Solo",
|
||||
L"A Cappella",
|
||||
L"Euro-House",
|
||||
L"Dance Hall",
|
||||
L"Dancehall",
|
||||
L"Goa",
|
||||
L"Drum & Bass",
|
||||
L"Club-House",
|
||||
L"Hardcore",
|
||||
L"Hardcore Techno",
|
||||
L"Terror",
|
||||
L"Indie",
|
||||
L"BritPop",
|
||||
L"Negerpunk",
|
||||
L"Britpop",
|
||||
L"Worldbeat",
|
||||
L"Polsk Punk",
|
||||
L"Beat",
|
||||
L"Christian Gangsta Rap",
|
||||
@@ -261,5 +261,26 @@ int ID3v1::genreIndex(const String &name)
|
||||
return i;
|
||||
}
|
||||
|
||||
// If the name was not found, try the names which have been changed
|
||||
static const struct {
|
||||
const wchar_t *genre;
|
||||
int code;
|
||||
} fixUpGenres[] = {
|
||||
{ L"Jazz+Funk", 29 },
|
||||
{ L"Folk/Rock", 81 },
|
||||
{ L"Bebob", 85 },
|
||||
{ L"Avantgarde", 90 },
|
||||
{ L"Dance Hall", 125 },
|
||||
{ L"Hardcore", 129 },
|
||||
{ L"BritPop", 132 },
|
||||
{ L"Negerpunk", 133 }
|
||||
};
|
||||
static const int fixUpGenresSize =
|
||||
sizeof(fixUpGenres) / sizeof(fixUpGenres[0]);
|
||||
for(int i = 0; i < fixUpGenresSize; ++i) {
|
||||
if(name == fixUpGenres[i].genre)
|
||||
return fixUpGenres[i].code;
|
||||
}
|
||||
|
||||
return 255;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace TagLib {
|
||||
* \a startTime, end time \a endTime, start offset \a startOffset,
|
||||
* end offset \a endOffset and optionally a list of embedded frames,
|
||||
* whose ownership will then be taken over by this Frame, in
|
||||
* \a embeededFrames;
|
||||
* \a embeddedFrames;
|
||||
*
|
||||
* All times are in milliseconds.
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "podcastframe.h"
|
||||
#include <tpropertymap.h>
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
@@ -55,6 +56,13 @@ String PodcastFrame::toString() const
|
||||
return String();
|
||||
}
|
||||
|
||||
PropertyMap PodcastFrame::asProperties() const
|
||||
{
|
||||
PropertyMap map;
|
||||
map.insert("PODCAST", StringList());
|
||||
return map;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -57,6 +57,8 @@ namespace TagLib {
|
||||
*/
|
||||
virtual String toString() const;
|
||||
|
||||
PropertyMap asProperties() const;
|
||||
|
||||
protected:
|
||||
// Reimplementations.
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ PrivateFrame::PrivateFrame(const ByteVector &data) :
|
||||
Frame(data),
|
||||
d(new PrivateFramePrivate())
|
||||
{
|
||||
setData(data);
|
||||
Frame::setData(data);
|
||||
}
|
||||
|
||||
PrivateFrame::~PrivateFrame()
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace TagLib {
|
||||
* available and returns 0 if the specified channel does not exist.
|
||||
*
|
||||
* \see setVolumeAdjustmentIndex()
|
||||
* \see volumeAjustment()
|
||||
* \see volumeAdjustment()
|
||||
*/
|
||||
short volumeAdjustmentIndex(ChannelType type = MasterVolume) const;
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace TagLib {
|
||||
* By default this sets the value for the master volume.
|
||||
*
|
||||
* \see volumeAdjustmentIndex()
|
||||
* \see setVolumeAjustment()
|
||||
* \see setVolumeAdjustment()
|
||||
*/
|
||||
void setVolumeAdjustmentIndex(short index, ChannelType type = MasterVolume);
|
||||
|
||||
|
||||
@@ -168,7 +168,8 @@ void TableOfContentsFrame::removeChildElement(const ByteVector &cE)
|
||||
if(it == d->childElements.end())
|
||||
it = d->childElements.find(cE + ByteVector("\0"));
|
||||
|
||||
d->childElements.erase(it);
|
||||
if(it != d->childElements.end())
|
||||
d->childElements.erase(it);
|
||||
}
|
||||
|
||||
const FrameListMap &TableOfContentsFrame::embeddedFrameListMap() const
|
||||
@@ -196,11 +197,14 @@ void TableOfContentsFrame::removeEmbeddedFrame(Frame *frame, bool del)
|
||||
{
|
||||
// remove the frame from the frame list
|
||||
FrameList::Iterator it = d->embeddedFrameList.find(frame);
|
||||
d->embeddedFrameList.erase(it);
|
||||
if(it != d->embeddedFrameList.end())
|
||||
d->embeddedFrameList.erase(it);
|
||||
|
||||
// ...and from the frame list map
|
||||
it = d->embeddedFrameListMap[frame->frameID()].find(frame);
|
||||
d->embeddedFrameListMap[frame->frameID()].erase(it);
|
||||
FrameList &mappedList = d->embeddedFrameListMap[frame->frameID()];
|
||||
it = mappedList.find(frame);
|
||||
if(it != mappedList.end())
|
||||
mappedList.erase(it);
|
||||
|
||||
// ...and delete as desired
|
||||
if(del)
|
||||
|
||||
@@ -63,7 +63,10 @@ TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const Property
|
||||
TextIdentificationFrame *frame = new TextIdentificationFrame("TIPL");
|
||||
StringList l;
|
||||
for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){
|
||||
l.append(it->first);
|
||||
const String role = involvedPeopleMap()[it->first];
|
||||
if(role.isEmpty()) // should not happen
|
||||
continue;
|
||||
l.append(role);
|
||||
l.append(it->second.toString(",")); // comma-separated list of names
|
||||
}
|
||||
frame->setText(l);
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "frames/commentsframe.h"
|
||||
#include "frames/uniquefileidentifierframe.h"
|
||||
#include "frames/unknownframe.h"
|
||||
#include "frames/podcastframe.h"
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
@@ -120,6 +121,8 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) //
|
||||
UrlLinkFrame* frame = new UrlLinkFrame(frameID);
|
||||
frame->setUrl(values.front());
|
||||
return frame;
|
||||
} else if(frameID == "PCST") {
|
||||
return new PodcastFrame();
|
||||
}
|
||||
}
|
||||
if(key == "MUSICBRAINZ_TRACKID" && values.size() == 1) {
|
||||
@@ -344,7 +347,7 @@ namespace
|
||||
{ "TEXT", "LYRICIST" },
|
||||
{ "TFLT", "FILETYPE" },
|
||||
//{ "TIPL", "INVOLVEDPEOPLE" }, handled separately
|
||||
{ "TIT1", "CONTENTGROUP" },
|
||||
{ "TIT1", "CONTENTGROUP" }, // 'Work' in iTunes
|
||||
{ "TIT2", "TITLE"},
|
||||
{ "TIT3", "SUBTITLE" },
|
||||
{ "TKEY", "INITIALKEY" },
|
||||
@@ -369,6 +372,7 @@ namespace
|
||||
{ "TRSN", "RADIOSTATION" },
|
||||
{ "TRSO", "RADIOSTATIONOWNER" },
|
||||
{ "TSOA", "ALBUMSORT" },
|
||||
{ "TSOC", "COMPOSERSORT" },
|
||||
{ "TSOP", "ARTISTSORT" },
|
||||
{ "TSOT", "TITLESORT" },
|
||||
{ "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes
|
||||
@@ -402,7 +406,11 @@ namespace
|
||||
{ "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "MUSICBRAINZ ALBUM RELEASE COUNTRY", "RELEASECOUNTRY" },
|
||||
{ "MUSICBRAINZ ALBUM STATUS", "RELEASESTATUS" },
|
||||
{ "MUSICBRAINZ ALBUM TYPE", "RELEASETYPE" },
|
||||
{ "MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "MUSICBRAINZ RELEASE TRACK ID", "MUSICBRAINZ_RELEASETRACKID" },
|
||||
{ "MUSICBRAINZ WORK ID", "MUSICBRAINZ_WORKID" },
|
||||
{ "ACOUSTID ID", "ACOUSTID_ID" },
|
||||
{ "ACOUSTID FINGERPRINT", "ACOUSTID_FINGERPRINT" },
|
||||
@@ -417,7 +425,7 @@ namespace
|
||||
{"TYER", "TDRC"}, // 2.3 -> 2.4
|
||||
{"TIME", "TDRC"}, // 2.3 -> 2.4
|
||||
};
|
||||
const size_t deprecatedFramesSize = sizeof(deprecatedFrames) / sizeof(deprecatedFrames[0]);;
|
||||
const size_t deprecatedFramesSize = sizeof(deprecatedFrames) / sizeof(deprecatedFrames[0]);
|
||||
}
|
||||
|
||||
String Frame::frameIDToKey(const ByteVector &id)
|
||||
@@ -490,6 +498,8 @@ PropertyMap Frame::asProperties() const
|
||||
return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties();
|
||||
else if(id == "UFID")
|
||||
return dynamic_cast< const UniqueFileIdentifierFrame* >(this)->asProperties();
|
||||
else if(id == "PCST")
|
||||
return dynamic_cast< const PodcastFrame* >(this)->asProperties();
|
||||
PropertyMap m;
|
||||
m.unsupportedData().append(id);
|
||||
return m;
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace TagLib {
|
||||
* split between a collection of frames (which are in turn split into fields
|
||||
* (Structure, <a href="id3v2-structure.html#4">4</a>)
|
||||
* (<a href="id3v2-frames.html">Frames</a>). This class provides an API for
|
||||
* gathering information about and modifying ID3v2 frames. Funtionallity
|
||||
* gathering information about and modifying ID3v2 frames. Functionality
|
||||
* specific to a given frame type is handed in one of the many subclasses.
|
||||
*/
|
||||
|
||||
|
||||
@@ -60,22 +60,24 @@ namespace
|
||||
|
||||
for(StringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) {
|
||||
String s = *it;
|
||||
int end = s.find(")");
|
||||
int offset = 0;
|
||||
int end = 0;
|
||||
|
||||
if(s.startsWith("(") && end > 0) {
|
||||
while(s.length() > offset && s[offset] == '(' &&
|
||||
(end = s.find(")", offset + 1)) > offset) {
|
||||
// "(12)Genre"
|
||||
String text = s.substr(end + 1);
|
||||
const String genreCode = s.substr(offset + 1, end - 1);
|
||||
s = s.substr(end + 1);
|
||||
bool ok;
|
||||
int number = s.substr(1, end - 1).toInt(&ok);
|
||||
if(ok && number >= 0 && number <= 255 && !(ID3v1::genre(number) == text))
|
||||
newfields.append(s.substr(1, end - 1));
|
||||
if(!text.isEmpty())
|
||||
newfields.append(text);
|
||||
int number = genreCode.toInt(&ok);
|
||||
if((ok && number >= 0 && number <= 255 &&
|
||||
!(ID3v1::genre(number) == s)) ||
|
||||
genreCode == "RX" || genreCode == "CR")
|
||||
newfields.append(genreCode);
|
||||
}
|
||||
else {
|
||||
if(!s.isEmpty())
|
||||
// "Genre" or "12"
|
||||
newfields.append(s);
|
||||
}
|
||||
}
|
||||
|
||||
if(newfields.isEmpty())
|
||||
@@ -282,7 +284,7 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, const Header *tagHe
|
||||
return f;
|
||||
}
|
||||
|
||||
// Synchronised lyrics/text (frames 4.9)
|
||||
// Synchronized lyrics/text (frames 4.9)
|
||||
|
||||
if(frameID == "SYLT") {
|
||||
SynchronizedLyricsFrame *f = new SynchronizedLyricsFrame(data, header);
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace TagLib {
|
||||
*
|
||||
* Reimplementing this factory is the key to adding support for frame types
|
||||
* not directly supported by TagLib to your application. To do so you would
|
||||
* subclass this factory reimplement createFrame(). Then by setting your
|
||||
* subclass this factory and reimplement createFrame(). Then by setting your
|
||||
* factory to be the default factory in ID3v2::Tag constructor you can
|
||||
* implement behavior that will allow for new ID3v2::Frame subclasses (also
|
||||
* provided by you) to be used.
|
||||
|
||||
@@ -54,6 +54,16 @@ namespace
|
||||
|
||||
const long MinPaddingSize = 1024;
|
||||
const long MaxPaddingSize = 1024 * 1024;
|
||||
|
||||
bool contains(const char **a, const ByteVector &v)
|
||||
{
|
||||
for(int i = 0; a[i]; i++)
|
||||
{
|
||||
if(v == a[i])
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ID3v2::Tag::TagPrivate
|
||||
@@ -250,13 +260,24 @@ void ID3v2::Tag::setComment(const String &s)
|
||||
return;
|
||||
}
|
||||
|
||||
if(!d->frameListMap["COMM"].isEmpty())
|
||||
d->frameListMap["COMM"].front()->setText(s);
|
||||
else {
|
||||
CommentsFrame *f = new CommentsFrame(d->factory->defaultTextEncoding());
|
||||
addFrame(f);
|
||||
f->setText(s);
|
||||
const FrameList &comments = d->frameListMap["COMM"];
|
||||
|
||||
if(!comments.isEmpty()) {
|
||||
for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it) {
|
||||
CommentsFrame *frame = dynamic_cast<CommentsFrame *>(*it);
|
||||
if(frame && frame->description().isEmpty()) {
|
||||
(*it)->setText(s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
comments.front()->setText(s);
|
||||
return;
|
||||
}
|
||||
|
||||
CommentsFrame *f = new CommentsFrame(d->factory->defaultTextEncoding());
|
||||
addFrame(f);
|
||||
f->setText(s);
|
||||
}
|
||||
|
||||
void ID3v2::Tag::setGenre(const String &s)
|
||||
@@ -468,13 +489,13 @@ ByteVector ID3v2::Tag::render() const
|
||||
void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
|
||||
{
|
||||
#ifdef NO_ITUNES_HACKS
|
||||
const char *unsupportedFrames[] = {
|
||||
static const char *unsupportedFrames[] = {
|
||||
"ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG",
|
||||
"TMOO", "TPRO", "TSOA", "TSOT", "TSST", "TSOP", 0
|
||||
};
|
||||
#else
|
||||
// iTunes writes and reads TSOA, TSOT, TSOP to ID3v2.3.
|
||||
const char *unsupportedFrames[] = {
|
||||
static const char *unsupportedFrames[] = {
|
||||
"ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG",
|
||||
"TMOO", "TPRO", "TSST", 0
|
||||
};
|
||||
@@ -483,60 +504,62 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
|
||||
ID3v2::TextIdentificationFrame *frameTDRC = 0;
|
||||
ID3v2::TextIdentificationFrame *frameTIPL = 0;
|
||||
ID3v2::TextIdentificationFrame *frameTMCL = 0;
|
||||
ID3v2::TextIdentificationFrame *frameTCON = 0;
|
||||
|
||||
for(FrameList::ConstIterator it = d->frameList.begin(); it != d->frameList.end(); it++) {
|
||||
ID3v2::Frame *frame = *it;
|
||||
ByteVector frameID = frame->header()->frameID();
|
||||
for(int i = 0; unsupportedFrames[i]; i++) {
|
||||
if(frameID == unsupportedFrames[i]) {
|
||||
debug("A frame that is not supported in ID3v2.3 \'"
|
||||
+ String(frameID) + "\' has been discarded");
|
||||
frame = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if(contains(unsupportedFrames, frameID))
|
||||
{
|
||||
debug("A frame that is not supported in ID3v2.3 \'" + String(frameID) +
|
||||
"\' has been discarded");
|
||||
continue;
|
||||
}
|
||||
if(frame && frameID == "TDOR") {
|
||||
|
||||
if(frameID == "TDOR")
|
||||
frameTDOR = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
|
||||
frame = 0;
|
||||
}
|
||||
if(frame && frameID == "TDRC") {
|
||||
else if(frameID == "TDRC")
|
||||
frameTDRC = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
|
||||
frame = 0;
|
||||
}
|
||||
if(frame && frameID == "TIPL") {
|
||||
else if(frameID == "TIPL")
|
||||
frameTIPL = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
|
||||
frame = 0;
|
||||
}
|
||||
if(frame && frameID == "TMCL") {
|
||||
else if(frameID == "TMCL")
|
||||
frameTMCL = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
|
||||
frame = 0;
|
||||
}
|
||||
if(frame) {
|
||||
else if(frame && frameID == "TCON")
|
||||
frameTCON = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame);
|
||||
else
|
||||
frames->append(frame);
|
||||
}
|
||||
}
|
||||
|
||||
if(frameTDOR) {
|
||||
String content = frameTDOR->toString();
|
||||
|
||||
if(content.size() >= 4) {
|
||||
ID3v2::TextIdentificationFrame *frameTORY = new ID3v2::TextIdentificationFrame("TORY", String::Latin1);
|
||||
ID3v2::TextIdentificationFrame *frameTORY =
|
||||
new ID3v2::TextIdentificationFrame("TORY", String::Latin1);
|
||||
frameTORY->setText(content.substr(0, 4));
|
||||
frames->append(frameTORY);
|
||||
newFrames->append(frameTORY);
|
||||
}
|
||||
}
|
||||
|
||||
if(frameTDRC) {
|
||||
String content = frameTDRC->toString();
|
||||
if(content.size() >= 4) {
|
||||
ID3v2::TextIdentificationFrame *frameTYER = new ID3v2::TextIdentificationFrame("TYER", String::Latin1);
|
||||
ID3v2::TextIdentificationFrame *frameTYER =
|
||||
new ID3v2::TextIdentificationFrame("TYER", String::Latin1);
|
||||
frameTYER->setText(content.substr(0, 4));
|
||||
frames->append(frameTYER);
|
||||
newFrames->append(frameTYER);
|
||||
if(content.size() >= 10 && content[4] == '-' && content[7] == '-') {
|
||||
ID3v2::TextIdentificationFrame *frameTDAT = new ID3v2::TextIdentificationFrame("TDAT", String::Latin1);
|
||||
ID3v2::TextIdentificationFrame *frameTDAT =
|
||||
new ID3v2::TextIdentificationFrame("TDAT", String::Latin1);
|
||||
frameTDAT->setText(content.substr(8, 2) + content.substr(5, 2));
|
||||
frames->append(frameTDAT);
|
||||
newFrames->append(frameTDAT);
|
||||
if(content.size() >= 16 && content[10] == 'T' && content[13] == ':') {
|
||||
ID3v2::TextIdentificationFrame *frameTIME = new ID3v2::TextIdentificationFrame("TIME", String::Latin1);
|
||||
ID3v2::TextIdentificationFrame *frameTIME =
|
||||
new ID3v2::TextIdentificationFrame("TIME", String::Latin1);
|
||||
frameTIME->setText(content.substr(11, 2) + content.substr(14, 2));
|
||||
frames->append(frameTIME);
|
||||
newFrames->append(frameTIME);
|
||||
@@ -544,9 +567,13 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(frameTIPL || frameTMCL) {
|
||||
ID3v2::TextIdentificationFrame *frameIPLS = new ID3v2::TextIdentificationFrame("IPLS", String::Latin1);
|
||||
ID3v2::TextIdentificationFrame *frameIPLS =
|
||||
new ID3v2::TextIdentificationFrame("IPLS", String::Latin1);
|
||||
|
||||
StringList people;
|
||||
|
||||
if(frameTMCL) {
|
||||
StringList v24People = frameTMCL->fieldList();
|
||||
for(unsigned int i = 0; i + 1 < v24People.size(); i += 2) {
|
||||
@@ -561,10 +588,39 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
|
||||
people.append(v24People[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
frameIPLS->setText(people);
|
||||
frames->append(frameIPLS);
|
||||
newFrames->append(frameIPLS);
|
||||
}
|
||||
|
||||
if(frameTCON) {
|
||||
StringList genres = frameTCON->fieldList();
|
||||
String combined;
|
||||
String genreText;
|
||||
const bool hasMultipleGenres = genres.size() > 1;
|
||||
|
||||
// If there are multiple genres, add them as multiple references to ID3v1
|
||||
// genres if such a reference exists. The first genre for which no ID3v1
|
||||
// genre number exists can be finally added as a refinement.
|
||||
for(StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it) {
|
||||
bool ok = false;
|
||||
int number = it->toInt(&ok);
|
||||
if((ok && number >= 0 && number <= 255) || *it == "RX" || *it == "CR")
|
||||
combined += '(' + *it + ')';
|
||||
else if(hasMultipleGenres && (number = ID3v1::genreIndex(*it)) != 255)
|
||||
combined += '(' + String::number(number) + ')';
|
||||
else if(genreText.isEmpty())
|
||||
genreText = *it;
|
||||
}
|
||||
if(!genreText.isEmpty())
|
||||
combined += genreText;
|
||||
|
||||
frameTCON = new ID3v2::TextIdentificationFrame("TCON", String::Latin1);
|
||||
frameTCON->setText(combined);
|
||||
frames->append(frameTCON);
|
||||
newFrames->append(frameTCON);
|
||||
}
|
||||
}
|
||||
|
||||
ByteVector ID3v2::Tag::render(int version) const
|
||||
|
||||
@@ -393,7 +393,7 @@ namespace TagLib {
|
||||
void setTextFrame(const ByteVector &id, const String &value);
|
||||
|
||||
/*!
|
||||
* Dowgrade frames from ID3v2.4 (used internally and by default) to ID3v2.3
|
||||
* Downgrade frames from ID3v2.4 (used internally and by default) to ID3v2.3.
|
||||
*/
|
||||
void downgradeFrames(FrameList *existingFrames, FrameList *newFrames) const;
|
||||
|
||||
|
||||
@@ -205,12 +205,14 @@ bool MPEG::File::save(int tags)
|
||||
|
||||
bool MPEG::File::save(int tags, bool stripOthers)
|
||||
{
|
||||
return save(tags, stripOthers, 4);
|
||||
return save(tags, stripOthers ? StripOthers : StripNone, ID3v2::v4);
|
||||
}
|
||||
|
||||
bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version)
|
||||
{
|
||||
return save(tags, stripOthers, id3v2Version, true);
|
||||
return save(tags,
|
||||
stripOthers ? StripOthers : StripNone,
|
||||
id3v2Version == 3 ? ID3v2::v3 : ID3v2::v4);
|
||||
}
|
||||
|
||||
bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags)
|
||||
@@ -244,7 +246,7 @@ bool MPEG::File::save(int tags, StripTags strip, ID3v2::Version version, Duplica
|
||||
|
||||
// Remove all the tags not going to be saved.
|
||||
|
||||
if(strip == StripOthers || strip == StripAll)
|
||||
if(strip == StripOthers)
|
||||
File::strip(~tags, false);
|
||||
|
||||
if(ID3v2 & tags) {
|
||||
|
||||
@@ -200,13 +200,14 @@ void MPEG::Properties::read(File *file)
|
||||
const long lastFrameOffset = file->lastFrameOffset();
|
||||
if(lastFrameOffset < 0) {
|
||||
debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
const Header lastHeader(file, lastFrameOffset, false);
|
||||
const long streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength();
|
||||
if(streamLength > 0)
|
||||
d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5);
|
||||
else
|
||||
{
|
||||
const Header lastHeader(file, lastFrameOffset, false);
|
||||
const long streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength();
|
||||
if (streamLength > 0)
|
||||
d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
d->sampleRate = firstHeader.sampleRate();
|
||||
|
||||
@@ -302,13 +302,11 @@ void RIFF::File::read()
|
||||
|
||||
if(!isValidChunkName(chunkName)) {
|
||||
debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid ID");
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
|
||||
if(static_cast<long long>(offset) + 8 + chunkSize > length()) {
|
||||
debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid size (larger than the file size)");
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ bool RIFF::WAV::File::save(TagTypes tags, StripTags strip, ID3v2::Version versio
|
||||
return false;
|
||||
}
|
||||
|
||||
if(strip == StripOthers || strip == StripAll)
|
||||
if(strip == StripOthers)
|
||||
File::strip(static_cast<TagTypes>(AllTags & ~tags));
|
||||
|
||||
if(tags & ID3v2) {
|
||||
|
||||
@@ -35,7 +35,8 @@ namespace
|
||||
enum WaveFormat
|
||||
{
|
||||
FORMAT_UNKNOWN = 0x0000,
|
||||
FORMAT_PCM = 0x0001
|
||||
FORMAT_PCM = 0x0001,
|
||||
FORMAT_IEEE_FLOAT = 0x0003
|
||||
};
|
||||
}
|
||||
|
||||
@@ -183,7 +184,15 @@ void RIFF::WAV::Properties::read(File *file)
|
||||
}
|
||||
|
||||
d->format = data.toShort(0, false);
|
||||
if(d->format != FORMAT_PCM && totalSamples == 0) {
|
||||
if((d->format & 0xffff) == 0xfffe) {
|
||||
// if extensible then read the format from the subformat
|
||||
if(data.size() != 40) {
|
||||
debug("RIFF::WAV::Properties::read() - extensible size incorrect");
|
||||
return;
|
||||
}
|
||||
d->format = data.toShort(24, false);
|
||||
}
|
||||
if(d->format != FORMAT_PCM && d->format != FORMAT_IEEE_FLOAT && totalSamples == 0) {
|
||||
debug("RIFF::WAV::Properties::read() - Non-PCM format, but 'fact' chunk not found.");
|
||||
return;
|
||||
}
|
||||
@@ -192,7 +201,7 @@ void RIFF::WAV::Properties::read(File *file)
|
||||
d->sampleRate = data.toUInt(4, false);
|
||||
d->bitsPerSample = data.toShort(14, false);
|
||||
|
||||
if(d->format != FORMAT_PCM)
|
||||
if(d->format != FORMAT_PCM && !(d->format == FORMAT_IEEE_FLOAT && totalSamples == 0))
|
||||
d->sampleFrames = totalSamples;
|
||||
else if(d->channels > 0 && d->bitsPerSample > 0)
|
||||
d->sampleFrames = streamLength / (d->channels * ((d->bitsPerSample + 7) / 8));
|
||||
|
||||
@@ -183,7 +183,7 @@ void S3M::File::read(bool)
|
||||
// "ultra click" and "use panning values" (if == 0xFC).
|
||||
// I don't see them in any spec, though.
|
||||
// Hm, but there is "UltraClick-removal" and some other
|
||||
// variables in ScreamTracker IIIs GUI.
|
||||
// variables in ScreamTracker III's GUI.
|
||||
|
||||
seek(12, Current);
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
#include "taglib_config.h"
|
||||
|
||||
#define TAGLIB_MAJOR_VERSION 1
|
||||
#define TAGLIB_MINOR_VERSION 11
|
||||
#define TAGLIB_PATCH_VERSION 1
|
||||
#define TAGLIB_MINOR_VERSION 12
|
||||
#define TAGLIB_PATCH_VERSION 0
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)) || defined(__clang__)
|
||||
#define TAGLIB_IGNORE_MISSING_DESTRUCTOR _Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"")
|
||||
@@ -50,6 +50,8 @@
|
||||
#define TAGLIB_DEPRECATED __attribute__((deprecated))
|
||||
#elif defined(_MSC_VER)
|
||||
#define TAGLIB_DEPRECATED __declspec(deprecated)
|
||||
#else
|
||||
#define TAGLIB_DEPRECATED
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -67,17 +67,16 @@ namespace TagLib {
|
||||
*/
|
||||
enum StripTags {
|
||||
StripNone, //<! Don't strip any tags
|
||||
StripAll, //<! Strip all tags
|
||||
StripOthers //<! Strip all tags not explicitly referenced in method call
|
||||
};
|
||||
|
||||
/*!
|
||||
* Used to specify if when saving files, if values between different tag
|
||||
* types should be syncronized.
|
||||
* types should be synchronized.
|
||||
*/
|
||||
enum DuplicateTags {
|
||||
Duplicate, //<! Syncronize values between different tag types
|
||||
DoNotDuplicate //<! Do not syncronize values between different tag types
|
||||
Duplicate, //<! Synchronize values between different tag types
|
||||
DoNotDuplicate //<! Do not synchronize values between different tag types
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -496,7 +496,7 @@ void FileStream::truncate(long length)
|
||||
fflush(d->file);
|
||||
const int error = ftruncate(fileno(d->file), length);
|
||||
if(error != 0)
|
||||
debug("FileStream::truncate() -- Coundn't truncate the file.");
|
||||
debug("FileStream::truncate() -- Couldn't truncate the file.");
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace TagLib {
|
||||
* - ALBUMSORT
|
||||
* - ARTISTSORT
|
||||
* - ALBUMARTISTSORT
|
||||
* - COMPOSERSORT
|
||||
*
|
||||
* Credits:
|
||||
*
|
||||
@@ -90,12 +91,16 @@ namespace TagLib {
|
||||
* - LABEL
|
||||
* - CATALOGNUMBER
|
||||
* - BARCODE
|
||||
* - RELEASECOUNTRY
|
||||
* - RELEASESTATUS
|
||||
* - RELEASETYPE
|
||||
*
|
||||
* MusicBrainz identifiers:
|
||||
*
|
||||
* - MUSICBRAINZ_TRACKID
|
||||
* - MUSICBRAINZ_ALBUMID
|
||||
* - MUSICBRAINZ_RELEASEGROUPID
|
||||
* - MUSICBRAINZ_RELEASETRACKID
|
||||
* - MUSICBRAINZ_WORKID
|
||||
* - MUSICBRAINZ_ARTISTID
|
||||
* - MUSICBRAINZ_ALBUMARTISTID
|
||||
|
||||
@@ -29,12 +29,7 @@
|
||||
|
||||
#include "trefcounter.h"
|
||||
|
||||
#if defined(HAVE_STD_ATOMIC)
|
||||
# include <atomic>
|
||||
# define ATOMIC_INT std::atomic_int
|
||||
# define ATOMIC_INC(x) (++x)
|
||||
# define ATOMIC_DEC(x) (--x)
|
||||
#elif defined(HAVE_GCC_ATOMIC)
|
||||
#if defined(HAVE_GCC_ATOMIC)
|
||||
# define ATOMIC_INT int
|
||||
# define ATOMIC_INC(x) __sync_add_and_fetch(&x, 1)
|
||||
# define ATOMIC_DEC(x) __sync_sub_and_fetch(&x, 1)
|
||||
@@ -57,7 +52,7 @@
|
||||
# define ATOMIC_INC(x) __sync_add_and_fetch(&x, 1)
|
||||
# define ATOMIC_DEC(x) __sync_sub_and_fetch(&x, 1)
|
||||
#else
|
||||
# define ATOMIC_INT int
|
||||
# define ATOMIC_INT volatile int
|
||||
# define ATOMIC_INC(x) (++x)
|
||||
# define ATOMIC_DEC(x) (--x)
|
||||
#endif
|
||||
|
||||
@@ -57,7 +57,7 @@ ByteVector zlib::decompress(const ByteVector &data)
|
||||
z_stream stream = {};
|
||||
|
||||
if(inflateInit(&stream) != Z_OK) {
|
||||
debug("zlib::decompress() - Failed to initizlize zlib.");
|
||||
debug("zlib::decompress() - Failed to initialize zlib.");
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ void WavPack::File::read(bool readProperties)
|
||||
d->APELocation = d->APELocation + APE::Footer::size() - d->APESize;
|
||||
}
|
||||
|
||||
if(d->ID3v1Location >= 0)
|
||||
if(d->ID3v1Location < 0)
|
||||
APETag(true);
|
||||
|
||||
// Look for WavPack audio properties
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <tstring.h>
|
||||
#include <tdebug.h>
|
||||
|
||||
@@ -138,16 +139,10 @@ unsigned int WavPack::Properties::sampleFrames() const
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace
|
||||
{
|
||||
const unsigned int sample_rates[] = {
|
||||
6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000,
|
||||
32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 };
|
||||
}
|
||||
|
||||
#define BYTES_STORED 3
|
||||
#define MONO_FLAG 4
|
||||
#define LOSSLESS_FLAG 8
|
||||
#define HYBRID_FLAG 8
|
||||
#define DSD_FLAG 0x80000000 // block is encoded DSD (1-bit PCM)
|
||||
|
||||
#define SHIFT_LSB 13
|
||||
#define SHIFT_MASK (0x1fL << SHIFT_LSB)
|
||||
@@ -158,8 +153,110 @@ namespace
|
||||
#define MIN_STREAM_VERS 0x402
|
||||
#define MAX_STREAM_VERS 0x410
|
||||
|
||||
#define INITIAL_BLOCK 0x800
|
||||
#define FINAL_BLOCK 0x1000
|
||||
|
||||
#define ID_DSD_BLOCK 0x0e
|
||||
#define ID_OPTIONAL_DATA 0x20
|
||||
#define ID_UNIQUE 0x3f
|
||||
#define ID_ODD_SIZE 0x40
|
||||
#define ID_LARGE 0x80
|
||||
#define ID_SAMPLE_RATE (ID_OPTIONAL_DATA | 0x7)
|
||||
|
||||
namespace
|
||||
{
|
||||
const unsigned int sampleRates[] = {
|
||||
6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000,
|
||||
32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 };
|
||||
|
||||
/*!
|
||||
* Given a WavPack \a block (complete, but not including the 32-byte header),
|
||||
* parse the metadata blocks until an \a id block is found and return the
|
||||
* contained data, or zero if no such block is found.
|
||||
* Supported values for \a id are ID_SAMPLE_RATE and ID_DSD_BLOCK.
|
||||
*/
|
||||
int getMetaDataChunk(const ByteVector &block, unsigned char id)
|
||||
{
|
||||
if(id != ID_SAMPLE_RATE && id != ID_DSD_BLOCK)
|
||||
return 0;
|
||||
|
||||
const int blockSize = static_cast<int>(block.size());
|
||||
int index = 0;
|
||||
|
||||
while(index + 1 < blockSize) {
|
||||
const unsigned char metaId = static_cast<unsigned char>(block[index]);
|
||||
int metaBc = static_cast<unsigned char>(block[index + 1]) << 1;
|
||||
index += 2;
|
||||
|
||||
if(metaId & ID_LARGE) {
|
||||
if(index + 2 > blockSize)
|
||||
return 0;
|
||||
|
||||
metaBc += (static_cast<uint32_t>(static_cast<unsigned char>(block[index])) << 9)
|
||||
+ (static_cast<uint32_t>(static_cast<unsigned char>(block[index + 1])) << 17);
|
||||
index += 2;
|
||||
}
|
||||
|
||||
if(index + metaBc > blockSize)
|
||||
return 0;
|
||||
|
||||
// if we got a sample rate, return it
|
||||
|
||||
if(id == ID_SAMPLE_RATE && (metaId & ID_UNIQUE) == ID_SAMPLE_RATE && metaBc == 4) {
|
||||
int sampleRate = static_cast<int32_t>(static_cast<unsigned char>(block[index]));
|
||||
sampleRate |= static_cast<int32_t>(static_cast<unsigned char>(block[index + 1])) << 8;
|
||||
sampleRate |= static_cast<int32_t>(static_cast<unsigned char>(block[index + 2])) << 16;
|
||||
|
||||
// only use 4th byte if it's really there
|
||||
|
||||
if(!(metaId & ID_ODD_SIZE))
|
||||
sampleRate |= static_cast<int32_t>(static_cast<unsigned char>(block[index + 3]) & 0x7f) << 24;
|
||||
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
// if we got DSD block, return the specified rate shift amount
|
||||
|
||||
if(id == ID_DSD_BLOCK && (metaId & ID_UNIQUE) == ID_DSD_BLOCK && metaBc > 0) {
|
||||
const unsigned char rateShift = static_cast<unsigned char>(block[index]);
|
||||
if(rateShift <= 31)
|
||||
return rateShift;
|
||||
}
|
||||
|
||||
index += metaBc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Given a WavPack block (complete, but not including the 32-byte header),
|
||||
* parse the metadata blocks until an ID_SAMPLE_RATE block is found and
|
||||
* return the non-standard sample rate contained there, or zero if no such
|
||||
* block is found.
|
||||
*/
|
||||
int getNonStandardRate(const ByteVector &block)
|
||||
{
|
||||
return getMetaDataChunk(block, ID_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Given a WavPack block (complete, but not including the 32-byte header),
|
||||
* parse the metadata blocks until a DSD audio data block is found and return
|
||||
* the sample-rate shift value contained there, or zero if no such block is
|
||||
* found. The nominal sample rate of DSD audio files (found in the header)
|
||||
* must be left-shifted by this amount to get the actual "byte" sample rate.
|
||||
* Note that 8-bit bytes are the "atoms" of the DSD audio coding (for
|
||||
* decoding, seeking, etc), so the shifted rate must be further multiplied by
|
||||
* 8 to get the actual DSD bit sample rate.
|
||||
*/
|
||||
int getDsdRateShifter(const ByteVector &block)
|
||||
{
|
||||
return getMetaDataChunk(block, ID_DSD_BLOCK);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void WavPack::Properties::read(File *file, long streamLength)
|
||||
{
|
||||
long offset = 0;
|
||||
@@ -178,17 +275,50 @@ void WavPack::Properties::read(File *file, long streamLength)
|
||||
break;
|
||||
}
|
||||
|
||||
const unsigned int blockSize = data.toUInt(4, false);
|
||||
const unsigned int sampleFrames = data.toUInt(12, false);
|
||||
const unsigned int blockSamples = data.toUInt(20, false);
|
||||
const unsigned int flags = data.toUInt(24, false);
|
||||
unsigned int sampleRate = sampleRates[(flags & SRATE_MASK) >> SRATE_LSB];
|
||||
|
||||
if(offset == 0) {
|
||||
if(!blockSamples) { // ignore blocks with no samples
|
||||
offset += blockSize + 8;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(blockSize < 24 || blockSize > 1048576) {
|
||||
debug("WavPack::Properties::read() -- Invalid block header found.");
|
||||
break;
|
||||
}
|
||||
|
||||
// For non-standard sample rates or DSD audio files, we must read and parse the block
|
||||
// to actually determine the sample rate.
|
||||
|
||||
if(!sampleRate || (flags & DSD_FLAG)) {
|
||||
const unsigned int adjustedBlockSize = blockSize - 24;
|
||||
const ByteVector block = file->readBlock(adjustedBlockSize);
|
||||
|
||||
if(block.size() < adjustedBlockSize) {
|
||||
debug("WavPack::Properties::read() -- block is too short.");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!sampleRate)
|
||||
sampleRate = static_cast<unsigned int>(getNonStandardRate(block));
|
||||
|
||||
if(sampleRate && (flags & DSD_FLAG))
|
||||
sampleRate <<= getDsdRateShifter(block);
|
||||
}
|
||||
|
||||
if(flags & INITIAL_BLOCK) {
|
||||
d->version = data.toShort(8, false);
|
||||
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->sampleRate = sample_rates[(flags & SRATE_MASK) >> SRATE_LSB];
|
||||
d->lossless = !(flags & LOSSLESS_FLAG);
|
||||
d->sampleFrames = data.toUInt(12, false);
|
||||
d->sampleRate = static_cast<int>(sampleRate);
|
||||
d->lossless = !(flags & HYBRID_FLAG);
|
||||
d->sampleFrames = sampleFrames;
|
||||
}
|
||||
|
||||
d->channels += (flags & MONO_FLAG) ? 1 : 2;
|
||||
@@ -196,7 +326,6 @@ void WavPack::Properties::read(File *file, long streamLength)
|
||||
if(flags & FINAL_BLOCK)
|
||||
break;
|
||||
|
||||
const unsigned int blockSize = data.toUInt(4, false);
|
||||
offset += blockSize + 8;
|
||||
}
|
||||
|
||||
@@ -212,25 +341,34 @@ void WavPack::Properties::read(File *file, long streamLength)
|
||||
|
||||
unsigned int WavPack::Properties::seekFinalIndex(File *file, long streamLength)
|
||||
{
|
||||
const long offset = file->rfind("wvpk", streamLength);
|
||||
if(offset == -1)
|
||||
return 0;
|
||||
long offset = streamLength;
|
||||
|
||||
file->seek(offset);
|
||||
const ByteVector data = file->readBlock(32);
|
||||
if(data.size() < 32)
|
||||
return 0;
|
||||
while (offset >= 32) {
|
||||
offset = file->rfind("wvpk", offset - 4);
|
||||
|
||||
const int version = data.toShort(8, false);
|
||||
if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS)
|
||||
return 0;
|
||||
if(offset == -1)
|
||||
return 0;
|
||||
|
||||
const unsigned int flags = data.toUInt(24, false);
|
||||
if(!(flags & FINAL_BLOCK))
|
||||
return 0;
|
||||
file->seek(offset);
|
||||
const ByteVector data = file->readBlock(32);
|
||||
if(data.size() < 32)
|
||||
return 0;
|
||||
|
||||
const unsigned int blockIndex = data.toUInt(16, false);
|
||||
const unsigned int blockSamples = data.toUInt(20, false);
|
||||
const unsigned int blockSize = data.toUInt(4, false);
|
||||
const unsigned int blockIndex = data.toUInt(16, false);
|
||||
const unsigned int blockSamples = data.toUInt(20, false);
|
||||
const unsigned int flags = data.toUInt(24, false);
|
||||
const int version = data.toShort(8, false);
|
||||
|
||||
return blockIndex + blockSamples;
|
||||
// try not to trigger on a spurious "wvpk" in WavPack binary block data
|
||||
|
||||
if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS || (blockSize & 1) ||
|
||||
blockSize < 24 || blockSize >= 1048576 || blockSamples > 131072)
|
||||
continue;
|
||||
|
||||
if (blockSamples && (flags & FINAL_BLOCK))
|
||||
return blockIndex + blockSamples;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -566,7 +566,7 @@ void XM::File::read(bool)
|
||||
seek(patternHeaderLength - (4 + count) + dataSize, Current);
|
||||
}
|
||||
|
||||
StringList intrumentNames;
|
||||
StringList instrumentNames;
|
||||
StringList sampleNames;
|
||||
unsigned int sumSampleCount = 0;
|
||||
|
||||
@@ -630,12 +630,12 @@ void XM::File::read(bool)
|
||||
else {
|
||||
offset = instrumentHeaderSize - count;
|
||||
}
|
||||
intrumentNames.append(instrumentName);
|
||||
instrumentNames.append(instrumentName);
|
||||
seek(offset, Current);
|
||||
}
|
||||
|
||||
d->properties.setSampleCount(sumSampleCount);
|
||||
String comment(intrumentNames.toString("\n"));
|
||||
String comment(instrumentNames.toString("\n"));
|
||||
if(!sampleNames.isEmpty()) {
|
||||
comment += "\n";
|
||||
comment += sampleNames.toString("\n");
|
||||
|
||||
BIN
tests/data/dsd_stereo.wv
Normal file
BIN
tests/data/dsd_stereo.wv
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/data/non_standard_rate.wv
Normal file
BIN
tests/data/non_standard_rate.wv
Normal file
Binary file not shown.
BIN
tests/data/uint8we.wav
Normal file
BIN
tests/data/uint8we.wav
Normal file
Binary file not shown.
50
tests/plainfile.h
Normal file
50
tests/plainfile.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2015 by Tsuda Kageyu
|
||||
email : tsuda.kageyu@gmail.com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_PLAINFILE_H
|
||||
#define TAGLIB_PLAINFILE_H
|
||||
|
||||
#include <tfile.h>
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
//! File subclass that gives tests access to filesystem operations
|
||||
class PlainFile : public File {
|
||||
public:
|
||||
explicit PlainFile(FileName name) : File(name) { }
|
||||
Tag *tag() const { return NULL; }
|
||||
AudioProperties *audioProperties() const { return NULL; }
|
||||
bool save() { return false; }
|
||||
void truncate(long length) { File::truncate(length); }
|
||||
|
||||
ByteVector readAll() {
|
||||
seek(0, End);
|
||||
long end = tell();
|
||||
seek(0);
|
||||
return readBlock(end);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -148,13 +148,13 @@ public:
|
||||
void testFuzzedFile1()
|
||||
{
|
||||
RIFF::AIFF::File f(TEST_FILE_PATH_C("segfault.aif"));
|
||||
CPPUNIT_ASSERT(!f.isValid());
|
||||
CPPUNIT_ASSERT(f.isValid());
|
||||
}
|
||||
|
||||
void testFuzzedFile2()
|
||||
{
|
||||
RIFF::AIFF::File f(TEST_FILE_PATH_C("excessive_alloc.aif"));
|
||||
CPPUNIT_ASSERT(!f.isValid());
|
||||
CPPUNIT_ASSERT(f.isValid());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -155,6 +155,58 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void testProperties()
|
||||
{
|
||||
PropertyMap tags;
|
||||
tags["ALBUM"] = StringList("Album");
|
||||
tags["ALBUMARTIST"] = StringList("Album Artist");
|
||||
tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort");
|
||||
tags["ALBUMSORT"] = StringList("Album Sort");
|
||||
tags["ARTIST"] = StringList("Artist");
|
||||
tags["ARTISTS"] = StringList("Artists");
|
||||
tags["ARTISTSORT"] = StringList("Artist Sort");
|
||||
tags["ASIN"] = StringList("ASIN");
|
||||
tags["BARCODE"] = StringList("Barcode");
|
||||
tags["CATALOGNUMBER"] = StringList("Catalog Number 1").append("Catalog Number 2");
|
||||
tags["COMMENT"] = StringList("Comment");
|
||||
tags["DATE"] = StringList("2021-01-10");
|
||||
tags["DISCNUMBER"] = StringList("3/5");
|
||||
tags["GENRE"] = StringList("Genre");
|
||||
tags["ISRC"] = StringList("UKAAA0500001");
|
||||
tags["LABEL"] = StringList("Label 1").append("Label 2");
|
||||
tags["MEDIA"] = StringList("Media");
|
||||
tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID");
|
||||
tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID");
|
||||
tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID");
|
||||
tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID");
|
||||
tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID");
|
||||
tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID");
|
||||
tags["ORIGINALDATE"] = StringList("2021-01-09");
|
||||
tags["RELEASECOUNTRY"] = StringList("Release Country");
|
||||
tags["RELEASESTATUS"] = StringList("Release Status");
|
||||
tags["RELEASETYPE"] = StringList("Release Type");
|
||||
tags["SCRIPT"] = StringList("Script");
|
||||
tags["TITLE"] = StringList("Title");
|
||||
tags["TRACKNUMBER"] = StringList("2/3");
|
||||
|
||||
ScopedFileCopy copy("mac-399", ".ape");
|
||||
{
|
||||
APE::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
CPPUNIT_ASSERT(properties.isEmpty());
|
||||
f.setProperties(tags);
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
const APE::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
if (tags != properties) {
|
||||
CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString());
|
||||
}
|
||||
CPPUNIT_ASSERT(tags == properties);
|
||||
}
|
||||
}
|
||||
|
||||
void testRepeatedSave()
|
||||
{
|
||||
ScopedFileCopy copy("mac-399", ".ape");
|
||||
|
||||
@@ -79,6 +79,10 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(2u, tag.itemListMap()["ARTIST"].values().size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("artist 1 artist 2"), tag.artist());
|
||||
CPPUNIT_ASSERT_EQUAL(17u, tag.track());
|
||||
const APE::Item &textItem = tag.itemListMap()["TRACK"];
|
||||
CPPUNIT_ASSERT_EQUAL(APE::Item::Text, textItem.type());
|
||||
CPPUNIT_ASSERT(!textItem.isEmpty());
|
||||
CPPUNIT_ASSERT_EQUAL(9 + 5 + 2, textItem.size());
|
||||
}
|
||||
|
||||
void testPropertyInterface2()
|
||||
@@ -89,6 +93,8 @@ public:
|
||||
|
||||
APE::Item item2 = APE::Item();
|
||||
item2.setType(APE::Item::Binary);
|
||||
ByteVector binaryData1("first");
|
||||
item2.setBinaryData(binaryData1);
|
||||
tag.setItem("TESTBINARY", item2);
|
||||
|
||||
PropertyMap properties = tag.properties();
|
||||
@@ -96,6 +102,16 @@ public:
|
||||
CPPUNIT_ASSERT(properties.contains("TRACKNUMBER"));
|
||||
CPPUNIT_ASSERT(!properties.contains("TRACK"));
|
||||
CPPUNIT_ASSERT(tag.itemListMap().contains("TESTBINARY"));
|
||||
CPPUNIT_ASSERT_EQUAL(binaryData1,
|
||||
tag.itemListMap()["TESTBINARY"].binaryData());
|
||||
ByteVector binaryData2("second");
|
||||
tag.setData("TESTBINARY", binaryData2);
|
||||
const APE::Item &binaryItem = tag.itemListMap()["TESTBINARY"];
|
||||
CPPUNIT_ASSERT_EQUAL(APE::Item::Binary, binaryItem.type());
|
||||
CPPUNIT_ASSERT(!binaryItem.isEmpty());
|
||||
CPPUNIT_ASSERT_EQUAL(9 + 10 + static_cast<int>(binaryData2.size()),
|
||||
binaryItem.size());
|
||||
CPPUNIT_ASSERT_EQUAL(binaryData2, binaryItem.binaryData());
|
||||
|
||||
tag.removeUnsupportedProperties(properties.unsupportedData());
|
||||
CPPUNIT_ASSERT(!tag.itemListMap().contains("TESTBINARY"));
|
||||
|
||||
@@ -50,6 +50,7 @@ class TestASF : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testSavePicture);
|
||||
CPPUNIT_TEST(testSaveMultiplePictures);
|
||||
CPPUNIT_TEST(testProperties);
|
||||
CPPUNIT_TEST(testPropertiesAllSupported);
|
||||
CPPUNIT_TEST(testRepeatedSave);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
@@ -302,6 +303,84 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["DISCNUMBER"]);
|
||||
}
|
||||
|
||||
void testPropertiesAllSupported()
|
||||
{
|
||||
PropertyMap tags;
|
||||
tags["ACOUSTID_ID"] = StringList("Acoustid ID");
|
||||
tags["ACOUSTID_FINGERPRINT"] = StringList("Acoustid Fingerprint");
|
||||
tags["ALBUM"] = StringList("Album");
|
||||
tags["ALBUMARTIST"] = StringList("Album Artist");
|
||||
tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort");
|
||||
tags["ALBUMSORT"] = StringList("Album Sort");
|
||||
tags["ARTIST"] = StringList("Artist");
|
||||
tags["ARTISTS"] = StringList("Artists");
|
||||
tags["ARTISTSORT"] = StringList("Artist Sort");
|
||||
tags["ASIN"] = StringList("ASIN");
|
||||
tags["BARCODE"] = StringList("Barcode");
|
||||
tags["BPM"] = StringList("123");
|
||||
tags["CATALOGNUMBER"] = StringList("Catalog Number");
|
||||
tags["COMMENT"] = StringList("Comment");
|
||||
tags["COMPOSER"] = StringList("Composer");
|
||||
tags["CONDUCTOR"] = StringList("Conductor");
|
||||
tags["COPYRIGHT"] = StringList("2021 Copyright");
|
||||
tags["DATE"] = StringList("2021-01-03 12:29:23");
|
||||
tags["DISCNUMBER"] = StringList("3/5");
|
||||
tags["DISCSUBTITLE"] = StringList("Disc Subtitle");
|
||||
tags["ENCODEDBY"] = StringList("Encoded by");
|
||||
tags["GENRE"] = StringList("Genre");
|
||||
tags["GROUPING"] = StringList("Grouping");
|
||||
tags["ISRC"] = StringList("UKAAA0500001");
|
||||
tags["LABEL"] = StringList("Label");
|
||||
tags["LANGUAGE"] = StringList("eng");
|
||||
tags["LYRICIST"] = StringList("Lyricist");
|
||||
tags["LYRICS"] = StringList("Lyrics");
|
||||
tags["MEDIA"] = StringList("Media");
|
||||
tags["MOOD"] = StringList("Mood");
|
||||
tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID");
|
||||
tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID");
|
||||
tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID");
|
||||
tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID");
|
||||
tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID");
|
||||
tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID");
|
||||
tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID");
|
||||
tags["MUSICIP_PUID"] = StringList("MusicIP PUID");
|
||||
tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19");
|
||||
tags["PRODUCER"] = StringList("Producer");
|
||||
tags["RELEASECOUNTRY"] = StringList("Release Country");
|
||||
tags["RELEASESTATUS"] = StringList("Release Status");
|
||||
tags["RELEASETYPE"] = StringList("Release Type");
|
||||
tags["REMIXER"] = StringList("Remixer");
|
||||
tags["SCRIPT"] = StringList("Script");
|
||||
tags["SUBTITLE"] = StringList("Subtitle");
|
||||
tags["TITLE"] = StringList("Title");
|
||||
tags["TITLESORT"] = StringList("Title Sort");
|
||||
tags["TRACKNUMBER"] = StringList("2/4");
|
||||
|
||||
ScopedFileCopy copy("silence-1", ".wma");
|
||||
{
|
||||
ASF::File f(copy.fileName().c_str());
|
||||
ASF::Tag *asfTag = f.tag();
|
||||
asfTag->setTitle("");
|
||||
asfTag->attributeListMap().clear();
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
ASF::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
CPPUNIT_ASSERT(properties.isEmpty());
|
||||
f.setProperties(tags);
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
const ASF::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
if (tags != properties) {
|
||||
CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString());
|
||||
}
|
||||
CPPUNIT_ASSERT(tags == properties);
|
||||
}
|
||||
}
|
||||
|
||||
void testRepeatedSave()
|
||||
{
|
||||
ScopedFileCopy copy("silence-1", ".wma");
|
||||
|
||||
@@ -25,20 +25,11 @@
|
||||
|
||||
#include <tfile.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "plainfile.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
// File subclass that gives tests access to filesystem operations
|
||||
class PlainFile : public File {
|
||||
public:
|
||||
PlainFile(FileName name) : File(name) { }
|
||||
Tag *tag() const { return NULL; }
|
||||
AudioProperties *audioProperties() const { return NULL; }
|
||||
bool save(){ return false; }
|
||||
void truncate(long length) { File::truncate(length); }
|
||||
};
|
||||
|
||||
class TestFile : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TestFile);
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
#include <wavfile.h>
|
||||
#include <apefile.h>
|
||||
#include <aifffile.h>
|
||||
#include <wavpackfile.h>
|
||||
#include <opusfile.h>
|
||||
#include <xmfile.h>
|
||||
#include <tfilestream.h>
|
||||
#include <tbytevectorstream.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
@@ -79,8 +82,12 @@ class TestFileRef : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testWav);
|
||||
CPPUNIT_TEST(testAIFF_1);
|
||||
CPPUNIT_TEST(testAIFF_2);
|
||||
CPPUNIT_TEST(testWavPack);
|
||||
CPPUNIT_TEST(testOpus);
|
||||
CPPUNIT_TEST(testUnsupported);
|
||||
CPPUNIT_TEST(testCreate);
|
||||
CPPUNIT_TEST(testAudioProperties);
|
||||
CPPUNIT_TEST(testDefaultFileExtensions);
|
||||
CPPUNIT_TEST(testFileResolver);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
@@ -100,6 +107,7 @@ public:
|
||||
f.tag()->setTitle("test title");
|
||||
f.tag()->setGenre("Test!");
|
||||
f.tag()->setAlbum("albummmm");
|
||||
f.tag()->setComment("a comment");
|
||||
f.tag()->setTrack(5);
|
||||
f.tag()->setYear(2020);
|
||||
f.save();
|
||||
@@ -111,12 +119,14 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("a comment"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5);
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020);
|
||||
f.tag()->setArtist("ttest artist");
|
||||
f.tag()->setTitle("ytest title");
|
||||
f.tag()->setGenre("uTest!");
|
||||
f.tag()->setAlbum("ialbummmm");
|
||||
f.tag()->setComment("another comment");
|
||||
f.tag()->setTrack(7);
|
||||
f.tag()->setYear(2080);
|
||||
f.save();
|
||||
@@ -128,6 +138,7 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("another comment"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7);
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080);
|
||||
}
|
||||
@@ -141,12 +152,14 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("another comment"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7);
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080);
|
||||
f.tag()->setArtist("test artist");
|
||||
f.tag()->setTitle("test title");
|
||||
f.tag()->setGenre("Test!");
|
||||
f.tag()->setAlbum("albummmm");
|
||||
f.tag()->setComment("a comment");
|
||||
f.tag()->setTrack(5);
|
||||
f.tag()->setYear(2020);
|
||||
f.save();
|
||||
@@ -162,6 +175,7 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("a comment"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5);
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020);
|
||||
|
||||
@@ -178,12 +192,14 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("a comment"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5);
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020);
|
||||
f.tag()->setArtist("ttest artist");
|
||||
f.tag()->setTitle("ytest title");
|
||||
f.tag()->setGenre("uTest!");
|
||||
f.tag()->setAlbum("ialbummmm");
|
||||
f.tag()->setComment("another comment");
|
||||
f.tag()->setTrack(7);
|
||||
f.tag()->setYear(2080);
|
||||
f.save();
|
||||
@@ -199,6 +215,7 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("another comment"));
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7);
|
||||
CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080);
|
||||
}
|
||||
@@ -289,6 +306,16 @@ public:
|
||||
fileRefSave<RIFF::AIFF::File>("alaw", ".aifc");
|
||||
}
|
||||
|
||||
void testWavPack()
|
||||
{
|
||||
fileRefSave<WavPack::File>("click", ".wv");
|
||||
}
|
||||
|
||||
void testOpus()
|
||||
{
|
||||
fileRefSave<Ogg::Opus::File>("correctness_gain_silent_output", ".opus");
|
||||
}
|
||||
|
||||
void testUnsupported()
|
||||
{
|
||||
FileRef f1(TEST_FILE_PATH_C("no-extension"));
|
||||
@@ -309,6 +336,41 @@ public:
|
||||
f = FileRef::create(TEST_FILE_PATH_C("xing.mp3"));
|
||||
CPPUNIT_ASSERT(dynamic_cast<MPEG::File*>(f));
|
||||
delete f;
|
||||
|
||||
f = FileRef::create(TEST_FILE_PATH_C("test.xm"));
|
||||
CPPUNIT_ASSERT(dynamic_cast<XM::File*>(f));
|
||||
delete f;
|
||||
}
|
||||
|
||||
void testAudioProperties()
|
||||
{
|
||||
FileRef f(TEST_FILE_PATH_C("xing.mp3"));
|
||||
const AudioProperties *audioProperties = f.audioProperties();
|
||||
CPPUNIT_ASSERT_EQUAL(2, audioProperties->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(2064, audioProperties->lengthInMilliseconds());
|
||||
}
|
||||
|
||||
void testDefaultFileExtensions()
|
||||
{
|
||||
const StringList extensions = FileRef::defaultFileExtensions();
|
||||
CPPUNIT_ASSERT(extensions.contains("mpc"));
|
||||
CPPUNIT_ASSERT(extensions.contains("wma"));
|
||||
CPPUNIT_ASSERT(extensions.contains("ogg"));
|
||||
CPPUNIT_ASSERT(extensions.contains("spx"));
|
||||
CPPUNIT_ASSERT(extensions.contains("flac"));
|
||||
CPPUNIT_ASSERT(extensions.contains("mp3"));
|
||||
CPPUNIT_ASSERT(extensions.contains("tta"));
|
||||
CPPUNIT_ASSERT(extensions.contains("m4a"));
|
||||
CPPUNIT_ASSERT(extensions.contains("3g2"));
|
||||
CPPUNIT_ASSERT(extensions.contains("m4v"));
|
||||
CPPUNIT_ASSERT(extensions.contains("wav"));
|
||||
CPPUNIT_ASSERT(extensions.contains("oga"));
|
||||
CPPUNIT_ASSERT(extensions.contains("ape"));
|
||||
CPPUNIT_ASSERT(extensions.contains("aiff"));
|
||||
CPPUNIT_ASSERT(extensions.contains("aifc"));
|
||||
CPPUNIT_ASSERT(extensions.contains("wv"));
|
||||
CPPUNIT_ASSERT(extensions.contains("opus"));
|
||||
CPPUNIT_ASSERT(extensions.contains("xm"));
|
||||
}
|
||||
|
||||
void testFileResolver()
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <id3v1tag.h>
|
||||
#include <id3v2tag.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "plainfile.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -53,6 +54,7 @@ class TestFLAC : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testRepeatedSave3);
|
||||
CPPUNIT_TEST(testSaveMultipleValues);
|
||||
CPPUNIT_TEST(testDict);
|
||||
CPPUNIT_TEST(testProperties);
|
||||
CPPUNIT_TEST(testInvalid);
|
||||
CPPUNIT_TEST(testAudioProperties);
|
||||
CPPUNIT_TEST(testZeroSizedPadding1);
|
||||
@@ -64,6 +66,7 @@ class TestFLAC : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testStripTags);
|
||||
CPPUNIT_TEST(testRemoveXiphField);
|
||||
CPPUNIT_TEST(testEmptySeekTable);
|
||||
CPPUNIT_TEST(testPictureStoredAfterComment);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -311,6 +314,60 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void testProperties()
|
||||
{
|
||||
PropertyMap tags;
|
||||
tags["ALBUM"] = StringList("Album");
|
||||
tags["ALBUMARTIST"] = StringList("Album Artist");
|
||||
tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort");
|
||||
tags["ALBUMSORT"] = StringList("Album Sort");
|
||||
tags["ARTIST"] = StringList("Artist");
|
||||
tags["ARTISTS"] = StringList("Artists");
|
||||
tags["ARTISTSORT"] = StringList("Artist Sort");
|
||||
tags["ASIN"] = StringList("ASIN");
|
||||
tags["BARCODE"] = StringList("Barcode");
|
||||
tags["CATALOGNUMBER"] = StringList("Catalog Number 1").append("Catalog Number 2");
|
||||
tags["COMMENT"] = StringList("Comment");
|
||||
tags["DATE"] = StringList("2021-01-10");
|
||||
tags["DISCNUMBER"] = StringList("3");
|
||||
tags["DISCTOTAL"] = StringList("5");
|
||||
tags["GENRE"] = StringList("Genre");
|
||||
tags["ISRC"] = StringList("UKAAA0500001");
|
||||
tags["LABEL"] = StringList("Label 1").append("Label 2");
|
||||
tags["MEDIA"] = StringList("Media");
|
||||
tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID");
|
||||
tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID");
|
||||
tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID");
|
||||
tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID");
|
||||
tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID");
|
||||
tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID");
|
||||
tags["ORIGINALDATE"] = StringList("2021-01-09");
|
||||
tags["RELEASECOUNTRY"] = StringList("Release Country");
|
||||
tags["RELEASESTATUS"] = StringList("Release Status");
|
||||
tags["RELEASETYPE"] = StringList("Release Type");
|
||||
tags["SCRIPT"] = StringList("Script");
|
||||
tags["TITLE"] = StringList("Title");
|
||||
tags["TRACKNUMBER"] = StringList("2");
|
||||
tags["TRACKTOTAL"] = StringList("4");
|
||||
|
||||
ScopedFileCopy copy("no-tags", ".flac");
|
||||
{
|
||||
FLAC::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
CPPUNIT_ASSERT(properties.isEmpty());
|
||||
f.setProperties(tags);
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
const FLAC::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
if (tags != properties) {
|
||||
CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString());
|
||||
}
|
||||
CPPUNIT_ASSERT(tags == properties);
|
||||
}
|
||||
}
|
||||
|
||||
void testInvalid()
|
||||
{
|
||||
ScopedFileCopy copy("silence-44-s", ".flac");
|
||||
@@ -533,6 +590,83 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void testPictureStoredAfterComment()
|
||||
{
|
||||
// Blank.png from https://commons.wikimedia.org/wiki/File:Blank.png
|
||||
const unsigned char blankPngData[] = {
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02,
|
||||
0x08, 0x06, 0x00, 0x00, 0x00, 0x9d, 0x74, 0x66, 0x1a, 0x00, 0x00, 0x00,
|
||||
0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
|
||||
0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc,
|
||||
0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
|
||||
0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00,
|
||||
0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0xc0, 0x01,
|
||||
0x18, 0x18, 0x00, 0x00, 0x1a, 0x00, 0x01, 0x82, 0x92, 0x4d, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
|
||||
};
|
||||
const ByteVector picData(reinterpret_cast<const char *>(blankPngData),
|
||||
sizeof(blankPngData));
|
||||
|
||||
ScopedFileCopy copy("no-tags", ".flac");
|
||||
{
|
||||
FLAC::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(!f.hasID3v1Tag());
|
||||
CPPUNIT_ASSERT(!f.hasID3v2Tag());
|
||||
CPPUNIT_ASSERT(!f.hasXiphComment());
|
||||
CPPUNIT_ASSERT(f.pictureList().isEmpty());
|
||||
|
||||
FLAC::Picture *pic = new FLAC::Picture;
|
||||
pic->setData(picData);
|
||||
pic->setType(FLAC::Picture::FrontCover);
|
||||
pic->setMimeType("image/png");
|
||||
pic->setDescription("blank.png");
|
||||
pic->setWidth(3);
|
||||
pic->setHeight(2);
|
||||
pic->setColorDepth(32);
|
||||
pic->setNumColors(0);
|
||||
f.addPicture(pic);
|
||||
f.xiphComment(true)->setTitle("Title");
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
FLAC::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(!f.hasID3v1Tag());
|
||||
CPPUNIT_ASSERT(!f.hasID3v2Tag());
|
||||
CPPUNIT_ASSERT(f.hasXiphComment());
|
||||
const List<FLAC::Picture *> pictures = f.pictureList();
|
||||
CPPUNIT_ASSERT_EQUAL(1U, pictures.size());
|
||||
CPPUNIT_ASSERT_EQUAL(picData, pictures[0]->data());
|
||||
CPPUNIT_ASSERT_EQUAL(FLAC::Picture::FrontCover, pictures[0]->type());
|
||||
CPPUNIT_ASSERT_EQUAL(String("image/png"), pictures[0]->mimeType());
|
||||
CPPUNIT_ASSERT_EQUAL(String("blank.png"), pictures[0]->description());
|
||||
CPPUNIT_ASSERT_EQUAL(3, pictures[0]->width());
|
||||
CPPUNIT_ASSERT_EQUAL(2, pictures[0]->height());
|
||||
CPPUNIT_ASSERT_EQUAL(32, pictures[0]->colorDepth());
|
||||
CPPUNIT_ASSERT_EQUAL(0, pictures[0]->numColors());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Title"), f.xiphComment(false)->title());
|
||||
}
|
||||
|
||||
const unsigned char expectedHeadData[] = {
|
||||
'f', 'L', 'a', 'C', 0x00, 0x00, 0x00, 0x22, 0x12, 0x00, 0x12, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x00, 0x00, 0x10, 0x0a, 0xc4, 0x42, 0xf0, 0x00, 0x02,
|
||||
0x7a, 0xc0, 0xa1, 0xb1, 0x41, 0xf7, 0x66, 0xe9, 0x84, 0x9a, 0xc3, 0xdb,
|
||||
0x10, 0x30, 0xa2, 0x0a, 0x3c, 0x77, 0x04, 0x00, 0x00, 0x17, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 'T', 'I',
|
||||
'T', 'L', 'E', '=', 'T', 'i', 't', 'l', 'e', 0x06, 0x00, 0x00,
|
||||
0xa9, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x09, 'i', 'm', 'a',
|
||||
'g', 'e', '/', 'p', 'n', 'g', 0x00, 0x00, 0x00, 0x09, 'b', 'l',
|
||||
'a', 'n', 'k', '.', 'p', 'n', 'g', 0x00, 0x00, 0x00, 0x03, 0x00,
|
||||
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x77
|
||||
};
|
||||
ByteVector expectedData(reinterpret_cast<const char *>(expectedHeadData),
|
||||
sizeof(expectedHeadData));
|
||||
expectedData.append(picData);
|
||||
const ByteVector fileData = PlainFile(copy.fileName().c_str()).readAll();
|
||||
CPPUNIT_ASSERT(fileData.startsWith(expectedData));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC);
|
||||
|
||||
@@ -40,6 +40,7 @@ class TestID3v1 : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST_SUITE(TestID3v1);
|
||||
CPPUNIT_TEST(testStripWhiteSpace);
|
||||
CPPUNIT_TEST(testGenres);
|
||||
CPPUNIT_TEST(testRenamedGenres);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -66,6 +67,20 @@ public:
|
||||
{
|
||||
CPPUNIT_ASSERT_EQUAL(String("Darkwave"), ID3v1::genre(50));
|
||||
CPPUNIT_ASSERT_EQUAL(100, ID3v1::genreIndex("Humour"));
|
||||
CPPUNIT_ASSERT(ID3v1::genreList().contains("Heavy Metal"));
|
||||
CPPUNIT_ASSERT_EQUAL(79, ID3v1::genreMap()["Hard Rock"]);
|
||||
}
|
||||
|
||||
void testRenamedGenres()
|
||||
{
|
||||
CPPUNIT_ASSERT_EQUAL(String("Bebop"), ID3v1::genre(85));
|
||||
CPPUNIT_ASSERT_EQUAL(85, ID3v1::genreIndex("Bebop"));
|
||||
CPPUNIT_ASSERT_EQUAL(85, ID3v1::genreIndex("Bebob"));
|
||||
|
||||
ID3v1::Tag tag;
|
||||
tag.setGenre("Hardcore");
|
||||
CPPUNIT_ASSERT_EQUAL(String("Hardcore Techno"), tag.genre());
|
||||
CPPUNIT_ASSERT_EQUAL(129U, tag.genreNumber());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/***************************************************************************
|
||||
/***************************************************************************
|
||||
copyright : (C) 2007 by Lukas Lalinsky
|
||||
email : lukas@oxygene.sk
|
||||
***************************************************************************/
|
||||
@@ -42,10 +42,14 @@
|
||||
#include <unknownframe.h>
|
||||
#include <chapterframe.h>
|
||||
#include <tableofcontentsframe.h>
|
||||
#include <commentsframe.h>
|
||||
#include <podcastframe.h>
|
||||
#include <privateframe.h>
|
||||
#include <tdebug.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tzlib.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "plainfile.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -75,16 +79,20 @@ class TestID3v2 : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testParseAPIC);
|
||||
CPPUNIT_TEST(testParseAPIC_UTF16_BOM);
|
||||
CPPUNIT_TEST(testParseAPICv22);
|
||||
CPPUNIT_TEST(testRenderAPIC);
|
||||
CPPUNIT_TEST(testDontRender22);
|
||||
CPPUNIT_TEST(testParseGEOB);
|
||||
CPPUNIT_TEST(testRenderGEOB);
|
||||
CPPUNIT_TEST(testPOPMtoString);
|
||||
CPPUNIT_TEST(testParsePOPM);
|
||||
CPPUNIT_TEST(testParsePOPMWithoutCounter);
|
||||
CPPUNIT_TEST(testRenderPOPM);
|
||||
CPPUNIT_TEST(testPOPMFromFile);
|
||||
CPPUNIT_TEST(testParseRelativeVolumeFrame);
|
||||
CPPUNIT_TEST(testRenderRelativeVolumeFrame);
|
||||
CPPUNIT_TEST(testParseUniqueFileIdentifierFrame);
|
||||
CPPUNIT_TEST(testParseEmptyUniqueFileIdentifierFrame);
|
||||
CPPUNIT_TEST(testRenderUniqueFileIdentifierFrame);
|
||||
CPPUNIT_TEST(testBrokenFrame1);
|
||||
CPPUNIT_TEST(testItunes24FrameSize);
|
||||
CPPUNIT_TEST(testParseUrlLinkFrame);
|
||||
@@ -98,9 +106,16 @@ class TestID3v2 : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testRenderSynchronizedLyricsFrame);
|
||||
CPPUNIT_TEST(testParseEventTimingCodesFrame);
|
||||
CPPUNIT_TEST(testRenderEventTimingCodesFrame);
|
||||
CPPUNIT_TEST(testParseCommentsFrame);
|
||||
CPPUNIT_TEST(testRenderCommentsFrame);
|
||||
CPPUNIT_TEST(testParsePodcastFrame);
|
||||
CPPUNIT_TEST(testRenderPodcastFrame);
|
||||
CPPUNIT_TEST(testParsePrivateFrame);
|
||||
CPPUNIT_TEST(testRenderPrivateFrame);
|
||||
CPPUNIT_TEST(testSaveUTF16Comment);
|
||||
CPPUNIT_TEST(testUpdateGenre23_1);
|
||||
CPPUNIT_TEST(testUpdateGenre23_2);
|
||||
CPPUNIT_TEST(testUpdateGenre23_3);
|
||||
CPPUNIT_TEST(testUpdateGenre24);
|
||||
CPPUNIT_TEST(testUpdateDate22);
|
||||
CPPUNIT_TEST(testDowngradeTo23);
|
||||
@@ -264,6 +279,26 @@ public:
|
||||
delete frame;
|
||||
}
|
||||
|
||||
void testRenderAPIC()
|
||||
{
|
||||
ID3v2::AttachedPictureFrame f;
|
||||
f.setTextEncoding(String::UTF8);
|
||||
f.setMimeType("image/png");
|
||||
f.setType(ID3v2::AttachedPictureFrame::BackCover);
|
||||
f.setDescription("Description");
|
||||
f.setPicture("PNG data");
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
ByteVector("APIC"
|
||||
"\x00\x00\x00\x20"
|
||||
"\x00\x00"
|
||||
"\x03"
|
||||
"image/png\x00"
|
||||
"\x04"
|
||||
"Description\x00"
|
||||
"PNG data", 42),
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testDontRender22()
|
||||
{
|
||||
ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
|
||||
@@ -302,6 +337,26 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(String("d"), f.description());
|
||||
}
|
||||
|
||||
void testRenderGEOB()
|
||||
{
|
||||
ID3v2::GeneralEncapsulatedObjectFrame f;
|
||||
f.setTextEncoding(String::Latin1);
|
||||
f.setMimeType("application/octet-stream");
|
||||
f.setFileName("test.bin");
|
||||
f.setDescription("Description");
|
||||
f.setObject(ByteVector(3, '\x01'));
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
ByteVector("GEOB"
|
||||
"\x00\x00\x00\x32"
|
||||
"\x00\x00"
|
||||
"\x00"
|
||||
"application/octet-stream\x00"
|
||||
"test.bin\x00"
|
||||
"Description\x00"
|
||||
"\x01\x01\x01", 60),
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testParsePOPM()
|
||||
{
|
||||
ID3v2::PopularimeterFrame f(ByteVector("POPM"
|
||||
@@ -390,10 +445,36 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(String("ident"), f.identification());
|
||||
CPPUNIT_ASSERT_EQUAL(15.0f / 512.0f,
|
||||
f.volumeAdjustment(ID3v2::RelativeVolumeFrame::FrontRight));
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<short>(15),
|
||||
f.volumeAdjustmentIndex(ID3v2::RelativeVolumeFrame::FrontRight));
|
||||
CPPUNIT_ASSERT_EQUAL((unsigned char)8,
|
||||
f.peakVolume(ID3v2::RelativeVolumeFrame::FrontRight).bitsRepresentingPeak);
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("\x45"),
|
||||
f.peakVolume(ID3v2::RelativeVolumeFrame::FrontRight).peakVolume);
|
||||
const List<ID3v2::RelativeVolumeFrame::ChannelType> channels = f.channels();
|
||||
CPPUNIT_ASSERT_EQUAL(1U, channels.size());
|
||||
CPPUNIT_ASSERT_EQUAL(ID3v2::RelativeVolumeFrame::FrontRight, channels[0]);
|
||||
}
|
||||
|
||||
void testRenderRelativeVolumeFrame()
|
||||
{
|
||||
ID3v2::RelativeVolumeFrame f;
|
||||
f.setIdentification("ident");
|
||||
f.setVolumeAdjustment(15.0f / 512.0f, ID3v2::RelativeVolumeFrame::FrontRight);
|
||||
ID3v2::RelativeVolumeFrame::PeakVolume peakVolume;
|
||||
peakVolume.bitsRepresentingPeak = 8;
|
||||
peakVolume.peakVolume.setData("\x45");
|
||||
f.setPeakVolume(peakVolume, ID3v2::RelativeVolumeFrame::FrontRight);
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
ByteVector("RVA2"
|
||||
"\x00\x00\x00\x0B"
|
||||
"\x00\x00"
|
||||
"ident\x00"
|
||||
"\x02"
|
||||
"\x00\x0F"
|
||||
"\x08"
|
||||
"\x45", 21),
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testParseUniqueFileIdentifierFrame()
|
||||
@@ -424,6 +505,18 @@ public:
|
||||
f.identifier());
|
||||
}
|
||||
|
||||
void testRenderUniqueFileIdentifierFrame()
|
||||
{
|
||||
ID3v2::UniqueFileIdentifierFrame f("owner", "\x01\x02\x03");
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
ByteVector("UFID"
|
||||
"\x00\x00\x00\x09"
|
||||
"\x00\x00"
|
||||
"owner\x00"
|
||||
"\x01\x02\x03", 19),
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testParseUrlLinkFrame()
|
||||
{
|
||||
ID3v2::UrlLinkFrame f(
|
||||
@@ -633,6 +726,89 @@ public:
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testParseCommentsFrame()
|
||||
{
|
||||
ID3v2::CommentsFrame f(
|
||||
ByteVector("COMM"
|
||||
"\x00\x00\x00\x14"
|
||||
"\x00\x00"
|
||||
"\x03"
|
||||
"deu"
|
||||
"Description\x00"
|
||||
"Text", 30));
|
||||
CPPUNIT_ASSERT_EQUAL(String::UTF8, f.textEncoding());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("deu"), f.language());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Description"), f.description());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Text"), f.text());
|
||||
}
|
||||
|
||||
void testRenderCommentsFrame()
|
||||
{
|
||||
ID3v2::CommentsFrame f;
|
||||
f.setTextEncoding(String::UTF16);
|
||||
f.setLanguage("eng");
|
||||
f.setDescription("Description");
|
||||
f.setText("Text");
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
ByteVector("COMM"
|
||||
"\x00\x00\x00\x28"
|
||||
"\x00\x00"
|
||||
"\x01"
|
||||
"eng"
|
||||
"\xff\xfe" "D\0e\0s\0c\0r\0i\0p\0t\0i\0o\0n\0" "\x00\x00"
|
||||
"\xff\xfe" "T\0e\0x\0t\0", 50),
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testParsePodcastFrame()
|
||||
{
|
||||
ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
|
||||
ByteVector data = ByteVector("PCST"
|
||||
"\x00\x00\x00\x04"
|
||||
"\x00\x00"
|
||||
"\x00\x00\x00\x00", 14);
|
||||
const ID3v2::Header header;
|
||||
CPPUNIT_ASSERT(dynamic_cast<ID3v2::PodcastFrame *>(
|
||||
factory->createFrame(data, &header)));
|
||||
}
|
||||
|
||||
void testRenderPodcastFrame()
|
||||
{
|
||||
ID3v2::PodcastFrame f;
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
ByteVector("PCST"
|
||||
"\x00\x00\x00\x04"
|
||||
"\x00\x00"
|
||||
"\x00\x00\x00\x00", 14),
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testParsePrivateFrame()
|
||||
{
|
||||
ID3v2::PrivateFrame f(
|
||||
ByteVector("PRIV"
|
||||
"\x00\x00\x00\x0e"
|
||||
"\x00\x00"
|
||||
"WM/Provider\x00"
|
||||
"TL", 24));
|
||||
CPPUNIT_ASSERT_EQUAL(String("WM/Provider"), f.owner());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("TL"), f.data());
|
||||
}
|
||||
|
||||
void testRenderPrivateFrame()
|
||||
{
|
||||
ID3v2::PrivateFrame f;
|
||||
f.setOwner("WM/Provider");
|
||||
f.setData("TL");
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
ByteVector("PRIV"
|
||||
"\x00\x00\x00\x0e"
|
||||
"\x00\x00"
|
||||
"WM/Provider\x00"
|
||||
"TL", 24),
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testItunes24FrameSize()
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("005411.id3"), false);
|
||||
@@ -686,7 +862,7 @@ public:
|
||||
// "Refinement" is different from the ID3v1 genre
|
||||
ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
|
||||
ByteVector data = ByteVector("TCON" // Frame ID
|
||||
"\x00\x00\x00\x13" // Frame size
|
||||
"\x00\x00\x00\x0d" // Frame size
|
||||
"\x00\x00" // Frame flags
|
||||
"\x00" // Encoding
|
||||
"(4)Eurodisco", 23); // Text
|
||||
@@ -703,6 +879,29 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(String("Disco Eurodisco"), tag.genre());
|
||||
}
|
||||
|
||||
void testUpdateGenre23_3()
|
||||
{
|
||||
// Multiple references and a refinement
|
||||
ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
|
||||
ByteVector data = ByteVector("TCON" // Frame ID
|
||||
"\x00\x00\x00\x15" // Frame size
|
||||
"\x00\x00" // Frame flags
|
||||
"\x00" // Encoding
|
||||
"(9)(138)Viking Metal", 31); // Text
|
||||
ID3v2::Header header;
|
||||
header.setMajorVersion(3);
|
||||
ID3v2::TextIdentificationFrame *frame =
|
||||
dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(factory->createFrame(data, &header));
|
||||
CPPUNIT_ASSERT_EQUAL(3U, frame->fieldList().size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("9"), frame->fieldList()[0]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("138"), frame->fieldList()[1]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Viking Metal"), frame->fieldList()[2]);
|
||||
|
||||
ID3v2::Tag tag;
|
||||
tag.addFrame(frame);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Metal Black Metal Viking Metal"), tag.genre());
|
||||
}
|
||||
|
||||
void testUpdateGenre24()
|
||||
{
|
||||
ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
|
||||
@@ -757,6 +956,9 @@ public:
|
||||
tf = new ID3v2::TextIdentificationFrame("TIPL", String::Latin1);
|
||||
tf->setText(StringList().append("Producer").append("Artist 3").append("Mastering").append("Artist 4"));
|
||||
foo.ID3v2Tag()->addFrame(tf);
|
||||
tf = new ID3v2::TextIdentificationFrame("TCON", String::Latin1);
|
||||
tf->setText(StringList().append("51").append("Noise").append("Power Noise"));
|
||||
foo.ID3v2Tag()->addFrame(tf);
|
||||
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDRL", String::Latin1));
|
||||
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDTG", String::Latin1));
|
||||
foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TMOO", String::Latin1));
|
||||
@@ -788,6 +990,12 @@ public:
|
||||
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());
|
||||
CPPUNIT_ASSERT_EQUAL(String("51"), tf->fieldList()[0]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("39"), tf->fieldList()[1]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Power Noise"), tf->fieldList()[2]);
|
||||
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDRL"));
|
||||
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDTG"));
|
||||
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TMOO"));
|
||||
@@ -798,7 +1006,34 @@ public:
|
||||
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOP"));
|
||||
#endif
|
||||
CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSST"));
|
||||
}
|
||||
}
|
||||
{
|
||||
const ByteVector expectedId3v23Data(
|
||||
"ID3" "\x03\x00\x00\x00\x00\x09\x49"
|
||||
"TSOA" "\x00\x00\x00\x01\x00\x00\x00"
|
||||
"TSOT" "\x00\x00\x00\x01\x00\x00\x00"
|
||||
"TSOP" "\x00\x00\x00\x01\x00\x00\x00"
|
||||
"TORY" "\x00\x00\x00\x05\x00\x00\x00" "2011"
|
||||
"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"
|
||||
"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);
|
||||
const ByteVector actualId3v23Data =
|
||||
PlainFile(newname.c_str()).readBlock(expectedId3v23Data.size());
|
||||
CPPUNIT_ASSERT_EQUAL(expectedId3v23Data, actualId3v23Data);
|
||||
}
|
||||
|
||||
ScopedFileCopy rareFramesCopy("rare_frames", ".mp3");
|
||||
|
||||
{
|
||||
MPEG::File f(rareFramesCopy.fileName().c_str());
|
||||
f.save(MPEG::File::AllTags, File::StripOthers, ID3v2::v3);
|
||||
f.seek(f.find("TCON") + 11);
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("(13)"), f.readBlock(4));
|
||||
}
|
||||
}
|
||||
|
||||
void testCompressedFrameWithBrokenLength()
|
||||
@@ -1185,6 +1420,16 @@ public:
|
||||
CPPUNIT_ASSERT((unsigned int)0x01 == f.embeddedFrameList().size());
|
||||
CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1);
|
||||
CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "TC1");
|
||||
|
||||
f.removeChildElement("E"); // not existing
|
||||
CPPUNIT_ASSERT_EQUAL(2U, f.entryCount());
|
||||
f.removeChildElement("C");
|
||||
CPPUNIT_ASSERT_EQUAL(1U, f.entryCount());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("D"), f.childElements()[0]);
|
||||
|
||||
ID3v2::Frame *frame = f.embeddedFrameList("TIT2")[0];
|
||||
f.removeEmbeddedFrame(frame);
|
||||
CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").isEmpty());
|
||||
}
|
||||
|
||||
void testRenderTableOfContentsFrame()
|
||||
@@ -1309,6 +1554,57 @@ public:
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("toc_many_children.mp3"));
|
||||
CPPUNIT_ASSERT(f.isValid());
|
||||
|
||||
ID3v2::Tag *tag = f.ID3v2Tag();
|
||||
const ID3v2::FrameList &frames = tag->frameList();
|
||||
CPPUNIT_ASSERT_EQUAL(130U, frames.size());
|
||||
int i = 0;
|
||||
for(ID3v2::FrameList::ConstIterator it = frames.begin(); it != frames.end();
|
||||
++it, ++i) {
|
||||
if(i > 0) {
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("CHAP"), (*it)->frameID());
|
||||
const ID3v2::ChapterFrame *chapFrame =
|
||||
dynamic_cast<const ID3v2::ChapterFrame *>(*it);
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("chapter") +
|
||||
ByteVector(String::number(i - 1).toCString()),
|
||||
chapFrame->elementID());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(100 * i),
|
||||
chapFrame->startTime());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(100 * i),
|
||||
chapFrame->endTime());
|
||||
const ID3v2::FrameList &embeddedFrames = chapFrame->embeddedFrameList();
|
||||
CPPUNIT_ASSERT_EQUAL(1U, embeddedFrames.size());
|
||||
const ID3v2::TextIdentificationFrame *tit2Frame =
|
||||
dynamic_cast<const ID3v2::TextIdentificationFrame *>(
|
||||
embeddedFrames.front());
|
||||
CPPUNIT_ASSERT(tit2Frame);
|
||||
CPPUNIT_ASSERT_EQUAL(String("Marker ") + String::number(i),
|
||||
tit2Frame->fieldList().front());
|
||||
}
|
||||
else {
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("CTOC"), (*it)->frameID());
|
||||
const ID3v2::TableOfContentsFrame *ctocFrame =
|
||||
dynamic_cast<const ID3v2::TableOfContentsFrame *>(*it);
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("toc"), ctocFrame->elementID());
|
||||
CPPUNIT_ASSERT(!ctocFrame->isTopLevel());
|
||||
CPPUNIT_ASSERT(!ctocFrame->isOrdered());
|
||||
CPPUNIT_ASSERT_EQUAL(129U, ctocFrame->entryCount());
|
||||
const ID3v2::FrameList &embeddedFrames = ctocFrame->embeddedFrameList();
|
||||
CPPUNIT_ASSERT_EQUAL(1U, embeddedFrames.size());
|
||||
const ID3v2::TextIdentificationFrame *tit2Frame =
|
||||
dynamic_cast<const ID3v2::TextIdentificationFrame *>(
|
||||
embeddedFrames.front());
|
||||
CPPUNIT_ASSERT(tit2Frame);
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("toplevel toc"), tit2Frame->fieldList());
|
||||
}
|
||||
}
|
||||
|
||||
CPPUNIT_ASSERT(!ID3v2::ChapterFrame::findByElementID(tag, "chap2"));
|
||||
CPPUNIT_ASSERT(ID3v2::ChapterFrame::findByElementID(tag, "chapter2"));
|
||||
|
||||
CPPUNIT_ASSERT(!ID3v2::TableOfContentsFrame::findTopLevel(tag));
|
||||
CPPUNIT_ASSERT(!ID3v2::TableOfContentsFrame::findByElementID(tag, "ctoc"));
|
||||
CPPUNIT_ASSERT(ID3v2::TableOfContentsFrame::findByElementID(tag, "toc"));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -95,6 +95,9 @@ public:
|
||||
CPPUNIT_ASSERT(unsupported.contains("ARTIST"));
|
||||
CPPUNIT_ASSERT_EQUAL(properties["ARTIST"], unsupported["ARTIST"]);
|
||||
CPPUNIT_ASSERT(!unsupported.contains("TITLE"));
|
||||
|
||||
properties = t.properties();
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("title"), properties["TITLE"]);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -28,10 +28,12 @@
|
||||
#include <tag.h>
|
||||
#include <mp4tag.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <tbytevectorstream.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <mp4atom.h>
|
||||
#include <mp4file.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "plainfile.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -41,7 +43,9 @@ class TestMP4 : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TestMP4);
|
||||
CPPUNIT_TEST(testPropertiesAAC);
|
||||
CPPUNIT_TEST(testPropertiesAACWithoutBitrate);
|
||||
CPPUNIT_TEST(testPropertiesALAC);
|
||||
CPPUNIT_TEST(testPropertiesALACWithoutBitrate);
|
||||
CPPUNIT_TEST(testPropertiesM4V);
|
||||
CPPUNIT_TEST(testFreeForm);
|
||||
CPPUNIT_TEST(testCheckValid);
|
||||
@@ -55,10 +59,12 @@ class TestMP4 : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testCovrWrite);
|
||||
CPPUNIT_TEST(testCovrRead2);
|
||||
CPPUNIT_TEST(testProperties);
|
||||
CPPUNIT_TEST(testPropertiesAllSupported);
|
||||
CPPUNIT_TEST(testPropertiesMovement);
|
||||
CPPUNIT_TEST(testFuzzedFile);
|
||||
CPPUNIT_TEST(testRepeatedSave);
|
||||
CPPUNIT_TEST(testWithZeroLengthAtom);
|
||||
CPPUNIT_TEST(testEmptyValuesRemoveItems);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -77,6 +83,28 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec());
|
||||
}
|
||||
|
||||
void testPropertiesAACWithoutBitrate()
|
||||
{
|
||||
ByteVector aacData = PlainFile(TEST_FILE_PATH_C("has-tags.m4a")).readAll();
|
||||
CPPUNIT_ASSERT_GREATER(1960U, aacData.size());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("mp4a"), aacData.mid(1890, 4));
|
||||
// Set the bitrate to zero
|
||||
for (int offset = 1956; offset < 1960; ++offset) {
|
||||
aacData[offset] = 0;
|
||||
}
|
||||
ByteVectorStream aacStream(aacData);
|
||||
MP4::File f(&aacStream);
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(3708, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample());
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted());
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec());
|
||||
}
|
||||
|
||||
void testPropertiesALAC()
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("empty_alac.m4a"));
|
||||
@@ -91,6 +119,28 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec());
|
||||
}
|
||||
|
||||
void testPropertiesALACWithoutBitrate()
|
||||
{
|
||||
ByteVector alacData = PlainFile(TEST_FILE_PATH_C("empty_alac.m4a")).readAll();
|
||||
CPPUNIT_ASSERT_GREATER(474U, alacData.size());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("alac"), alacData.mid(446, 4));
|
||||
// Set the bitrate to zero
|
||||
for (int offset = 470; offset < 474; ++offset) {
|
||||
alacData[offset] = 0;
|
||||
}
|
||||
ByteVectorStream alacStream(alacData);
|
||||
MP4::File f(&alacStream);
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample());
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted());
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec());
|
||||
}
|
||||
|
||||
void testPropertiesM4V()
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("blank_video.m4v"));
|
||||
@@ -377,6 +427,97 @@ public:
|
||||
f.setProperties(tags);
|
||||
}
|
||||
|
||||
void testPropertiesAllSupported()
|
||||
{
|
||||
PropertyMap tags;
|
||||
tags["ALBUM"] = StringList("Album");
|
||||
tags["ALBUMARTIST"] = StringList("Album Artist");
|
||||
tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort");
|
||||
tags["ALBUMSORT"] = StringList("Album Sort");
|
||||
tags["ARTIST"] = StringList("Artist");
|
||||
tags["ARTISTS"] = StringList("Artists");
|
||||
tags["ARTISTSORT"] = StringList("Artist Sort");
|
||||
tags["ASIN"] = StringList("ASIN");
|
||||
tags["BARCODE"] = StringList("Barcode");
|
||||
tags["BPM"] = StringList("123");
|
||||
tags["CATALOGNUMBER"] = StringList("Catalog Number");
|
||||
tags["COMMENT"] = StringList("Comment");
|
||||
tags["COMPILATION"] = StringList("1");
|
||||
tags["COMPOSER"] = StringList("Composer");
|
||||
tags["COMPOSERSORT"] = StringList("Composer Sort");
|
||||
tags["CONDUCTOR"] = StringList("Conductor");
|
||||
tags["COPYRIGHT"] = StringList("2021 Copyright");
|
||||
tags["DATE"] = StringList("2021-01-03 12:29:23");
|
||||
tags["DISCNUMBER"] = StringList("3/5");
|
||||
tags["DISCSUBTITLE"] = StringList("Disc Subtitle");
|
||||
tags["DJMIXER"] = StringList("DJ Mixer");
|
||||
tags["ENCODEDBY"] = StringList("Encoded by");
|
||||
tags["ENGINEER"] = StringList("Engineer");
|
||||
tags["GAPLESSPLAYBACK"] = StringList("1");
|
||||
tags["GENRE"] = StringList("Genre");
|
||||
tags["GROUPING"] = StringList("Grouping");
|
||||
tags["ISRC"] = StringList("UKAAA0500001");
|
||||
tags["LABEL"] = StringList("Label");
|
||||
tags["LANGUAGE"] = StringList("eng");
|
||||
tags["LICENSE"] = StringList("License");
|
||||
tags["LYRICIST"] = StringList("Lyricist");
|
||||
tags["LYRICS"] = StringList("Lyrics");
|
||||
tags["MEDIA"] = StringList("Media");
|
||||
tags["MIXER"] = StringList("Mixer");
|
||||
tags["MOOD"] = StringList("Mood");
|
||||
tags["MOVEMENTCOUNT"] = StringList("3");
|
||||
tags["MOVEMENTNAME"] = StringList("Movement Name");
|
||||
tags["MOVEMENTNUMBER"] = StringList("2");
|
||||
tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID");
|
||||
tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID");
|
||||
tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID");
|
||||
tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID");
|
||||
tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID");
|
||||
tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID");
|
||||
tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID");
|
||||
tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19");
|
||||
tags["PODCAST"] = StringList("1");
|
||||
tags["PODCASTCATEGORY"] = StringList("Podcast Category");
|
||||
tags["PODCASTDESC"] = StringList("Podcast Description");
|
||||
tags["PODCASTID"] = StringList("Podcast ID");
|
||||
tags["PODCASTURL"] = StringList("Podcast URL");
|
||||
tags["PRODUCER"] = StringList("Producer");
|
||||
tags["RELEASECOUNTRY"] = StringList("Release Country");
|
||||
tags["RELEASESTATUS"] = StringList("Release Status");
|
||||
tags["RELEASETYPE"] = StringList("Release Type");
|
||||
tags["REMIXER"] = StringList("Remixer");
|
||||
tags["SCRIPT"] = StringList("Script");
|
||||
tags["SHOWSORT"] = StringList("Show Sort");
|
||||
tags["SHOWWORKMOVEMENT"] = StringList("1");
|
||||
tags["SUBTITLE"] = StringList("Subtitle");
|
||||
tags["TITLE"] = StringList("Title");
|
||||
tags["TITLESORT"] = StringList("Title Sort");
|
||||
tags["TRACKNUMBER"] = StringList("2/4");
|
||||
tags["TVEPISODE"] = StringList("3");
|
||||
tags["TVEPISODEID"] = StringList("TV Episode ID");
|
||||
tags["TVNETWORK"] = StringList("TV Network");
|
||||
tags["TVSEASON"] = StringList("2");
|
||||
tags["TVSHOW"] = StringList("TV Show");
|
||||
tags["WORK"] = StringList("Work");
|
||||
|
||||
ScopedFileCopy copy("no-tags", ".m4a");
|
||||
{
|
||||
MP4::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
CPPUNIT_ASSERT(properties.isEmpty());
|
||||
f.setProperties(tags);
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
const MP4::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
if (tags != properties) {
|
||||
CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString());
|
||||
}
|
||||
CPPUNIT_ASSERT(tags == properties);
|
||||
}
|
||||
}
|
||||
|
||||
void testPropertiesMovement()
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("has-tags.m4a"));
|
||||
@@ -455,6 +596,64 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(22050, f.audioProperties()->sampleRate());
|
||||
}
|
||||
|
||||
void testEmptyValuesRemoveItems()
|
||||
{
|
||||
const MP4::File f(TEST_FILE_PATH_C("has-tags.m4a"));
|
||||
MP4::Tag *tag = f.tag();
|
||||
const String testTitle("Title");
|
||||
const String testArtist("Artist");
|
||||
const String testAlbum("Album");
|
||||
const String testComment("Comment");
|
||||
const String testGenre("Genre");
|
||||
const String nullString;
|
||||
const unsigned int testYear = 2020;
|
||||
const unsigned int testTrack = 1;
|
||||
const unsigned int zeroUInt = 0;
|
||||
|
||||
tag->setTitle(testTitle);
|
||||
CPPUNIT_ASSERT_EQUAL(testTitle, tag->title());
|
||||
CPPUNIT_ASSERT(tag->contains("\251nam"));
|
||||
tag->setArtist(testArtist);
|
||||
CPPUNIT_ASSERT_EQUAL(testArtist, tag->artist());
|
||||
CPPUNIT_ASSERT(tag->contains("\251ART"));
|
||||
tag->setAlbum(testAlbum);
|
||||
CPPUNIT_ASSERT_EQUAL(testAlbum, tag->album());
|
||||
CPPUNIT_ASSERT(tag->contains("\251alb"));
|
||||
tag->setComment(testComment);
|
||||
CPPUNIT_ASSERT_EQUAL(testComment, tag->comment());
|
||||
CPPUNIT_ASSERT(tag->contains("\251cmt"));
|
||||
tag->setGenre(testGenre);
|
||||
CPPUNIT_ASSERT_EQUAL(testGenre, tag->genre());
|
||||
CPPUNIT_ASSERT(tag->contains("\251gen"));
|
||||
tag->setYear(testYear);
|
||||
CPPUNIT_ASSERT_EQUAL(testYear, tag->year());
|
||||
CPPUNIT_ASSERT(tag->contains("\251day"));
|
||||
tag->setTrack(testTrack);
|
||||
CPPUNIT_ASSERT_EQUAL(testTrack, tag->track());
|
||||
CPPUNIT_ASSERT(tag->contains("trkn"));
|
||||
|
||||
tag->setTitle(nullString);
|
||||
CPPUNIT_ASSERT_EQUAL(nullString, tag->title());
|
||||
CPPUNIT_ASSERT(!tag->contains("\251nam"));
|
||||
tag->setArtist(nullString);
|
||||
CPPUNIT_ASSERT_EQUAL(nullString, tag->artist());
|
||||
CPPUNIT_ASSERT(!tag->contains("\251ART"));
|
||||
tag->setAlbum(nullString);
|
||||
CPPUNIT_ASSERT_EQUAL(nullString, tag->album());
|
||||
CPPUNIT_ASSERT(!tag->contains("\251alb"));
|
||||
tag->setComment(nullString);
|
||||
CPPUNIT_ASSERT_EQUAL(nullString, tag->comment());
|
||||
CPPUNIT_ASSERT(!tag->contains("\251cmt"));
|
||||
tag->setGenre(nullString);
|
||||
CPPUNIT_ASSERT_EQUAL(nullString, tag->genre());
|
||||
CPPUNIT_ASSERT(!tag->contains("\251gen"));
|
||||
tag->setYear(zeroUInt);
|
||||
CPPUNIT_ASSERT_EQUAL(zeroUInt, tag->year());
|
||||
CPPUNIT_ASSERT(!tag->contains("\251day"));
|
||||
tag->setTrack(zeroUInt);
|
||||
CPPUNIT_ASSERT_EQUAL(zeroUInt, tag->track());
|
||||
CPPUNIT_ASSERT(!tag->contains("trkn"));
|
||||
}
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4);
|
||||
|
||||
@@ -151,6 +151,14 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front());
|
||||
f.strip(MPC::File::ID3v1);
|
||||
CPPUNIT_ASSERT(f.properties().isEmpty());
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
MPC::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(!f.hasAPETag());
|
||||
CPPUNIT_ASSERT(!f.hasID3v1Tag());
|
||||
CPPUNIT_ASSERT(f.properties()["TITLE"].isEmpty());
|
||||
CPPUNIT_ASSERT(f.properties().isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ class TestMPEG : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testFuzzedFile);
|
||||
CPPUNIT_TEST(testFrameOffset);
|
||||
CPPUNIT_TEST(testStripAndProperties);
|
||||
CPPUNIT_TEST(testProperties);
|
||||
CPPUNIT_TEST(testRepeatedSave1);
|
||||
CPPUNIT_TEST(testRepeatedSave2);
|
||||
CPPUNIT_TEST(testRepeatedSave3);
|
||||
@@ -295,6 +296,109 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void testProperties()
|
||||
{
|
||||
PropertyMap tags;
|
||||
tags["ALBUM"] = StringList("Album");
|
||||
tags["ALBUMARTIST"] = StringList("Album Artist");
|
||||
tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort");
|
||||
tags["ALBUMSORT"] = StringList("Album Sort");
|
||||
tags["ARRANGER"] = StringList("Arranger");
|
||||
tags["ARTIST"] = StringList("Artist");
|
||||
tags["ARTISTSORT"] = StringList("Artist Sort");
|
||||
tags["ARTISTWEBPAGE"] = StringList("Artist Web Page");
|
||||
tags["ASIN"] = StringList("ASIN");
|
||||
tags["AUDIOSOURCEWEBPAGE"] = StringList("Audio Source Web Page");
|
||||
tags["BARCODE"] = StringList("Barcode");
|
||||
tags["BPM"] = StringList("123");
|
||||
tags["CATALOGNUMBER"] = StringList("Catalog Number");
|
||||
tags["COMMENT"] = StringList("Comment");
|
||||
tags["COMMENT:CDESC"] = StringList("Comment with Description");
|
||||
tags["COMPOSER"] = StringList("Composer");
|
||||
tags["COMPOSERSORT"] = StringList("Composer Sort");
|
||||
tags["CONDUCTOR"] = StringList("Conductor");
|
||||
tags["CONTENTGROUP"] = StringList("Content Group");
|
||||
tags["COPYRIGHT"] = StringList("2021 Copyright");
|
||||
tags["COPYRIGHTURL"] = StringList("Copyright URL");
|
||||
tags["DATE"] = StringList("2021-01-03 12:29:23");
|
||||
tags["DISCNUMBER"] = StringList("3/5");
|
||||
tags["DJMIXER"] = StringList("DJ Mixer");
|
||||
tags["ENCODEDBY"] = StringList("Encoded by");
|
||||
tags["ENCODING"] = StringList("Encoding");
|
||||
tags["ENCODINGTIME"] = StringList("2021-01-03 13:48:44");
|
||||
tags["ENGINEER"] = StringList("Engineer");
|
||||
tags["FILETYPE"] = StringList("File Type");
|
||||
tags["FILEWEBPAGE"] = StringList("File Web Page");
|
||||
tags["GENRE"] = StringList("Genre");
|
||||
tags["GROUPING"] = StringList("Grouping");
|
||||
tags["INITIALKEY"] = StringList("Dbm");
|
||||
tags["ISRC"] = StringList("UKAAA0500001");
|
||||
tags["LABEL"] = StringList("Label");
|
||||
tags["LANGUAGE"] = StringList("eng");
|
||||
tags["LENGTH"] = StringList("1234");
|
||||
tags["LYRICIST"] = StringList("Lyricist");
|
||||
tags["LYRICS:LDESC"] = StringList("Lyrics");
|
||||
tags["MEDIA"] = StringList("Media");
|
||||
tags["MIXER"] = StringList("Mixer");
|
||||
tags["MOOD"] = StringList("Mood");
|
||||
tags["MOVEMENTNAME"] = StringList("Movement Name");
|
||||
tags["MOVEMENTNUMBER"] = StringList("2");
|
||||
tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID");
|
||||
tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID");
|
||||
tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID");
|
||||
tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID");
|
||||
tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID");
|
||||
tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID");
|
||||
tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID");
|
||||
tags["ORIGINALALBUM"] = StringList("Original Album");
|
||||
tags["ORIGINALARTIST"] = StringList("Original Artist");
|
||||
tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19");
|
||||
tags["ORIGINALFILENAME"] = StringList("Original Filename");
|
||||
tags["ORIGINALLYRICIST"] = StringList("Original Lyricist");
|
||||
tags["OWNER"] = StringList("Owner");
|
||||
tags["PAYMENTWEBPAGE"] = StringList("Payment Web Page");
|
||||
tags["PERFORMER:DRUMS"] = StringList("Drummer");
|
||||
tags["PERFORMER:GUITAR"] = StringList("Guitarist");
|
||||
tags["PLAYLISTDELAY"] = StringList("10");
|
||||
tags["PODCAST"] = StringList();
|
||||
tags["PODCASTCATEGORY"] = StringList("Podcast Category");
|
||||
tags["PODCASTDESC"] = StringList("Podcast Description");
|
||||
tags["PODCASTID"] = StringList("Podcast ID");
|
||||
tags["PODCASTURL"] = StringList("Podcast URL");
|
||||
tags["PRODUCEDNOTICE"] = StringList("2021 Produced Notice");
|
||||
tags["PRODUCER"] = StringList("Producer");
|
||||
tags["PUBLISHERWEBPAGE"] = StringList("Publisher Web Page");
|
||||
tags["RADIOSTATION"] = StringList("Radio Station");
|
||||
tags["RADIOSTATIONOWNER"] = StringList("Radio Station Owner");
|
||||
tags["RELEASECOUNTRY"] = StringList("Release Country");
|
||||
tags["RELEASESTATUS"] = StringList("Release Status");
|
||||
tags["RELEASETYPE"] = StringList("Release Type");
|
||||
tags["REMIXER"] = StringList("Remixer");
|
||||
tags["SCRIPT"] = StringList("Script");
|
||||
tags["SUBTITLE"] = StringList("Subtitle");
|
||||
tags["TITLE"] = StringList("Title");
|
||||
tags["TITLESORT"] = StringList("Title Sort");
|
||||
tags["TRACKNUMBER"] = StringList("2/4");
|
||||
tags["URL:UDESC"] = StringList("URL");
|
||||
|
||||
ScopedFileCopy copy("xing", ".mp3");
|
||||
{
|
||||
MPEG::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
CPPUNIT_ASSERT(properties.isEmpty());
|
||||
f.setProperties(tags);
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
const MPEG::File f(copy.fileName().c_str());
|
||||
PropertyMap properties = f.properties();
|
||||
if (tags != properties) {
|
||||
CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString());
|
||||
}
|
||||
CPPUNIT_ASSERT(tags == properties);
|
||||
}
|
||||
}
|
||||
|
||||
void testRepeatedSave1()
|
||||
{
|
||||
ScopedFileCopy copy("xing", ".mp3");
|
||||
|
||||
@@ -89,6 +89,19 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(String("Test Artist 2"), tag.artist());
|
||||
CPPUNIT_ASSERT_EQUAL(5U, tag.track());
|
||||
|
||||
PropertyMap props = tag.properties();
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("Test Artist 2"), props.find("ARTIST")->second);
|
||||
CPPUNIT_ASSERT(props.find("COMMENT") == props.end());
|
||||
props.replace("ARTIST", StringList("Test Artist 3"));
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("Test Artist 3"), props["ARTIST"]);
|
||||
|
||||
PropertyMap eraseMap;
|
||||
eraseMap.insert("ARTIST", StringList());
|
||||
eraseMap.insert("ALBUM", StringList());
|
||||
eraseMap.insert("TITLE", StringList());
|
||||
props.erase(eraseMap);
|
||||
CPPUNIT_ASSERT_EQUAL(String("DATE=2015\nTRACKNUMBER=5\n"), props.toString());
|
||||
|
||||
tag.setProperties(PropertyMap());
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(String(""), tag.title());
|
||||
|
||||
@@ -86,11 +86,21 @@ public:
|
||||
}
|
||||
{
|
||||
TrueAudio::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(f.hasID3v1Tag());
|
||||
CPPUNIT_ASSERT(f.hasID3v2Tag());
|
||||
CPPUNIT_ASSERT_EQUAL(String("ID3v2"), f.properties()["TITLE"].front());
|
||||
f.strip(TrueAudio::File::ID3v2);
|
||||
CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front());
|
||||
f.strip(TrueAudio::File::ID3v1);
|
||||
CPPUNIT_ASSERT(f.properties().isEmpty());
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
TrueAudio::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(!f.hasID3v1Tag());
|
||||
CPPUNIT_ASSERT(!f.hasID3v2Tag());
|
||||
CPPUNIT_ASSERT(f.properties()["TITLE"].isEmpty());
|
||||
CPPUNIT_ASSERT(f.properties().isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,12 @@
|
||||
#include <id3v2tag.h>
|
||||
#include <infotag.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <tbytevectorstream.h>
|
||||
#include <tfilestream.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <wavfile.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "plainfile.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -42,6 +45,7 @@ class TestWAV : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testPCMProperties);
|
||||
CPPUNIT_TEST(testALAWProperties);
|
||||
CPPUNIT_TEST(testFloatProperties);
|
||||
CPPUNIT_TEST(testFloatWithoutFactChunkProperties);
|
||||
CPPUNIT_TEST(testZeroSizeDataChunk);
|
||||
CPPUNIT_TEST(testID3v2Tag);
|
||||
CPPUNIT_TEST(testSaveID3v23);
|
||||
@@ -50,8 +54,10 @@ class TestWAV : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testDuplicateTags);
|
||||
CPPUNIT_TEST(testFuzzedFile1);
|
||||
CPPUNIT_TEST(testFuzzedFile2);
|
||||
CPPUNIT_TEST(testFileWithGarbageAppended);
|
||||
CPPUNIT_TEST(testStripAndProperties);
|
||||
CPPUNIT_TEST(testPCMWithFactChunk);
|
||||
CPPUNIT_TEST(testWaveFormatExtensible);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -98,10 +104,29 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->format());
|
||||
}
|
||||
|
||||
void testFloatWithoutFactChunkProperties()
|
||||
{
|
||||
ByteVector wavData = PlainFile(TEST_FILE_PATH_C("float64.wav")).readAll();
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("fact"), wavData.mid(36, 4));
|
||||
// Remove the fact chunk by renaming it to fakt
|
||||
wavData[38] = 'k';
|
||||
ByteVectorStream wavStream(wavData);
|
||||
RIFF::WAV::File f(&wavStream);
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(97, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(5645, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitsPerSample());
|
||||
CPPUNIT_ASSERT_EQUAL(4281U, f.audioProperties()->sampleFrames());
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->format());
|
||||
}
|
||||
|
||||
void testZeroSizeDataChunk()
|
||||
{
|
||||
RIFF::WAV::File f(TEST_FILE_PATH_C("zero-size-chunk.wav"));
|
||||
CPPUNIT_ASSERT(!f.isValid());
|
||||
CPPUNIT_ASSERT(f.isValid());
|
||||
}
|
||||
|
||||
void testID3v2Tag()
|
||||
@@ -262,7 +287,17 @@ public:
|
||||
void testFuzzedFile1()
|
||||
{
|
||||
RIFF::WAV::File f1(TEST_FILE_PATH_C("infloop.wav"));
|
||||
CPPUNIT_ASSERT(!f1.isValid());
|
||||
CPPUNIT_ASSERT(f1.isValid());
|
||||
// The file has problems:
|
||||
// Chunk 'ISTt' has invalid size (larger than the file size).
|
||||
// Its properties can nevertheless be read.
|
||||
RIFF::WAV::Properties* properties = f1.audioProperties();
|
||||
CPPUNIT_ASSERT_EQUAL(1, properties->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(88, properties->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(8, properties->bitsPerSample());
|
||||
CPPUNIT_ASSERT_EQUAL(11025, properties->sampleRate());
|
||||
CPPUNIT_ASSERT(!f1.hasInfoTag());
|
||||
CPPUNIT_ASSERT(!f1.hasID3v2Tag());
|
||||
}
|
||||
|
||||
void testFuzzedFile2()
|
||||
@@ -271,6 +306,36 @@ public:
|
||||
CPPUNIT_ASSERT(f2.isValid());
|
||||
}
|
||||
|
||||
void testFileWithGarbageAppended()
|
||||
{
|
||||
ScopedFileCopy copy("empty", ".wav");
|
||||
ByteVector contentsBeforeModification;
|
||||
{
|
||||
FileStream stream(copy.fileName().c_str());
|
||||
stream.seek(0, IOStream::End);
|
||||
const char garbage[] = "12345678";
|
||||
stream.writeBlock(ByteVector(garbage, sizeof(garbage) - 1));
|
||||
stream.seek(0);
|
||||
contentsBeforeModification = stream.readBlock(stream.length());
|
||||
}
|
||||
{
|
||||
RIFF::WAV::File f(copy.fileName().c_str());
|
||||
CPPUNIT_ASSERT(f.isValid());
|
||||
f.ID3v2Tag()->setTitle("ID3v2 Title");
|
||||
f.InfoTag()->setTitle("INFO Title");
|
||||
CPPUNIT_ASSERT(f.save());
|
||||
}
|
||||
{
|
||||
RIFF::WAV::File f(copy.fileName().c_str());
|
||||
f.strip();
|
||||
}
|
||||
{
|
||||
FileStream stream(copy.fileName().c_str());
|
||||
ByteVector contentsAfterModification = stream.readBlock(stream.length());
|
||||
CPPUNIT_ASSERT_EQUAL(contentsBeforeModification, contentsAfterModification);
|
||||
}
|
||||
}
|
||||
|
||||
void testStripAndProperties()
|
||||
{
|
||||
ScopedFileCopy copy("empty", ".wav");
|
||||
@@ -305,6 +370,20 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->format());
|
||||
}
|
||||
|
||||
void testWaveFormatExtensible()
|
||||
{
|
||||
RIFF::WAV::File f(TEST_FILE_PATH_C("uint8we.wav"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(2937, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(128, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(8000, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->bitsPerSample());
|
||||
CPPUNIT_ASSERT_EQUAL(23493U, f.audioProperties()->sampleFrames());
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->format());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestWAV);
|
||||
|
||||
@@ -41,6 +41,8 @@ class TestWavPack : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST_SUITE(TestWavPack);
|
||||
CPPUNIT_TEST(testNoLengthProperties);
|
||||
CPPUNIT_TEST(testMultiChannelProperties);
|
||||
CPPUNIT_TEST(testDsdStereoProperties);
|
||||
CPPUNIT_TEST(testNonStandardRateProperties);
|
||||
CPPUNIT_TEST(testTaggedProperties);
|
||||
CPPUNIT_TEST(testFuzzedFile);
|
||||
CPPUNIT_TEST(testStripAndProperties);
|
||||
@@ -79,6 +81,36 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version());
|
||||
}
|
||||
|
||||
void testDsdStereoProperties()
|
||||
{
|
||||
WavPack::File f(TEST_FILE_PATH_C("dsd_stereo.wv"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(200, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(2096, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->bitsPerSample());
|
||||
CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isLossless());
|
||||
CPPUNIT_ASSERT_EQUAL(352800, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(70560U, f.audioProperties()->sampleFrames());
|
||||
CPPUNIT_ASSERT_EQUAL(1040, f.audioProperties()->version());
|
||||
}
|
||||
|
||||
void testNonStandardRateProperties()
|
||||
{
|
||||
WavPack::File f(TEST_FILE_PATH_C("non_standard_rate.wv"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(3675, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample());
|
||||
CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isLossless());
|
||||
CPPUNIT_ASSERT_EQUAL(1000, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(3675U, f.audioProperties()->sampleFrames());
|
||||
CPPUNIT_ASSERT_EQUAL(1040, f.audioProperties()->version());
|
||||
}
|
||||
|
||||
void testTaggedProperties()
|
||||
{
|
||||
WavPack::File f(TEST_FILE_PATH_C("tagged.wv"));
|
||||
|
||||
Reference in New Issue
Block a user