70 Commits

Author SHA1 Message Date
Urs Fleisch
4c14571647 Update version to 1.12.0 2021-01-23 09:15:32 +01:00
Urs Fleisch
f4b476a620 Add more unit tests 2021-01-18 20:31:52 +01:00
Urs Fleisch
f8d78a61f7 Merge pull request #990 from ufleisch/ufleisch/more-tests
More unit tests and bug fixes
2021-01-12 18:07:23 +01:00
Urs Fleisch
cf6c68bafc Support a consistent set of MusicBrainz properties where possible
The support for MusicBrainz properties is enhanced with "ARTISTS", "ASIN",
"RELEASECOUNTRY", "RELEASESTATUS", "RELEASETYPE", "MUSICBRAINZ_RELEASETRACKID",
"ORIGINALDATE" on APE, ASF, MP4, ID3v2, and Xiph tags.
2021-01-10 15:19:10 +01:00
Urs Fleisch
f7c24930cd Add missing iTunes properties for MP4 tags 2021-01-10 15:18:59 +01:00
Urs Fleisch
310c3bc043 Add missing 'COMPOSERSORT' property for ID3v2 tags 2021-01-10 15:18:43 +01:00
Urs Fleisch
e6c03c6de8 Create an APE tag when reading a WavPack file without tags
Now it is handled in the same way as for other tags with a
TagUnion. Without this patch, WavPack files without tags cannot
be edited via a FileRef.
2021-01-03 19:06:59 +01:00
Urs Fleisch
2c29fbeabb Use mapped roles instead of property keys for TIPL roles
For an ID3v2 "DJMIXER" property, the "DJ-MIX" TIPL role must be used.
For an ID3v2 "MIXER" property, the "MIX" TIPL role must be used.
Otherwise it will not work when reading the tag and creating
properties from the wrong TIPL roles.
2021-01-03 19:06:59 +01:00
Urs Fleisch
4828a3b925 Do not crash when removing non existing TableOfContentsFrame child 2021-01-03 19:06:59 +01:00
Urs Fleisch
794a2a0b2b Correctly construct PrivateFrame from frame data
Frame::setData() has to be used instead of PrivateFrame::setData().
2021-01-03 19:06:59 +01:00
Urs Fleisch
f32d27973f Use PCST and not TXXX:PODCAST frame for ID3v2 'PODCAST' property 2021-01-03 19:06:59 +01:00
Urs Fleisch
f74b166435 Add missing extensions to FileRef::defaultFileExtensions() 2021-01-03 19:06:59 +01:00
Urs Fleisch
c28dc78060 Add more unit tests
This not only increased the test coverage but also revealed some
bugs which are fixed in the following commits.
2021-01-03 19:06:59 +01:00
Urs Fleisch
89fb62cfb1 Update NEWS 2021-01-02 10:50:52 +01:00
Urs Fleisch
59a2994f4e Fix spelling and typos 2021-01-02 10:50:52 +01:00
Urs Fleisch
f3bdd416da Remove some DSF and DSDIFF leftovers
These can be merged back into master from the branch
add_dsf_and_dsdiff_file_formats.
2021-01-02 10:50:52 +01:00
Urs Fleisch
39f86d02e7 Merge pull request #989 from ufleisch/ufleisch/id3v2-genres
Correctly read and write multiple ID3v2.3.0 genres (#988)
2021-01-01 11:48:35 +01:00
Urs Fleisch
1a2e1d08ac Merge pull request #986 from ufleisch/ufleisch/wav-extensible-subformat
WAV: Support subformat in WAVE_FORMAT_EXTENSIBLE (#850)
2021-01-01 11:48:06 +01:00
Urs Fleisch
3f3b48353c Merge pull request #983 from ufleisch/ufleisch/flac-comment-before-picture
FLAC: Store comment block before picture block (#954)
2021-01-01 11:45:14 +01:00
Urs Fleisch
54f5c66b65 Merge pull request #981 from ufleisch/ufleisch/alac-without-bitrate
Calculate bitrate for ALAC files without it (#961)
2021-01-01 11:45:12 +01:00
Urs Fleisch
3749dd7b75 Write ID3v2.3 genres with multiple references to ID3v1 genres (#988)
As described in id3v2.3.0.txt (4.2.1, TCON), multiple genres are
only possible as references to ID3v1 genres with an optional
refinement as a text. When downgrading multiple genres from
ID3v2.4.0, they are now converted to numbers when possible and
the first genre text without ID3v1 reference is added as a
refinement. The keywords RX and CR are supported too.
2020-12-30 17:38:04 +01:00
Urs Fleisch
d602ae483e Read ID3v2.3 genres with multiple references to ID3v1 genres (#988)
As described in id3v2.3.0.txt (4.2.1, TCON), now multiple genres
are possible, e.g. "(51)(39)". Additionally, support the keywords
RX and CR.
2020-12-30 17:37:54 +01:00
Urs Fleisch
5374cb1ac4 Merge pull request #980 from ufleisch/ufleisch/wav-float-without-fact
Calculate bitrate for IEEE Float WAV files without fact chunk (#959)
2020-12-28 09:16:44 +01:00
Urs Fleisch
8a461ccedc Merge pull request #978 from ufleisch/ufleisch/wavs-with-garbage
Accept WAV files with garbage appended (#973)
2020-12-28 09:07:39 +01:00
Urs Fleisch
17c2220588 Merge pull request #963 from dbry/wavpack-fixes
WavPack fixes with test file verifying issue #962
2020-12-28 09:07:37 +01:00
Urs Fleisch
271c004b30 Merge pull request #942 from uqs/master
Fix spelling of Bebop and update Fusion and Hardcore to match Wikipedia
2020-12-28 09:07:34 +01:00
Urs Fleisch
25d9bd1814 Merge pull request #935 from jiblime/static-config
taglib-config.cmake has static libdir and includedir variables
2020-12-28 09:07:32 +01:00
Urs Fleisch
b0bb7f8c0f Merge pull request #888 from chouquette/use_resolvers_on_streams
fileref: Use user defined resolvers on streams
2020-12-28 09:07:30 +01:00
Urs Fleisch
4d3ab73d2e Merge pull request #855 from shaforostoff/broken_mp3
fix reading audioproperties for broken mp3 files
2020-12-28 09:05:31 +01:00
Urs Fleisch
ae867cbd8c ID3v1: Improve compatibility by mapping renamed genre names to codes
Also added a test to check if the renamed genre names are used and
check if using the old names still works.
2020-12-27 19:53:11 +01:00
Urs Fleisch
563fbaf82a Handle the case when MP4 file header has zero bitrate
This incorporates [6ca536b5] (mp4 properties: handle the case when
mp4 file header has zero bitrate) from PR #899 with a more accurate
bitrate calculation and a unit test.
2020-12-27 10:17:34 +01:00
Urs Fleisch
e5ad041e42 A few more corrections to genre names 2020-12-24 10:08:47 +01:00
Hugo Beauzée-Luyssen
9ca7b0c33a fileref: Use user defined resolvers on streams
Since the resolve only use the filename, there is no reason not to probe
them, as a filename might be available through the IOStream interface.
When using a ByteVectorStream, the filename will be empty and user
defined resolvers won't be probed.
2020-12-23 12:34:54 +01:00
Urs Fleisch
a00b3499b4 WavPack: Add test with non-standard sample rate 2020-12-23 07:04:51 +01:00
Urs Fleisch
d84e86da9c Clean up code to better match the TagLib style 2020-12-23 07:04:51 +01:00
Urs Fleisch
741ed4e4bf Add test for IEEE Float WAV file without fact chunk 2020-12-22 16:02:01 +01:00
Urs Fleisch
e4813f4996 Calculate bitrate for IEEE Float WAV files without fact chunk (#959)
When a WAV file with float format without a fact chunk containing
a totalSamples value is encountered, the bitrate is not calculated.
However, since all samples have the same number of bits, the
number of samples can be calculated from the size of the data chunk
and number of channels and bits per sample, as it is done in the
PCM case.
2020-12-22 15:46:48 +01:00
Urs Fleisch
1332d44ff6 WAV: Test properties of file with WAVE_FORMAT_EXTENSIBLE
uint8we.wav: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Samples/AFsp/M1F1-uint8WE-AFsp.wav
2020-12-21 15:42:13 +01:00
Urs Fleisch
3d71ea1ad2 ALAC: Test properties of file without bitrate 2020-12-21 13:31:24 +01:00
Urs Fleisch
02c8995273 Calculate bitrate for ALAC files without it (#961)
If the "alac" atom of an MP4 file has a zero bitrate, it is calculated.
2020-12-21 12:22:25 +01:00
Urs Fleisch
7e92af6e8b FLAC: Test if picture is stored after comment 2020-12-21 11:46:10 +01:00
Urs Fleisch
7ec1127f3e FLAC: Store comment block before picture block (#954)
When the picture block is large and comes before the comment block,
Windows will no longer display the tags. This can be fixed by
storing the comment block before the picture block instead of
appending it at the end.
2020-12-21 11:09:15 +01:00
Urs Fleisch
30d839538d Make PlainFile available to all tests 2020-12-21 08:44:32 +01:00
Scott Wheeler
8f6b6ac055 Don't pull in C++11+ for one class 2020-12-20 18:38:05 +01:00
Scott Wheeler
a5f11697f7 Get things building again with C++98 2020-12-20 18:12:14 +01:00
Scott Wheeler
1721354627 Explicitly set C98 for this target
Sometime I'd like to get the lib over to C++1x, but for now, enforce
the currently in-use standard.
2020-12-20 14:22:20 +00:00
Scott Wheeler
ac7e5303a6 Merge pull request #969 from ujjwalsh/master
Adding support for Linux on power
2020-12-20 15:17:40 +01:00
Scott Wheeler
8ba246cdbe Merge pull request #984 from ufleisch/ufleisch/m4a-empty-strings-remove-items
MP4: Remove item when empty string is set (#929)
2020-12-20 15:13:58 +01:00
Scott Wheeler
6656528f18 Also allow #include <taglib/foo.h> with taglib.pc and friends
Closes #495
2020-12-20 14:09:49 +00:00
Urs Fleisch
bad2cea122 WAV: Support subformat in WAVE_FORMAT_EXTENSIBLE (#850) 2020-12-20 11:47:04 +01:00
Urs Fleisch
61d5bcfd5b MP4: Remove item when empty string is set (#929)
This will make the behavior for MP4 tags compliant to the API
documentation and consistent with other tag formats.
2020-12-13 12:37:20 +01:00
Urs Fleisch
2dd6931eee Accept WAV files with garbage appended (#973)
This will allow editing the tags of WAV files which have data
appended at the end. The corresponding unit test checks that the
original contents are still available after editing the metadata
of such files.
2020-12-06 08:56:35 +01:00
Scott Wheeler
91b00b17b2 Missing const here 2020-10-12 16:52:09 +02:00
Scott Wheeler
e116824380 Downgrade numerical genres back to ID3v2.3 format
Closes #631
2020-10-12 08:46:51 +02:00
Scott Wheeler
2db13ad8cf Add a little sanity to the formatting here 2020-10-12 08:36:57 +02:00
ujjwalsh
cd767738fc adding support for linux on power 2020-09-01 16:55:07 +00:00
David Bryant
43bc11541d correctly read very high sample rates from WavPack files 2020-07-02 17:22:03 -07:00
David Bryant
6806dc4cf2 make WavPack seekFinalIndex() more robust to false triggers 2020-07-02 16:15:51 -07:00
David Bryant
ec9c49b964 add test for WavPack DSD (issue #962) 2020-07-01 08:35:20 -07:00
David Bryant
872cafb0b9 Several fixes for WavPack files, including issue #962
Fixes to properly handle WavPack files with leading and
trailing non-audio blocks, non-standard sampling rates,
and DSD audio (introduced in WavPack 5).
2020-06-30 22:19:38 -07:00
Scott Wheeler
47342f6974 Prefer COMM frames with no description for setComment()
This creates symetry with ID3v2::Tag::comment() in preferring frames with no description
when choosing which COMM frame should be updated.  (Previously setComment() simply updated
the first COMM frame.)

Fixes #950
2020-03-27 12:14:51 +01:00
Ulrich Spörlein
5763d042a6 Fix spelling of Bebop and update Fusion and Hardcore to match Wikipedia
Source: https://en.wikipedia.org/wiki/List_of_ID3v1_Genres

Similar patches will be sent to libid3tag and ffmpeg to harmonize the
genre names and spellings.
2019-12-19 14:53:57 +01:00
jiblime
cd9e6b7502 Changed libdir/includedir variables to change based on a user's system and match syntax 2019-10-19 15:13:28 -07:00
Scott Wheeler
54508df30b Needs to be defined to nothing if none of the #if blocks match 2019-09-20 10:32:33 +02:00
Scott Wheeler
dcf0331eb1 Use newer function signatures 2019-09-19 15:23:34 +02:00
Scott Wheeler
96155f35fa Use new method signatures 2019-09-19 15:19:17 +02:00
Scott Wheeler
ebd3373d6d Correctly decode signed values
In SV7 these are a mix of signed and unsigned shorts; in SV8 they're all
signed.  Storing them as an int is fine for signed or unsigned shorts as
it's wide enough to contain either of them.

Unfortunately there are no explicit tests for SV7 at the moment; that
would be ideal to add before the next release.

CC @carewolf
2019-09-12 12:13:24 +02:00
Scott Wheeler
f3ecfa11bb Completely remove StripAll from the API
I'd imagined it being useful for calls to `strip()`, but it's not
actually used there since that's an OR-ed together set of flags 
representing which tags to strip.
2019-09-12 11:19:57 +02:00
Scott Wheeler
7082b9f66b Unsaved (and incorrect looking) field 2019-09-12 10:46:11 +02:00
Nick Shaforostov
2d90d938fe fix reading audioproperties for broken mp3 files 2018-03-02 16:18:10 +01:00
73 changed files with 1724 additions and 215 deletions

View File

@@ -12,6 +12,9 @@ compiler:
- gcc
- clang
arch:
- ppc64le
addons:
apt:
packages:

View File

@@ -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}")

View File

@@ -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
View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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/
)

View File

@@ -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;

View File

@@ -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@

View File

@@ -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}

View File

@@ -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

View File

@@ -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})

View File

@@ -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

View File

@@ -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]);
}

View File

@@ -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" },

View File

@@ -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.

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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.
*/

View File

@@ -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
////////////////////////////////////////////////////////////////////////////////

View File

@@ -57,6 +57,8 @@ namespace TagLib {
*/
virtual String toString() const;
PropertyMap asProperties() const;
protected:
// Reimplementations.

View File

@@ -55,7 +55,7 @@ PrivateFrame::PrivateFrame(const ByteVector &data) :
Frame(data),
d(new PrivateFramePrivate())
{
setData(data);
Frame::setData(data);
}
PrivateFrame::~PrivateFrame()

View File

@@ -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);

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;

View File

@@ -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.
*/

View File

@@ -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);

View File

@@ -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.

View File

@@ -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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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));

View File

@@ -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);

View File

@@ -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>

View File

@@ -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
};
/*!

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/data/uint8we.wav Normal file

Binary file not shown.

50
tests/plainfile.h Normal file
View 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

View File

@@ -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());
}
};

View File

@@ -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");

View File

@@ -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"));

View File

@@ -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");

View File

@@ -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);

View File

@@ -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()

View File

@@ -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);

View File

@@ -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());
}
};

View File

@@ -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"));
}
};

View File

@@ -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:

View File

@@ -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);

View File

@@ -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());
}
}

View File

@@ -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");

View File

@@ -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());

View File

@@ -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());
}
}

View File

@@ -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);

View File

@@ -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"));