101 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
Scott Wheeler
de25bc6111 StripAll should be treated as equivalent to StripOthers in save() 2019-09-12 07:57:16 +02:00
Scott Wheeler
074f30e3fa Remove DSF and DSDIFF from master to a feature branch
These can be merged back into master once they're in a more mature state.
2019-09-12 07:55:34 +02:00
Scott Wheeler
f1b40de66b Unify File::save(...) APIs between file formats that support ID3v2
Closes #922
2019-09-11 06:48:27 +02:00
Scott Wheeler
0b99cd9bac Revert switch to other static size method
This was based on a misread of the header:  at present there is no
non-static size() method, so removing the argument makes the behavior
incorrect.
2019-09-11 00:58:18 +02:00
Scott Wheeler
4668cf0f93 Missing header that should have been added in b8dc105 2019-09-11 00:42:15 +02:00
Scott Wheeler
c05fa78406 Mark deprected methods and remove internal usage
This does not put the deprecated marker on methods that will or could resolve
to the same overload, e.g.:

void foo(bool bar = true); // <-- not marked
void foo(Bar bar) // <-- since this will have a default argument in the new version
2019-09-11 00:39:37 +02:00
Scott Wheeler
b8dc105ae3 Deprecate calls to MPEG::File::save(...) that use boolean params
This uses explicit enums for e.g. the ID3v2 version, making calls more
readable:

  file.save(ID3v1 | ID3v2, StripOthers, ID3v2::v4, Duplicate);

Instead of:

  file.save(ID3v1 | ID3v2, true, 4, true);

Needs to be ported to other types, per #922
2019-09-10 22:59:07 +02:00
Scott Wheeler
fced0f46e9 Add docs for this method 2019-09-10 13:31:44 +02:00
Scott Wheeler
dc0f667a4c No tabs in TagLib 2019-09-10 13:08:11 +02:00
Scott Wheeler
085180e9a4 Require at least CMake 3 2019-09-10 12:45:36 +02:00
Scott Wheeler
ef1312d622 Add -lz to taglib.pc and taglib-config when built with zlib
Closes #872
2019-09-10 12:41:11 +02:00
StefanBruens
86c0428475 Clear valid flag for invalid Speex files
This matches the corresponding code in vorbisfile.cpp, opusfile.cpp and flagfile.cpp, and fixes taglib/taglib#902.
2019-09-10 11:44:21 +02:00
Scott Wheeler
3c78c4cfc9 Merge pull request #883 from ufleisch/riff-padding
Do not ignore non zero RIFF padding if leading to parse error (#882)
2019-09-10 11:25:46 +02:00
Scott Wheeler
c8bb6271e5 Merge pull request #917 from ufleisch/ogg-bitrate
Calculate Ogg bitrate without comment size (#874)
2019-09-10 11:14:15 +02:00
Scott Wheeler
84f7462526 Tests need C++11 2019-09-05 17:29:40 +02:00
Urs Fleisch
2f23892182 Calculate Ogg bitrate without overhead size (#874) 2019-09-02 22:14:41 +02:00
Scott Wheeler
2918602ad0 Merge pull request #910 from joerg-krause/patch-1
Drop CMAKE_SYSROOT from taglib-config
2019-08-26 23:37:24 +02:00
Scott Wheeler
c146cd7e92 Merge pull request #909 from williamjcm/patch-1
Make TagLib seach in its source dir for utf8-cpp.
2019-08-26 23:34:47 +02:00
Scott Wheeler
b124e621fe Merge pull request #912 from whatdoineed2do/m4a-track-year-equal0-bugfix
MP4 - setTrack()/setYear() accepts 0 to remove the tag
2019-08-26 23:32:44 +02:00
Scott Wheeler
044fba2921 Merge pull request #919 from jonaski/spelling
Fix spelling and typos
2019-08-26 23:26:55 +02:00
Jonas Kvinge
e72a98903f Fix spelling and typos 2019-08-26 23:23:33 +02:00
whatdoineed2do
79bc9ccf8e Call fflush() before ftruncate() to drop all buffered data (#914)
This avoids stale data presented to caller via a fread()

Current bug due to the buffered data can be seen in stripping mp3s of tags

    f.strip(ID3v1);
    f.strip(APE);

The ID3v1 tag sits at the end of file (strip calls ftruncate()) and the APE
strip performs a readFile() that would return the stream buffered/truncated data
and reinsert
2019-07-25 07:27:49 +04:30
whatdoineed2do/Ray
850a3565a4 setTrack()/setYear() accepts 0 to remove the tag as per
documentation/functionality across other tpyes (mp3/flac/...); m4a do not honour
this and instead sets the underlying value to 0.

This commit fixes this issue (#911)
2019-07-13 16:03:51 +01:00
Jörg Krause
18d424995f Drop CMAKE_SYSROOT from taglib-config
Commit d4c938cbc7 is about fixing taglib-config for proper cross-compilation. The fix is right in principle, but wrong about adding `CMAKE_SYSROOT`. The correct prefix path should be set outside of the config file as some embedded Linux distros like OpenWrt or OpenEmbedded install with a different DESTDIR, and dependent packages see a different sysroot.
2019-06-16 15:33:52 +02:00
Guillaume Jacquemin
d04db70bc6 Make TagLib seach in its source dir for utf8-cpp.
Without this change, TagLib is unusable as part of CMake projects that rely on `add_subdirectory()` for dependencies.
2019-06-01 10:19:27 +02:00
StefanBruens
ba7adc2bc2 Respect atom type when converting rate tag (#896)
* Respect atom type when converting rate tag

TagLib prior to #818 (commit ff28cf276c) read and wrote the "rate" tag as
text, and switched to reading it as integer value even when the atom class
is a text type. This breaks reading existing files, and can be avoided by
taking the atom type into account.

This fixes issue #885.

* Respect MP4::Item type when writing the rate tag

TagLib prior to #818 (commit ff28cf276c) read and wrote the "rate" tag
as text, and switched to writing the integer value of the MP4::Item.
This breaks writing from applications which supply the value as
StringList, which was the previously implemented API. Applications using
an MP4::Item(UInt) are still supported without changes on the application
side.

This is the complementary writing part for issue #885.
2019-05-31 15:51:16 +04:30
Urs Fleisch
79bb1428c0 Support ID3v2 GRP1 frame introduced with iTunes 12.5.4.42, #903. (#904) 2019-05-17 15:45:48 +04:30
Jörg Krause
7470f92a67 fix taglib-config file for cross compiling (#906) 2019-05-17 15:43:35 +04:30
Tim Malseed
6455671ece Update mp4properties.cpp (#893)
When parsing mp4 media header version 1 (mdhd) atoms, the timescale (unit) is parsed as a `LongLong` (8 bytes), but instead should be a `UInt` (4 bytes). This results in an incorrect timescale, and also pushes the offset of the duration (length) off by 4 bytes.

The end result being that the AudioProperties track length for mp4's with mdhd v1 comes back as 0.

See: https://wiki.multimedia.cx/index.php/QuickTime_container

|  Entry | Bytes (v0) | Bytes (v1) |
| :---         |     :---:      | :---: |
| size  | 4  | 4 |
| type  | 4  | 4 |
| version | 1 | 1 |
| flags | 3 | 3 |
| creation time* | 4 | **8** |
| modification time* | 4 | **8** |
| time scale | 4 | 4 |
| duration* | 4 | **8** |
| language | 2 | 2 |
| quality | 2 | 2 |
2019-03-17 08:22:19 -05:00
Tim Malseed
660748210f Minor fix for mp4 media header v0 minimum size check (#895)
Mp4 media header (mdhd) v0 atoms are a minimum of 8 bytes for size & type information, plus 24 bytes for remaining entries (`24 +8`) bytes in total, rather than (`24 + 4`).

See https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25615
2019-03-17 08:20:43 -05:00
Urs Fleisch
1cf176af61 Do not ignore non zero RIFF padding if leading to parse error (#882) 2019-02-10 08:40:20 +01:00
Nick Shaforostov
2d90d938fe fix reading audioproperties for broken mp3 files 2018-03-02 16:18:10 +01:00
129 changed files with 2135 additions and 2877 deletions

View File

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

View File

@@ -1,11 +1,7 @@
cmake_minimum_required(VERSION 2.8.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
project(taglib)
if(NOT ${CMAKE_VERSION} VERSION_LESS 2.8.12)
cmake_policy(SET CMP0022 OLD)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
if(DEFINED ENABLE_STATIC)
@@ -94,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}")
@@ -104,8 +100,12 @@ math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}")
include(ConfigureChecks.cmake)
if(${ZLIB_FOUND})
set(ZLIB_LIBRARIES_FLAGS -lz)
endif()
if(NOT WIN32)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config" @ONLY)
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config" DESTINATION "${BIN_INSTALL_DIR}")
endif()

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

20
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)
@@ -245,7 +257,7 @@ TagLib 1.6.3 (Apr 17, 2010)
* Fixed definitions of the TAGLIB_WITH_MP4 and TAGLIB_WITH_ASF macros.
* Fixed upgrading of ID3v2.3 genre frame with ID3v1 code 0 (Blues).
* New method `int String::toInt(bool *ok)` which can return whether the
conversion to a number was successfull.
conversion to a number was successful.
* Fixed parsing of incorrectly written lengths in ID3v2 (affects mainly
compressed frames). (BUG:231075)
@@ -253,7 +265,7 @@ TagLib 1.6.2 (Apr 9, 2010)
==========================
* Read Vorbis Comments from the first FLAC metadata block, if there are
multipe ones. (BUG:211089)
multiple ones. (BUG:211089)
* Fixed a memory leak in FileRef's OGA format detection.
* Fixed compilation with the Sun Studio compiler. (BUG:215225)
* Handle WM/TrackNumber attributes with DWORD content in WMA files.
@@ -288,7 +300,7 @@ TagLib 1.6 (Sep 13, 2009)
* Added support for disabling dllimport/dllexport on Windows using the
TAGLIB_STATIC macro.
* Support for parsing the obsolete 'gnre' MP4 atom.
* New cpp macros TAGLIB_WITH_MP4 and TAGLIB_WITH_ASF to determin if
* New cpp macros TAGLIB_WITH_MP4 and TAGLIB_WITH_ASF to determine if
TagLib was built with MP4/ASF support.
1.6 RC1:

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

@@ -14,10 +14,10 @@ EOH
exit 1;
}
prefix=${CMAKE_INSTALL_PREFIX}
exec_prefix=${CMAKE_INSTALL_PREFIX}
libdir=${LIB_INSTALL_DIR}
includedir=${INCLUDE_INSTALL_DIR}
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=@LIB_INSTALL_DIR@
includedir=@INCLUDE_INSTALL_DIR@
flags=""
@@ -29,13 +29,13 @@ while test $# -gt 0
do
case $1 in
--libs)
flags="$flags -L$libdir -ltag"
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}
echo @TAGLIB_LIB_VERSION_STRING@
;;
--prefix)
echo $prefix

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

@@ -7,5 +7,5 @@ Name: TagLib
Description: Audio meta-data library
Requires:
Version: @TAGLIB_LIB_VERSION_STRING@
Libs: -L${libdir} -ltag
Cflags: -I${includedir}/taglib
Libs: -L${libdir} -ltag @ZLIB_LIBRARIES_FLAGS@
Cflags: -I${includedir} -I${includedir}/taglib

View File

@@ -24,9 +24,7 @@ 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
${CMAKE_SOURCE_DIR}/3rdparty
${taglib_SOURCE_DIR}/3rdparty
)
if(ZLIB_FOUND)
@@ -63,6 +61,7 @@ set(tag_HDRS
mpeg/xingheader.h
mpeg/id3v1/id3v1tag.h
mpeg/id3v1/id3v1genres.h
mpeg/id3v2/id3v2.h
mpeg/id3v2/id3v2extendedheader.h
mpeg/id3v2/id3v2frame.h
mpeg/id3v2/id3v2header.h
@@ -140,11 +139,6 @@ set(tag_HDRS
s3m/s3mproperties.h
xm/xmfile.h
xm/xmproperties.h
dsf/dsffile.h
dsf/dsfproperties.h
dsdiff/dsdifffile.h
dsdiff/dsdiffproperties.h
dsdiff/dsdiffdiintag.h
)
set(mpeg_SRCS
@@ -300,17 +294,6 @@ set(xm_SRCS
xm/xmproperties.cpp
)
set(dsf_SRCS
dsf/dsffile.cpp
dsf/dsfproperties.cpp
)
set(dsdiff_SRCS
dsdiff/dsdifffile.cpp
dsdiff/dsdiffproperties.cpp
dsdiff/dsdiffdiintag.cpp
)
set(toolkit_SRCS
toolkit/tstring.cpp
toolkit/tstringlist.cpp
@@ -342,7 +325,7 @@ set(tag_LIB_SRCS
${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS}
${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS}
${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS}
${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} ${dsf_SRCS} ${dsdiff_SRCS}
${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS}
${zlib_SRCS}
tag.cpp
tagunion.cpp
@@ -352,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})
@@ -366,8 +350,7 @@ set_target_properties(tag PROPERTIES
PUBLIC_HEADER "${tag_HDRS}"
)
if(VISIBILITY_HIDDEN)
set_target_properties(tag PROPERTIES C_VISIBILITY_PRESET hidden
)
set_target_properties(tag PROPERTIES C_VISIBILITY_PRESET hidden)
endif()
if(BUILD_FRAMEWORK)

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

@@ -55,7 +55,7 @@ namespace TagLib {
*
* \deprecated
*/
Properties(File *file, ReadStyle style = Average);
TAGLIB_DEPRECATED Properties(File *file, ReadStyle style = Average);
/*!
* Create an instance of APE::Properties with the data read from the
@@ -76,7 +76,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

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

@@ -88,7 +88,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

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

@@ -160,6 +160,7 @@ namespace TagLib {
* Returns a reference to the item list map. This is an AttributeListMap of
* all of the items in the tag.
*/
// BIC: return by value
const AttributeListMap &attributeListMap() const;
/*!

View File

@@ -38,8 +38,6 @@
#include "vorbisproperties.h"
#include "wavproperties.h"
#include "wavpackproperties.h"
#include "dsfproperties.h"
#include "dsdiffproperties.h"
#include "audioproperties.h"
@@ -75,10 +73,6 @@ using namespace TagLib;
return dynamic_cast<const Vorbis::Properties*>(this)->function_name(); \
else if(dynamic_cast<const WavPack::Properties*>(this)) \
return dynamic_cast<const WavPack::Properties*>(this)->function_name(); \
else if(dynamic_cast<const DSF::Properties*>(this)) \
return dynamic_cast<const DSF::Properties*>(this)->function_name(); \
else if(dynamic_cast<const DSDIFF::Properties*>(this)) \
return dynamic_cast<const DSDIFF::Properties*>(this)->function_name(); \
else \
return (default_value);

View File

@@ -1,162 +0,0 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.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/ *
***************************************************************************/
#include "dsdiffdiintag.h"
#include "tstringlist.h"
#include "tpropertymap.h"
using namespace TagLib;
using namespace DSDIFF::DIIN;
class DSDIFF::DIIN::Tag::TagPrivate
{
public:
TagPrivate()
{
}
String title;
String artist;
};
DSDIFF::DIIN::Tag::Tag() : TagLib::Tag()
{
d = new TagPrivate;
}
DSDIFF::DIIN::Tag::~Tag()
{
delete d;
}
String DSDIFF::DIIN::Tag::title() const
{
return d->title;
}
String DSDIFF::DIIN::Tag::artist() const
{
return d->artist;
}
String DSDIFF::DIIN::Tag::album() const
{
return String();
}
String DSDIFF::DIIN::Tag::comment() const
{
return String();
}
String DSDIFF::DIIN::Tag::genre() const
{
return String();
}
unsigned int DSDIFF::DIIN::Tag::year() const
{
return 0;
}
unsigned int DSDIFF::DIIN::Tag::track() const
{
return 0;
}
void DSDIFF::DIIN::Tag::setTitle(const String &title)
{
if(title.isNull() || title.isEmpty())
d->title = String();
else
d->title = title;
}
void DSDIFF::DIIN::Tag::setArtist(const String &artist)
{
if(artist.isNull() || artist.isEmpty())
d->artist = String();
else
d->artist = artist;
}
void DSDIFF::DIIN::Tag::setAlbum(const String &)
{
}
void DSDIFF::DIIN::Tag::setComment(const String &)
{
}
void DSDIFF::DIIN::Tag::setGenre(const String &)
{
}
void DSDIFF::DIIN::Tag::setYear(unsigned int)
{
}
void DSDIFF::DIIN::Tag::setTrack(unsigned int)
{
}
PropertyMap DSDIFF::DIIN::Tag::properties() const
{
PropertyMap properties;
properties["TITLE"] = d->title;
properties["ARTIST"] = d->artist;
return properties;
}
PropertyMap DSDIFF::DIIN::Tag::setProperties(const PropertyMap &origProps)
{
PropertyMap properties(origProps);
properties.removeEmpty();
StringList oneValueSet;
if(properties.contains("TITLE")) {
d->title = properties["TITLE"].front();
oneValueSet.append("TITLE");
} else
d->title = String();
if(properties.contains("ARTIST")) {
d->artist = properties["ARTIST"].front();
oneValueSet.append("ARTIST");
} else
d->artist = String();
// for each tag that has been set above, remove the first entry in the corresponding
// value list. The others will be returned as unsupported by this format.
for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) {
if(properties[*it].size() == 1)
properties.erase(*it);
else
properties[*it].erase(properties[*it].begin());
}
return properties;
}

View File

@@ -1,150 +0,0 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.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_DSDIFFDIINTAG_H
#define TAGLIB_DSDIFFDIINTAG_H
#include "tag.h"
namespace TagLib {
namespace DSDIFF {
namespace DIIN {
/*!
* Tags from the Edited Master Chunk Info
*
* Only Title and Artist tags are supported
*/
class TAGLIB_EXPORT Tag : public TagLib::Tag
{
public:
Tag();
virtual ~Tag();
/*!
* Returns the track name; if no track name is present in the tag
* String() will be returned.
*/
String title() const;
/*!
* Returns the artist name; if no artist name is present in the tag
* String() will be returned.
*/
String artist() const;
/*!
* Not supported. Therefore always returns String().
*/
String album() const;
/*!
* Not supported. Therefore always returns String().
*/
String comment() const;
/*!
* Not supported. Therefore always returns String().
*/
String genre() const;
/*!
* Not supported. Therefore always returns 0.
*/
unsigned int year() const;
/*!
* Not supported. Therefore always returns 0.
*/
unsigned int track() const;
/*!
* Sets the title to \a title. If \a title is String() then this
* value will be cleared.
*/
void setTitle(const String &title);
/*!
* Sets the artist to \a artist. If \a artist is String() then this
* value will be cleared.
*/
void setArtist(const String &artist);
/*!
* Not supported and therefore ignored.
*/
void setAlbum(const String &album);
/*!
* Not supported and therefore ignored.
*/
void setComment(const String &comment);
/*!
* Not supported and therefore ignored.
*/
void setGenre(const String &genre);
/*!
* Not supported and therefore ignored.
*/
void setYear(unsigned int year);
/*!
* Not supported and therefore ignored.
*/
void setTrack(unsigned int track);
/*!
* Implements the unified property interface -- export function.
* Since the DIIN tag is very limited, the exported map is as well.
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* Because of the limitations of the DIIN file tag, any tags besides
* TITLE and ARTIST, will be
* returned. Additionally, if the map contains tags with multiple values,
* all but the first will be contained in the returned map of unsupported
* properties.
*/
PropertyMap setProperties(const PropertyMap &);
private:
Tag(const Tag &);
Tag &operator=(const Tag &);
class TagPrivate;
TagPrivate *d;
};
}
}
}
#endif

View File

@@ -1,812 +0,0 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.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/ *
***************************************************************************/
#include <tbytevector.h>
#include <tdebug.h>
#include <id3v2tag.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "tagunion.h"
#include "dsdifffile.h"
using namespace TagLib;
struct Chunk64
{
ByteVector name;
unsigned long long offset;
unsigned long long size;
char padding;
};
namespace
{
enum {
ID3v2Index = 0,
DIINIndex = 1
};
enum {
PROPChunk = 0,
DIINChunk = 1
};
}
class DSDIFF::File::FilePrivate
{
public:
FilePrivate() :
endianness(BigEndian),
size(0),
isID3InPropChunk(false),
duplicateID3V2chunkIndex(-1),
properties(0),
id3v2TagChunkID("ID3 "),
hasID3v2(false),
hasDiin(false)
{
childChunkIndex[ID3v2Index] = -1;
childChunkIndex[DIINIndex] = -1;
}
~FilePrivate()
{
delete properties;
}
Endianness endianness;
ByteVector type;
unsigned long long size;
ByteVector format;
std::vector<Chunk64> chunks;
std::vector<Chunk64> childChunks[2];
int childChunkIndex[2];
bool isID3InPropChunk; // Two possibilities can be found: ID3V2 chunk inside PROP chunk or at root level
int duplicateID3V2chunkIndex; // 2 ID3 chunks are present. This is then the index of the one in
// PROP chunk that will be removed upon next save to remove duplicates.
Properties *properties;
TagUnion tag;
ByteVector id3v2TagChunkID;
bool hasID3v2;
bool hasDiin;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool DSDIFF::File::isSupported(IOStream *stream)
{
// A DSDIFF file has to start with "FRM8????????DSD ".
const ByteVector id = Utils::readHeader(stream, 16, false);
return (id.startsWith("FRM8") && id.containsAt("DSD ", 12));
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSDIFF::File::File(FileName file, bool readProperties,
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
{
d = new FilePrivate;
d->endianness = BigEndian;
if(isOpen())
read(readProperties, propertiesStyle);
}
DSDIFF::File::File(IOStream *stream, bool readProperties,
Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
{
d = new FilePrivate;
d->endianness = BigEndian;
if(isOpen())
read(readProperties, propertiesStyle);
}
DSDIFF::File::~File()
{
delete d;
}
TagLib::Tag *DSDIFF::File::tag() const
{
return &d->tag;
}
ID3v2::Tag *DSDIFF::File::ID3v2Tag() const
{
return d->tag.access<ID3v2::Tag>(ID3v2Index, false);
}
bool DSDIFF::File::hasID3v2Tag() const
{
return d->hasID3v2;
}
DSDIFF::DIIN::Tag *DSDIFF::File::DIINTag() const
{
return d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false);
}
bool DSDIFF::File::hasDIINTag() const
{
return d->hasDiin;
}
PropertyMap DSDIFF::File::properties() const
{
if(d->hasID3v2)
return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->properties();
return PropertyMap();
}
void DSDIFF::File::removeUnsupportedProperties(const StringList &unsupported)
{
if(d->hasID3v2)
d->tag.access<ID3v2::Tag>(ID3v2Index, false)->removeUnsupportedProperties(unsupported);
if(d->hasDiin)
d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false)->removeUnsupportedProperties(unsupported);
}
PropertyMap DSDIFF::File::setProperties(const PropertyMap &properties)
{
return d->tag.access<ID3v2::Tag>(ID3v2Index, true)->setProperties(properties);
}
DSDIFF::Properties *DSDIFF::File::audioProperties() const
{
return d->properties;
}
bool DSDIFF::File::save()
{
if(readOnly()) {
debug("DSDIFF::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("DSDIFF::File::save() -- Trying to save invalid file.");
return false;
}
// First: save ID3V2 chunk
ID3v2::Tag *id3v2Tag = d->tag.access<ID3v2::Tag>(ID3v2Index, false);
if(d->isID3InPropChunk) {
if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) {
setChildChunkData(d->id3v2TagChunkID, id3v2Tag->render(), PROPChunk);
d->hasID3v2 = true;
}
else {
// Empty tag: remove it
setChildChunkData(d->id3v2TagChunkID, ByteVector(), PROPChunk);
d->hasID3v2 = false;
}
}
else {
if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) {
setRootChunkData(d->id3v2TagChunkID, id3v2Tag->render());
d->hasID3v2 = true;
}
else {
// Empty tag: remove it
setRootChunkData(d->id3v2TagChunkID, ByteVector());
d->hasID3v2 = false;
}
}
// Second: save the DIIN chunk
if(d->hasDiin) {
DSDIFF::DIIN::Tag *diinTag = d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false);
if(!diinTag->title().isNull() && !diinTag->title().isEmpty()) {
ByteVector diinTitle;
diinTitle.append(ByteVector::fromUInt(diinTag->title().size(), d->endianness == BigEndian));
diinTitle.append(ByteVector::fromCString(diinTag->title().toCString()));
setChildChunkData("DITI", diinTitle, DIINChunk);
}
else
setChildChunkData("DITI", ByteVector(), DIINChunk);
if(!diinTag->artist().isNull() && !diinTag->artist().isEmpty()) {
ByteVector diinArtist;
diinArtist.append(ByteVector::fromUInt(diinTag->artist().size(), d->endianness == BigEndian));
diinArtist.append(ByteVector::fromCString(diinTag->artist().toCString()));
setChildChunkData("DIAR", diinArtist, DIINChunk);
}
else
setChildChunkData("DIAR", ByteVector(), DIINChunk);
}
// Third: remove the duplicate ID3V2 chunk (inside PROP chunk) if any
if(d->duplicateID3V2chunkIndex>=0) {
setChildChunkData(d->duplicateID3V2chunkIndex, ByteVector(), PROPChunk);
d->duplicateID3V2chunkIndex = -1;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void DSDIFF::File::setRootChunkData(unsigned int i, const ByteVector &data)
{
if(data.isNull() || data.isEmpty()) {
// Null data: remove chunk
// Update global size
unsigned long long removedChunkTotalSize = d->chunks[i].size + d->chunks[i].padding + 12;
d->size -= removedChunkTotalSize;
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
removeBlock(d->chunks[i].offset - 12, removedChunkTotalSize);
// Update the internal offsets
for(unsigned long r = i + 1; r < d->chunks.size(); r++)
d->chunks[r].offset = d->chunks[r - 1].offset + 12
+ d->chunks[r - 1].size + d->chunks[r - 1].padding;
d->chunks.erase(d->chunks.begin() + i);
}
else {
// Non null data: update chunk
// First we update the global size
d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding);
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// Now update the specific chunk
writeChunk(d->chunks[i].name,
data,
d->chunks[i].offset - 12,
d->chunks[i].size + d->chunks[i].padding + 12);
d->chunks[i].size = data.size();
d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0;
// Finally update the internal offsets
updateRootChunksStructure(i + 1);
}
}
void DSDIFF::File::setRootChunkData(const ByteVector &name, const ByteVector &data)
{
if(d->chunks.size() == 0) {
debug("DSDIFF::File::setPropChunkData - No valid chunks found.");
return;
}
for(unsigned int i = 0; i < d->chunks.size(); i++) {
if(d->chunks[i].name == name) {
setRootChunkData(i, data);
return;
}
}
// Couldn't find an existing chunk, so let's create a new one.
unsigned int i = d->chunks.size() - 1;
unsigned long offset = d->chunks[i].offset + d->chunks[i].size + d->chunks[i].padding;
// First we update the global size
d->size += (offset & 1) + ((data.size() + 1) & ~1) + 12;
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// Now add the chunk to the file
writeChunk(name,
data,
offset,
std::max<unsigned long long>(0, length() - offset),
(offset & 1) ? 1 : 0);
Chunk64 chunk;
chunk.name = name;
chunk.size = data.size();
chunk.offset = offset + 12;
chunk.padding = (data.size() & 0x01) ? 1 : 0;
d->chunks.push_back(chunk);
}
void DSDIFF::File::setChildChunkData(unsigned int i,
const ByteVector &data,
unsigned int childChunkNum)
{
std::vector<Chunk64> &childChunks = d->childChunks[childChunkNum];
if(data.isNull() || data.isEmpty()) {
// Null data: remove chunk
// Update global size
unsigned long long removedChunkTotalSize = childChunks[i].size + childChunks[i].padding + 12;
d->size -= removedChunkTotalSize;
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// Update child chunk size
d->chunks[d->childChunkIndex[childChunkNum]].size -= removedChunkTotalSize;
insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
d->endianness == BigEndian),
d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
// Remove the chunk
removeBlock(childChunks[i].offset - 12, removedChunkTotalSize);
// Update the internal offsets
// For child chunks
if((i + 1) < childChunks.size()) {
childChunks[i + 1].offset = childChunks[i].offset;
i++;
for(i++; i < childChunks.size(); i++)
childChunks[i].offset = childChunks[i - 1].offset + 12
+ childChunks[i - 1].size + childChunks[i - 1].padding;
}
// And for root chunks
for(i = d->childChunkIndex[childChunkNum] + 1; i < d->chunks.size(); i++)
d->chunks[i].offset = d->chunks[i - 1].offset + 12
+ d->chunks[i - 1].size + d->chunks[i - 1].padding;
childChunks.erase(childChunks.begin() + i);
}
else {
// Non null data: update chunk
// First we update the global size
d->size += ((data.size() + 1) & ~1) - (childChunks[i].size + childChunks[i].padding);
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// And the PROP chunk size
d->chunks[d->childChunkIndex[childChunkNum]].size += ((data.size() + 1) & ~1)
- (childChunks[i].size + childChunks[i].padding);
insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
d->endianness == BigEndian),
d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
// Now update the specific chunk
writeChunk(childChunks[i].name,
data,
childChunks[i].offset - 12,
childChunks[i].size + childChunks[i].padding + 12);
childChunks[i].size = data.size();
childChunks[i].padding = (data.size() & 0x01) ? 1 : 0;
// Now update the internal offsets
// For child Chunks
for(i++; i < childChunks.size(); i++)
childChunks[i].offset = childChunks[i - 1].offset + 12
+ childChunks[i - 1].size + childChunks[i - 1].padding;
// And for root chunks
updateRootChunksStructure(d->childChunkIndex[childChunkNum] + 1);
}
}
void DSDIFF::File::setChildChunkData(const ByteVector &name,
const ByteVector &data,
unsigned int childChunkNum)
{
std::vector<Chunk64> &childChunks = d->childChunks[childChunkNum];
if(childChunks.size() == 0) {
debug("DSDIFF::File::setPropChunkData - No valid chunks found.");
return;
}
for(unsigned int i = 0; i < childChunks.size(); i++) {
if(childChunks[i].name == name) {
setChildChunkData(i, data, childChunkNum);
return;
}
}
// Do not attempt to remove a non existing chunk
if(data.isNull() || data.isEmpty())
return;
// Couldn't find an existing chunk, so let's create a new one.
unsigned int i = childChunks.size() - 1;
unsigned long offset = childChunks[i].offset + childChunks[i].size + childChunks[i].padding;
// First we update the global size
d->size += (offset & 1) + ((data.size() + 1) & ~1) + 12;
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// And the child chunk size
d->chunks[d->childChunkIndex[childChunkNum]].size += (offset & 1)
+ ((data.size() + 1) & ~1) + 12;
insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
d->endianness == BigEndian),
d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
// Now add the chunk to the file
unsigned long long nextRootChunkIdx = length();
if((d->childChunkIndex[childChunkNum] + 1) < static_cast<int>(d->chunks.size()))
nextRootChunkIdx = d->chunks[d->childChunkIndex[childChunkNum] + 1].offset - 12;
writeChunk(name, data, offset,
std::max<unsigned long long>(0, nextRootChunkIdx - offset),
(offset & 1) ? 1 : 0);
// For root chunks
updateRootChunksStructure(d->childChunkIndex[childChunkNum] + 1);
Chunk64 chunk;
chunk.name = name;
chunk.size = data.size();
chunk.offset = offset + 12;
chunk.padding = (data.size() & 0x01) ? 1 : 0;
childChunks.push_back(chunk);
}
static bool isValidChunkID(const ByteVector &name)
{
if(name.size() != 4)
return false;
for(int i = 0; i < 4; i++) {
if(name[i] < 32 || name[i] > 127)
return false;
}
return true;
}
void DSDIFF::File::updateRootChunksStructure(unsigned int startingChunk)
{
for(unsigned int i = startingChunk; i < d->chunks.size(); i++)
d->chunks[i].offset = d->chunks[i - 1].offset + 12
+ d->chunks[i - 1].size + d->chunks[i - 1].padding;
// Update childchunks structure as well
if(d->childChunkIndex[PROPChunk] >= static_cast<int>(startingChunk)) {
std::vector<Chunk64> &childChunksToUpdate = d->childChunks[PROPChunk];
if(childChunksToUpdate.size() > 0) {
childChunksToUpdate[0].offset = d->chunks[d->childChunkIndex[PROPChunk]].offset + 12;
for(unsigned int i = 1; i < childChunksToUpdate.size(); i++)
childChunksToUpdate[i].offset = childChunksToUpdate[i - 1].offset + 12
+ childChunksToUpdate[i - 1].size + childChunksToUpdate[i - 1].padding;
}
}
if(d->childChunkIndex[DIINChunk] >= static_cast<int>(startingChunk)) {
std::vector<Chunk64> &childChunksToUpdate = d->childChunks[DIINChunk];
if(childChunksToUpdate.size() > 0) {
childChunksToUpdate[0].offset = d->chunks[d->childChunkIndex[DIINChunk]].offset + 12;
for(unsigned int i = 1; i < childChunksToUpdate.size(); i++)
childChunksToUpdate[i].offset = childChunksToUpdate[i - 1].offset + 12
+ childChunksToUpdate[i - 1].size + childChunksToUpdate[i - 1].padding;
}
}
}
void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
{
bool bigEndian = (d->endianness == BigEndian);
d->type = readBlock(4);
d->size = readBlock(8).toLongLong(bigEndian);
d->format = readBlock(4);
// + 12: chunk header at least, fix for additional junk bytes
while(tell() + 12 <= length()) {
ByteVector chunkName = readBlock(4);
unsigned long long chunkSize = readBlock(8).toLongLong(bigEndian);
if(!isValidChunkID(chunkName)) {
debug("DSDIFF::File::read() -- Chunk '" + chunkName + "' has invalid ID");
setValid(false);
break;
}
if(static_cast<unsigned long long>(tell()) + chunkSize > static_cast<unsigned long long>(length())) {
debug("DSDIFF::File::read() -- Chunk '" + chunkName
+ "' has invalid size (larger than the file size)");
setValid(false);
break;
}
Chunk64 chunk;
chunk.name = chunkName;
chunk.size = chunkSize;
chunk.offset = tell();
seek(chunk.size, Current);
// Check padding
chunk.padding = 0;
long uPosNotPadded = tell();
if((uPosNotPadded & 0x01) != 0) {
ByteVector iByte = readBlock(1);
if((iByte.size() != 1) || (iByte[0] != 0))
// Not well formed, re-seek
seek(uPosNotPadded, Beginning);
else
chunk.padding = 1;
}
d->chunks.push_back(chunk);
}
unsigned long long lengthDSDSamplesTimeChannels = 0; // For DSD uncompressed
unsigned long long audioDataSizeinBytes = 0; // For computing bitrate
unsigned long dstNumFrames = 0; // For DST compressed frames
unsigned short dstFrameRate = 0; // For DST compressed frames
for(unsigned int i = 0; i < d->chunks.size(); i++) {
if(d->chunks[i].name == "DSD ") {
lengthDSDSamplesTimeChannels = d->chunks[i].size * 8;
audioDataSizeinBytes = d->chunks[i].size;
}
else if(d->chunks[i].name == "DST ") {
// Now decode the chunks inside the DST chunk to read the DST Frame Information one
long long dstChunkEnd = d->chunks[i].offset + d->chunks[i].size;
seek(d->chunks[i].offset);
audioDataSizeinBytes = d->chunks[i].size;
while(tell() + 12 <= dstChunkEnd) {
ByteVector dstChunkName = readBlock(4);
long long dstChunkSize = readBlock(8).toLongLong(bigEndian);
if(!isValidChunkID(dstChunkName)) {
debug("DSDIFF::File::read() -- DST Chunk '" + dstChunkName + "' has invalid ID");
setValid(false);
break;
}
if(static_cast<long long>(tell()) + dstChunkSize > dstChunkEnd) {
debug("DSDIFF::File::read() -- DST Chunk '" + dstChunkName
+ "' has invalid size (larger than the DST chunk)");
setValid(false);
break;
}
if(dstChunkName == "FRTE") {
// Found the DST frame information chunk
dstNumFrames = readBlock(4).toUInt(bigEndian);
dstFrameRate = readBlock(2).toUShort(bigEndian);
break; // Found the wanted one, no need to look at the others
}
seek(dstChunkSize, Current);
// Check padding
long uPosNotPadded = tell();
if((uPosNotPadded & 0x01) != 0) {
ByteVector iByte = readBlock(1);
if((iByte.size() != 1) || (iByte[0] != 0))
// Not well formed, re-seek
seek(uPosNotPadded, Beginning);
}
}
}
else if(d->chunks[i].name == "PROP") {
d->childChunkIndex[PROPChunk] = i;
// Now decodes the chunks inside the PROP chunk
long long propChunkEnd = d->chunks[i].offset + d->chunks[i].size;
seek(d->chunks[i].offset + 4); // +4 to remove the 'SND ' marker at beginning of 'PROP' chunk
while(tell() + 12 <= propChunkEnd) {
ByteVector propChunkName = readBlock(4);
long long propChunkSize = readBlock(8).toLongLong(bigEndian);
if(!isValidChunkID(propChunkName)) {
debug("DSDIFF::File::read() -- PROP Chunk '" + propChunkName + "' has invalid ID");
setValid(false);
break;
}
if(static_cast<long long>(tell()) + propChunkSize > propChunkEnd) {
debug("DSDIFF::File::read() -- PROP Chunk '" + propChunkName
+ "' has invalid size (larger than the PROP chunk)");
setValid(false);
break;
}
Chunk64 chunk;
chunk.name = propChunkName;
chunk.size = propChunkSize;
chunk.offset = tell();
seek(chunk.size, Current);
// Check padding
chunk.padding = 0;
long uPosNotPadded = tell();
if((uPosNotPadded & 0x01) != 0) {
ByteVector iByte = readBlock(1);
if((iByte.size() != 1) || (iByte[0] != 0))
// Not well formed, re-seek
seek(uPosNotPadded, Beginning);
else
chunk.padding = 1;
}
d->childChunks[PROPChunk].push_back(chunk);
}
}
else if(d->chunks[i].name == "DIIN") {
d->childChunkIndex[DIINChunk] = i;
d->hasDiin = true;
// Now decode the chunks inside the DIIN chunk
long long diinChunkEnd = d->chunks[i].offset + d->chunks[i].size;
seek(d->chunks[i].offset);
while(tell() + 12 <= diinChunkEnd) {
ByteVector diinChunkName = readBlock(4);
long long diinChunkSize = readBlock(8).toLongLong(bigEndian);
if(!isValidChunkID(diinChunkName)) {
debug("DSDIFF::File::read() -- DIIN Chunk '" + diinChunkName + "' has invalid ID");
setValid(false);
break;
}
if(static_cast<long long>(tell()) + diinChunkSize > diinChunkEnd) {
debug("DSDIFF::File::read() -- DIIN Chunk '" + diinChunkName
+ "' has invalid size (larger than the DIIN chunk)");
setValid(false);
break;
}
Chunk64 chunk;
chunk.name = diinChunkName;
chunk.size = diinChunkSize;
chunk.offset = tell();
seek(chunk.size, Current);
// Check padding
chunk.padding = 0;
long uPosNotPadded = tell();
if((uPosNotPadded & 0x01) != 0) {
ByteVector iByte = readBlock(1);
if((iByte.size() != 1) || (iByte[0] != 0))
// Not well formed, re-seek
seek(uPosNotPadded, Beginning);
else
chunk.padding = 1;
}
d->childChunks[DIINChunk].push_back(chunk);
}
}
else if(d->chunks[i].name == "ID3 " || d->chunks[i].name == "id3 ") {
d->id3v2TagChunkID = d->chunks[i].name;
d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->chunks[i].offset));
d->isID3InPropChunk = false;
d->hasID3v2 = true;
}
}
if(!isValid())
return;
if(d->childChunkIndex[PROPChunk] < 0) {
debug("DSDIFF::File::read() -- no PROP chunk found");
setValid(false);
return;
}
// Read properties
unsigned int sampleRate=0;
unsigned short channels=0;
for(unsigned int i = 0; i < d->childChunks[PROPChunk].size(); i++) {
if(d->childChunks[PROPChunk][i].name == "ID3 " || d->childChunks[PROPChunk][i].name == "id3 ") {
if(d->hasID3v2) {
d->duplicateID3V2chunkIndex = i;
continue; // ID3V2 tag has already been found at root level
}
d->id3v2TagChunkID = d->childChunks[PROPChunk][i].name;
d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->childChunks[PROPChunk][i].offset));
d->isID3InPropChunk = true;
d->hasID3v2 = true;
}
else if(d->childChunks[PROPChunk][i].name == "FS ") {
// Sample rate
seek(d->childChunks[PROPChunk][i].offset);
sampleRate = readBlock(4).toUInt(0, 4, bigEndian);
}
else if(d->childChunks[PROPChunk][i].name == "CHNL") {
// Channels
seek(d->childChunks[PROPChunk][i].offset);
channels = readBlock(2).toShort(0, bigEndian);
}
}
// Read title & artist from DIIN chunk
d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, true);
if(d->hasDiin) {
for(unsigned int i = 0; i < d->childChunks[DIINChunk].size(); i++) {
if(d->childChunks[DIINChunk][i].name == "DITI") {
seek(d->childChunks[DIINChunk][i].offset);
unsigned int titleStrLength = readBlock(4).toUInt(0, 4, bigEndian);
if(titleStrLength <= d->childChunks[DIINChunk][i].size) {
ByteVector titleStr = readBlock(titleStrLength);
d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false)->setTitle(titleStr);
}
}
else if(d->childChunks[DIINChunk][i].name == "DIAR") {
seek(d->childChunks[DIINChunk][i].offset);
unsigned int artistStrLength = readBlock(4).toUInt(0, 4, bigEndian);
if(artistStrLength <= d->childChunks[DIINChunk][i].size) {
ByteVector artistStr = readBlock(artistStrLength);
d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false)->setArtist(artistStr);
}
}
}
}
if(readProperties) {
if(lengthDSDSamplesTimeChannels == 0) {
// DST compressed signal : need to compute length of DSD uncompressed frames
if(dstFrameRate > 0)
lengthDSDSamplesTimeChannels = (unsigned long long)dstNumFrames
* (unsigned long long)sampleRate / (unsigned long long)dstFrameRate;
else
lengthDSDSamplesTimeChannels = 0;
}
else {
// In DSD uncompressed files, the read number of samples is the total for each channel
if(channels > 0)
lengthDSDSamplesTimeChannels /= channels;
}
int bitrate = 0;
if(lengthDSDSamplesTimeChannels > 0)
bitrate = (audioDataSizeinBytes*8*sampleRate) / lengthDSDSamplesTimeChannels / 1000;
d->properties = new Properties(sampleRate,
channels,
lengthDSDSamplesTimeChannels,
bitrate,
propertiesStyle);
}
if(!ID3v2Tag()) {
d->tag.access<ID3v2::Tag>(ID3v2Index, true);
d->isID3InPropChunk = false; // By default, ID3 chunk is at root level
d->hasID3v2 = false;
}
}
void DSDIFF::File::writeChunk(const ByteVector &name, const ByteVector &data,
unsigned long long offset, unsigned long replace,
unsigned int leadingPadding)
{
ByteVector combined;
if(leadingPadding)
combined.append(ByteVector(leadingPadding, '\x00'));
combined.append(name);
combined.append(ByteVector::fromLongLong(data.size(), d->endianness == BigEndian));
combined.append(data);
if((data.size() & 0x01) != 0)
combined.append('\x00');
insert(combined, offset, replace);
}

View File

@@ -1,260 +0,0 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.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_DSDIFFFILE_H
#define TAGLIB_DSDIFFFILE_H
#include "rifffile.h"
#include "id3v2tag.h"
#include "dsdiffproperties.h"
#include "dsdiffdiintag.h"
namespace TagLib {
//! An implementation of DSDIFF metadata
/*!
* This is implementation of DSDIFF metadata.
*
* This supports an ID3v2 tag as well as reading stream from the ID3 RIFF
* chunk as well as properties from the file.
* Description of the DSDIFF format is available
* at http://dsd-guide.com/sites/default/files/white-papers/DSDIFF_1.5_Spec.pdf
* DSDIFF standard does not explictly specify the ID3V2 chunk
* It can be found at the root level, but also sometimes inside the PROP chunk
* In addition, title and artist info are stored as part of the standard
*/
namespace DSDIFF {
//! An implementation of TagLib::File with DSDIFF specific methods
/*!
* This implements and provides an interface for DSDIFF files to the
* TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing
* the abstract TagLib::File API as well as providing some additional
* information specific to DSDIFF files.
*/
class TAGLIB_EXPORT File : public TagLib::File
{
public:
/*!
* Constructs an DSDIFF file from \a file. If \a readProperties is true
* the file's audio properties will also be read.
*
* \note In the current implementation, \a propertiesStyle is ignored.
*/
File(FileName file, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
/*!
* Constructs an DSDIFF file from \a stream. If \a readProperties is true
* the file's audio properties will also be read.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*
* \note In the current implementation, \a propertiesStyle is ignored.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
/*!
* Destroys this instance of the File.
*/
virtual ~File();
/*!
* Returns a pointer to a tag that is the union of the ID3v2 and DIIN
* tags. The ID3v2 tag is given priority in reading the information -- if
* requested information exists in both the ID3v2 tag and the ID3v1 tag,
* the information from the ID3v2 tag will be returned.
*
* If you would like more granular control over the content of the tags,
* with the concession of generality, use the tag-type specific calls.
*
* \note As this tag is not implemented as an ID3v2 tag or a DIIN tag,
* but a union of the two this pointer may not be cast to the specific
* tag types.
*
* \see ID3v2Tag()
* \see DIINTag()
*/
virtual Tag *tag() const;
/*!
* Returns the ID3V2 Tag for this file.
*
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the
* file on disk actually has an ID3v2 tag.
*
* \see hasID3v2Tag()
*/
virtual ID3v2::Tag *ID3v2Tag() const;
/*!
* Returns the DSDIFF DIIN Tag for this file
*
*/
DSDIFF::DIIN::Tag *DIINTag() const;
/*!
* Implements the unified property interface -- export function.
* This method forwards to ID3v2::Tag::properties().
*/
PropertyMap properties() const;
void removeUnsupportedProperties(const StringList &properties);
/*!
* Implements the unified property interface -- import function.
* This method forwards to ID3v2::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the AIFF::Properties for this file. If no audio properties
* were read then this will return a null pointer.
*/
virtual Properties *audioProperties() const;
/*!
* Save the file. If at least one tag -- ID3v1 or DIIN -- exists this
* will duplicate its content into the other tag. This returns true
* if saving was successful.
*
* If neither exists or if both tags are empty, this will strip the tags
* from the file.
*
* This is the same as calling save(AllTags);
*
* If you would like more granular control over the content of the tags,
* with the concession of generality, use paramaterized save call below.
*
* \see save(int tags)
*/
virtual bool save();
/*!
* Save the file. This will attempt to save all of the tag types that are
* specified by OR-ing together TagTypes values. The save() method above
* uses AllTags. This returns true if saving was successful.
*
* This strips all tags not included in the mask, but does not modify them
* in memory, so later calls to save() which make use of these tags will
* remain valid. This also strips empty tags.
*/
bool save(int tags);
/*!
* Returns whether or not the file on disk actually has an ID3v2 tag.
*
* \see ID3v2Tag()
*/
bool hasID3v2Tag() const;
/*!
* Returns whether or not the file on disk actually has the DSDIFF
* Title & Artist tag.
*
* \see DIINTag()
*/
bool hasDIINTag() const;
/*!
* Returns whether or not the given \a stream can be opened as a DSDIFF
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
protected:
enum Endianness { BigEndian, LittleEndian };
File(FileName file, Endianness endianness);
File(IOStream *stream, Endianness endianness);
private:
File(const File &);
File &operator=(const File &);
/*!
* Sets the data for the the specified chunk at root level to \a data.
*
* \warning This will update the file immediately.
*/
void setRootChunkData(unsigned int i, const ByteVector &data);
/*!
* Sets the data for the root-level chunk \a name to \a data.
* If a root-level chunk with the given name already exists
* it will be overwritten, otherwise it will be
* created after the existing chunks.
*
* \warning This will update the file immediately.
*/
void setRootChunkData(const ByteVector &name, const ByteVector &data);
/*!
* Sets the data for the the specified child chunk to \a data.
*
* If data is null, then remove the chunk
*
* \warning This will update the file immediately.
*/
void setChildChunkData(unsigned int i, const ByteVector &data,
unsigned int childChunkNum);
/*!
* Sets the data for the child chunk \a name to \a data. If a chunk with
* the given name already exists it will be overwritten, otherwise it will
* be created after the existing chunks inside child chunk.
*
* If data is null, then remove the chunks with \a name name
*
* \warning This will update the file immediately.
*/
void setChildChunkData(const ByteVector &name, const ByteVector &data,
unsigned int childChunkNum);
void updateRootChunksStructure(unsigned int startingChunk);
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
void writeChunk(const ByteVector &name, const ByteVector &data,
unsigned long long offset, unsigned long replace = 0,
unsigned int leadingPadding = 0);
class FilePrivate;
FilePrivate *d;
};
}
}
#endif

View File

@@ -1,120 +0,0 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.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/ *
***************************************************************************/
#include <tstring.h>
#include <tdebug.h>
#include "dsdiffproperties.h"
using namespace TagLib;
class DSDIFF::Properties::PropertiesPrivate
{
public:
PropertiesPrivate() :
length(0),
bitrate(0),
sampleRate(0),
channels(0),
sampleWidth(0),
sampleCount(0)
{
}
int length;
int bitrate;
int sampleRate;
int channels;
int sampleWidth;
unsigned long long sampleCount;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSDIFF::Properties::Properties(const unsigned int sampleRate,
const unsigned short channels,
const unsigned long long samplesCount,
const int bitrate,
ReadStyle style) : AudioProperties(style)
{
d = new PropertiesPrivate;
d->channels = channels;
d->sampleCount = samplesCount;
d->sampleWidth = 1;
d->sampleRate = sampleRate;
d->bitrate = bitrate;
d->length = d->sampleRate > 0
? static_cast<int>((d->sampleCount * 1000.0) / d->sampleRate + 0.5)
: 0;
}
DSDIFF::Properties::~Properties()
{
delete d;
}
int DSDIFF::Properties::length() const
{
return lengthInSeconds();
}
int DSDIFF::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int DSDIFF::Properties::lengthInMilliseconds() const
{
return d->length;
}
int DSDIFF::Properties::bitrate() const
{
return d->bitrate;
}
int DSDIFF::Properties::sampleRate() const
{
return d->sampleRate;
}
int DSDIFF::Properties::channels() const
{
return d->channels;
}
int DSDIFF::Properties::bitsPerSample() const
{
return d->sampleWidth;
}
long long DSDIFF::Properties::sampleCount() const
{
return d->sampleCount;
}

View File

@@ -1,241 +0,0 @@
/***************************************************************************
copyright : (C) 2013 - 2018 by Stephen F. Booth
email : me@sbooth.org
***************************************************************************/
/***************************************************************************
* 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/ *
***************************************************************************/
#include <tbytevector.h>
#include <tdebug.h>
#include <id3v2tag.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "dsffile.h"
using namespace TagLib;
// The DSF specification is located at http://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
class DSF::File::FilePrivate
{
public:
FilePrivate() :
properties(0),
tag(0)
{
}
~FilePrivate()
{
delete properties;
delete tag;
}
long long fileSize;
long long metadataOffset;
Properties *properties;
ID3v2::Tag *tag;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool DSF::File::isSupported(IOStream *stream)
{
// A DSF file has to start with "DSD "
const ByteVector id = Utils::readHeader(stream, 4, false);
return id.startsWith("DSD ");
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSF::File::File(FileName file, bool readProperties,
Properties::ReadStyle propertiesStyle) :
TagLib::File(file),
d(new FilePrivate())
{
if(isOpen())
read(readProperties, propertiesStyle);
}
DSF::File::File(IOStream *stream, bool readProperties,
Properties::ReadStyle propertiesStyle) :
TagLib::File(stream),
d(new FilePrivate())
{
if(isOpen())
read(readProperties, propertiesStyle);
}
DSF::File::~File()
{
delete d;
}
ID3v2::Tag *DSF::File::tag() const
{
return d->tag;
}
PropertyMap DSF::File::properties() const
{
return d->tag->properties();
}
PropertyMap DSF::File::setProperties(const PropertyMap &properties)
{
return d->tag->setProperties(properties);
}
DSF::Properties *DSF::File::audioProperties() const
{
return d->properties;
}
bool DSF::File::save()
{
if(readOnly()) {
debug("DSF::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("DSF::File::save() -- Trying to save invalid file.");
return false;
}
// Three things must be updated: the file size, the tag data, and the metadata offset
if(d->tag->isEmpty()) {
long long newFileSize = d->metadataOffset ? d->metadataOffset : d->fileSize;
// Update the file size
if(d->fileSize != newFileSize) {
insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
d->fileSize = newFileSize;
}
// Update the metadata offset to 0 since there is no longer a tag
if(d->metadataOffset) {
insert(ByteVector::fromLongLong(0ULL, false), 20, 8);
d->metadataOffset = 0;
}
// Delete the old tag
truncate(newFileSize);
}
else {
ByteVector tagData = d->tag->render();
long long newMetadataOffset = d->metadataOffset ? d->metadataOffset : d->fileSize;
long long newFileSize = newMetadataOffset + tagData.size();
long long oldTagSize = d->fileSize - newMetadataOffset;
// Update the file size
if(d->fileSize != newFileSize) {
insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
d->fileSize = newFileSize;
}
// Update the metadata offset
if(d->metadataOffset != newMetadataOffset) {
insert(ByteVector::fromLongLong(newMetadataOffset, false), 20, 8);
d->metadataOffset = newMetadataOffset;
}
// Delete the old tag and write the new one
insert(tagData, newMetadataOffset, static_cast<size_t>(oldTagSize));
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void DSF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
{
// A DSF file consists of four chunks: DSD chunk, format chunk, data chunk, and metadata chunk
// The file format is not chunked in the sense of a RIFF File, though
// DSD chunk
ByteVector chunkName = readBlock(4);
if(chunkName != "DSD ") {
debug("DSF::File::read() -- Not a DSF file.");
setValid(false);
return;
}
long long chunkSize = readBlock(8).toLongLong(false);
// Integrity check
if(28 != chunkSize) {
debug("DSF::File::read() -- File is corrupted, wrong chunk size");
setValid(false);
return;
}
d->fileSize = readBlock(8).toLongLong(false);
// File is malformed or corrupted
if(d->fileSize != length()) {
debug("DSF::File::read() -- File is corrupted wrong length");
setValid(false);
return;
}
d->metadataOffset = readBlock(8).toLongLong(false);
// File is malformed or corrupted
if(d->metadataOffset > d->fileSize) {
debug("DSF::File::read() -- Invalid metadata offset.");
setValid(false);
return;
}
// Format chunk
chunkName = readBlock(4);
if(chunkName != "fmt ") {
debug("DSF::File::read() -- Missing 'fmt ' chunk.");
setValid(false);
return;
}
chunkSize = readBlock(8).toLongLong(false);
d->properties = new Properties(readBlock(chunkSize), propertiesStyle);
// Skip the data chunk
// A metadata offset of 0 indicates the absence of an ID3v2 tag
if(0 == d->metadataOffset)
d->tag = new ID3v2::Tag();
else
d->tag = new ID3v2::Tag(this, d->metadataOffset);
}

View File

@@ -1,128 +0,0 @@
/***************************************************************************
copyright : (C) 2013 - 2018 by Stephen F. Booth
email : me@sbooth.org
***************************************************************************/
/***************************************************************************
* 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_DSFFILE_H
#define TAGLIB_DSFFILE_H
#include "tfile.h"
#include "id3v2tag.h"
#include "dsfproperties.h"
namespace TagLib {
//! An implementation of DSF metadata
/*!
* This is implementation of DSF metadata.
*
* This supports an ID3v2 tag as well as properties from the file.
*/
namespace DSF {
//! An implementation of TagLib::File with DSF specific methods
/*!
* This implements and provides an interface for DSF files to the
* TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing
* the abstract TagLib::File API as well as providing some additional
* information specific to DSF files.
*/
class TAGLIB_EXPORT File : public TagLib::File
{
public:
/*!
* Contructs an DSF file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*/
File(FileName file, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
/*!
* Contructs an DSF file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
/*!
* Destroys this instance of the File.
*/
virtual ~File();
/*!
* Returns the Tag for this file.
*/
ID3v2::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* This method forwards to ID3v2::Tag::properties().
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* This method forwards to ID3v2::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the DSF::AudioProperties for this file. If no audio properties
* were read then this will return a null pointer.
*/
virtual Properties *audioProperties() const;
/*!
* Saves the file.
*/
virtual bool save();
/*!
* Returns whether or not the given \a stream can be opened as a DSF
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
class FilePrivate;
FilePrivate *d;
};
}
}
#endif

View File

@@ -1,161 +0,0 @@
/***************************************************************************
copyright : (C) 2013 by Stephen F. Booth
email : me@sbooth.org
***************************************************************************/
/***************************************************************************
* 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/ *
***************************************************************************/
#include <tstring.h>
#include <tdebug.h>
#include "dsfproperties.h"
using namespace TagLib;
class DSF::Properties::PropertiesPrivate
{
public:
PropertiesPrivate() :
formatVersion(0),
formatID(0),
channelType(0),
channelNum(0),
samplingFrequency(0),
bitsPerSample(0),
sampleCount(0),
blockSizePerChannel(0),
bitrate(0),
length(0)
{
}
// Nomenclature is from DSF file format specification
unsigned int formatVersion;
unsigned int formatID;
unsigned int channelType;
unsigned int channelNum;
unsigned int samplingFrequency;
unsigned int bitsPerSample;
long long sampleCount;
unsigned int blockSizePerChannel;
// Computed
unsigned int bitrate;
unsigned int length;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSF::Properties::Properties(const ByteVector &data, ReadStyle style) : TagLib::AudioProperties(style)
{
d = new PropertiesPrivate;
read(data);
}
DSF::Properties::~Properties()
{
delete d;
}
int DSF::Properties::length() const
{
return lengthInSeconds();
}
int DSF::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int DSF::Properties::lengthInMilliseconds() const
{
return d->length;
}
int DSF::Properties::bitrate() const
{
return d->bitrate;
}
int DSF::Properties::sampleRate() const
{
return d->samplingFrequency;
}
int DSF::Properties::channels() const
{
return d->channelNum;
}
// DSF specific
int DSF::Properties::formatVersion() const
{
return d->formatVersion;
}
int DSF::Properties::formatID() const
{
return d->formatID;
}
int DSF::Properties::channelType() const
{
return d->channelType;
}
int DSF::Properties::bitsPerSample() const
{
return d->bitsPerSample;
}
long long DSF::Properties::sampleCount() const
{
return d->sampleCount;
}
int DSF::Properties::blockSizePerChannel() const
{
return d->blockSizePerChannel;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void DSF::Properties::read(const ByteVector &data)
{
d->formatVersion = data.toUInt(0U,false);
d->formatID = data.toUInt(4U,false);
d->channelType = data.toUInt(8U,false);
d->channelNum = data.toUInt(12U,false);
d->samplingFrequency = data.toUInt(16U,false);
d->bitsPerSample = data.toUInt(20U,false);
d->sampleCount = data.toLongLong(24U,false);
d->blockSizePerChannel = data.toUInt(32U,false);
d->bitrate
= static_cast<unsigned int>((d->samplingFrequency * d->bitsPerSample * d->channelNum) / 1000.0 + 0.5);
d->length
= d->samplingFrequency > 0 ? static_cast<unsigned int>(d->sampleCount * 1000.0 / d->samplingFrequency + 0.5) : 0;
}

View File

@@ -1,92 +0,0 @@
/***************************************************************************
copyright : (C) 2013 by Stephen F. Booth
email : me@sbooth.org
***************************************************************************/
/***************************************************************************
* 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_DSFPROPERTIES_H
#define TAGLIB_DSFPROPERTIES_H
#include "audioproperties.h"
namespace TagLib {
namespace DSF {
class File;
//! An implementation of audio property reading for DSF
/*!
* This reads the data from a DSF stream found in the AudioProperties
* API.
*/
class TAGLIB_EXPORT Properties : public TagLib::AudioProperties
{
public:
/*!
* Create an instance of DSF::AudioProperties with the data read from the
* ByteVector \a data.
*/
Properties(const ByteVector &data, ReadStyle style);
/*!
* Destroys this DSF::AudioProperties instance.
*/
virtual ~Properties();
// Reimplementations.
virtual int length() const;
virtual int lengthInSeconds() const;
virtual int lengthInMilliseconds() const;
virtual int bitrate() const;
virtual int sampleRate() const;
virtual int channels() const;
int formatVersion() const;
int formatID() const;
/*!
* Channel type values: 1 = mono, 2 = stereo, 3 = 3 channels,
* 4 = quad, 5 = 4 channels, 6 = 5 channels, 7 = 5.1 channels
*/
int channelType() const;
int bitsPerSample() const;
long long sampleCount() const;
int blockSizePerChannel() const;
private:
Properties(const Properties &);
Properties &operator=(const Properties &);
void read(const ByteVector &data);
class PropertiesPrivate;
PropertiesPrivate *d;
};
}
}
#endif

View File

@@ -27,6 +27,8 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <cstring>
#include <tfile.h>
#include <tfilestream.h>
#include <tstring.h>
@@ -52,8 +54,6 @@
#include "s3mfile.h"
#include "itfile.h"
#include "xmfile.h"
#include "dsffile.h"
#include "dsdifffile.h"
using namespace TagLib;
@@ -67,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);
@@ -137,10 +139,6 @@ namespace
return new IT::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "XM")
return new XM::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "DFF" || ext == "DSDIFF")
return new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "DSF")
return new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
return 0;
}
@@ -180,10 +178,6 @@ namespace
file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
else if(APE::File::isSupported(stream))
file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
else if(DSDIFF::File::isSupported(stream))
file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(DSF::File::isSupported(stream))
file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
// isSupported() only does a quick check, so double check the file here.
@@ -265,10 +259,6 @@ namespace
return new IT::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "XM")
return new XM::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "DFF" || ext == "DSDIFF")
return new DSDIFF::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "DSF")
return new DSF::File(fileName, readAudioProperties, audioPropertiesStyle);
return 0;
}
@@ -376,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");
@@ -392,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");
@@ -401,9 +394,6 @@ StringList FileRef::defaultFileExtensions()
l.append("s3m");
l.append("it");
l.append("xm");
l.append("dsf");
l.append("dff");
l.append("dsdiff"); // alias for "dff"
return l;
}
@@ -477,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

@@ -240,7 +240,7 @@ namespace TagLib {
* \see ID3v2FrameFactory
* \deprecated This value should be passed in via the constructor
*/
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
TAGLIB_DEPRECATED void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
/*!
* Returns the block of data used by FLAC::Properties for parsing the
@@ -248,7 +248,7 @@ namespace TagLib {
*
* \deprecated Always returns an empty vector.
*/
ByteVector streamInfoData(); // BIC: remove
TAGLIB_DEPRECATED ByteVector streamInfoData(); // BIC: remove
/*!
* Returns the length of the audio-stream, used by FLAC::Properties for
@@ -256,7 +256,7 @@ namespace TagLib {
*
* \deprecated Always returns zero.
*/
long streamLength(); // BIC: remove
TAGLIB_DEPRECATED long streamLength(); // BIC: remove
/*!
* Returns a list of pictures attached to the FLAC file.

View File

@@ -72,7 +72,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
@@ -120,7 +120,7 @@ namespace TagLib {
*
* \deprecated
*/
int sampleWidth() const;
TAGLIB_DEPRECATED int sampleWidth() const;
/*!
* Return the number of sample frames.

View File

@@ -277,7 +277,7 @@ void IT::File::read(bool)
// in the instrument/sample names and more characters
// afterwards. The spec does not mention such a case.
// Currently I just discard anything after a nil, but
// e.g. VLC seems to interprete a nil as a space. I
// e.g. VLC seems to interpret a nil as a space. I
// don't know what is the proper behaviour.
for(unsigned short i = 0; i < instrumentCount; ++ i) {
seek(192L + length + ((long)i << 2));

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:
@@ -175,11 +196,11 @@ MP4::Properties::read(File *file, Atoms *atoms)
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
return;
}
unit = data.toLongLong(28U);
length = data.toLongLong(36U);
unit = data.toUInt(28U);
length = data.toLongLong(32U);
}
else {
if(data.size() < 24 + 4) {
if(data.size() < 24 + 8) {
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
return;
}
@@ -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

@@ -57,7 +57,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

View File

@@ -74,9 +74,20 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) :
atom->name == "hdvd" || atom->name == "shwm") {
parseBool(atom);
}
else if(atom->name == "tmpo" || atom->name == "rate" || atom->name == "\251mvi" || atom->name == "\251mvc") {
else if(atom->name == "tmpo" || atom->name == "\251mvi" || atom->name == "\251mvc") {
parseInt(atom);
}
else if(atom->name == "rate") {
AtomDataList data = parseData2(atom);
if(!data.isEmpty()) {
AtomData val = data[0];
if (val.type == TypeUTF8) {
addItem(atom->name, StringList(String(val.data, String::UTF8)));
} else {
addItem(atom->name, (int)(val.data.toShort()));
}
}
}
else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" ||
atom->name == "sfID" || atom->name == "atID" || atom->name == "geID" ||
atom->name == "cmID") {
@@ -294,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);
@@ -480,9 +491,19 @@ MP4::Tag::save()
name == "shwm") {
data.append(renderBool(name.data(String::Latin1), it->second));
}
else if(name == "tmpo" || name == "rate" || name == "\251mvi" || name == "\251mvc") {
else if(name == "tmpo" || name == "\251mvi" || name == "\251mvc") {
data.append(renderInt(name.data(String::Latin1), it->second));
}
else if (name == "rate") {
const MP4::Item& item = it->second;
StringList value = item.toStringList();
if (value.isEmpty()) {
data.append(renderInt(name.data(String::Latin1), item));
}
else {
data.append(renderText(name.data(String::Latin1), item));
}
}
else if(name == "tvsn" || name == "tves" || name == "cnID" ||
name == "sfID" || name == "atID" || name == "geID" ||
name == "cmID") {
@@ -754,43 +775,63 @@ 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
MP4::Tag::setYear(unsigned int value)
{
d->items["\251day"] = StringList(String::number(value));
if (value == 0) {
d->items.erase("\251day");
}
else {
d->items["\251day"] = StringList(String::number(value));
}
}
void
MP4::Tag::setTrack(unsigned int value)
{
d->items["trkn"] = MP4::Item(value, 0);
if (value == 0) {
d->items.erase("trkn");
}
else {
d->items["trkn"] = MP4::Item(value, 0);
}
}
bool MP4::Tag::isEmpty() const
@@ -854,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" },
@@ -863,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" },
@@ -911,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 {
@@ -966,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

@@ -42,7 +42,7 @@ namespace TagLib {
/*!
* \deprecated
*/
typedef TagLib::Map<String, Item> ItemListMap;
TAGLIB_DEPRECATED typedef TagLib::Map<String, Item> ItemListMap;
typedef TagLib::Map<String, Item> ItemMap;
class TAGLIB_EXPORT Tag: public TagLib::Tag
@@ -74,7 +74,7 @@ namespace TagLib {
/*!
* \deprecated Use the item() and setItem() API instead
*/
ItemMap &itemListMap();
TAGLIB_DEPRECATED ItemMap &itemListMap();
/*!
* Returns a string-keyed map of the MP4::Items for this tag.
@@ -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

@@ -198,7 +198,7 @@ namespace TagLib {
* \deprecated
* \see strip
*/
void remove(int tags = AllTags);
TAGLIB_DEPRECATED void remove(int tags = AllTags);
/*!
* Returns whether or not the file on disk actually has an ID3v1 tag.

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

@@ -74,7 +74,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

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

@@ -267,7 +267,7 @@ void ChapterFrame::parseFields(const ByteVector &data)
return;
while(embPos < size - header()->size()) {
Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), (d->tagHeader != 0));
Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader);
if(!frame)
return;

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

@@ -131,12 +131,12 @@ namespace TagLib {
/*!
* \deprecated Always returns master volume.
*/
ChannelType channelType() const;
TAGLIB_DEPRECATED ChannelType channelType() const;
/*!
* \deprecated This method no longer has any effect.
*/
void setChannelType(ChannelType t);
TAGLIB_DEPRECATED void setChannelType(ChannelType t);
/*
* There was a terrible API goof here, and while this can't be changed to
@@ -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

@@ -55,7 +55,7 @@ public:
namespace {
// These functions are needed to try to aim for backward compatibility with
// an API that previously (unreasonably) required null bytes to be appeneded
// an API that previously (unreasonably) required null bytes to be appended
// at the end of identifiers explicitly by the API user.
// BIC: remove these
@@ -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)
@@ -304,7 +308,7 @@ void TableOfContentsFrame::parseFields(const ByteVector &data)
return;
while(embPos < size - header()->size()) {
Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), (d->tagHeader != 0));
Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader);
if(!frame)
return;

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);
@@ -277,7 +280,7 @@ PropertyMap TextIdentificationFrame::makeTIPLProperties() const
break;
}
if(!found){
// invalid involved role -> mark whole frame as unsupported in order to be consisten with writing
// invalid involved role -> mark whole frame as unsupported in order to be consistent with writing
map.clear();
map.unsupportedData().append(frameID());
return map;

24
taglib/mpeg/id3v2/id3v2.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef TAGLIB_ID3V2_H
#define TAGLIB_ID3V2_H
namespace TagLib {
//! An ID3v2 implementation
/*!
* This is a relatively complete and flexible framework for working with ID3v2
* tags.
*
* \see ID3v2::Tag
*/
namespace ID3v2 {
/*!
* Used to specify which version of the ID3 standard to use when saving tags.
*/
enum Version {
v3 = 3, //<! ID3v2.3
v4 = 4 //<! ID3v2.4
};
}
}
#endif

View File

@@ -38,7 +38,7 @@ namespace TagLib {
/*!
* This class implements ID3v2 extended headers. It attempts to follow,
* both semantically and programatically, the structure specified in
* both semantically and programmatically, the structure specified in
* the ID3v2 standard. The API is based on the properties of ID3v2 extended
* headers specified there. If any of the terms used in this documentation
* are unclear please check the specification in the linked section.

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;
@@ -111,8 +112,8 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) //
// check if the key is contained in the key<=>frameID mapping
ByteVector frameID = keyToFrameID(key);
if(!frameID.isEmpty()) {
// Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number) are in fact text frames.
if(frameID[0] == 'T' || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN"){ // text frame
// Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number), GRP1 (Grouping) are in fact text frames.
if(frameID[0] == 'T' || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN" || frameID == "GRP1"){ // text frame
TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8);
frame->setText(values);
return frame;
@@ -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
@@ -394,6 +398,7 @@ namespace
{ "WFED", "PODCASTURL" },
{ "MVNM", "MOVEMENTNAME" },
{ "MVIN", "MOVEMENTNUMBER" },
{ "GRP1", "GROUPING" },
};
const size_t frameTranslationSize = sizeof(frameTranslation) / sizeof(frameTranslation[0]);
@@ -401,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" },
@@ -416,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)
@@ -476,8 +485,8 @@ PropertyMap Frame::asProperties() const
// workaround until this function is virtual
if(id == "TXXX")
return dynamic_cast< const UserTextIdentificationFrame* >(this)->asProperties();
// Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number) are in fact text frames.
else if(id[0] == 'T' || id == "WFED" || id == "MVNM" || id == "MVIN")
// Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number), GRP1 (Grouping) are in fact text frames.
else if(id[0] == 'T' || id == "WFED" || id == "MVNM" || id == "MVIN" || id == "GRP1")
return dynamic_cast< const TextIdentificationFrame* >(this)->asProperties();
else if(id == "WXXX")
return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties();
@@ -489,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.
*/
@@ -89,14 +89,15 @@ namespace TagLib {
* non-binary compatible release this will be made into a non-static
* member that checks the internal ID3v2 version.
*/
static unsigned int headerSize(); // BIC: remove and make non-static
static unsigned int headerSize(); // BIC: make non-static
/*!
* Returns the size of the frame header for the given ID3v2 version.
*
* \deprecated Please see the explanation above.
*/
static unsigned int headerSize(unsigned int version); // BIC: remove and make non-static
// BIC: remove
static unsigned int headerSize(unsigned int version);
/*!
* Sets the data that will be used as the frame. Since the length is not
@@ -224,7 +225,7 @@ namespace TagLib {
* This is useful for reading strings sequentially.
*/
String readStringField(const ByteVector &data, String::Type encoding,
int *positon = 0);
int *position = 0);
/*!
* Checks a the list of string values to see if they can be used with the
@@ -255,7 +256,7 @@ namespace TagLib {
/*!
* Parses the contents of this frame as PropertyMap. If that fails, the returend
* Parses the contents of this frame as PropertyMap. If that fails, the returned
* PropertyMap will be empty, and its unsupportedData() will contain this frame's
* ID.
* BIC: Will be a virtual function in future releases.
@@ -334,7 +335,7 @@ namespace TagLib {
* \deprecated Please use the constructor below that accepts a version
* number.
*/
Header(const ByteVector &data, bool synchSafeInts);
TAGLIB_DEPRECATED Header(const ByteVector &data, bool synchSafeInts);
/*!
* Construct a Frame Header based on \a data. \a data must at least
@@ -356,7 +357,7 @@ namespace TagLib {
* \deprecated Please use the version below that accepts an ID3v2 version
* number.
*/
void setData(const ByteVector &data, bool synchSafeInts);
TAGLIB_DEPRECATED void setData(const ByteVector &data, bool synchSafeInts);
/*!
* Sets the data for the Header. \a version should indicate the ID3v2
@@ -411,6 +412,7 @@ namespace TagLib {
* removed in the next binary incompatible release (2.0) and will be
* replaced with a non-static method that checks the frame version.
*/
// BIC: make non-static
static unsigned int size();
/*!
@@ -419,6 +421,7 @@ namespace TagLib {
*
* \deprecated Please see the explanation in the version above.
*/
// BIC: remove
static unsigned int size(unsigned int version);
/*!
@@ -502,7 +505,7 @@ namespace TagLib {
/*!
* \deprecated
*/
bool frameAlterPreservation() const;
TAGLIB_DEPRECATED bool frameAlterPreservation() const;
private:
Header(const Header &);

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())
@@ -126,6 +128,11 @@ Frame *FrameFactory::createFrame(const ByteVector &data, unsigned int version) c
}
Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) const
{
return createFrame(origData, const_cast<const Header *>(tagHeader));
}
Frame *FrameFactory::createFrame(const ByteVector &origData, const Header *tagHeader) const
{
ByteVector data = origData;
unsigned int version = tagHeader->majorVersion();
@@ -198,8 +205,8 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
// Text Identification (frames 4.2)
// Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number) are in fact text frames.
if(frameID.startsWith("T") || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN") {
// Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number), GRP1 (Grouping) are in fact text frames.
if(frameID.startsWith("T") || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN" || frameID == "GRP1") {
TextIdentificationFrame *f = frameID != "TXXX"
? new TextIdentificationFrame(data, header)
@@ -277,7 +284,7 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
return f;
}
// Synchronised lyrics/text (frames 4.9)
// Synchronized lyrics/text (frames 4.9)
if(frameID == "SYLT") {
SynchronizedLyricsFrame *f = new SynchronizedLyricsFrame(data, header);
@@ -459,6 +466,7 @@ namespace
{ "WFD", "WFED" },
{ "MVN", "MVNM" },
{ "MVI", "MVIN" },
{ "GP1", "GRP1" },
};
const size_t frameConversion2Size = sizeof(frameConversion2) / sizeof(frameConversion2[0]);

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.
@@ -74,7 +74,7 @@ namespace TagLib {
* \deprecated Please use the method below that accepts a ID3v2::Header
* instance in new code.
*/
Frame *createFrame(const ByteVector &data, bool synchSafeInts) const;
TAGLIB_DEPRECATED Frame *createFrame(const ByteVector &data, bool synchSafeInts) const;
/*!
* Create a frame based on \a data. \a version should indicate the ID3v2
@@ -84,14 +84,19 @@ namespace TagLib {
* \deprecated Please use the method below that accepts a ID3v2::Header
* instance in new code.
*/
Frame *createFrame(const ByteVector &data, unsigned int version = 4) const;
TAGLIB_DEPRECATED Frame *createFrame(const ByteVector &data, unsigned int version = 4) const;
/*!
* \deprecated
*/
// BIC: remove
Frame *createFrame(const ByteVector &data, Header *tagHeader) const;
/*!
* Create a frame based on \a data. \a tagHeader should be a valid
* ID3v2::Header instance.
*/
// BIC: make virtual
Frame *createFrame(const ByteVector &data, Header *tagHeader) const;
Frame *createFrame(const ByteVector &data, const Header *tagHeader) const;
/*!
* After a tag has been read, this tries to rebuild some of them

View File

@@ -28,6 +28,7 @@
#include "tbytevector.h"
#include "taglib_export.h"
#include "id3v2.h"
namespace TagLib {

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)
@@ -462,19 +483,19 @@ PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps)
ByteVector ID3v2::Tag::render() const
{
return render(4);
return render(ID3v2::v4);
}
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,24 +588,53 @@ 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
{
return render(version == 3 ? v3 : v4);
}
ByteVector ID3v2::Tag::render(Version version) const
{
// We need to render the "tag data" first so that we have to correct size to
// render in the tag's header. The "tag data" -- everything that is included
// in ID3v2::Header::tagSize() -- includes the extended header, frames and
// padding, but does not include the tag's header or footer.
if(version != 3 && version != 4) {
debug("Unknown ID3v2 version, using ID3v2.4");
version = 4;
}
// TODO: Render the extended header.
// Downgrade the frames that ID3v2.3 doesn't support.
@@ -587,7 +643,7 @@ ByteVector ID3v2::Tag::render(int version) const
newFrames.setAutoDelete(true);
FrameList frameList;
if(version == 4) {
if(version == v4) {
frameList = d->frameList;
}
else {
@@ -601,7 +657,7 @@ ByteVector ID3v2::Tag::render(int version) const
// Loop through the frames rendering them and adding them to the tagData.
for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) {
(*it)->header()->setVersion(version);
(*it)->header()->setVersion(version == v3 ? 3 : 4);
if((*it)->header()->frameID().size() != 4) {
debug("An ID3v2 frame of unsupported or unknown type \'"
+ String((*it)->header()->frameID()) + "\' has been discarded");

View File

@@ -33,21 +33,13 @@
#include "tmap.h"
#include "taglib_export.h"
#include "id3v2.h"
#include "id3v2framefactory.h"
namespace TagLib {
class File;
//! An ID3v2 implementation
/*!
* This is a relatively complete and flexible framework for working with ID3v2
* tags.
*
* \see ID3v2::Tag
*/
namespace ID3v2 {
class Header;
@@ -201,7 +193,7 @@ namespace TagLib {
* prone to change my mind, so this gets to stay around until near a
* release.
*/
Footer *footer() const;
TAGLIB_DEPRECATED Footer *footer() const;
/*!
* Returns a reference to the frame list map. This is an FrameListMap of
@@ -310,7 +302,7 @@ namespace TagLib {
* - otherwise, the key "LYRICS:<description>" is used;
* - if the frame ID is "TIPL" (involved peoples list), and if all the
* roles defined in the frame are known in TextIdentificationFrame::involvedPeopleMap(),
* then "<role>=<name>" will be contained in the returned obejct for each
* then "<role>=<name>" will be contained in the returned object for each
* - if the frame ID is "TMCL" (musician credit list), then
* "PERFORMER:<instrument>=<name>" will be contained in the returned
* PropertyMap for each defined musician
@@ -345,14 +337,18 @@ namespace TagLib {
*/
ByteVector render() const;
/*!
* \deprecated
*/
TAGLIB_DEPRECATED ByteVector render(int version) const;
/*!
* Render the tag back to binary data, suitable to be written to disk.
*
* The \a version parameter specifies the version of the rendered
* ID3v2 tag. It can be either 4 or 3.
* The \a version parameter specifies whether ID3v2.4 (default) or ID3v2.3
* should be used.
*/
// BIC: combine with the above method
ByteVector render(int version) const;
ByteVector render(Version version) const;
/*!
* Gets the current string handler that decides how the "Latin-1" data
@@ -396,6 +392,9 @@ namespace TagLib {
*/
void setTextFrame(const ByteVector &id, const String &value);
/*!
* Downgrade frames from ID3v2.4 (used internally and by default) to ID3v2.3.
*/
void downgradeFrames(FrameList *existingFrames, FrameList *newFrames) const;
private:

View File

@@ -109,8 +109,8 @@ bool MPEG::File::isSupported(IOStream *stream)
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true, &headerOffset);
if(buffer.isEmpty())
return false;
return false;
const long originalPosition = stream->tell();
AdapterFile file(stream);
@@ -200,20 +200,30 @@ bool MPEG::File::save()
bool MPEG::File::save(int tags)
{
return save(tags, true);
return save(tags, StripOthers);
}
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)
{
return save(tags,
stripOthers ? StripOthers : StripNone,
id3v2Version == 3 ? ID3v2::v3 : ID3v2::v4,
duplicateTags ? Duplicate : DoNotDuplicate);
}
bool MPEG::File::save(int tags, StripTags strip, ID3v2::Version version, DuplicateTags duplicate)
{
if(readOnly()) {
debug("MPEG::File::save() -- File is read only.");
@@ -222,22 +232,22 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplica
// Create the tags if we've been asked to.
if(duplicateTags) {
if(duplicate == Duplicate) {
// Copy the values from the tag that does exist into the new tag,
// except if the existing tag is to be stripped.
if((tags & ID3v2) && ID3v1Tag() && !(stripOthers && !(tags & ID3v1)))
if((tags & ID3v2) && ID3v1Tag() && !(strip == StripOthers && !(tags & ID3v1)))
Tag::duplicate(ID3v1Tag(), ID3v2Tag(true), false);
if((tags & ID3v1) && d->tag[ID3v2Index] && !(stripOthers && !(tags & ID3v2)))
if((tags & ID3v1) && d->tag[ID3v2Index] && !(strip == StripOthers && !(tags & ID3v2)))
Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false);
}
// Remove all the tags not going to be saved.
if(stripOthers)
strip(~tags, false);
if(strip == StripOthers)
File::strip(~tags, false);
if(ID3v2 & tags) {
@@ -248,7 +258,7 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplica
if(d->ID3v2Location < 0)
d->ID3v2Location = 0;
const ByteVector data = ID3v2Tag()->render(id3v2Version);
const ByteVector data = ID3v2Tag()->render(version);
insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
if(d->APELocation >= 0)
@@ -263,7 +273,7 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplica
// ID3v2 tag is empty. Remove the old one.
strip(ID3v2, false);
File::strip(ID3v2, false);
}
}
@@ -287,7 +297,7 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplica
// ID3v1 tag is empty. Remove the old one.
strip(ID3v1, false);
File::strip(ID3v1, false);
}
}
@@ -316,7 +326,7 @@ bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplica
// APE tag is empty. Remove the old one.
strip(APE, false);
File::strip(APE, false);
}
}

View File

@@ -32,6 +32,8 @@
#include "mpegproperties.h"
#include "id3v2.h"
namespace TagLib {
namespace ID3v2 { class Tag; class FrameFactory; }
@@ -191,49 +193,39 @@ namespace TagLib {
bool save(int tags);
/*!
* Save the file. This will attempt to save all of the tag types that are
* specified by OR-ing together TagTypes values. The save() method above
* uses AllTags. This returns true if saving was successful.
*
* If \a stripOthers is true this strips all tags not included in the mask,
* but does not modify them in memory, so later calls to save() which make
* use of these tags will remain valid. This also strips empty tags.
* \deprecated
*/
// BIC: combine with the above method
bool save(int tags, bool stripOthers);
TAGLIB_DEPRECATED bool save(int tags, bool stripOthers);
/*!
* \deprecated
*/
// BIC: combine with the above method
TAGLIB_DEPRECATED bool save(int tags, bool stripOthers, int id3v2Version);
/*!
* \deprecated
*/
// BIC: combine with the above method
TAGLIB_DEPRECATED bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags);
/*!
* Save the file. This will attempt to save all of the tag types that are
* specified by OR-ing together TagTypes values. The save() method above
* uses AllTags. This returns true if saving was successful.
* specified by OR-ing together TagTypes values.
*
* If \a stripOthers is true this strips all tags not included in the mask,
* but does not modify them in memory, so later calls to save() which make
* use of these tags will remain valid. This also strips empty tags.
* \a strip can be set to strip all tags except those in \a tags. Those
* tags will not be modified in memory, and thus remain valid.
*
* The \a id3v2Version parameter specifies the version of the saved
* ID3v2 tag. It can be either 4 or 3.
* \a version specifies the ID3v2 version to be used for writing tags. By
* default, the latest standard, ID3v2.4 is used.
*
* If \a duplicate is set to DuplicateTags and at least one tag -- ID3v1
* or ID3v2 -- exists this will duplicate its content into the other tag.
*/
// BIC: combine with the above method
bool save(int tags, bool stripOthers, int id3v2Version);
/*!
* Save the file. This will attempt to save all of the tag types that are
* specified by OR-ing together TagTypes values. The save() method above
* uses AllTags. This returns true if saving was successful.
*
* If \a stripOthers is true this strips all tags not included in the mask,
* but does not modify them in memory, so later calls to save() which make
* use of these tags will remain valid. This also strips empty tags.
*
* The \a id3v2Version parameter specifies the version of the saved
* ID3v2 tag. It can be either 4 or 3.
*
* If \a duplicateTags is true and at least one tag -- ID3v1 or ID3v2 --
* exists this will duplicate its content into the other tag.
*/
// BIC: combine with the above method
bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags);
bool save(int tags, StripTags strip,
ID3v2::Version version = ID3v2::v4,
DuplicateTags duplicate = Duplicate);
/*!
* Returns a pointer to the ID3v2 tag of the file.
@@ -325,7 +317,7 @@ namespace TagLib {
* \see ID3v2FrameFactory
* \deprecated This value should be passed in via the constructor
*/
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
TAGLIB_DEPRECATED void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
/*!
* Returns the position in the file of the first MPEG frame.

View File

@@ -52,7 +52,7 @@ namespace TagLib {
*
* \deprecated
*/
Header(const ByteVector &data);
TAGLIB_DEPRECATED Header(const ByteVector &data);
/*!
* Parses an MPEG header based on \a file and \a offset.

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

@@ -67,7 +67,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

View File

@@ -111,8 +111,8 @@ namespace TagLib {
*
* \deprecated Always returns 0.
*/
static int xingHeaderOffset(TagLib::MPEG::Header::Version v,
TagLib::MPEG::Header::ChannelMode c);
TAGLIB_DEPRECATED static int xingHeaderOffset(TagLib::MPEG::Header::Version v,
TagLib::MPEG::Header::ChannelMode c);
private:
XingHeader(const XingHeader &);

View File

@@ -93,7 +93,7 @@ namespace TagLib {
*
* \deprecated Always returns null.
*/
Page* getCopyWithNewPageSequenceNumber(int sequenceNumber);
TAGLIB_DEPRECATED Page *getCopyWithNewPageSequenceNumber(int sequenceNumber);
/*!
* Returns the index of the first packet wholly or partially contained in

View File

@@ -156,7 +156,7 @@ namespace TagLib {
/*!
* A special value of containing the position of the packet to be
* interpreted by the codec. It is only supported here so that it may be
* coppied from one page to another.
* copied from one page to another.
*
* \see absoluteGranularPosition()
*/

View File

@@ -163,8 +163,14 @@ void Opus::Properties::read(File *file)
if(frameCount > 0) {
const double length = frameCount * 1000.0 / 48000.0;
long fileLengthWithoutOverhead = file->length();
// Ignore the two mandatory header packets, see "3. Packet Organization"
// in https://tools.ietf.org/html/rfc7845.html
for (unsigned int i = 0; i < 2; ++i) {
fileLengthWithoutOverhead -= file->packet(i).size();
}
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(file->length() * 8.0 / length + 0.5);
d->bitrate = static_cast<int>(fileLengthWithoutOverhead * 8.0 / length + 0.5);
}
}
else {

View File

@@ -69,7 +69,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

View File

@@ -131,6 +131,7 @@ void Speex::File::read(bool readProperties)
if(!speexHeaderData.startsWith("Speex ")) {
debug("Speex::File::read() -- invalid Speex identification header");
setValid(false);
return;
}

View File

@@ -182,8 +182,14 @@ void Speex::Properties::read(File *file)
if(frameCount > 0) {
const double length = frameCount * 1000.0 / d->sampleRate;
long fileLengthWithoutOverhead = file->length();
// Ignore the two header packets, see "Ogg file format" in
// https://www.speex.org/docs/manual/speex-manual/node8.html
for (unsigned int i = 0; i < 2; ++i) {
fileLengthWithoutOverhead -= file->packet(i).size();
}
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(file->length() * 8.0 / length + 0.5);
d->bitrate = static_cast<int>(fileLengthWithoutOverhead * 8.0 / length + 0.5);
}
}
else {

View File

@@ -69,7 +69,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

View File

@@ -186,9 +186,14 @@ void Vorbis::Properties::read(File *file)
if(frameCount > 0) {
const double length = frameCount * 1000.0 / d->sampleRate;
long fileLengthWithoutOverhead = file->length();
// Ignore the three initial header packets, see "1.3.1. Decode Setup" in
// https://xiph.org/vorbis/doc/Vorbis_I_spec.html
for (unsigned int i = 0; i < 3; ++i) {
fileLengthWithoutOverhead -= file->packet(i).size();
}
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(file->length() * 8.0 / length + 0.5);
d->bitrate = static_cast<int>(fileLengthWithoutOverhead * 8.0 / length + 0.5);
}
}
else {

View File

@@ -75,7 +75,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

View File

@@ -186,7 +186,7 @@ namespace TagLib {
* \deprecated Using this method may lead to a linkage error.
*/
// BIC: remove and merge with below
void removeField(const String &key, const String &value = String::null);
TAGLIB_DEPRECATED void removeField(const String &key, const String &value = String());
/*!
* Remove all the fields specified by \a key.

View File

@@ -117,6 +117,11 @@ RIFF::AIFF::Properties *RIFF::AIFF::File::audioProperties() const
}
bool RIFF::AIFF::File::save()
{
return save(ID3v2::v4);
}
bool RIFF::AIFF::File::save(ID3v2::Version version)
{
if(readOnly()) {
debug("RIFF::AIFF::File::save() -- File is read only.");
@@ -135,7 +140,7 @@ bool RIFF::AIFF::File::save()
}
if(tag() && !tag()->isEmpty()) {
setChunkData("ID3 ", d->tag->render());
setChunkData("ID3 ", d->tag->render(version));
d->hasID3v2 = true;
}

View File

@@ -119,6 +119,11 @@ namespace TagLib {
*/
virtual bool save();
/*!
* Save using a specific ID3v2 version (e.g. v3)
*/
bool save(ID3v2::Version version);
/*!
* Returns whether or not the file on disk actually has an ID3v2 tag.
*

View File

@@ -52,7 +52,7 @@ namespace TagLib {
*
* \deprecated
*/
Properties(const ByteVector &data, ReadStyle style);
TAGLIB_DEPRECATED Properties(const ByteVector &data, ReadStyle style);
/*!
* Create an instance of AIFF::Properties with the data read from the
@@ -73,7 +73,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
@@ -119,7 +119,7 @@ namespace TagLib {
*
* \deprecated
*/
int sampleWidth() const;
TAGLIB_DEPRECATED int sampleWidth() const;
/*!
* Returns the number of sample frames

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;
}
@@ -325,9 +323,20 @@ void RIFF::File::read()
if(offset & 1) {
seek(offset);
const ByteVector iByte = readBlock(1);
if(iByte.size() == 1 && iByte[0] == '\0') {
chunk.padding = 1;
offset++;
if(iByte.size() == 1) {
bool skipPadding = iByte[0] == '\0';
if(!skipPadding) {
// Padding byte is not zero, check if it is good to ignore it
const ByteVector fourCcAfterPadding = readBlock(4);
if(isValidChunkName(fourCcAfterPadding)) {
// Use the padding, it is followed by a valid chunk name.
skipPadding = true;
}
}
if(skipPadding) {
chunk.padding = 1;
offset++;
}
}
}

View File

@@ -151,6 +151,13 @@ bool RIFF::WAV::File::save()
}
bool RIFF::WAV::File::save(TagTypes tags, bool stripOthers, int id3v2Version)
{
return save(tags,
stripOthers ? StripOthers : StripNone,
id3v2Version == 3 ? ID3v2::v3 : ID3v2::v4);
}
bool RIFF::WAV::File::save(TagTypes tags, StripTags strip, ID3v2::Version version)
{
if(readOnly()) {
debug("RIFF::WAV::File::save() -- File is read only.");
@@ -162,14 +169,14 @@ bool RIFF::WAV::File::save(TagTypes tags, bool stripOthers, int id3v2Version)
return false;
}
if(stripOthers)
strip(static_cast<TagTypes>(AllTags & ~tags));
if(strip == StripOthers)
File::strip(static_cast<TagTypes>(AllTags & ~tags));
if(tags & ID3v2) {
removeTagChunks(ID3v2);
if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
setChunkData("ID3 ", ID3v2Tag()->render(id3v2Version));
setChunkData("ID3 ", ID3v2Tag()->render(version));
d->hasID3v2 = true;
}
}

View File

@@ -159,7 +159,19 @@ namespace TagLib {
*/
virtual bool save();
bool save(TagTypes tags, bool stripOthers = true, int id3v2Version = 4);
/*!
* \deprecated
*/
TAGLIB_DEPRECATED bool save(TagTypes tags, bool stripOthers, int id3v2Version = 4);
/*!
* Save the file. If \a strip is specified, it is possible to choose if
* tags not specified in \a tags should be stripped from the file or
* retained. With \a version, it is possible to specify whether ID3v2.4
* or ID3v2.3 should be used.
*/
bool save(TagTypes tags, StripTags strip = StripOthers,
ID3v2::Version version = ID3v2::v4);
/*!
* Returns whether or not the file on disk actually has an ID3v2 tag.

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

@@ -55,7 +55,7 @@ namespace TagLib {
*
* \deprecated
*/
Properties(const ByteVector &data, ReadStyle style);
TAGLIB_DEPRECATED Properties(const ByteVector &data, ReadStyle style);
/*!
* Create an instance of WAV::Properties with the data read from the
@@ -63,7 +63,7 @@ namespace TagLib {
*
* \deprecated
*/
Properties(const ByteVector &data, unsigned int streamLength, ReadStyle style);
TAGLIB_DEPRECATED Properties(const ByteVector &data, unsigned int streamLength, ReadStyle style);
/*!
* Create an instance of WAV::Properties with the data read from the
@@ -84,7 +84,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
@@ -130,7 +130,7 @@ namespace TagLib {
*
* \deprecated
*/
int sampleWidth() const;
TAGLIB_DEPRECATED int sampleWidth() const;
/*!
* Returns the number of sample frames.

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\"")
@@ -44,6 +44,16 @@
#define TAGLIB_CONSTRUCT_BITSET(x) static_cast<unsigned long>(x)
#endif
#if __cplusplus >= 201402
#define TAGLIB_DEPRECATED [[deprecated]]
#elif defined(__GNUC__) || defined(__clang__)
#define TAGLIB_DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER)
#define TAGLIB_DEPRECATED __declspec(deprecated)
#else
#define TAGLIB_DEPRECATED
#endif
#include <string>
//! A namespace for all TagLib related classes and functions

View File

@@ -281,7 +281,7 @@ namespace TagLib {
* \deprecated
*/
// BIC: remove
bool isNull() const;
TAGLIB_DEPRECATED bool isNull() const;
/*!
* Returns true if the ByteVector is empty.
@@ -595,7 +595,7 @@ namespace TagLib {
* \deprecated
*/
// BIC: remove
static ByteVector null;
TAGLIB_DEPRECATED static ByteVector null;
/*!
* Returns a hex-encoded copy of the byte vector.

View File

@@ -63,8 +63,6 @@
#include "itfile.h"
#include "xmfile.h"
#include "mp4file.h"
#include "dsffile.h"
#include "dsdifffile.h"
using namespace TagLib;
@@ -150,10 +148,6 @@ PropertyMap File::properties() const
return dynamic_cast<const MP4::File* >(this)->properties();
if(dynamic_cast<const ASF::File* >(this))
return dynamic_cast<const ASF::File* >(this)->properties();
if(dynamic_cast<const DSF::File* >(this))
return dynamic_cast<const DSF::File* >(this)->properties();
if(dynamic_cast<const DSDIFF::File* >(this))
return dynamic_cast<const DSDIFF::File* >(this)->properties();
return tag()->properties();
}
@@ -183,10 +177,6 @@ void File::removeUnsupportedProperties(const StringList &properties)
dynamic_cast<MP4::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<ASF::File* >(this))
dynamic_cast<ASF::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<DSF::File* >(this))
dynamic_cast<DSF::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<DSDIFF::File* >(this))
dynamic_cast<DSDIFF::File* >(this)->removeUnsupportedProperties(properties);
else
tag()->removeUnsupportedProperties(properties);
}
@@ -229,10 +219,6 @@ PropertyMap File::setProperties(const PropertyMap &properties)
return dynamic_cast<MP4::File* >(this)->setProperties(properties);
else if(dynamic_cast<ASF::File* >(this))
return dynamic_cast<ASF::File* >(this)->setProperties(properties);
else if(dynamic_cast<DSF::File* >(this))
return dynamic_cast<DSF::File* >(this)->setProperties(properties);
else if(dynamic_cast<DSDIFF::File* >(this))
return dynamic_cast<DSDIFF::File* >(this)->setProperties(properties);
else
return tag()->setProperties(properties);
}

View File

@@ -62,6 +62,23 @@ namespace TagLib {
End
};
/*!
* Specify which tags to strip either explicitly, or on save.
*/
enum StripTags {
StripNone, //<! Don't strip any 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 synchronized.
*/
enum DuplicateTags {
Duplicate, //<! Synchronize values between different tag types
DoNotDuplicate //<! Do not synchronize values between different tag types
};
/*!
* Destroys this File instance.
*/
@@ -246,14 +263,14 @@ namespace TagLib {
*
* \deprecated
*/
static bool isReadable(const char *file);
TAGLIB_DEPRECATED static bool isReadable(const char *file);
/*!
* Returns true if \a file can be opened for writing.
*
* \deprecated
*/
static bool isWritable(const char *name);
TAGLIB_DEPRECATED static bool isWritable(const char *name);
protected:
/*!

View File

@@ -264,7 +264,7 @@ void FileStream::insert(const ByteVector &data, unsigned long start, unsigned lo
// Now I'll explain the steps in this ugliness:
// First, make sure that we're working with a buffer that is longer than
// the *differnce* in the tag sizes. We want to avoid overwriting parts
// the *difference* in the tag sizes. We want to avoid overwriting parts
// that aren't yet in memory, so this is necessary.
unsigned long bufferLength = bufferSize();
@@ -493,9 +493,10 @@ void FileStream::truncate(long length)
#else
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

@@ -340,7 +340,7 @@ namespace TagLib {
* \deprecated
*/
// BIC: remove
bool isNull() const;
TAGLIB_DEPRECATED bool isNull() const;
/*!
* Returns a ByteVector containing the string's data. If \a t is Latin1 or
@@ -525,7 +525,7 @@ namespace TagLib {
* \deprecated
*/
// BIC: remove
static String null;
TAGLIB_DEPRECATED static String null;
protected:
/*!
@@ -541,7 +541,7 @@ namespace TagLib {
* may lead to a linkage error.
*/
// BIC: remove
static const Type WCharByteOrder;
TAGLIB_DEPRECATED static const Type WCharByteOrder;
class StringPrivate;
StringPrivate *d;

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

@@ -166,7 +166,7 @@ namespace TagLib {
* \see ID3v2FrameFactory
* \deprecated This value should be passed in via the constructor
*/
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
TAGLIB_DEPRECATED void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
/*!
* Saves the file.

View File

@@ -69,7 +69,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

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

@@ -58,12 +58,12 @@ namespace TagLib {
* \deprecated This constructor will be dropped in favor of the one below
* in a future version.
*/
Properties(const ByteVector &data, long streamLength, ReadStyle style = Average);
TAGLIB_DEPRECATED Properties(const ByteVector &data, long streamLength,
ReadStyle style = Average);
/*!
* Create an instance of WavPack::Properties.
*/
// BIC: merge with the above constructor
Properties(File *file, long streamLength, ReadStyle style = Average);
/*!
@@ -79,7 +79,7 @@ namespace TagLib {
*
* \deprecated
*/
virtual int length() const;
TAGLIB_DEPRECATED virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to

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;
@@ -592,7 +592,7 @@ void XM::File::read(bool)
sumSampleCount += sampleCount;
// wouldn't know which header size to assume otherwise:
READ_ASSERT(instrumentHeaderSize >= count + 4 && readU32L(sampleHeaderSize));
// skip unhandeled header proportion:
// skip unhandled header proportion:
seek(instrumentHeaderSize - count - 4, Current);
for(unsigned short j = 0; j < sampleCount; ++ j) {
@@ -620,7 +620,7 @@ void XM::File::read(bool)
unsigned int count = sample.read(*this, sampleHeaderSize);
READ_ASSERT(count == std::min(sampleHeaderSize, (unsigned long)sample.size()));
// skip unhandeled header proportion:
// skip unhandled header proportion:
seek(sampleHeaderSize - count, Current);
offset += sampleLength;
@@ -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");

View File

@@ -1,3 +1,5 @@
set(CMAKE_CXX_STANDARD 11)
INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_SOURCE_DIR}/../taglib
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/toolkit
@@ -24,8 +26,6 @@ INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/s3m
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/it
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/xm
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsf
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsdiff
)
SET(test_runner_SRCS
@@ -68,8 +68,6 @@ SET(test_runner_SRCS
test_mpc.cpp
test_opus.cpp
test_speex.cpp
test_dsf.cpp
test_dsdiff.cpp
)
INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR})

BIN
tests/data/dsd_stereo.wv Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More