mirror of
https://github.com/taglib/taglib.git
synced 2026-08-14 14:27:01 -04:00
Merge remote-tracking branch 'official/master'
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
cmake_install.cmake
|
||||
cmake_uninstall.cmake
|
||||
Makefile
|
||||
CTestTestfile.cmake
|
||||
CMakeFiles/
|
||||
*.so
|
||||
*.so.*
|
||||
@@ -16,6 +18,7 @@ CMakeFiles/
|
||||
/config.h
|
||||
/taglib.pc
|
||||
/tests/test_runner
|
||||
/tests/Testing
|
||||
/taglib_config.h
|
||||
/taglib-config
|
||||
/bindings/c/taglib_c.pc
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
language: cpp
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
install: sudo apt-get install libcppunit-dev zlib1g-dev
|
||||
script: cmake -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON . && make && make check
|
||||
|
||||
Regular → Executable
+11
-4
@@ -56,9 +56,9 @@ set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VE
|
||||
# 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 12)
|
||||
set(TAGLIB_SOVERSION_CURRENT 13)
|
||||
set(TAGLIB_SOVERSION_REVISION 0)
|
||||
set(TAGLIB_SOVERSION_AGE 11)
|
||||
set(TAGLIB_SOVERSION_AGE 12)
|
||||
|
||||
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
|
||||
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")
|
||||
@@ -66,8 +66,15 @@ math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}")
|
||||
|
||||
include(ConfigureChecks.cmake)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config )
|
||||
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config DESTINATION ${BIN_INSTALL_DIR})
|
||||
if(NOT WIN32)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config )
|
||||
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config DESTINATION ${BIN_INSTALL_DIR})
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd )
|
||||
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd DESTINATION ${BIN_INSTALL_DIR})
|
||||
endif()
|
||||
|
||||
if(NOT WIN32 AND NOT BUILD_FRAMEWORK)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc )
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
TagLib 1.8 BETA (Jul 14, 2012)
|
||||
==============================
|
||||
TagLib 1.9 (In Development)
|
||||
==========================
|
||||
|
||||
* Added support for the Ogg Opus file format.
|
||||
* Added support for INFO tags in WAV files.
|
||||
* Changed FileStream to use Windows file API.
|
||||
* Included taglib-config.cmd script for Windows.
|
||||
* New ID3v1::Tag methods for working directly with genre numbers.
|
||||
* New MPEG::File methods for checking which tags are saved in the file.
|
||||
* Better parsing of corrupted FLAC files.
|
||||
* Fixed saving of PropertyMap comments without description into ID3v2 tags.
|
||||
* Fixed crash when parsing certain XM files.
|
||||
* Fixed compilation of unit test with clang.
|
||||
|
||||
TagLib 1.8 (Sep 6, 2012)
|
||||
========================
|
||||
|
||||
1.8:
|
||||
|
||||
* Added support for OWNE ID3 frames.
|
||||
* Changed key validation in the new PropertyMap API.
|
||||
* ID3v1::Tag::setStringHandler will no londer delete the previous handler,
|
||||
the caller is responsible for this.
|
||||
* File objects will also no longer delete the passed IOStream objects. It
|
||||
should be done in the caller code after the File object is no longer
|
||||
used.
|
||||
* Added ID3v2::Tag::setLatin1StringHandler for custom handling of
|
||||
latin1-encoded text in ID3v2 frames.
|
||||
* Fixed validation of ID3v2 frame IDs (IDs with '0' were ignored).
|
||||
|
||||
1.8 BETA:
|
||||
|
||||
* New API for accessing tags by name.
|
||||
* New abstract I/O stream layer to allow custom I/O handlers.
|
||||
|
||||
+20
-1
@@ -23,10 +23,12 @@
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <fileref.h>
|
||||
#include <tag.h>
|
||||
#include <tpropertymap.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -49,7 +51,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
TagLib::Tag *tag = f.tag();
|
||||
|
||||
cout << "-- TAG --" << endl;
|
||||
cout << "-- TAG (basic) --" << endl;
|
||||
cout << "title - \"" << tag->title() << "\"" << endl;
|
||||
cout << "artist - \"" << tag->artist() << "\"" << endl;
|
||||
cout << "album - \"" << tag->album() << "\"" << endl;
|
||||
@@ -57,6 +59,23 @@ int main(int argc, char *argv[])
|
||||
cout << "comment - \"" << tag->comment() << "\"" << endl;
|
||||
cout << "track - \"" << tag->track() << "\"" << endl;
|
||||
cout << "genre - \"" << tag->genre() << "\"" << endl;
|
||||
|
||||
TagLib::PropertyMap tags = f.file()->properties();
|
||||
|
||||
unsigned int longest = 0;
|
||||
for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) {
|
||||
if (i->first.size() > longest) {
|
||||
longest = i->first.size();
|
||||
}
|
||||
}
|
||||
|
||||
cout << "-- TAG (properties) --" << endl;
|
||||
for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) {
|
||||
for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
cout << left << std::setw(longest) << i->first << " - " << '"' << *j << '"' << endl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(!f.isNull() && f.audioProperties()) {
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
@echo off
|
||||
goto beginning
|
||||
*
|
||||
* It is what it is, you can do with it as you please.
|
||||
*
|
||||
* Just don't blame me if it teaches your computer to smoke!
|
||||
*
|
||||
* -Enjoy
|
||||
* fh :)_~
|
||||
*
|
||||
:beginning
|
||||
if /i "%1#" == "--libs#" goto doit
|
||||
if /i "%1#" == "--cflags#" goto doit
|
||||
if /i "%1#" == "--version#" goto doit
|
||||
if /i "%1#" == "--prefix#" goto doit
|
||||
|
||||
echo "usage: %0 [OPTIONS]"
|
||||
echo [--libs]
|
||||
echo [--cflags]
|
||||
echo [--version]
|
||||
echo [--prefix]
|
||||
goto theend
|
||||
|
||||
*
|
||||
* NOTE: Windows does not assume libraries are prefixed with 'lib'.
|
||||
* NOTE: If '-llibtag' is the last element, it is easily appended in the users installation/makefile process
|
||||
* to allow for static, shared or debug builds.
|
||||
* 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#" == "--version#" echo ${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}
|
||||
if /i "%1#" == "--prefix#" echo ${CMAKE_INSTALL_PREFIX}
|
||||
|
||||
:theend
|
||||
|
||||
+13
-1
@@ -10,6 +10,7 @@ include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mp4
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ogg/vorbis
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ogg/speex
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ogg/opus
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2/frames
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v1
|
||||
@@ -65,6 +66,7 @@ set(tag_HDRS
|
||||
mpeg/id3v2/frames/attachedpictureframe.h
|
||||
mpeg/id3v2/frames/commentsframe.h
|
||||
mpeg/id3v2/frames/generalencapsulatedobjectframe.h
|
||||
mpeg/id3v2/frames/ownershipframe.h
|
||||
mpeg/id3v2/frames/popularimeterframe.h
|
||||
mpeg/id3v2/frames/privateframe.h
|
||||
mpeg/id3v2/frames/relativevolumeframe.h
|
||||
@@ -82,6 +84,8 @@ set(tag_HDRS
|
||||
ogg/flac/oggflacfile.h
|
||||
ogg/speex/speexfile.h
|
||||
ogg/speex/speexproperties.h
|
||||
ogg/opus/opusfile.h
|
||||
ogg/opus/opusproperties.h
|
||||
flac/flacfile.h
|
||||
flac/flacpicture.h
|
||||
flac/flacproperties.h
|
||||
@@ -102,6 +106,7 @@ set(tag_HDRS
|
||||
riff/aiff/aiffproperties.h
|
||||
riff/wav/wavfile.h
|
||||
riff/wav/wavproperties.h
|
||||
riff/wav/infotag.h
|
||||
asf/asffile.h
|
||||
asf/asfproperties.h
|
||||
asf/asftag.h
|
||||
@@ -151,6 +156,7 @@ set(frames_SRCS
|
||||
mpeg/id3v2/frames/attachedpictureframe.cpp
|
||||
mpeg/id3v2/frames/commentsframe.cpp
|
||||
mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp
|
||||
mpeg/id3v2/frames/ownershipframe.cpp
|
||||
mpeg/id3v2/frames/popularimeterframe.cpp
|
||||
mpeg/id3v2/frames/privateframe.cpp
|
||||
mpeg/id3v2/frames/relativevolumeframe.cpp
|
||||
@@ -217,6 +223,11 @@ set(speex_SRCS
|
||||
ogg/speex/speexproperties.cpp
|
||||
)
|
||||
|
||||
set(opus_SRCS
|
||||
ogg/opus/opusfile.cpp
|
||||
ogg/opus/opusproperties.cpp
|
||||
)
|
||||
|
||||
set(trueaudio_SRCS
|
||||
trueaudio/trueaudiofile.cpp
|
||||
trueaudio/trueaudioproperties.cpp
|
||||
@@ -242,6 +253,7 @@ set(aiff_SRCS
|
||||
set(wav_SRCS
|
||||
riff/wav/wavfile.cpp
|
||||
riff/wav/wavproperties.cpp
|
||||
riff/wav/infotag.cpp
|
||||
)
|
||||
|
||||
set(mod_SRCS
|
||||
@@ -284,7 +296,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}
|
||||
${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS}
|
||||
tag.cpp
|
||||
tagunion.cpp
|
||||
fileref.cpp
|
||||
|
||||
@@ -90,14 +90,16 @@ APE::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
APE::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
APE::File::~File()
|
||||
|
||||
@@ -95,6 +95,9 @@ namespace TagLib {
|
||||
* Contructs an WavPack 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
@@ -129,6 +132,7 @@ namespace TagLib {
|
||||
* has no tag at all, APE will be created.
|
||||
*/
|
||||
PropertyMap setProperties(const PropertyMap &);
|
||||
|
||||
/*!
|
||||
* Returns the APE::Properties for this file. If no audio properties
|
||||
* were read then this will return a null pointer.
|
||||
|
||||
@@ -260,7 +260,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
|
||||
|
||||
bool APE::Tag::checkKey(const String &key)
|
||||
{
|
||||
if(key.size() < 2 or key.size() > 16)
|
||||
if(key.size() < 2 || key.size() > 16)
|
||||
return false;
|
||||
for(String::ConstIterator it = key.begin(); it != key.end(); it++)
|
||||
// only allow printable ASCII including space (32..127)
|
||||
|
||||
+22
-4
@@ -29,6 +29,7 @@
|
||||
|
||||
#include <tdebug.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tstring.h>
|
||||
#include "asffile.h"
|
||||
#include "asftag.h"
|
||||
@@ -348,7 +349,7 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
|
||||
else {
|
||||
obj = new UnknownObject(guid);
|
||||
}
|
||||
obj->parse(file, size);
|
||||
obj->parse(file, (unsigned int)size);
|
||||
objects.append(obj);
|
||||
dataPos += size;
|
||||
}
|
||||
@@ -372,14 +373,16 @@ ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle proper
|
||||
: TagLib::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
ASF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
: TagLib::File(stream)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
ASF::File::~File()
|
||||
@@ -401,6 +404,21 @@ ASF::Tag *ASF::File::tag() const
|
||||
return d->tag;
|
||||
}
|
||||
|
||||
PropertyMap ASF::File::properties() const
|
||||
{
|
||||
return d->tag->properties();
|
||||
}
|
||||
|
||||
void ASF::File::removeUnsupportedProperties(const StringList &properties)
|
||||
{
|
||||
d->tag->removeUnsupportedProperties(properties);
|
||||
}
|
||||
|
||||
PropertyMap ASF::File::setProperties(const PropertyMap &properties)
|
||||
{
|
||||
return d->tag->setProperties(properties);
|
||||
}
|
||||
|
||||
ASF::Properties *ASF::File::audioProperties() const
|
||||
{
|
||||
return d->properties;
|
||||
@@ -535,7 +553,7 @@ bool ASF::File::save()
|
||||
data.append(d->objects[i]->render(this));
|
||||
}
|
||||
data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data;
|
||||
insert(data, 0, d->size);
|
||||
insert(data, 0, (TagLib::ulong)d->size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ namespace TagLib {
|
||||
*
|
||||
* \note In the current implementation, both \a readProperties and
|
||||
* \a propertiesStyle are ignored.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
@@ -64,6 +67,9 @@ namespace TagLib {
|
||||
*
|
||||
* \note In the current implementation, both \a readProperties and
|
||||
* \a propertiesStyle are ignored.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
@@ -84,6 +90,22 @@ namespace TagLib {
|
||||
*/
|
||||
virtual Tag *tag() const;
|
||||
|
||||
/*!
|
||||
* Implements the unified property interface -- export function.
|
||||
*/
|
||||
PropertyMap properties() const;
|
||||
|
||||
/*!
|
||||
* Removes unsupported properties. Forwards to the actual Tag's
|
||||
* removeUnsupportedProperties() function.
|
||||
*/
|
||||
void removeUnsupportedProperties(const StringList &properties);
|
||||
|
||||
/*!
|
||||
* Implements the unified property interface -- import function.
|
||||
*/
|
||||
PropertyMap setProperties(const PropertyMap &);
|
||||
|
||||
/*!
|
||||
* Returns the ASF audio properties for this file.
|
||||
*/
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <tpropertymap.h>
|
||||
#include "asftag.h"
|
||||
|
||||
using namespace TagLib;
|
||||
@@ -196,3 +197,162 @@ bool ASF::Tag::isEmpty() const
|
||||
d->attributeListMap.isEmpty();
|
||||
}
|
||||
|
||||
static const char *keyTranslation[][2] = {
|
||||
{ "WM/AlbumTitle", "ALBUM" },
|
||||
{ "WM/Composer", "COMPOSER" },
|
||||
{ "WM/Writer", "WRITER" },
|
||||
{ "WM/Conductor", "CONDUCTOR" },
|
||||
{ "WM/ModifiedBy", "REMIXER" },
|
||||
{ "WM/Year", "DATE" },
|
||||
{ "WM/OriginalReleaseYear", "ORIGINALDATE" },
|
||||
{ "WM/Producer", "PRODUCER" },
|
||||
{ "WM/ContentGroupDescription", "GROUPING" },
|
||||
{ "WM/SubTitle", "SUBTITLE" },
|
||||
{ "WM/SetSubTitle", "DISCSUBTITLE" },
|
||||
{ "WM/TrackNumber", "TRACKNUMBER" },
|
||||
{ "WM/PartOfSet", "DISCNUMBER" },
|
||||
{ "WM/Genre", "GENRE" },
|
||||
{ "WM/BeatsPerMinute", "BPM" },
|
||||
{ "WM/Mood", "MOOD" },
|
||||
{ "WM/ISRC", "ISRC" },
|
||||
{ "WM/Lyrics", "LYRICS" },
|
||||
{ "WM/Media", "MEDIA" },
|
||||
{ "WM/Publisher", "LABEL" },
|
||||
{ "WM/CatalogNo", "CATALOGNUMBER" },
|
||||
{ "WM/Barcode", "BARCODE" },
|
||||
{ "WM/EncodedBy", "ENCODEDBY" },
|
||||
{ "WM/AlbumSortOrder", "ALBUMSORT" },
|
||||
{ "WM/AlbumArtistSortOrder", "ALBUMARTISTSORT" },
|
||||
{ "WM/ArtistSortOrder", "ARTISTSORT" },
|
||||
{ "WM/TitleSortOrder", "TITLESORT" },
|
||||
{ "WM/Script", "SCRIPT" },
|
||||
{ "WM/Language", "LANGUAGE" },
|
||||
{ "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" },
|
||||
{ "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "MusicIP/PUID", "MUSICIP_PUID" },
|
||||
{ "Acoustid/Id", "ACOUSTID_ID" },
|
||||
{ "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" },
|
||||
};
|
||||
|
||||
PropertyMap ASF::Tag::properties() const
|
||||
{
|
||||
static Map<String, String> keyMap;
|
||||
if(keyMap.isEmpty()) {
|
||||
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
|
||||
for(int i = 0; i < numKeys; i++) {
|
||||
keyMap[keyTranslation[i][0]] = keyTranslation[i][1];
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap props;
|
||||
|
||||
if(!d->title.isEmpty()) {
|
||||
props["TITLE"] = d->title;
|
||||
}
|
||||
if(!d->artist.isEmpty()) {
|
||||
props["ARTIST"] = d->artist;
|
||||
}
|
||||
if(!d->copyright.isEmpty()) {
|
||||
props["COPYRIGHT"] = d->copyright;
|
||||
}
|
||||
if(!d->comment.isEmpty()) {
|
||||
props["COMMENT"] = d->comment;
|
||||
}
|
||||
|
||||
ASF::AttributeListMap::ConstIterator it = d->attributeListMap.begin();
|
||||
for(; it != d->attributeListMap.end(); ++it) {
|
||||
if(keyMap.contains(it->first)) {
|
||||
String key = keyMap[it->first];
|
||||
AttributeList::ConstIterator it2 = it->second.begin();
|
||||
for(; it2 != it->second.end(); ++it2) {
|
||||
if(key == "TRACKNUMBER") {
|
||||
if(it2->type() == ASF::Attribute::DWordType)
|
||||
props.insert(key, String::number(it2->toUInt()));
|
||||
else
|
||||
props.insert(key, it2->toString());
|
||||
}
|
||||
else {
|
||||
props.insert(key, it2->toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
props.unsupportedData().append(it->first);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
void ASF::Tag::removeUnsupportedProperties(const StringList &props)
|
||||
{
|
||||
StringList::ConstIterator it = props.begin();
|
||||
for(; it != props.end(); ++it)
|
||||
d->attributeListMap.erase(*it);
|
||||
}
|
||||
|
||||
PropertyMap ASF::Tag::setProperties(const PropertyMap &props)
|
||||
{
|
||||
static Map<String, String> reverseKeyMap;
|
||||
if(reverseKeyMap.isEmpty()) {
|
||||
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
|
||||
for(int i = 0; i < numKeys; i++) {
|
||||
reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0];
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap origProps = properties();
|
||||
PropertyMap::ConstIterator it = origProps.begin();
|
||||
for(; it != origProps.end(); ++it) {
|
||||
if(!props.contains(it->first) || props[it->first].isEmpty()) {
|
||||
if(it->first == "TITLE") {
|
||||
d->title = String::null;
|
||||
}
|
||||
else if(it->first == "ARTIST") {
|
||||
d->artist = String::null;
|
||||
}
|
||||
else if(it->first == "COMMENT") {
|
||||
d->comment = String::null;
|
||||
}
|
||||
else if(it->first == "COPYRIGHT") {
|
||||
d->copyright = String::null;
|
||||
}
|
||||
else {
|
||||
d->attributeListMap.erase(reverseKeyMap[it->first]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap ignoredProps;
|
||||
it = props.begin();
|
||||
for(; it != props.end(); ++it) {
|
||||
if(reverseKeyMap.contains(it->first)) {
|
||||
String name = reverseKeyMap[it->first];
|
||||
removeItem(name);
|
||||
StringList::ConstIterator it2 = it->second.begin();
|
||||
for(; it2 != it->second.end(); ++it2) {
|
||||
addAttribute(name, *it2);
|
||||
}
|
||||
}
|
||||
else if(it->first == "TITLE") {
|
||||
d->title = it->second.toString();
|
||||
}
|
||||
else if(it->first == "ARTIST") {
|
||||
d->artist = it->second.toString();
|
||||
}
|
||||
else if(it->first == "COMMENT") {
|
||||
d->comment = it->second.toString();
|
||||
}
|
||||
else if(it->first == "COPYRIGHT") {
|
||||
d->copyright = it->second.toString();
|
||||
}
|
||||
else {
|
||||
ignoredProps.insert(it->first, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
return ignoredProps;
|
||||
}
|
||||
|
||||
@@ -176,6 +176,10 @@ namespace TagLib {
|
||||
*/
|
||||
void addAttribute(const String &name, const Attribute &attribute);
|
||||
|
||||
PropertyMap properties() const;
|
||||
void removeUnsupportedProperties(const StringList& properties);
|
||||
PropertyMap setProperties(const PropertyMap &properties);
|
||||
|
||||
private:
|
||||
|
||||
class TagPrivate;
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "mp4file.h"
|
||||
#include "wavpackfile.h"
|
||||
#include "speexfile.h"
|
||||
#include "opusfile.h"
|
||||
#include "trueaudiofile.h"
|
||||
#include "aifffile.h"
|
||||
#include "wavfile.h"
|
||||
@@ -252,6 +253,8 @@ File *FileRef::create(FileName fileName, bool readAudioProperties,
|
||||
return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "SPX")
|
||||
return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "OPUS")
|
||||
return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "TTA")
|
||||
return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2")
|
||||
|
||||
@@ -70,7 +70,8 @@ public:
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
for(uint i = 0; i < blocks.size(); i++) {
|
||||
uint size = blocks.size();
|
||||
for(uint i = 0; i < size; i++) {
|
||||
delete blocks[i];
|
||||
}
|
||||
delete properties;
|
||||
@@ -108,7 +109,8 @@ FLAC::File::File(FileName file, bool readProperties,
|
||||
TagLib::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
|
||||
@@ -117,7 +119,8 @@ FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
|
||||
{
|
||||
d = new FilePrivate;
|
||||
d->ID3v2FrameFactory = frameFactory;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
|
||||
@@ -126,7 +129,8 @@ FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
|
||||
{
|
||||
d = new FilePrivate;
|
||||
d->ID3v2FrameFactory = frameFactory;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
FLAC::File::~File()
|
||||
@@ -243,7 +247,7 @@ bool FLAC::File::save()
|
||||
}
|
||||
ByteVector padding = ByteVector::fromUInt(paddingLength);
|
||||
padding.resize(paddingLength + 4);
|
||||
padding[0] = FLAC::MetadataBlock::Padding | LastBlockFlag;
|
||||
padding[0] = (char)(FLAC::MetadataBlock::Padding | LastBlockFlag);
|
||||
data.append(padding);
|
||||
|
||||
// Write the data to the file
|
||||
@@ -425,7 +429,7 @@ void FLAC::File::scan()
|
||||
length = header.mid(1, 3).toUInt();
|
||||
|
||||
ByteVector data = readBlock(length);
|
||||
if(data.size() != length) {
|
||||
if(data.size() != length || length == 0) {
|
||||
debug("FLAC::File::scan() -- FLAC stream corrupted");
|
||||
setValid(false);
|
||||
return;
|
||||
|
||||
@@ -97,6 +97,9 @@ namespace TagLib {
|
||||
*
|
||||
* If this file contains and ID3v2 tag the frames will be created using
|
||||
* \a frameFactory.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
// BIC: merge with the above constructor
|
||||
File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
|
||||
|
||||
@@ -45,7 +45,8 @@ IT::File::File(FileName file, bool readProperties,
|
||||
Mod::FileBase(file),
|
||||
d(new FilePrivate(propertiesStyle))
|
||||
{
|
||||
read(readProperties);
|
||||
if(isOpen())
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
IT::File::File(IOStream *stream, bool readProperties,
|
||||
@@ -53,7 +54,8 @@ IT::File::File(IOStream *stream, bool readProperties,
|
||||
Mod::FileBase(stream),
|
||||
d(new FilePrivate(propertiesStyle))
|
||||
{
|
||||
read(readProperties);
|
||||
if(isOpen())
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
IT::File::~File()
|
||||
@@ -128,7 +130,7 @@ bool IT::File::save()
|
||||
|
||||
seek(sampleOffset + 20);
|
||||
|
||||
if((i + instrumentCount) < lines.size())
|
||||
if((TagLib::uint)(i + instrumentCount) < lines.size())
|
||||
writeString(lines[i + instrumentCount], 25);
|
||||
else
|
||||
writeString(String::null, 25);
|
||||
|
||||
@@ -48,6 +48,9 @@ namespace TagLib {
|
||||
* Contructs a Impulse Tracker file from \a stream. If \a readProperties
|
||||
* is true the file's audio properties will also be read using
|
||||
* \a propertiesStyle. If false, \a propertiesStyle is ignored.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stram, bool readProperties = true,
|
||||
AudioProperties::ReadStyle propertiesStyle =
|
||||
|
||||
@@ -45,7 +45,8 @@ Mod::File::File(FileName file, bool readProperties,
|
||||
Mod::FileBase(file),
|
||||
d(new FilePrivate(propertiesStyle))
|
||||
{
|
||||
read(readProperties);
|
||||
if(isOpen())
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
Mod::File::File(IOStream *stream, bool readProperties,
|
||||
@@ -53,7 +54,8 @@ Mod::File::File(IOStream *stream, bool readProperties,
|
||||
Mod::FileBase(stream),
|
||||
d(new FilePrivate(propertiesStyle))
|
||||
{
|
||||
read(readProperties);
|
||||
if(isOpen())
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
Mod::File::~File()
|
||||
|
||||
@@ -49,6 +49,9 @@ namespace TagLib {
|
||||
* Contructs a Protracker file from \a stream. If \a readProperties
|
||||
* is true the file's audio properties will also be read using
|
||||
* \a propertiesStyle. If false, \a propertiesStyle is ignored.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
AudioProperties::ReadStyle propertiesStyle =
|
||||
|
||||
@@ -42,10 +42,11 @@ namespace TagLib {
|
||||
* This describes the image type.
|
||||
*/
|
||||
enum Format {
|
||||
JPEG = TypeJPEG,
|
||||
PNG = TypePNG,
|
||||
BMP = TypeBMP,
|
||||
GIF = TypeGIF
|
||||
JPEG = TypeJPEG,
|
||||
PNG = TypePNG,
|
||||
BMP = TypeBMP,
|
||||
GIF = TypeGIF,
|
||||
Unknown = TypeImplicit,
|
||||
};
|
||||
|
||||
CoverArt(Format format, const ByteVector &data);
|
||||
|
||||
+20
-2
@@ -29,6 +29,7 @@
|
||||
|
||||
#include <tdebug.h>
|
||||
#include <tstring.h>
|
||||
#include <tpropertymap.h>
|
||||
#include "mp4atom.h"
|
||||
#include "mp4tag.h"
|
||||
#include "mp4file.h"
|
||||
@@ -67,14 +68,16 @@ MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle a
|
||||
: TagLib::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, audioPropertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, audioPropertiesStyle);
|
||||
}
|
||||
|
||||
MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle)
|
||||
: TagLib::File(stream)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, audioPropertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, audioPropertiesStyle);
|
||||
}
|
||||
|
||||
MP4::File::~File()
|
||||
@@ -88,6 +91,21 @@ MP4::File::tag() const
|
||||
return d->tag;
|
||||
}
|
||||
|
||||
PropertyMap MP4::File::properties() const
|
||||
{
|
||||
return d->tag->properties();
|
||||
}
|
||||
|
||||
void MP4::File::removeUnsupportedProperties(const StringList &properties)
|
||||
{
|
||||
d->tag->removeUnsupportedProperties(properties);
|
||||
}
|
||||
|
||||
PropertyMap MP4::File::setProperties(const PropertyMap &properties)
|
||||
{
|
||||
return d->tag->setProperties(properties);
|
||||
}
|
||||
|
||||
MP4::Properties *
|
||||
MP4::File::audioProperties() const
|
||||
{
|
||||
|
||||
@@ -65,6 +65,9 @@ namespace TagLib {
|
||||
*
|
||||
* \note In the current implementation, both \a readProperties and
|
||||
* \a propertiesStyle are ignored.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true, Properties::ReadStyle audioPropertiesStyle = Properties::Average);
|
||||
|
||||
@@ -85,6 +88,22 @@ namespace TagLib {
|
||||
*/
|
||||
Tag *tag() const;
|
||||
|
||||
/*!
|
||||
* Implements the unified property interface -- export function.
|
||||
*/
|
||||
PropertyMap properties() const;
|
||||
|
||||
/*!
|
||||
* Removes unsupported properties. Forwards to the actual Tag's
|
||||
* removeUnsupportedProperties() function.
|
||||
*/
|
||||
void removeUnsupportedProperties(const StringList &properties);
|
||||
|
||||
/*!
|
||||
* Implements the unified property interface -- import function.
|
||||
*/
|
||||
PropertyMap setProperties(const PropertyMap &);
|
||||
|
||||
/*!
|
||||
* Returns the MP4 audio properties for this file.
|
||||
*/
|
||||
|
||||
+156
-2
@@ -29,6 +29,7 @@
|
||||
|
||||
#include <tdebug.h>
|
||||
#include <tstring.h>
|
||||
#include <tpropertymap.h>
|
||||
#include "mp4atom.h"
|
||||
#include "mp4tag.h"
|
||||
#include "id3v1genres.h"
|
||||
@@ -283,10 +284,13 @@ MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file)
|
||||
debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\"");
|
||||
break;
|
||||
}
|
||||
if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF) {
|
||||
if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF || flags == TypeImplicit) {
|
||||
value.append(MP4::CoverArt(MP4::CoverArt::Format(flags),
|
||||
data.mid(pos + 16, length - 16)));
|
||||
}
|
||||
else {
|
||||
debug("MP4: Unknown covr format " + String::number(flags));
|
||||
}
|
||||
pos += length;
|
||||
}
|
||||
if(value.size() > 0)
|
||||
@@ -570,7 +574,7 @@ MP4::Tag::updateOffsets(long delta, long offset)
|
||||
atom->offset += delta;
|
||||
}
|
||||
d->file->seek(atom->offset + 9);
|
||||
ByteVector data = d->file->readBlock(atom->offset - 9);
|
||||
ByteVector data = d->file->readBlock(atom->length - 9);
|
||||
unsigned int flags = (ByteVector(1, '\0') + data.mid(0, 3)).toUInt();
|
||||
if(flags & 1) {
|
||||
long long o = data.mid(7, 8).toLongLong();
|
||||
@@ -756,3 +760,153 @@ MP4::Tag::itemListMap()
|
||||
return d->items;
|
||||
}
|
||||
|
||||
static const char *keyTranslation[][2] = {
|
||||
{ "\251nam", "TITLE" },
|
||||
{ "\251ART", "ARTIST" },
|
||||
{ "\251alb", "ALBUM" },
|
||||
{ "\251cmt", "COMMENT" },
|
||||
{ "\251gen", "GENRE" },
|
||||
{ "\251day", "DATE" },
|
||||
{ "\251wrt", "COMPOSER" },
|
||||
{ "\251grp", "GROUPING" },
|
||||
{ "trkn", "TRACKNUMBER" },
|
||||
{ "disk", "DISCNUMBER" },
|
||||
{ "cpil", "COMPILATION" },
|
||||
{ "tmpo", "BPM" },
|
||||
{ "cprt", "COPYRIGHT" },
|
||||
{ "\251lyr", "LYRICS" },
|
||||
{ "\251too", "ENCODEDBY" },
|
||||
{ "soal", "ALBUMSORT" },
|
||||
{ "soaa", "ALBUMARTISTSORT" },
|
||||
{ "soar", "ARTISTSORT" },
|
||||
{ "sonm", "TITLESORT" },
|
||||
{ "soco", "COMPOSERSORT" },
|
||||
{ "sosn", "SHOWSORT" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" },
|
||||
{ "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "----: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 Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "----:com.apple.iTunes:ASIN", "ASIN" },
|
||||
{ "----:com.apple.iTunes:LABEL", "LABEL" },
|
||||
{ "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
|
||||
{ "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" },
|
||||
{ "----:com.apple.iTunes:REMIXER", "REMIXER" },
|
||||
{ "----:com.apple.iTunes:ENGINEER", "ENGINEER" },
|
||||
{ "----:com.apple.iTunes:PRODUCER", "PRODUCER" },
|
||||
{ "----:com.apple.iTunes:DJMIXER", "DJMIXER" },
|
||||
{ "----:com.apple.iTunes:MIXER", "MIXER" },
|
||||
{ "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" },
|
||||
{ "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" },
|
||||
{ "----:com.apple.iTunes:MOOD", "MOOD" },
|
||||
{ "----:com.apple.iTunes:ISRC", "ISRC" },
|
||||
{ "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" },
|
||||
{ "----:com.apple.iTunes:BARCODE", "BARCODE" },
|
||||
{ "----:com.apple.iTunes:SCRIPT", "SCRIPT" },
|
||||
{ "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
|
||||
{ "----:com.apple.iTunes:LICENSE", "LICENSE" },
|
||||
{ "----:com.apple.iTunes:MEDIA", "MEDIA" },
|
||||
};
|
||||
|
||||
PropertyMap MP4::Tag::properties() const
|
||||
{
|
||||
static Map<String, String> keyMap;
|
||||
if(keyMap.isEmpty()) {
|
||||
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
|
||||
for(int i = 0; i < numKeys; i++) {
|
||||
keyMap[keyTranslation[i][0]] = keyTranslation[i][1];
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap props;
|
||||
MP4::ItemListMap::ConstIterator it = d->items.begin();
|
||||
for(; it != d->items.end(); ++it) {
|
||||
if(keyMap.contains(it->first)) {
|
||||
String key = keyMap[it->first];
|
||||
if(key == "TRACKNUMBER" || key == "DISCNUMBER") {
|
||||
MP4::Item::IntPair ip = it->second.toIntPair();
|
||||
String value = String::number(ip.first);
|
||||
if(ip.second) {
|
||||
value += "/" + String::number(ip.second);
|
||||
}
|
||||
props[key] = value;
|
||||
}
|
||||
else if(key == "BPM") {
|
||||
props[key] = String::number(it->second.toInt());
|
||||
}
|
||||
else if(key == "COMPILATION") {
|
||||
props[key] = String::number(it->second.toBool());
|
||||
}
|
||||
else {
|
||||
props[key] = it->second.toStringList();
|
||||
}
|
||||
}
|
||||
else {
|
||||
props.unsupportedData().append(it->first);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
void MP4::Tag::removeUnsupportedProperties(const StringList &props)
|
||||
{
|
||||
StringList::ConstIterator it = props.begin();
|
||||
for(; it != props.end(); ++it)
|
||||
d->items.erase(*it);
|
||||
}
|
||||
|
||||
PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
|
||||
{
|
||||
static Map<String, String> reverseKeyMap;
|
||||
if(reverseKeyMap.isEmpty()) {
|
||||
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
|
||||
for(int i = 0; i < numKeys; i++) {
|
||||
reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0];
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap origProps = properties();
|
||||
PropertyMap::ConstIterator it = origProps.begin();
|
||||
for(; it != origProps.end(); ++it) {
|
||||
if(!props.contains(it->first) || props[it->first].isEmpty()) {
|
||||
d->items.erase(reverseKeyMap[it->first]);
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap ignoredProps;
|
||||
it = props.begin();
|
||||
for(; it != props.end(); ++it) {
|
||||
if(reverseKeyMap.contains(it->first)) {
|
||||
String name = reverseKeyMap[it->first];
|
||||
if(it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") {
|
||||
int first = 0, second = 0;
|
||||
StringList parts = StringList::split(it->second.front(), "/");
|
||||
if(parts.size() > 0) {
|
||||
first = parts[0].toInt();
|
||||
if(parts.size() > 1) {
|
||||
second = parts[1].toInt();
|
||||
}
|
||||
d->items[name] = MP4::Item(first, second);
|
||||
}
|
||||
}
|
||||
else if(it->first == "BPM") {
|
||||
int value = it->second.front().toInt();
|
||||
d->items[name] = MP4::Item(value);
|
||||
}
|
||||
else if(it->first == "COMPILATION") {
|
||||
bool value = it->second.front().toInt();
|
||||
d->items[name] = MP4::Item(value > 0);
|
||||
}
|
||||
else {
|
||||
d->items[name] = it->second;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ignoredProps.insert(it->first, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
return ignoredProps;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,10 @@ namespace TagLib {
|
||||
|
||||
ItemListMap &itemListMap();
|
||||
|
||||
PropertyMap properties() const;
|
||||
void removeUnsupportedProperties(const StringList& properties);
|
||||
PropertyMap setProperties(const PropertyMap &properties);
|
||||
|
||||
private:
|
||||
AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
|
||||
TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
|
||||
|
||||
@@ -94,14 +94,16 @@ MPC::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
MPC::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
MPC::File::~File()
|
||||
|
||||
@@ -95,6 +95,9 @@ namespace TagLib {
|
||||
* Contructs an MPC 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
@@ -214,7 +214,7 @@ void MPC::Properties::readSV8(File *file)
|
||||
d->channels = flags[7] * 8 + flags[6] * 4 + flags[5] * 2 + flags[4] + 1;
|
||||
|
||||
if((d->sampleFrames - begSilence) != 0)
|
||||
d->bitrate = d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence);
|
||||
d->bitrate = (int)(d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence));
|
||||
d->bitrate = d->bitrate / 1000;
|
||||
|
||||
d->length = (d->sampleFrames - begSilence) / d->sampleRate;
|
||||
@@ -279,10 +279,10 @@ void MPC::Properties::readSV7(const ByteVector &data)
|
||||
}
|
||||
|
||||
if (d->trackPeak != 0)
|
||||
d->trackPeak = (int)(log10(d->trackPeak) * 20 * 256 + .5);
|
||||
d->trackPeak = (int)(log10((double)d->trackPeak) * 20 * 256 + .5);
|
||||
|
||||
if (d->albumPeak != 0)
|
||||
d->albumPeak = (int)(log10(d->albumPeak) * 20 * 256 + .5);
|
||||
d->albumPeak = (int)(log10((double)d->albumPeak) * 20 * 256 + .5);
|
||||
|
||||
bool trueGapless = (gapless >> 31) & 0x0001;
|
||||
if(trueGapless) {
|
||||
|
||||
@@ -182,22 +182,32 @@ void ID3v1::Tag::setGenre(const String &s)
|
||||
d->genre = ID3v1::genreIndex(s);
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setYear(uint i)
|
||||
void ID3v1::Tag::setYear(TagLib::uint i)
|
||||
{
|
||||
d->year = i > 0 ? String::number(i) : String::null;
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setTrack(uint i)
|
||||
void ID3v1::Tag::setTrack(TagLib::uint i)
|
||||
{
|
||||
d->track = i < 256 ? i : 0;
|
||||
}
|
||||
|
||||
TagLib::uint ID3v1::Tag::genreNumber() const
|
||||
{
|
||||
return d->genre;
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setGenreNumber(TagLib::uint i)
|
||||
{
|
||||
d->genre = i < 256 ? i : 255;
|
||||
}
|
||||
|
||||
void ID3v1::Tag::setStringHandler(const StringHandler *handler)
|
||||
{
|
||||
if(TagPrivate::stringHandler != &defaultStringHandler)
|
||||
delete TagPrivate::stringHandler;
|
||||
|
||||
TagPrivate::stringHandler = handler;
|
||||
if (handler)
|
||||
TagPrivate::stringHandler = handler;
|
||||
else
|
||||
TagPrivate::stringHandler = &defaultStringHandler;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -140,20 +140,40 @@ namespace TagLib {
|
||||
virtual String album() const;
|
||||
virtual String comment() const;
|
||||
virtual String genre() const;
|
||||
virtual uint year() const;
|
||||
virtual uint track() const;
|
||||
virtual TagLib::uint year() const;
|
||||
virtual TagLib::uint track() const;
|
||||
|
||||
virtual void setTitle(const String &s);
|
||||
virtual void setArtist(const String &s);
|
||||
virtual void setAlbum(const String &s);
|
||||
virtual void setComment(const String &s);
|
||||
virtual void setGenre(const String &s);
|
||||
virtual void setYear(uint i);
|
||||
virtual void setTrack(uint i);
|
||||
virtual void setYear(TagLib::uint i);
|
||||
virtual void setTrack(TagLib::uint i);
|
||||
|
||||
/*!
|
||||
* Returns the genre in number.
|
||||
*
|
||||
* /note Normally 255 indicates that this tag contains no genre.
|
||||
*/
|
||||
TagLib::uint genreNumber() const;
|
||||
|
||||
/*!
|
||||
* Sets the genre in number to \a i.
|
||||
*
|
||||
* /note Valid value is from 0 up to 255. Normally 255 indicates that
|
||||
* this tag contains no genre.
|
||||
*/
|
||||
void setGenreNumber(TagLib::uint i);
|
||||
|
||||
/*!
|
||||
* Sets the string handler that decides how the ID3v1 data will be
|
||||
* converted to and from binary data.
|
||||
* If the parameter \a handler is null, the previous handler is
|
||||
* released and default ISO-8859-1 handler is restored.
|
||||
*
|
||||
* \note The caller is responsible for deleting the previous handler
|
||||
* as needed after it is released.
|
||||
*
|
||||
* \see StringHandler
|
||||
*/
|
||||
|
||||
@@ -158,8 +158,13 @@ void CommentsFrame::parseFields(const ByteVector &data)
|
||||
ByteVectorList l = ByteVectorList::split(data.mid(4), textDelimiter(d->textEncoding), byteAlign, 2);
|
||||
|
||||
if(l.size() == 2) {
|
||||
d->description = String(l.front(), d->textEncoding);
|
||||
d->text = String(l.back(), d->textEncoding);
|
||||
if(d->textEncoding == String::Latin1) {
|
||||
d->description = Tag::latin1StringHandler()->parse(l.front());
|
||||
d->text = Tag::latin1StringHandler()->parse(l.back());
|
||||
} else {
|
||||
d->description = String(l.front(), d->textEncoding);
|
||||
d->text = String(l.back(), d->textEncoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2012 by Rupert Daniel
|
||||
email : rupert@cancelmonday.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 <tdebug.h>
|
||||
|
||||
#include "ownershipframe.h"
|
||||
#include <id3v2tag.h>
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
|
||||
class OwnershipFrame::OwnershipFramePrivate
|
||||
{
|
||||
public:
|
||||
String pricePaid;
|
||||
String datePurchased;
|
||||
String seller;
|
||||
String::Type textEncoding;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
OwnershipFrame::OwnershipFrame(String::Type encoding) : Frame("OWNE")
|
||||
{
|
||||
d = new OwnershipFramePrivate;
|
||||
d->textEncoding = encoding;
|
||||
}
|
||||
|
||||
OwnershipFrame::OwnershipFrame(const ByteVector &data) : Frame(data)
|
||||
{
|
||||
d = new OwnershipFramePrivate;
|
||||
setData(data);
|
||||
}
|
||||
|
||||
OwnershipFrame::~OwnershipFrame()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
String OwnershipFrame::toString() const
|
||||
{
|
||||
return "pricePaid=" + d->pricePaid + " datePurchased=" + d->datePurchased + " seller=" + d->seller;
|
||||
}
|
||||
|
||||
String OwnershipFrame::pricePaid() const
|
||||
{
|
||||
return d->pricePaid;
|
||||
}
|
||||
|
||||
void OwnershipFrame::setPricePaid(const String &s)
|
||||
{
|
||||
d->pricePaid = s;
|
||||
}
|
||||
|
||||
String OwnershipFrame::datePurchased() const
|
||||
{
|
||||
return d->datePurchased;
|
||||
}
|
||||
|
||||
void OwnershipFrame::setDatePurchased(const String &s)
|
||||
{
|
||||
d->datePurchased = s;
|
||||
}
|
||||
|
||||
String OwnershipFrame::seller() const
|
||||
{
|
||||
return d->seller;
|
||||
}
|
||||
|
||||
void OwnershipFrame::setSeller(const String &s)
|
||||
{
|
||||
d->seller = s;
|
||||
}
|
||||
|
||||
String::Type OwnershipFrame::textEncoding() const
|
||||
{
|
||||
return d->textEncoding;
|
||||
}
|
||||
|
||||
void OwnershipFrame::setTextEncoding(String::Type encoding)
|
||||
{
|
||||
d->textEncoding = encoding;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OwnershipFrame::parseFields(const ByteVector &data)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
// Get the text encoding
|
||||
d->textEncoding = String::Type(data[0]);
|
||||
pos += 1;
|
||||
|
||||
// Read the price paid this is a null terminate string
|
||||
d->pricePaid = readStringField(data, String::Latin1, &pos);
|
||||
|
||||
// If we don't have at least 8 bytes left then don't parse the rest of the
|
||||
// data
|
||||
if(data.size() - pos < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the date purchased YYYYMMDD
|
||||
d->datePurchased = String(data.mid(pos, 8));
|
||||
pos += 8;
|
||||
|
||||
// Read the seller
|
||||
if(d->textEncoding == String::Latin1)
|
||||
d->seller = Tag::latin1StringHandler()->parse(data.mid(pos));
|
||||
else
|
||||
d->seller = String(data.mid(pos), d->textEncoding);
|
||||
}
|
||||
|
||||
ByteVector OwnershipFrame::renderFields() const
|
||||
{
|
||||
ByteVector v;
|
||||
|
||||
v.append(char(d->textEncoding));
|
||||
v.append(d->pricePaid.data(String::Latin1));
|
||||
v.append(textDelimiter(String::Latin1));
|
||||
v.append(d->datePurchased.data(String::Latin1));
|
||||
v.append(d->seller.data(d->textEncoding));
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
OwnershipFrame::OwnershipFrame(const ByteVector &data, Header *h) : Frame(h)
|
||||
{
|
||||
d = new OwnershipFramePrivate;
|
||||
parseFields(fieldData(data));
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2012 by Rupert Daniel
|
||||
email : rupert@cancelmonday.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_OWNERSHIPFRAME_H
|
||||
#define TAGLIB_OWNERSHIPFRAME_H
|
||||
|
||||
#include "id3v2frame.h"
|
||||
#include "taglib_export.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
namespace ID3v2 {
|
||||
|
||||
//! An implementation of ID3v2 "ownership"
|
||||
|
||||
/*!
|
||||
* This implements the ID3v2 ownership (OWNE frame). It consists of
|
||||
* a price paid, a date purchased (YYYYMMDD) and the name of the seller.
|
||||
*/
|
||||
|
||||
class TAGLIB_EXPORT OwnershipFrame : public Frame
|
||||
{
|
||||
friend class FrameFactory;
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Construct an empty ownership frame.
|
||||
*/
|
||||
explicit OwnershipFrame(String::Type encoding = String::Latin1);
|
||||
|
||||
/*!
|
||||
* Construct a ownership based on the data in \a data.
|
||||
*/
|
||||
explicit OwnershipFrame(const ByteVector &data);
|
||||
|
||||
/*!
|
||||
* Destroys this OwnershipFrame instance.
|
||||
*/
|
||||
virtual ~OwnershipFrame();
|
||||
|
||||
/*!
|
||||
* Returns the text of this popularimeter.
|
||||
*
|
||||
* \see text()
|
||||
*/
|
||||
virtual String toString() const;
|
||||
|
||||
/*!
|
||||
* Returns the date purchased.
|
||||
*
|
||||
* \see setDatePurchased()
|
||||
*/
|
||||
String datePurchased() const;
|
||||
|
||||
/*!
|
||||
* Set the date purchased.
|
||||
*
|
||||
* \see datePurchased()
|
||||
*/
|
||||
void setDatePurchased(const String &datePurchased);
|
||||
|
||||
/*!
|
||||
* Returns the price paid.
|
||||
*
|
||||
* \see setPricePaid()
|
||||
*/
|
||||
String pricePaid() const;
|
||||
|
||||
/*!
|
||||
* Set the price paid.
|
||||
*
|
||||
* \see pricePaid()
|
||||
*/
|
||||
void setPricePaid(const String &pricePaid);
|
||||
|
||||
/*!
|
||||
* Returns the seller.
|
||||
*
|
||||
* \see setSeller()
|
||||
*/
|
||||
String seller() const;
|
||||
|
||||
/*!
|
||||
* Set the seller.
|
||||
*
|
||||
* \see seller()
|
||||
*/
|
||||
void setSeller(const String &seller);
|
||||
|
||||
/*!
|
||||
* Returns the text encoding that will be used in rendering this frame.
|
||||
* This defaults to the type that was either specified in the constructor
|
||||
* or read from the frame when parsed.
|
||||
*
|
||||
* \see setTextEncoding()
|
||||
* \see render()
|
||||
*/
|
||||
String::Type textEncoding() const;
|
||||
|
||||
/*!
|
||||
* Sets the text encoding to be used when rendering this frame to
|
||||
* \a encoding.
|
||||
*
|
||||
* \see textEncoding()
|
||||
* \see render()
|
||||
*/
|
||||
void setTextEncoding(String::Type encoding);
|
||||
|
||||
protected:
|
||||
// Reimplementations.
|
||||
|
||||
virtual void parseFields(const ByteVector &data);
|
||||
virtual ByteVector renderFields() const;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* The constructor used by the FrameFactory.
|
||||
*/
|
||||
OwnershipFrame(const ByteVector &data, Header *h);
|
||||
OwnershipFrame(const OwnershipFrame &);
|
||||
OwnershipFrame &operator=(const OwnershipFrame &);
|
||||
|
||||
class OwnershipFramePrivate;
|
||||
OwnershipFramePrivate *d;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -213,8 +213,10 @@ void TextIdentificationFrame::parseFields(const ByteVector &data)
|
||||
|
||||
for(ByteVectorList::Iterator it = l.begin(); it != l.end(); it++) {
|
||||
if(!(*it).isEmpty()) {
|
||||
String s(*it, d->textEncoding);
|
||||
d->fieldList.append(s);
|
||||
if(d->textEncoding == String::Latin1)
|
||||
d->fieldList.append(Tag::latin1StringHandler()->parse(*it));
|
||||
else
|
||||
d->fieldList.append(String(*it, d->textEncoding));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -379,18 +381,12 @@ void UserTextIdentificationFrame::setDescription(const String &s)
|
||||
|
||||
PropertyMap UserTextIdentificationFrame::asProperties() const
|
||||
{
|
||||
String tagName = description();
|
||||
|
||||
PropertyMap map;
|
||||
String key = tagName.upper();
|
||||
if(key.isNull()) // this frame's description is not a valid PropertyMap key -> add to unsupported list
|
||||
map.unsupportedData().append(L"TXXX/" + description());
|
||||
else {
|
||||
StringList v = fieldList();
|
||||
for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it)
|
||||
if(*it != description())
|
||||
map.insert(key, *it);
|
||||
}
|
||||
String tagName = txxxToKey(description());
|
||||
StringList v = fieldList();
|
||||
for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it)
|
||||
if(it != v.begin())
|
||||
map.insert(tagName, *it);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,10 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include <tbytevectorlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tdebug.h>
|
||||
|
||||
#include "id3v2tag.h"
|
||||
#include "uniquefileidentifierframe.h"
|
||||
|
||||
using namespace TagLib;
|
||||
@@ -87,6 +89,34 @@ String UniqueFileIdentifierFrame::toString() const
|
||||
return String::null;
|
||||
}
|
||||
|
||||
PropertyMap UniqueFileIdentifierFrame::asProperties() const
|
||||
{
|
||||
PropertyMap map;
|
||||
if(d->owner == "http://musicbrainz.org") {
|
||||
map.insert("MUSICBRAINZ_TRACKID", String(d->identifier));
|
||||
}
|
||||
else {
|
||||
map.unsupportedData().append(frameID() + String("/") + d->owner);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static
|
||||
{
|
||||
ID3v2::FrameList comments = tag->frameList("UFID");
|
||||
|
||||
for(ID3v2::FrameList::ConstIterator it = comments.begin();
|
||||
it != comments.end();
|
||||
++it)
|
||||
{
|
||||
UniqueFileIdentifierFrame *frame = dynamic_cast<UniqueFileIdentifierFrame *>(*it);
|
||||
if(frame && frame->owner() == o)
|
||||
return frame;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UniqueFileIdentifierFrame::parseFields(const ByteVector &data)
|
||||
{
|
||||
if(data.size() < 1) {
|
||||
|
||||
@@ -94,6 +94,16 @@ namespace TagLib {
|
||||
|
||||
virtual String toString() const;
|
||||
|
||||
PropertyMap asProperties() const;
|
||||
|
||||
/*!
|
||||
* UFID frames each have a unique owner. This searches for a UFID
|
||||
* frame with the owner \a o and returns a pointer to it.
|
||||
*
|
||||
* \see owner()
|
||||
*/
|
||||
static UniqueFileIdentifierFrame *findByOwner(const Tag *tag, const String &o);
|
||||
|
||||
protected:
|
||||
virtual void parseFields(const ByteVector &data);
|
||||
virtual ByteVector renderFields() const;
|
||||
|
||||
@@ -158,8 +158,13 @@ void UnsynchronizedLyricsFrame::parseFields(const ByteVector &data)
|
||||
ByteVectorList::split(data.mid(4), textDelimiter(d->textEncoding), byteAlign, 2);
|
||||
|
||||
if(l.size() == 2) {
|
||||
d->description = String(l.front(), d->textEncoding);
|
||||
d->text = String(l.back(), d->textEncoding);
|
||||
if(d->textEncoding == String::Latin1) {
|
||||
d->description = Tag::latin1StringHandler()->parse(l.front());
|
||||
d->text = Tag::latin1StringHandler()->parse(l.back());
|
||||
} else {
|
||||
d->description = String(l.front(), d->textEncoding);
|
||||
d->text = String(l.back(), d->textEncoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <tdebug.h>
|
||||
#include <tstringlist.h>
|
||||
|
||||
#include "id3v2tag.h"
|
||||
#include "id3v2frame.h"
|
||||
#include "id3v2synchdata.h"
|
||||
#include "tpropertymap.h"
|
||||
@@ -43,6 +44,7 @@
|
||||
#include "frames/urllinkframe.h"
|
||||
#include "frames/unsynchronizedlyricsframe.h"
|
||||
#include "frames/commentsframe.h"
|
||||
#include "frames/uniquefileidentifierframe.h"
|
||||
#include "frames/unknownframe.h"
|
||||
|
||||
using namespace TagLib;
|
||||
@@ -71,7 +73,7 @@ namespace
|
||||
return false;
|
||||
|
||||
for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) {
|
||||
if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) {
|
||||
if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -119,12 +121,16 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) //
|
||||
TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8);
|
||||
frame->setText(values);
|
||||
return frame;
|
||||
} else if(values.size() == 1){ // URL frame (not WXXX); support only one value
|
||||
} else if((frameID[0] == 'W') && (values.size() == 1)){ // URL frame (not WXXX); support only one value
|
||||
UrlLinkFrame* frame = new UrlLinkFrame(frameID);
|
||||
frame->setUrl(values.front());
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
if(key == "MUSICBRAINZ_TRACKID" && values.size() == 1) {
|
||||
UniqueFileIdentifierFrame *frame = new UniqueFileIdentifierFrame("http://musicbrainz.org", values.front().data(String::UTF8));
|
||||
return frame;
|
||||
}
|
||||
// now we check if it's one of the "special" cases:
|
||||
// -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS)
|
||||
if((key == "LYRICS" || key.startsWith(lyricsPrefix)) && values.size() == 1){
|
||||
@@ -143,12 +149,14 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) //
|
||||
// -COMMENT: depending on the number of values, use COMM or TXXX (with description=COMMENT)
|
||||
if((key == "COMMENT" || key.startsWith(commentPrefix)) && values.size() == 1){
|
||||
CommentsFrame *frame = new CommentsFrame(String::UTF8);
|
||||
frame->setDescription(key == "COMMENT" ? key : key.substr(commentPrefix.size()));
|
||||
if (key != "COMMENT"){
|
||||
frame->setDescription(key.substr(commentPrefix.size()));
|
||||
}
|
||||
frame->setText(values.front());
|
||||
return frame;
|
||||
}
|
||||
// if non of the above cases apply, we use a TXXX frame with the key as description
|
||||
return new UserTextIdentificationFrame(key, values, String::UTF8);
|
||||
return new UserTextIdentificationFrame(keyToTXXX(key), values, String::UTF8);
|
||||
}
|
||||
|
||||
Frame::~Frame()
|
||||
@@ -273,7 +281,11 @@ String Frame::readStringField(const ByteVector &data, String::Type encoding, int
|
||||
if(end < *position)
|
||||
return String::null;
|
||||
|
||||
String str = String(data.mid(*position, end - *position), encoding);
|
||||
String str;
|
||||
if(encoding == String::Latin1)
|
||||
str = Tag::latin1StringHandler()->parse(data.mid(*position, end - *position));
|
||||
else
|
||||
str = String(data.mid(*position, end - *position), encoding);
|
||||
|
||||
*position = end + delimiter.size();
|
||||
|
||||
@@ -343,7 +355,7 @@ static const char *frameTranslation[][2] = {
|
||||
{ "TLAN", "LANGUAGE" },
|
||||
{ "TLEN", "LENGTH" },
|
||||
//{ "TMCL", "MUSICIANCREDITS" }, handled separately
|
||||
{ "TMED", "MEDIATYPE" },
|
||||
{ "TMED", "MEDIA" },
|
||||
{ "TMOO", "MOOD" },
|
||||
{ "TOAL", "ORIGINALALBUM" },
|
||||
{ "TOFN", "ORIGINALFILENAME" },
|
||||
@@ -356,7 +368,7 @@ static const char *frameTranslation[][2] = {
|
||||
{ "TPE4", "REMIXER" }, // could also be ARRANGER
|
||||
{ "TPOS", "DISCNUMBER" },
|
||||
{ "TPRO", "PRODUCEDNOTICE" },
|
||||
{ "TPUB", "PUBLISHER" },
|
||||
{ "TPUB", "LABEL" },
|
||||
{ "TRCK", "TRACKNUMBER" },
|
||||
{ "TRSN", "RADIOSTATION" },
|
||||
{ "TRSO", "RADIOSTATIONOWNER" },
|
||||
@@ -380,6 +392,18 @@ static const char *frameTranslation[][2] = {
|
||||
//{ "USLT", "LYRICS" }, handled specially
|
||||
};
|
||||
|
||||
static const TagLib::uint txxxFrameTranslationSize = 7;
|
||||
static const char *txxxFrameTranslation[][2] = {
|
||||
{ "MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
|
||||
{ "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
|
||||
{ "MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
|
||||
{ "MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
|
||||
{ "MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
|
||||
{ "Acoustid Id", "ACOUSTID_ID" },
|
||||
{ "Acoustid Fingerprint", "ACOUSTID_FINGERPRINT" },
|
||||
{ "MusicIP PUID", "MUSICIP_PUID" },
|
||||
};
|
||||
|
||||
Map<ByteVector, String> &idMap()
|
||||
{
|
||||
static Map<ByteVector, String> m;
|
||||
@@ -389,6 +413,18 @@ Map<ByteVector, String> &idMap()
|
||||
return m;
|
||||
}
|
||||
|
||||
Map<String, String> &txxxMap()
|
||||
{
|
||||
static Map<String, String> m;
|
||||
if(m.isEmpty()) {
|
||||
for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
|
||||
String key = String(txxxFrameTranslation[i][0]).upper();
|
||||
m[key] = txxxFrameTranslation[i][1];
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// list of deprecated frames and their successors
|
||||
static const TagLib::uint deprecatedFramesSize = 4;
|
||||
static const char *deprecatedFrames[][2] = {
|
||||
@@ -428,6 +464,26 @@ ByteVector Frame::keyToFrameID(const String &s)
|
||||
return ByteVector::null;
|
||||
}
|
||||
|
||||
String Frame::txxxToKey(const String &description)
|
||||
{
|
||||
Map<String, String> &m = txxxMap();
|
||||
String d = description.upper();
|
||||
if(m.contains(d))
|
||||
return m[d];
|
||||
return d;
|
||||
}
|
||||
|
||||
String Frame::keyToTXXX(const String &s)
|
||||
{
|
||||
static Map<String, String> m;
|
||||
if(m.isEmpty())
|
||||
for(size_t i = 0; i < txxxFrameTranslationSize; ++i)
|
||||
m[txxxFrameTranslation[i][1]] = txxxFrameTranslation[i][0];
|
||||
if(m.contains(s.upper()))
|
||||
return m[s];
|
||||
return s;
|
||||
}
|
||||
|
||||
PropertyMap Frame::asProperties() const
|
||||
{
|
||||
if(dynamic_cast< const UnknownFrame *>(this)) {
|
||||
@@ -449,6 +505,8 @@ PropertyMap Frame::asProperties() const
|
||||
return dynamic_cast< const CommentsFrame* >(this)->asProperties();
|
||||
else if(id == "USLT")
|
||||
return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties();
|
||||
else if(id == "UFID")
|
||||
return dynamic_cast< const UniqueFileIdentifierFrame* >(this)->asProperties();
|
||||
PropertyMap m;
|
||||
m.unsupportedData().append(id);
|
||||
return m;
|
||||
|
||||
@@ -274,6 +274,15 @@ namespace TagLib {
|
||||
*/
|
||||
static String frameIDToKey(const ByteVector &);
|
||||
|
||||
/*!
|
||||
* Returns an appropriate TXXX frame description for the given free-form tag key.
|
||||
*/
|
||||
static String keyToTXXX(const String &);
|
||||
|
||||
/*!
|
||||
* Returns a free-form tag name for the given ID3 frame description.
|
||||
*/
|
||||
static String txxxToKey(const String &);
|
||||
|
||||
/*!
|
||||
* This helper function splits the PropertyMap \a original into three ProperytMaps
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "frames/unsynchronizedlyricsframe.h"
|
||||
#include "frames/popularimeterframe.h"
|
||||
#include "frames/privateframe.h"
|
||||
#include "frames/ownershipframe.h"
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace ID3v2;
|
||||
@@ -98,7 +99,7 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
|
||||
// A quick sanity check -- make sure that the frameID is 4 uppercase Latin1
|
||||
// characters. Also make sure that there is data in the frame.
|
||||
|
||||
if(!frameID.size() == (version < 3 ? 3 : 4) ||
|
||||
if(frameID.size() != (version < 3 ? 3 : 4) ||
|
||||
header->frameSize() <= uint(header->dataLengthIndicator() ? 4 : 0) ||
|
||||
header->frameSize() > data.size())
|
||||
{
|
||||
@@ -106,8 +107,19 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef NO_ITUNES_HACKS
|
||||
if(version == 3 && frameID.size() == 4 && frameID[3] == '\0') {
|
||||
// iTunes v2.3 tags store v2.2 frames - convert now
|
||||
frameID = frameID.mid(0, 3);
|
||||
header->setFrameID(frameID);
|
||||
header->setVersion(2);
|
||||
updateFrame(header);
|
||||
header->setVersion(3);
|
||||
}
|
||||
#endif
|
||||
|
||||
for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) {
|
||||
if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) {
|
||||
if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) {
|
||||
delete header;
|
||||
return 0;
|
||||
}
|
||||
@@ -238,6 +250,14 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
|
||||
|
||||
if(frameID == "PRIV")
|
||||
return new PrivateFrame(data, header);
|
||||
|
||||
// Ownership (frames 4.22)
|
||||
|
||||
if(frameID == "OWNE") {
|
||||
OwnershipFrame *f = new OwnershipFrame(data, header);
|
||||
d->setTextEncoding(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
return new UnknownFrame(data, header);
|
||||
}
|
||||
|
||||
@@ -123,8 +123,7 @@ namespace TagLib {
|
||||
FrameFactory();
|
||||
|
||||
/*!
|
||||
* Destroys the frame factory. In most cases this will never be called (as
|
||||
* is typical of singletons).
|
||||
* Destroys the frame factory.
|
||||
*/
|
||||
virtual ~FrameFactory();
|
||||
|
||||
|
||||
@@ -70,8 +70,30 @@ public:
|
||||
|
||||
FrameListMap frameListMap;
|
||||
FrameList frameList;
|
||||
|
||||
static const Latin1StringHandler *stringHandler;
|
||||
};
|
||||
|
||||
static const Latin1StringHandler defaultStringHandler;
|
||||
const ID3v2::Latin1StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStringHandler;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// StringHandler implementation
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Latin1StringHandler::Latin1StringHandler()
|
||||
{
|
||||
}
|
||||
|
||||
Latin1StringHandler::~Latin1StringHandler()
|
||||
{
|
||||
}
|
||||
|
||||
String Latin1StringHandler::parse(const ByteVector &data) const
|
||||
{
|
||||
return String(data, String::Latin1);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -357,10 +379,12 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties)
|
||||
for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++)
|
||||
if (dynamic_cast<const UnknownFrame *>(*fit) != 0)
|
||||
removeFrame(*fit);
|
||||
} else if(it->size() == 4){
|
||||
}
|
||||
else if(it->size() == 4){
|
||||
ByteVector id = it->data(String::Latin1);
|
||||
removeFrames(id);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
ByteVector id = it->substr(0,4).data(String::Latin1);
|
||||
if(it->size() <= 5)
|
||||
continue; // invalid specification
|
||||
@@ -374,6 +398,8 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties)
|
||||
frame = CommentsFrame::findByDescription(this, description);
|
||||
else if(id == "USLT")
|
||||
frame = UnsynchronizedLyricsFrame::findByDescription(this, description);
|
||||
else if(id == "UFID")
|
||||
frame = UniqueFileIdentifierFrame::findByOwner(this, description);
|
||||
if(frame)
|
||||
removeFrame(frame);
|
||||
}
|
||||
@@ -584,6 +610,19 @@ ByteVector ID3v2::Tag::render(int version) const
|
||||
return d->header.render() + tagData;
|
||||
}
|
||||
|
||||
Latin1StringHandler const *ID3v2::Tag::latin1StringHandler()
|
||||
{
|
||||
return TagPrivate::stringHandler;
|
||||
}
|
||||
|
||||
void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler)
|
||||
{
|
||||
if(handler)
|
||||
TagPrivate::stringHandler = handler;
|
||||
else
|
||||
TagPrivate::stringHandler = &defaultStringHandler;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -645,8 +684,9 @@ void ID3v2::Tag::parse(const ByteVector &origData)
|
||||
// portion of the frame data.
|
||||
|
||||
if(data.at(frameDataPosition) == 0) {
|
||||
if(d->header.footerPresent())
|
||||
if(d->header.footerPresent()) {
|
||||
debug("Padding *and* a footer found. This is not allowed by the spec.");
|
||||
}
|
||||
|
||||
d->paddingSize = frameDataLength - frameDataPosition;
|
||||
return;
|
||||
|
||||
@@ -57,6 +57,36 @@ namespace TagLib {
|
||||
typedef List<Frame *> FrameList;
|
||||
typedef Map<ByteVector, FrameList> FrameListMap;
|
||||
|
||||
//! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags.
|
||||
|
||||
/*!
|
||||
* ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only
|
||||
* supports genuine ISO-8859-1 by default. However, in practice, non
|
||||
* ISO-8859-1 encodings are often used instead of ISO-8859-1, such as
|
||||
* Windows-1252 for western languages, Shift_JIS for Japanese and so on.
|
||||
*
|
||||
* Here is an option to read such tags by subclassing this class,
|
||||
* reimplementing parse() and setting your reimplementation as the default
|
||||
* with ID3v2::Tag::setStringHandler().
|
||||
*
|
||||
* \note Writing non-ISO-8859-1 tags is not implemented intentionally.
|
||||
* Use UTF-16 or UTF-8 instead.
|
||||
*
|
||||
* \see ID3v2::Tag::setStringHandler()
|
||||
*/
|
||||
class TAGLIB_EXPORT Latin1StringHandler
|
||||
{
|
||||
public:
|
||||
Latin1StringHandler();
|
||||
virtual ~Latin1StringHandler();
|
||||
|
||||
/*!
|
||||
* Decode a string from \a data. The default implementation assumes that
|
||||
* \a data is an ISO-8859-1 (Latin1) character array.
|
||||
*/
|
||||
virtual String parse(const ByteVector &data) const;
|
||||
};
|
||||
|
||||
//! The main class in the ID3v2 implementation
|
||||
|
||||
/*!
|
||||
@@ -323,6 +353,27 @@ namespace TagLib {
|
||||
*/
|
||||
// BIC: combine with the above method
|
||||
ByteVector render(int version) const;
|
||||
|
||||
/*!
|
||||
* Gets the current string handler that decides how the "Latin-1" data
|
||||
* will be converted to and from binary data.
|
||||
*
|
||||
* \see Latin1StringHandler
|
||||
*/
|
||||
static Latin1StringHandler const *latin1StringHandler();
|
||||
|
||||
/*!
|
||||
* Sets the string handler that decides how the "Latin-1" data will be
|
||||
* converted to and from binary data.
|
||||
* If the parameter \a handler is null, the previous handler is
|
||||
* released and default ISO-8859-1 handler is restored.
|
||||
*
|
||||
* \note The caller is responsible for deleting the previous handler
|
||||
* as needed after it is released.
|
||||
*
|
||||
* \see Latin1StringHandler
|
||||
*/
|
||||
static void setLatin1StringHandler(const Latin1StringHandler *handler);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
|
||||
@@ -437,6 +437,21 @@ long MPEG::File::lastFrameOffset()
|
||||
return previousFrameOffset(ID3v1Tag() ? d->ID3v1Location - 1 : length());
|
||||
}
|
||||
|
||||
bool MPEG::File::hasID3v1Tag() const
|
||||
{
|
||||
return d->hasID3v1;
|
||||
}
|
||||
|
||||
bool MPEG::File::hasID3v2Tag() const
|
||||
{
|
||||
return d->hasID3v2;
|
||||
}
|
||||
|
||||
bool MPEG::File::hasAPETag() const
|
||||
{
|
||||
return d->hasAPE;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -99,6 +99,9 @@ namespace TagLib {
|
||||
* file's audio properties will also be read using \a propertiesStyle. If
|
||||
* false, \a propertiesStyle is ignored. The frames will be created using
|
||||
* \a frameFactory.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
// BIC: merge with the above constructor
|
||||
File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
|
||||
@@ -298,6 +301,21 @@ namespace TagLib {
|
||||
*/
|
||||
long lastFrameOffset();
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file on disk contains ID3v1 tag.
|
||||
*/
|
||||
bool hasID3v1Tag() const;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file on disk contains ID3v2 tag.
|
||||
*/
|
||||
bool hasID3v2Tag() const;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file on disk contains APE tag.
|
||||
*/
|
||||
bool hasAPETag() const;
|
||||
|
||||
private:
|
||||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
||||
@@ -221,7 +221,7 @@ void MPEG::Properties::read()
|
||||
double length = timePerFrame * d->xingHeader->totalFrames();
|
||||
|
||||
d->length = int(length);
|
||||
d->bitrate = d->length > 0 ? d->xingHeader->totalSize() * 8 / length / 1000 : 0;
|
||||
d->bitrate = d->length > 0 ? (int)(d->xingHeader->totalSize() * 8 / length / 1000) : 0;
|
||||
}
|
||||
else {
|
||||
// Since there was no valid Xing header found, we hope that we're in a constant
|
||||
|
||||
@@ -72,14 +72,16 @@ Ogg::FLAC::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
Ogg::FLAC::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(stream)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
Ogg::FLAC::File::~File()
|
||||
@@ -263,9 +265,9 @@ void Ogg::FLAC::File::scan()
|
||||
d->hasXiphComment = true;
|
||||
d->commentPacket = ipacket;
|
||||
}
|
||||
else if(blockType > 5)
|
||||
else if(blockType > 5) {
|
||||
debug("Ogg::FLAC::File::scan() -- Unknown metadata block");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// End of metadata, now comes the datastream
|
||||
|
||||
@@ -75,6 +75,9 @@ namespace TagLib {
|
||||
* Contructs an Ogg/FLAC 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
@@ -100,6 +100,9 @@ namespace TagLib {
|
||||
* \note This constructor is protected since Ogg::File shouldn't be
|
||||
* instantiated directly but rather should be used through the codec
|
||||
* specific subclasses.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream);
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2012 by Lukáš Lalinský
|
||||
email : lalinsky@gmail.com
|
||||
|
||||
copyright : (C) 2002 - 2008 by Scott Wheeler
|
||||
email : wheeler@kde.org
|
||||
(original Vorbis implementation)
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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 <bitset>
|
||||
|
||||
#include <tstring.h>
|
||||
#include <tdebug.h>
|
||||
|
||||
#include "opusfile.h"
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace TagLib::Ogg;
|
||||
|
||||
class Opus::File::FilePrivate
|
||||
{
|
||||
public:
|
||||
FilePrivate() :
|
||||
comment(0),
|
||||
properties(0) {}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
delete comment;
|
||||
delete properties;
|
||||
}
|
||||
|
||||
Ogg::XiphComment *comment;
|
||||
Properties *properties;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Opus::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
Opus::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(stream)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
Opus::File::~File()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
Ogg::XiphComment *Opus::File::tag() const
|
||||
{
|
||||
return d->comment;
|
||||
}
|
||||
|
||||
Opus::Properties *Opus::File::audioProperties() const
|
||||
{
|
||||
return d->properties;
|
||||
}
|
||||
|
||||
bool Opus::File::save()
|
||||
{
|
||||
if(!d->comment)
|
||||
d->comment = new Ogg::XiphComment;
|
||||
|
||||
setPacket(1, ByteVector("OpusTags", 8) + d->comment->render(false));
|
||||
|
||||
return Ogg::File::save();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Opus::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
{
|
||||
ByteVector opusHeaderData = packet(0);
|
||||
|
||||
if(!opusHeaderData.startsWith("OpusHead")) {
|
||||
setValid(false);
|
||||
debug("Opus::File::read() -- invalid Opus identification header");
|
||||
return;
|
||||
}
|
||||
|
||||
ByteVector commentHeaderData = packet(1);
|
||||
|
||||
if(!commentHeaderData.startsWith("OpusTags")) {
|
||||
setValid(false);
|
||||
debug("Opus::File::read() -- invalid Opus tags header");
|
||||
return;
|
||||
}
|
||||
|
||||
d->comment = new Ogg::XiphComment(commentHeaderData.mid(8));
|
||||
|
||||
if(readProperties)
|
||||
d->properties = new Properties(this, propertiesStyle);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2012 by Lukáš Lalinský
|
||||
email : lalinsky@gmail.com
|
||||
|
||||
copyright : (C) 2002 - 2008 by Scott Wheeler
|
||||
email : wheeler@kde.org
|
||||
(original Vorbis implementation)
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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_OPUSFILE_H
|
||||
#define TAGLIB_OPUSFILE_H
|
||||
|
||||
#include "oggfile.h"
|
||||
#include "xiphcomment.h"
|
||||
|
||||
#include "opusproperties.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
namespace Ogg {
|
||||
|
||||
//! A namespace containing classes for Opus metadata
|
||||
|
||||
namespace Opus {
|
||||
|
||||
//! An implementation of Ogg::File with Opus specific methods
|
||||
|
||||
/*!
|
||||
* This is the central class in the Ogg Opus metadata processing collection
|
||||
* of classes. It's built upon Ogg::File which handles processing of the Ogg
|
||||
* logical bitstream and breaking it down into pages which are handled by
|
||||
* the codec implementations, in this case Opus specifically.
|
||||
*/
|
||||
|
||||
class TAGLIB_EXPORT File : public Ogg::File
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Contructs a Opus 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 a Opus 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
/*!
|
||||
* Destroys this instance of the File.
|
||||
*/
|
||||
virtual ~File();
|
||||
|
||||
/*!
|
||||
* Returns the XiphComment for this file. XiphComment implements the tag
|
||||
* interface, so this serves as the reimplementation of
|
||||
* TagLib::File::tag().
|
||||
*/
|
||||
virtual Ogg::XiphComment *tag() const;
|
||||
|
||||
/*!
|
||||
* Returns the Opus::Properties for this file. If no audio properties
|
||||
* were read then this will return a null pointer.
|
||||
*/
|
||||
virtual Properties *audioProperties() const;
|
||||
|
||||
virtual bool save();
|
||||
|
||||
private:
|
||||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,161 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2012 by Lukáš Lalinský
|
||||
email : lalinsky@gmail.com
|
||||
|
||||
copyright : (C) 2002 - 2008 by Scott Wheeler
|
||||
email : wheeler@kde.org
|
||||
(original Vorbis implementation)
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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 <oggpageheader.h>
|
||||
|
||||
#include "opusproperties.h"
|
||||
#include "opusfile.h"
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace TagLib::Ogg;
|
||||
|
||||
class Opus::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(File *f, ReadStyle s) :
|
||||
file(f),
|
||||
style(s),
|
||||
length(0),
|
||||
inputSampleRate(0),
|
||||
channels(0),
|
||||
opusVersion(0) {}
|
||||
|
||||
File *file;
|
||||
ReadStyle style;
|
||||
int length;
|
||||
int inputSampleRate;
|
||||
int channels;
|
||||
int opusVersion;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Opus::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style)
|
||||
{
|
||||
d = new PropertiesPrivate(file, style);
|
||||
read();
|
||||
}
|
||||
|
||||
Opus::Properties::~Properties()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
int Opus::Properties::length() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
|
||||
int Opus::Properties::bitrate() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Opus::Properties::sampleRate() const
|
||||
{
|
||||
// Opus can decode any stream at a sample rate of 8, 12, 16, 24, or 48 kHz,
|
||||
// so there is no single sample rate. Let's assume it's the highest
|
||||
// possible.
|
||||
return 48000;
|
||||
}
|
||||
|
||||
int Opus::Properties::channels() const
|
||||
{
|
||||
return d->channels;
|
||||
}
|
||||
|
||||
int Opus::Properties::inputSampleRate() const
|
||||
{
|
||||
return d->inputSampleRate;
|
||||
}
|
||||
|
||||
int Opus::Properties::opusVersion() const
|
||||
{
|
||||
return d->opusVersion;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Opus::Properties::read()
|
||||
{
|
||||
// Get the identification header from the Ogg implementation.
|
||||
|
||||
// http://tools.ietf.org/html/draft-terriberry-oggopus-01#section-5.1
|
||||
|
||||
ByteVector data = d->file->packet(0);
|
||||
|
||||
// *Magic Signature*
|
||||
int pos = 8;
|
||||
|
||||
// *Version* (8 bits, unsigned)
|
||||
d->opusVersion = uchar(data.at(pos));
|
||||
pos += 1;
|
||||
|
||||
// *Output Channel Count* 'C' (8 bits, unsigned)
|
||||
d->channels = uchar(data.at(pos));
|
||||
pos += 1;
|
||||
|
||||
// *Pre-skip* (16 bits, unsigned, little endian)
|
||||
ushort preSkip = data.mid(pos, 2).toUShort(false);
|
||||
pos += 2;
|
||||
|
||||
// *Input Sample Rate* (32 bits, unsigned, little endian)
|
||||
d->inputSampleRate = data.mid(pos, 4).toUInt(false);
|
||||
pos += 4;
|
||||
|
||||
// *Output Gain* (16 bits, signed, little endian)
|
||||
pos += 2;
|
||||
|
||||
// *Channel Mapping Family* (8 bits, unsigned)
|
||||
pos += 1;
|
||||
|
||||
const Ogg::PageHeader *first = d->file->firstPageHeader();
|
||||
const Ogg::PageHeader *last = d->file->lastPageHeader();
|
||||
|
||||
if(first && last) {
|
||||
long long start = first->absoluteGranularPosition();
|
||||
long long end = last->absoluteGranularPosition();
|
||||
|
||||
if(start >= 0 && end >= 0)
|
||||
d->length = (int) ((end - start - preSkip) / 48000);
|
||||
else {
|
||||
debug("Opus::Properties::read() -- The PCM values for the start or "
|
||||
"end of this file was incorrect.");
|
||||
}
|
||||
}
|
||||
else
|
||||
debug("Opus::Properties::read() -- Could not find valid first and last Ogg pages.");
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2012 by Lukáš Lalinský
|
||||
email : lalinsky@gmail.com
|
||||
|
||||
copyright : (C) 2002 - 2008 by Scott Wheeler
|
||||
email : wheeler@kde.org
|
||||
(original Vorbis implementation)
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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_OPUSPROPERTIES_H
|
||||
#define TAGLIB_OPUSPROPERTIES_H
|
||||
|
||||
#include "audioproperties.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
namespace Ogg {
|
||||
|
||||
namespace Opus {
|
||||
|
||||
class File;
|
||||
|
||||
//! An implementation of audio property reading for Ogg Opus
|
||||
|
||||
/*!
|
||||
* This reads the data from an Ogg Opus stream found in the AudioProperties
|
||||
* API.
|
||||
*/
|
||||
|
||||
class TAGLIB_EXPORT Properties : public AudioProperties
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Create an instance of Opus::Properties with the data read from the
|
||||
* Opus::File \a file.
|
||||
*/
|
||||
Properties(File *file, ReadStyle style = Average);
|
||||
|
||||
/*!
|
||||
* Destroys this Opus::Properties instance.
|
||||
*/
|
||||
virtual ~Properties();
|
||||
|
||||
// Reimplementations.
|
||||
|
||||
virtual int length() const;
|
||||
virtual int bitrate() const;
|
||||
virtual int sampleRate() const;
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* The Opus codec supports decoding at multiple sample rates, there is no
|
||||
* single sample rate of the encoded stream. This returns the sample rate
|
||||
* of the original audio stream.
|
||||
*/
|
||||
int inputSampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the Opus version, currently "0" (as specified by the spec).
|
||||
*/
|
||||
int opusVersion() const;
|
||||
|
||||
private:
|
||||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void read();
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -62,14 +62,16 @@ Speex::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
Speex::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(stream)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
Speex::File::~File()
|
||||
|
||||
@@ -67,6 +67,9 @@ namespace TagLib {
|
||||
* Contructs a Speex 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
@@ -67,14 +67,16 @@ Vorbis::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
Vorbis::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(stream)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
Vorbis::File::~File()
|
||||
|
||||
@@ -74,6 +74,9 @@ namespace TagLib {
|
||||
* Contructs a Vorbis 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
@@ -173,7 +173,7 @@ void Vorbis::Properties::read()
|
||||
long long end = last->absoluteGranularPosition();
|
||||
|
||||
if(start >= 0 && end >= 0 && d->sampleRate > 0)
|
||||
d->length = (end - start) / (long long) d->sampleRate;
|
||||
d->length = (int)((end - start) / (long long) d->sampleRate);
|
||||
else
|
||||
debug("Vorbis::Properties::read() -- Either the PCM values for the start or "
|
||||
"end of this file was incorrect or the sample rate is zero.");
|
||||
|
||||
@@ -69,6 +69,9 @@ namespace TagLib {
|
||||
* Contructs an AIFF 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
@@ -154,7 +154,7 @@ void RIFF::AIFF::Properties::read(const ByteVector &data)
|
||||
d->sampleFrames = data.mid(2, 4).toUInt();
|
||||
d->sampleWidth = data.mid(6, 2).toShort();
|
||||
double sampleRate = ConvertFromIeeeExtended(reinterpret_cast<unsigned char *>(data.mid(8, 10).data()));
|
||||
d->sampleRate = sampleRate;
|
||||
d->bitrate = (sampleRate * d->sampleWidth * d->channels) / 1000.0;
|
||||
d->sampleRate = (int)sampleRate;
|
||||
d->bitrate = (int)((sampleRate * d->sampleWidth * d->channels) / 1000.0);
|
||||
d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0;
|
||||
}
|
||||
|
||||
+54
-22
@@ -138,34 +138,44 @@ ByteVector RIFF::File::chunkData(uint i)
|
||||
return readBlock(d->chunks[i].size);
|
||||
}
|
||||
|
||||
void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
|
||||
void RIFF::File::setChunkData(uint i, const ByteVector &data)
|
||||
{
|
||||
// First we update the global size
|
||||
|
||||
d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding);
|
||||
insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4);
|
||||
|
||||
// Now update the specific chunk
|
||||
|
||||
writeChunk(chunkName(i), data, d->chunks[i].offset - 8, d->chunks[i].size + d->chunks[i].padding + 8);
|
||||
|
||||
d->chunks[i].size = data.size();
|
||||
d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0;
|
||||
|
||||
// Now update the internal offsets
|
||||
|
||||
for(i++; i < d->chunks.size(); i++)
|
||||
d->chunks[i].offset = d->chunks[i-1].offset + 8 + d->chunks[i-1].size + d->chunks[i-1].padding;
|
||||
}
|
||||
|
||||
void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data, bool alwaysCreate)
|
||||
{
|
||||
if(d->chunks.size() == 0) {
|
||||
debug("RIFF::File::setChunkData - No valid chunks found.");
|
||||
return;
|
||||
}
|
||||
|
||||
for(uint i = 0; i < d->chunks.size(); i++) {
|
||||
if(d->chunks[i].name == name) {
|
||||
if(alwaysCreate && name != "LIST") {
|
||||
debug("RIFF::File::setChunkData - alwaysCreate should be used for only \"LIST\" chunks.");
|
||||
return;
|
||||
}
|
||||
|
||||
// First we update the global size
|
||||
|
||||
d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding);
|
||||
insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4);
|
||||
|
||||
// Now update the specific chunk
|
||||
|
||||
writeChunk(name, data, d->chunks[i].offset - 8, d->chunks[i].size + d->chunks[i].padding + 8);
|
||||
|
||||
d->chunks[i].size = data.size();
|
||||
d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0;
|
||||
|
||||
// Now update the internal offsets
|
||||
|
||||
for(i++; i < d->chunks.size(); i++)
|
||||
d->chunks[i].offset = d->chunks[i-1].offset + 8 + d->chunks[i-1].size + d->chunks[i-1].padding;
|
||||
|
||||
return;
|
||||
if(!alwaysCreate) {
|
||||
for(uint i = 0; i < d->chunks.size(); i++) {
|
||||
if(d->chunks[i].name == name) {
|
||||
setChunkData(i, data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +191,7 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
|
||||
|
||||
// Now add the chunk to the file
|
||||
|
||||
writeChunk(name, data, offset, std::max(ulong(0), length() - offset), (offset & 1) ? 1 : 0);
|
||||
writeChunk(name, data, offset, std::max<long>(0, length() - offset), (offset & 1) ? 1 : 0);
|
||||
|
||||
// And update our internal structure
|
||||
|
||||
@@ -199,6 +209,28 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
|
||||
d->chunks.push_back(chunk);
|
||||
}
|
||||
|
||||
void RIFF::File::removeChunk(uint i)
|
||||
{
|
||||
if(i >= d->chunks.size())
|
||||
return;
|
||||
|
||||
removeBlock(d->chunks[i].offset - 8, d->chunks[i].size + 8);
|
||||
d->chunks.erase(d->chunks.begin() + i);
|
||||
}
|
||||
|
||||
void RIFF::File::removeChunk(const ByteVector &name)
|
||||
{
|
||||
std::vector<Chunk> newChunks;
|
||||
for(size_t i = 0; i < d->chunks.size(); ++i) {
|
||||
if(d->chunks[i].name == name)
|
||||
removeBlock(d->chunks[i].offset - 8, d->chunks[i].size + 8);
|
||||
else
|
||||
newChunks.push_back(d->chunks[i]);
|
||||
}
|
||||
|
||||
d->chunks.swap(newChunks);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+26
-1
@@ -95,14 +95,39 @@ namespace TagLib {
|
||||
*/
|
||||
ByteVector chunkData(uint i);
|
||||
|
||||
/*!
|
||||
* Sets the data for the the specified chunk to \a data.
|
||||
*
|
||||
* \warning This will update the file immediately.
|
||||
*/
|
||||
void setChunkData(uint i, const ByteVector &data);
|
||||
|
||||
/*!
|
||||
* Sets the data for the 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.
|
||||
*
|
||||
* \note If \a alwaysCreate is true, a new chunk is created regardless of
|
||||
* existence of chunk \a name. It should be used for only "LIST" chunks.
|
||||
*
|
||||
* \warning This will update the file immediately.
|
||||
*/
|
||||
void setChunkData(const ByteVector &name, const ByteVector &data);
|
||||
void setChunkData(const ByteVector &name, const ByteVector &data, bool alwaysCreate = false);
|
||||
|
||||
/*!
|
||||
* Removes the specified chunk.
|
||||
*
|
||||
* \warning This will update the file immediately.
|
||||
*/
|
||||
void removeChunk(uint i);
|
||||
|
||||
/*!
|
||||
* Removes the chunk \a name.
|
||||
*
|
||||
* \warning This will update the file immediately.
|
||||
* \warning This removes all the chunks with the given name.
|
||||
*/
|
||||
void removeChunk(const ByteVector &name);
|
||||
|
||||
private:
|
||||
File(const File &);
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2002 - 2008 by Scott Wheeler
|
||||
email : wheeler@kde.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 <tdebug.h>
|
||||
#include <tfile.h>
|
||||
|
||||
#include "infotag.h"
|
||||
|
||||
using namespace TagLib;
|
||||
using namespace RIFF::Info;
|
||||
|
||||
namespace {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
class RIFF::Info::Tag::TagPrivate
|
||||
{
|
||||
public:
|
||||
TagPrivate()
|
||||
{}
|
||||
|
||||
FieldListMap fieldListMap;
|
||||
|
||||
static const StringHandler *stringHandler;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// StringHandler implementation
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
StringHandler::StringHandler()
|
||||
{
|
||||
}
|
||||
|
||||
StringHandler::~StringHandler()
|
||||
{
|
||||
}
|
||||
|
||||
String RIFF::Info::StringHandler::parse(const ByteVector &data) const
|
||||
{
|
||||
return String(data, String::UTF8);
|
||||
}
|
||||
|
||||
ByteVector RIFF::Info::StringHandler::render(const String &s) const
|
||||
{
|
||||
return s.data(String::UTF8);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const StringHandler defaultStringHandler;
|
||||
const RIFF::Info::StringHandler *RIFF::Info::Tag::TagPrivate::stringHandler = &defaultStringHandler;
|
||||
|
||||
RIFF::Info::Tag::Tag(const ByteVector &data) : TagLib::Tag()
|
||||
{
|
||||
d = new TagPrivate;
|
||||
parse(data);
|
||||
}
|
||||
|
||||
RIFF::Info::Tag::Tag() : TagLib::Tag()
|
||||
{
|
||||
d = new TagPrivate;
|
||||
}
|
||||
|
||||
RIFF::Info::Tag::~Tag()
|
||||
{
|
||||
}
|
||||
|
||||
String RIFF::Info::Tag::title() const
|
||||
{
|
||||
return fieldText("INAM");
|
||||
}
|
||||
|
||||
String RIFF::Info::Tag::artist() const
|
||||
{
|
||||
return fieldText("IART");
|
||||
}
|
||||
|
||||
String RIFF::Info::Tag::album() const
|
||||
{
|
||||
return fieldText("IPRD");
|
||||
}
|
||||
|
||||
String RIFF::Info::Tag::comment() const
|
||||
{
|
||||
return fieldText("ICMT");
|
||||
}
|
||||
|
||||
String RIFF::Info::Tag::genre() const
|
||||
{
|
||||
return fieldText("IGNR");
|
||||
}
|
||||
|
||||
TagLib::uint RIFF::Info::Tag::year() const
|
||||
{
|
||||
return fieldText("ICRD").substr(0, 4).toInt();
|
||||
}
|
||||
|
||||
TagLib::uint RIFF::Info::Tag::track() const
|
||||
{
|
||||
return fieldText("IPRT").toInt();
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::setTitle(const String &s)
|
||||
{
|
||||
setFieldText("INAM", s);
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::setArtist(const String &s)
|
||||
{
|
||||
setFieldText("IART", s);
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::setAlbum(const String &s)
|
||||
{
|
||||
setFieldText("IPRD", s);
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::setComment(const String &s)
|
||||
{
|
||||
setFieldText("ICMT", s);
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::setGenre(const String &s)
|
||||
{
|
||||
setFieldText("IGNR", s);
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::setYear(uint i)
|
||||
{
|
||||
if(i != 0)
|
||||
setFieldText("ICRD", String::number(i));
|
||||
else
|
||||
d->fieldListMap.erase("ICRD");
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::setTrack(uint i)
|
||||
{
|
||||
if(i != 0)
|
||||
setFieldText("IPRT", String::number(i));
|
||||
else
|
||||
d->fieldListMap.erase("IPRT");
|
||||
}
|
||||
|
||||
bool RIFF::Info::Tag::isEmpty() const
|
||||
{
|
||||
return d->fieldListMap.isEmpty();
|
||||
}
|
||||
|
||||
String RIFF::Info::Tag::fieldText(const ByteVector &id) const
|
||||
{
|
||||
if(d->fieldListMap.contains(id))
|
||||
return String(d->fieldListMap[id]);
|
||||
else
|
||||
return String();
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::setFieldText(const ByteVector &id, const String &s)
|
||||
{
|
||||
// id must be four-byte long pure ascii string.
|
||||
if(!isValidChunkID(id))
|
||||
return;
|
||||
|
||||
if(!s.isEmpty())
|
||||
d->fieldListMap[id] = s;
|
||||
else
|
||||
removeField(id);
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::removeField(const ByteVector &id)
|
||||
{
|
||||
if(d->fieldListMap.contains(id))
|
||||
d->fieldListMap.erase(id);
|
||||
}
|
||||
|
||||
ByteVector RIFF::Info::Tag::render() const
|
||||
{
|
||||
ByteVector data("INFO");
|
||||
|
||||
FieldListMap::ConstIterator it = d->fieldListMap.begin();
|
||||
for(; it != d->fieldListMap.end(); ++it) {
|
||||
ByteVector text = TagPrivate::stringHandler->render(it->second);
|
||||
if(text.isEmpty())
|
||||
continue;
|
||||
|
||||
data.append(it->first);
|
||||
data.append(ByteVector::fromUInt(text.size() + 1, false));
|
||||
data.append(text);
|
||||
|
||||
do {
|
||||
data.append('\0');
|
||||
} while(data.size() & 1);
|
||||
}
|
||||
|
||||
if(data.size() == 4)
|
||||
return ByteVector();
|
||||
else
|
||||
return data;
|
||||
}
|
||||
|
||||
void RIFF::Info::Tag::setStringHandler(const StringHandler *handler)
|
||||
{
|
||||
if(handler)
|
||||
TagPrivate::stringHandler = handler;
|
||||
else
|
||||
TagPrivate::stringHandler = &defaultStringHandler;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RIFF::Info::Tag::parse(const ByteVector &data)
|
||||
{
|
||||
uint p = 4;
|
||||
while(p < data.size()) {
|
||||
uint size = data.mid(p + 4, 4).toUInt(false);
|
||||
d->fieldListMap[data.mid(p, 4)] = TagPrivate::stringHandler->parse(data.mid(p + 8, size));
|
||||
|
||||
p += ((size + 1) & ~1) + 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2002 - 2008 by Scott Wheeler
|
||||
email : wheeler@kde.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_INFOTAG_H
|
||||
#define TAGLIB_INFOTAG_H
|
||||
|
||||
#include "tag.h"
|
||||
#include "tmap.h"
|
||||
#include "tstring.h"
|
||||
#include "tstringlist.h"
|
||||
#include "tbytevector.h"
|
||||
#include "taglib_export.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
class File;
|
||||
|
||||
//! A RIFF Info tag implementation.
|
||||
namespace RIFF {
|
||||
namespace Info {
|
||||
|
||||
typedef Map<ByteVector, String> FieldListMap;
|
||||
|
||||
//! A abstraction for the string to data encoding in Info tags.
|
||||
|
||||
/*!
|
||||
* RIFF Info tag has no clear definitions about character encodings.
|
||||
* In practice, local encoding of each system is largely used and UTF-8 is
|
||||
* popular too.
|
||||
*
|
||||
* Here is an option to read and write tags in your preferrd encoding
|
||||
* by subclassing this class, reimplementing parse() and render() and setting
|
||||
* your reimplementation as the default with Info::Tag::setStringHandler().
|
||||
*
|
||||
* \see ID3v1::Tag::setStringHandler()
|
||||
*/
|
||||
|
||||
class TAGLIB_EXPORT StringHandler
|
||||
{
|
||||
public:
|
||||
StringHandler();
|
||||
~StringHandler();
|
||||
|
||||
/*!
|
||||
* Decode a string from \a data. The default implementation assumes that
|
||||
* \a data is an UTF-8 character array.
|
||||
*/
|
||||
virtual String parse(const ByteVector &data) const;
|
||||
|
||||
/*!
|
||||
* Encode a ByteVector with the data from \a s. The default implementation
|
||||
* assumes that \a s is an UTF-8 string.
|
||||
*/
|
||||
virtual ByteVector render(const String &s) const;
|
||||
};
|
||||
|
||||
//! The main class in the ID3v2 implementation
|
||||
|
||||
/*!
|
||||
* This is the main class in the INFO tag implementation. RIFF INFO tag is a
|
||||
* metadata format found in WAV audio and AVI video files. Though it is a part
|
||||
* of Microsoft/IBM's RIFF specification, the author could not find the official
|
||||
* documents about it. So, this implementation is refering to unofficial documents
|
||||
* online and some applications' behaviors especially Windows Explorer.
|
||||
*/
|
||||
class TAGLIB_EXPORT Tag : public TagLib::Tag
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Constructs an empty Info tag.
|
||||
*/
|
||||
Tag();
|
||||
|
||||
/*!
|
||||
* Constructs an Info tag read from \a data which is contents of "LIST" chunk.
|
||||
*/
|
||||
Tag(const ByteVector &data);
|
||||
|
||||
virtual ~Tag();
|
||||
|
||||
// Reimplementations
|
||||
|
||||
virtual String title() const;
|
||||
virtual String artist() const;
|
||||
virtual String album() const;
|
||||
virtual String comment() const;
|
||||
virtual String genre() const;
|
||||
virtual uint year() const;
|
||||
virtual uint track() const;
|
||||
|
||||
virtual void setTitle(const String &s);
|
||||
virtual void setArtist(const String &s);
|
||||
virtual void setAlbum(const String &s);
|
||||
virtual void setComment(const String &s);
|
||||
virtual void setGenre(const String &s);
|
||||
virtual void setYear(uint i);
|
||||
virtual void setTrack(uint i);
|
||||
|
||||
virtual bool isEmpty() const;
|
||||
/*
|
||||
* Gets the value of the field with the ID \a id.
|
||||
*/
|
||||
String fieldText(const ByteVector &id) const;
|
||||
|
||||
/*
|
||||
* Sets the value of the field with the ID \a id to \a s.
|
||||
* If the field does not exist, it is created.
|
||||
* If \s is empty, the field is removed.
|
||||
*
|
||||
* \note fieldId must be four-byte long pure ascii string. This function
|
||||
* performs nothing if fieldId is invalid.
|
||||
*/
|
||||
void setFieldText(const ByteVector &id, const String &s);
|
||||
|
||||
/*
|
||||
* Removes the field with the ID \a id.
|
||||
*/
|
||||
void removeField(const ByteVector &id);
|
||||
|
||||
/*!
|
||||
* Render the tag back to binary data, suitable to be written to disk.
|
||||
*
|
||||
* \note Returns empty ByteVector is the tag contains no fields.
|
||||
*/
|
||||
ByteVector render() const;
|
||||
|
||||
/*!
|
||||
* Sets the string handler that decides how the text data will be
|
||||
* converted to and from binary data.
|
||||
* If the parameter \a handler is null, the previous handler is
|
||||
* released and default UTF-8 handler is restored.
|
||||
*
|
||||
* \note The caller is responsible for deleting the previous handler
|
||||
* as needed after it is released.
|
||||
*
|
||||
* \see StringHandler
|
||||
*/
|
||||
static void setStringHandler(const StringHandler *handler);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Pareses the body of the tag in \a data.
|
||||
*/
|
||||
void parse(const ByteVector &data);
|
||||
|
||||
|
||||
private:
|
||||
Tag(const Tag &);
|
||||
Tag &operator=(const Tag &);
|
||||
|
||||
class TagPrivate;
|
||||
TagPrivate *d;
|
||||
};
|
||||
}}
|
||||
}
|
||||
|
||||
#endif
|
||||
+92
-20
@@ -23,36 +23,42 @@
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include <tbytevector.h>
|
||||
#include <tdebug.h>
|
||||
#include <id3v2tag.h>
|
||||
#include <tstringlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include "tbytevector.h"
|
||||
#include "tdebug.h"
|
||||
#include "tstringlist.h"
|
||||
#include "tpropertymap.h"
|
||||
|
||||
#include "wavfile.h"
|
||||
#include "id3v2tag.h"
|
||||
#include "infotag.h"
|
||||
#include "tagunion.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
namespace
|
||||
{
|
||||
enum { ID3v2Index = 0, InfoIndex = 1 };
|
||||
}
|
||||
|
||||
class RIFF::WAV::File::FilePrivate
|
||||
{
|
||||
public:
|
||||
FilePrivate() :
|
||||
properties(0),
|
||||
tag(0),
|
||||
tagChunkID("ID3 ")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
delete properties;
|
||||
delete tag;
|
||||
}
|
||||
|
||||
Properties *properties;
|
||||
ID3v2::Tag *tag;
|
||||
|
||||
ByteVector tagChunkID;
|
||||
|
||||
TagUnion tag;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -82,26 +88,40 @@ RIFF::WAV::File::~File()
|
||||
|
||||
ID3v2::Tag *RIFF::WAV::File::tag() const
|
||||
{
|
||||
return d->tag;
|
||||
return ID3v2Tag();
|
||||
}
|
||||
|
||||
ID3v2::Tag *RIFF::WAV::File::ID3v2Tag() const
|
||||
{
|
||||
return d->tag.access<ID3v2::Tag>(ID3v2Index, false);
|
||||
}
|
||||
|
||||
RIFF::Info::Tag *RIFF::WAV::File::InfoTag() const
|
||||
{
|
||||
return d->tag.access<RIFF::Info::Tag>(InfoIndex, false);
|
||||
}
|
||||
|
||||
PropertyMap RIFF::WAV::File::properties() const
|
||||
{
|
||||
return d->tag->properties();
|
||||
return d->tag.properties();
|
||||
}
|
||||
|
||||
PropertyMap RIFF::WAV::File::setProperties(const PropertyMap &properties)
|
||||
{
|
||||
return d->tag->setProperties(properties);
|
||||
return d->tag.setProperties(properties);
|
||||
}
|
||||
|
||||
|
||||
RIFF::WAV::Properties *RIFF::WAV::File::audioProperties() const
|
||||
{
|
||||
return d->properties;
|
||||
}
|
||||
|
||||
bool RIFF::WAV::File::save()
|
||||
{
|
||||
return RIFF::WAV::File::save(AllTags);
|
||||
}
|
||||
|
||||
bool RIFF::WAV::File::save(TagTypes tags, bool stripOthers, int id3v2Version)
|
||||
{
|
||||
if(readOnly()) {
|
||||
debug("RIFF::WAV::File::save() -- File is read only.");
|
||||
@@ -113,7 +133,25 @@ bool RIFF::WAV::File::save()
|
||||
return false;
|
||||
}
|
||||
|
||||
setChunkData(d->tagChunkID, d->tag->render());
|
||||
if(stripOthers)
|
||||
strip(static_cast<TagTypes>(AllTags & ~tags));
|
||||
|
||||
ID3v2::Tag *id3v2tag = d->tag.access<ID3v2::Tag>(ID3v2Index, false);
|
||||
if(!id3v2tag->isEmpty()) {
|
||||
if(tags & ID3v2)
|
||||
setChunkData(d->tagChunkID, id3v2tag->render(id3v2Version));
|
||||
}
|
||||
|
||||
Info::Tag *infotag = d->tag.access<Info::Tag>(InfoIndex, false);
|
||||
if(!infotag->isEmpty()) {
|
||||
if(tags & Info) {
|
||||
int chunkId = findInfoTagChunk();
|
||||
if(chunkId != -1)
|
||||
setChunkData(chunkId, infotag->render());
|
||||
else
|
||||
setChunkData("LIST", infotag->render(), true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -127,19 +165,53 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties
|
||||
ByteVector formatData;
|
||||
uint streamLength = 0;
|
||||
for(uint i = 0; i < chunkCount(); i++) {
|
||||
if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") {
|
||||
String name = chunkName(i);
|
||||
if(name == "ID3 " || name == "id3 ") {
|
||||
d->tagChunkID = chunkName(i);
|
||||
d->tag = new ID3v2::Tag(this, chunkOffset(i));
|
||||
d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i)));
|
||||
}
|
||||
else if(chunkName(i) == "fmt " && readProperties)
|
||||
else if(name == "fmt " && readProperties)
|
||||
formatData = chunkData(i);
|
||||
else if(chunkName(i) == "data" && readProperties)
|
||||
else if(name == "data" && readProperties)
|
||||
streamLength = chunkDataSize(i);
|
||||
else if(name == "LIST") {
|
||||
ByteVector data = chunkData(i);
|
||||
ByteVector type = data.mid(0, 4);
|
||||
|
||||
if(type == "INFO")
|
||||
d->tag.set(InfoIndex, new RIFF::Info::Tag(data));
|
||||
}
|
||||
}
|
||||
|
||||
if (!d->tag[ID3v2Index])
|
||||
d->tag.set(ID3v2Index, new ID3v2::Tag);
|
||||
|
||||
if (!d->tag[InfoIndex])
|
||||
d->tag.set(InfoIndex, new RIFF::Info::Tag);
|
||||
|
||||
if(!formatData.isEmpty())
|
||||
d->properties = new Properties(formatData, streamLength, propertiesStyle);
|
||||
}
|
||||
|
||||
if(!d->tag)
|
||||
d->tag = new ID3v2::Tag;
|
||||
void RIFF::WAV::File::strip(TagTypes tags)
|
||||
{
|
||||
if(tags & ID3v2)
|
||||
removeChunk(d->tagChunkID);
|
||||
|
||||
if(tags & Info){
|
||||
TagLib::uint chunkId = findInfoTagChunk();
|
||||
if(chunkId != TagLib::uint(-1))
|
||||
removeChunk(chunkId);
|
||||
}
|
||||
}
|
||||
|
||||
TagLib::uint RIFF::WAV::File::findInfoTagChunk()
|
||||
{
|
||||
for(uint i = 0; i < chunkCount(); ++i) {
|
||||
if(chunkName(i) == "LIST" && chunkData(i).mid(0, 4) == "INFO") {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return TagLib::uint(-1);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "rifffile.h"
|
||||
#include "id3v2tag.h"
|
||||
#include "infotag.h"
|
||||
#include "wavproperties.h"
|
||||
|
||||
namespace TagLib {
|
||||
@@ -57,6 +58,17 @@ namespace TagLib {
|
||||
class TAGLIB_EXPORT File : public TagLib::RIFF::File
|
||||
{
|
||||
public:
|
||||
enum TagTypes {
|
||||
//! Empty set. Matches no tag types.
|
||||
NoTags = 0x0000,
|
||||
//! Matches ID3v2 tags.
|
||||
ID3v2 = 0x0001,
|
||||
//! Matches Info tags.
|
||||
Info = 0x0002,
|
||||
//! Matches all tag types.
|
||||
AllTags = 0xffff
|
||||
};
|
||||
|
||||
/*!
|
||||
* Contructs an WAV file from \a file. If \a readProperties is true the
|
||||
* file's audio properties will also be read using \a propertiesStyle. If
|
||||
@@ -69,6 +81,9 @@ namespace TagLib {
|
||||
* Contructs an WAV 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
@@ -79,9 +94,22 @@ namespace TagLib {
|
||||
virtual ~File();
|
||||
|
||||
/*!
|
||||
* Returns the Tag for this file.
|
||||
* Returns the ID3v2 Tag for this file.
|
||||
*
|
||||
* \note This method does not return all the tags for this file for
|
||||
* backward compatibility. Will be fixed in TagLib 2.0.
|
||||
*/
|
||||
virtual ID3v2::Tag *tag() const;
|
||||
ID3v2::Tag *tag() const;
|
||||
|
||||
/*!
|
||||
* Returns the ID3v2 Tag for this file.
|
||||
*/
|
||||
ID3v2::Tag *ID3v2Tag() const;
|
||||
|
||||
/*!
|
||||
* Returns the RIFF INFO Tag for this file.
|
||||
*/
|
||||
Info::Tag *InfoTag() const;
|
||||
|
||||
/*!
|
||||
* Implements the unified property interface -- export function.
|
||||
@@ -106,12 +134,21 @@ namespace TagLib {
|
||||
*/
|
||||
virtual bool save();
|
||||
|
||||
bool save(TagTypes tags, bool stripOthers = true, int id3v2Version = 4);
|
||||
|
||||
private:
|
||||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
|
||||
void strip(TagTypes tags);
|
||||
|
||||
/*!
|
||||
* Returns the index of the chunk that its name is "LIST" and list type is "INFO".
|
||||
*/
|
||||
uint findInfoTagChunk();
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
};
|
||||
|
||||
@@ -47,7 +47,8 @@ S3M::File::File(FileName file, bool readProperties,
|
||||
Mod::FileBase(file),
|
||||
d(new FilePrivate(propertiesStyle))
|
||||
{
|
||||
read(readProperties);
|
||||
if(isOpen())
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
S3M::File::File(IOStream *stream, bool readProperties,
|
||||
@@ -55,7 +56,8 @@ S3M::File::File(IOStream *stream, bool readProperties,
|
||||
Mod::FileBase(stream),
|
||||
d(new FilePrivate(propertiesStyle))
|
||||
{
|
||||
read(readProperties);
|
||||
if(isOpen())
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
S3M::File::~File()
|
||||
|
||||
@@ -48,6 +48,9 @@ namespace TagLib {
|
||||
* Contructs a ScreamTracker III file from \a stream. If \a readProperties
|
||||
* is true the file's audio properties will also be read using
|
||||
* \a propertiesStyle. If false, \a propertiesStyle is ignored.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
AudioProperties::ReadStyle propertiesStyle =
|
||||
|
||||
Regular → Executable
+5
-3
@@ -27,7 +27,7 @@
|
||||
#define TAGLIB_H
|
||||
|
||||
#define TAGLIB_MAJOR_VERSION 1
|
||||
#define TAGLIB_MINOR_VERSION 7
|
||||
#define TAGLIB_MINOR_VERSION 8
|
||||
#define TAGLIB_PATCH_VERSION 0
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1))
|
||||
@@ -48,7 +48,9 @@
|
||||
# include <libkern/OSAtomic.h>
|
||||
# define TAGLIB_ATOMIC_MAC
|
||||
#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
|
||||
# define NOMINMAX
|
||||
# if !defined(NOMINMAX)
|
||||
# define NOMINMAX
|
||||
# endif
|
||||
# include <windows.h>
|
||||
# define TAGLIB_ATOMIC_WIN
|
||||
#elif defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 401) \
|
||||
@@ -143,7 +145,7 @@ namespace TagLib {
|
||||
* - A clean, high level, C++ API to handling audio meta data.
|
||||
* - Format specific APIs for advanced API users.
|
||||
* - ID3v1, ID3v2, APE, FLAC, Xiph, iTunes-style MP4 and WMA tag formats.
|
||||
* - MP3, MPC, FLAC, MP4, ASF, AIFF, WAV, TrueAudio, WavPack, Ogg FLAC, Ogg Vorbis and Speex file formats.
|
||||
* - MP3, MPC, FLAC, MP4, ASF, AIFF, WAV, TrueAudio, WavPack, Ogg FLAC, Ogg Vorbis, Speex and Opus file formats.
|
||||
* - Basic audio file properties such as length, sample rate, etc.
|
||||
* - Long term binary and source compatibility.
|
||||
* - Extensible design, notably the ability to add other formats or extend current formats as a library user.
|
||||
|
||||
+47
-11
@@ -60,6 +60,7 @@
|
||||
#include "mp4file.h"
|
||||
#include "wavpackfile.h"
|
||||
#include "speexfile.h"
|
||||
#include "opusfile.h"
|
||||
#include "trueaudiofile.h"
|
||||
#include "aifffile.h"
|
||||
#include "wavfile.h"
|
||||
@@ -68,21 +69,24 @@
|
||||
#include "s3mfile.h"
|
||||
#include "itfile.h"
|
||||
#include "xmfile.h"
|
||||
#include "mp4file.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
class File::FilePrivate
|
||||
{
|
||||
public:
|
||||
FilePrivate(IOStream *stream);
|
||||
FilePrivate(IOStream *stream, bool owner);
|
||||
|
||||
IOStream *stream;
|
||||
bool streamOwner;
|
||||
bool valid;
|
||||
static const uint bufferSize = 1024;
|
||||
};
|
||||
|
||||
File::FilePrivate::FilePrivate(IOStream *stream) :
|
||||
File::FilePrivate::FilePrivate(IOStream *stream, bool owner) :
|
||||
stream(stream),
|
||||
streamOwner(owner),
|
||||
valid(true)
|
||||
{
|
||||
}
|
||||
@@ -94,17 +98,17 @@ File::FilePrivate::FilePrivate(IOStream *stream) :
|
||||
File::File(FileName fileName)
|
||||
{
|
||||
IOStream *stream = new FileStream(fileName);
|
||||
d = new FilePrivate(stream);
|
||||
d = new FilePrivate(stream, true);
|
||||
}
|
||||
|
||||
File::File(IOStream *stream)
|
||||
{
|
||||
d = new FilePrivate(stream);
|
||||
d = new FilePrivate(stream, false);
|
||||
}
|
||||
|
||||
File::~File()
|
||||
{
|
||||
if(d->stream)
|
||||
if(d->stream && d->streamOwner)
|
||||
delete d->stream;
|
||||
delete d;
|
||||
}
|
||||
@@ -133,6 +137,8 @@ PropertyMap File::properties() const
|
||||
return dynamic_cast<const Ogg::FLAC::File* >(this)->properties();
|
||||
if(dynamic_cast<const Ogg::Speex::File* >(this))
|
||||
return dynamic_cast<const Ogg::Speex::File* >(this)->properties();
|
||||
if(dynamic_cast<const Ogg::Opus::File* >(this))
|
||||
return dynamic_cast<const Ogg::Opus::File* >(this)->properties();
|
||||
if(dynamic_cast<const Ogg::Vorbis::File* >(this))
|
||||
return dynamic_cast<const Ogg::Vorbis::File* >(this)->properties();
|
||||
if(dynamic_cast<const RIFF::AIFF::File* >(this))
|
||||
@@ -147,12 +153,10 @@ PropertyMap File::properties() const
|
||||
return dynamic_cast<const WavPack::File* >(this)->properties();
|
||||
if(dynamic_cast<const XM::File* >(this))
|
||||
return dynamic_cast<const XM::File* >(this)->properties();
|
||||
// no specialized implementation available -> use generic one
|
||||
// - ASF: ugly format, largely undocumented, not worth implementing
|
||||
// dict interface ...
|
||||
// - MP4: taglib's MP4::Tag does not really support anything beyond
|
||||
// the basic implementation, therefor we use just the default Tag
|
||||
// interface
|
||||
if(dynamic_cast<const MP4::File* >(this))
|
||||
return dynamic_cast<const MP4::File* >(this)->properties();
|
||||
if(dynamic_cast<const ASF::File* >(this))
|
||||
return dynamic_cast<const ASF::File* >(this)->properties();
|
||||
return tag()->properties();
|
||||
}
|
||||
|
||||
@@ -172,6 +176,8 @@ void File::removeUnsupportedProperties(const StringList &properties)
|
||||
dynamic_cast<Ogg::FLAC::File* >(this)->removeUnsupportedProperties(properties);
|
||||
else if(dynamic_cast<Ogg::Speex::File* >(this))
|
||||
dynamic_cast<Ogg::Speex::File* >(this)->removeUnsupportedProperties(properties);
|
||||
else if(dynamic_cast<Ogg::Opus::File* >(this))
|
||||
dynamic_cast<Ogg::Opus::File* >(this)->removeUnsupportedProperties(properties);
|
||||
else if(dynamic_cast<Ogg::Vorbis::File* >(this))
|
||||
dynamic_cast<Ogg::Vorbis::File* >(this)->removeUnsupportedProperties(properties);
|
||||
else if(dynamic_cast<RIFF::AIFF::File* >(this))
|
||||
@@ -186,6 +192,10 @@ void File::removeUnsupportedProperties(const StringList &properties)
|
||||
dynamic_cast<WavPack::File* >(this)->removeUnsupportedProperties(properties);
|
||||
else if(dynamic_cast<XM::File* >(this))
|
||||
dynamic_cast<XM::File* >(this)->removeUnsupportedProperties(properties);
|
||||
else if(dynamic_cast<MP4::File* >(this))
|
||||
dynamic_cast<MP4::File* >(this)->removeUnsupportedProperties(properties);
|
||||
else if(dynamic_cast<ASF::File* >(this))
|
||||
dynamic_cast<ASF::File* >(this)->removeUnsupportedProperties(properties);
|
||||
else
|
||||
tag()->removeUnsupportedProperties(properties);
|
||||
}
|
||||
@@ -208,6 +218,8 @@ PropertyMap File::setProperties(const PropertyMap &properties)
|
||||
return dynamic_cast<Ogg::FLAC::File* >(this)->setProperties(properties);
|
||||
else if(dynamic_cast<Ogg::Speex::File* >(this))
|
||||
return dynamic_cast<Ogg::Speex::File* >(this)->setProperties(properties);
|
||||
else if(dynamic_cast<Ogg::Opus::File* >(this))
|
||||
return dynamic_cast<Ogg::Opus::File* >(this)->setProperties(properties);
|
||||
else if(dynamic_cast<Ogg::Vorbis::File* >(this))
|
||||
return dynamic_cast<Ogg::Vorbis::File* >(this)->setProperties(properties);
|
||||
else if(dynamic_cast<RIFF::AIFF::File* >(this))
|
||||
@@ -222,6 +234,10 @@ PropertyMap File::setProperties(const PropertyMap &properties)
|
||||
return dynamic_cast<WavPack::File* >(this)->setProperties(properties);
|
||||
else if(dynamic_cast<XM::File* >(this))
|
||||
return dynamic_cast<XM::File* >(this)->setProperties(properties);
|
||||
else if(dynamic_cast<MP4::File* >(this))
|
||||
return dynamic_cast<MP4::File* >(this)->setProperties(properties);
|
||||
else if(dynamic_cast<ASF::File* >(this))
|
||||
return dynamic_cast<ASF::File* >(this)->setProperties(properties);
|
||||
else
|
||||
return tag()->setProperties(properties);
|
||||
}
|
||||
@@ -452,12 +468,32 @@ long File::length()
|
||||
|
||||
bool File::isReadable(const char *file)
|
||||
{
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
|
||||
|
||||
return _access_s(file, R_OK) == 0;
|
||||
|
||||
#else
|
||||
|
||||
return access(file, R_OK) == 0;
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
bool File::isWritable(const char *file)
|
||||
{
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
|
||||
|
||||
return _access_s(file, W_OK) == 0;
|
||||
|
||||
#else
|
||||
|
||||
return access(file, W_OK) == 0;
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -260,6 +260,9 @@ namespace TagLib {
|
||||
/*!
|
||||
* Construct a File object and use the \a stream instance.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*
|
||||
* \note Constructor is protected since this class should only be
|
||||
* instantiated through subclasses.
|
||||
*/
|
||||
|
||||
+131
-51
@@ -35,43 +35,86 @@
|
||||
# include <wchar.h>
|
||||
# include <windows.h>
|
||||
# include <io.h>
|
||||
# define ftruncate _chsize
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef R_OK
|
||||
# define R_OK 4
|
||||
#endif
|
||||
#ifndef W_OK
|
||||
# define W_OK 2
|
||||
#endif
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
|
||||
typedef FileName FileNameHandle;
|
||||
// For Windows
|
||||
|
||||
typedef FileName FileNameHandle;
|
||||
|
||||
// Using native file handles instead of file descriptors for reducing the resource consumption.
|
||||
|
||||
const HANDLE InvalidFile = INVALID_HANDLE_VALUE;
|
||||
|
||||
HANDLE openFile(const FileName &path, bool readOnly)
|
||||
{
|
||||
DWORD access = readOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE);
|
||||
|
||||
if(wcslen(path) > 0)
|
||||
return CreateFileW(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
||||
else
|
||||
return CreateFileA(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
||||
}
|
||||
|
||||
size_t fread(void *ptr, size_t size, size_t nmemb, HANDLE stream)
|
||||
{
|
||||
DWORD readLen;
|
||||
ReadFile(stream, ptr, size * nmemb, &readLen, NULL);
|
||||
|
||||
return (readLen / size);
|
||||
}
|
||||
|
||||
size_t fwrite(const void *ptr, size_t size, size_t nmemb, HANDLE stream)
|
||||
{
|
||||
DWORD writtenLen;
|
||||
WriteFile(stream, ptr, size * nmemb, &writtenLen, NULL);
|
||||
|
||||
return writtenLen;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
struct FileNameHandle : public std::string
|
||||
{
|
||||
FileNameHandle(FileName name) : std::string(name) {}
|
||||
operator FileName () const { return c_str(); }
|
||||
};
|
||||
// For non-Windows
|
||||
|
||||
FILE *const InvalidFile = 0;
|
||||
|
||||
struct FileNameHandle : public std::string
|
||||
{
|
||||
FileNameHandle(FileName name) : std::string(name) {}
|
||||
operator FileName () const { return c_str(); }
|
||||
};
|
||||
|
||||
FILE *openFile(const FileName &path, bool readOnly)
|
||||
{
|
||||
return fopen(path, readOnly ? "rb" : "rb+");
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
class FileStream::FileStreamPrivate
|
||||
{
|
||||
public:
|
||||
FileStreamPrivate(FileName fileName, bool openReadOnly);
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
HANDLE file;
|
||||
|
||||
#else
|
||||
|
||||
FILE *file;
|
||||
|
||||
#endif
|
||||
|
||||
FileNameHandle name;
|
||||
|
||||
bool readOnly;
|
||||
@@ -80,48 +123,22 @@ public:
|
||||
};
|
||||
|
||||
FileStream::FileStreamPrivate::FileStreamPrivate(FileName fileName, bool openReadOnly) :
|
||||
file(0),
|
||||
file(InvalidFile),
|
||||
name(fileName),
|
||||
readOnly(true),
|
||||
size(0)
|
||||
{
|
||||
// First try with read / write mode, if that fails, fall back to read only.
|
||||
|
||||
#ifdef _WIN32
|
||||
if(!openReadOnly)
|
||||
file = openFile(name, false);
|
||||
|
||||
if(wcslen((const wchar_t *) fileName) > 0) {
|
||||
if(file != InvalidFile)
|
||||
readOnly = false;
|
||||
else
|
||||
file = openFile(name, true);
|
||||
|
||||
if(openReadOnly)
|
||||
file = _wfopen(name, L"rb");
|
||||
else {
|
||||
file = _wfopen(name, L"rb+");
|
||||
|
||||
if(file)
|
||||
readOnly = false;
|
||||
else
|
||||
file = _wfopen(name, L"rb");
|
||||
}
|
||||
|
||||
if(file)
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if(openReadOnly)
|
||||
file = fopen(name, "rb");
|
||||
else {
|
||||
file = fopen(name, "rb+");
|
||||
|
||||
if(file)
|
||||
readOnly = false;
|
||||
else
|
||||
file = fopen(name, "rb");
|
||||
}
|
||||
|
||||
if(!file)
|
||||
{
|
||||
if(file == InvalidFile) {
|
||||
debug("Could not open file " + String((const char *) name));
|
||||
}
|
||||
}
|
||||
@@ -137,8 +154,18 @@ FileStream::FileStream(FileName file, bool openReadOnly)
|
||||
|
||||
FileStream::~FileStream()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
||||
if(d->file)
|
||||
CloseHandle(d->file);
|
||||
|
||||
#else
|
||||
|
||||
if(d->file)
|
||||
fclose(d->file);
|
||||
|
||||
#endif
|
||||
|
||||
delete d;
|
||||
}
|
||||
|
||||
@@ -329,27 +356,67 @@ void FileStream::seek(long offset, Position p)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
DWORD whence;
|
||||
switch(p) {
|
||||
case Beginning:
|
||||
fseek(d->file, offset, SEEK_SET);
|
||||
whence = FILE_BEGIN;
|
||||
break;
|
||||
case Current:
|
||||
fseek(d->file, offset, SEEK_CUR);
|
||||
whence = FILE_CURRENT;
|
||||
break;
|
||||
case End:
|
||||
fseek(d->file, offset, SEEK_END);
|
||||
whence = FILE_END;
|
||||
break;
|
||||
}
|
||||
|
||||
SetFilePointer(d->file, offset, NULL, whence);
|
||||
|
||||
#else
|
||||
|
||||
int whence;
|
||||
switch(p) {
|
||||
case Beginning:
|
||||
whence = SEEK_SET;
|
||||
break;
|
||||
case Current:
|
||||
whence = SEEK_CUR;
|
||||
break;
|
||||
case End:
|
||||
whence = SEEK_END;
|
||||
break;
|
||||
}
|
||||
|
||||
fseek(d->file, offset, whence);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileStream::clear()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
||||
// NOP
|
||||
|
||||
#else
|
||||
|
||||
clearerr(d->file);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
long FileStream::tell() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
||||
return (long)SetFilePointer(d->file, 0, NULL, FILE_CURRENT);
|
||||
|
||||
#else
|
||||
|
||||
return ftell(d->file);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
long FileStream::length()
|
||||
@@ -379,7 +446,20 @@ long FileStream::length()
|
||||
|
||||
void FileStream::truncate(long length)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
||||
long currentPos = tell();
|
||||
|
||||
seek(length);
|
||||
SetEndOfFile(d->file);
|
||||
|
||||
seek(currentPos);
|
||||
|
||||
#else
|
||||
|
||||
ftruncate(fileno(d->file), length);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
TagLib::uint FileStream::bufferSize()
|
||||
|
||||
@@ -40,6 +40,65 @@ namespace TagLib {
|
||||
* Note that most metadata formats pose additional conditions on the tag keys. The
|
||||
* most popular ones (Vorbis, APE, ID3v2) should support all ASCII only words of
|
||||
* length between 2 and 16.
|
||||
*
|
||||
* This class can contain any tags, but here is a list of "well-known" tags that
|
||||
* you might want to use:
|
||||
*
|
||||
* Basic tags:
|
||||
*
|
||||
* - TITLE
|
||||
* - ALBUM
|
||||
* - ARTIST
|
||||
* - ALBUMARTIST
|
||||
* - SUBTITLE
|
||||
* - TRACKNUMBER
|
||||
* - DISCNUMBER
|
||||
* - DATE
|
||||
* - ORIGINALDATE
|
||||
* - GENRE
|
||||
* - COMMENT
|
||||
*
|
||||
* Sort names:
|
||||
*
|
||||
* - TITLESORT
|
||||
* - ALBUMSORT
|
||||
* - ARTISTSORT
|
||||
* - ALBUMARTISTSORT
|
||||
*
|
||||
* Credits:
|
||||
*
|
||||
* - COMPOSER
|
||||
* - LYRICIST
|
||||
* - CONDUCTOR
|
||||
* - REMIXER
|
||||
* - PERFORMER:<XXXX>
|
||||
*
|
||||
* Other tags:
|
||||
*
|
||||
* - ISRC
|
||||
* - ASIN
|
||||
* - BPM
|
||||
* - COPYRIGHT
|
||||
* - ENCODEDBY
|
||||
* - MOOD
|
||||
* - COMMENT
|
||||
* - MEDIA
|
||||
* - LABEL
|
||||
* - CATALOGNUMBER
|
||||
* - BARCODE
|
||||
*
|
||||
* MusicBrainz identifiers:
|
||||
*
|
||||
* - MUSICBRAINZ_TRACKID
|
||||
* - MUSICBRAINZ_ALBUMID
|
||||
* - MUSICBRAINZ_RELEASEGROUPID
|
||||
* - MUSICBRAINZ_WORKID
|
||||
* - MUSICBRAINZ_ARTISTID
|
||||
* - MUSICBRAINZ_ALBUMARTISTID
|
||||
* - ACOUSTID_ID
|
||||
* - ACOUSTID_FINGERPRINT
|
||||
* - MUSICIP_PUID
|
||||
*
|
||||
*/
|
||||
|
||||
class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap
|
||||
|
||||
@@ -233,8 +233,9 @@ std::string String::to8Bit(bool unicode) const
|
||||
&target, targetBuffer + outputBufferSize,
|
||||
Unicode::lenientConversion);
|
||||
|
||||
if(result != Unicode::conversionOK)
|
||||
if(result != Unicode::conversionOK) {
|
||||
debug("String::to8Bit() - Unicode conversion error.");
|
||||
}
|
||||
|
||||
int newSize = target - targetBuffer;
|
||||
s.resize(newSize);
|
||||
@@ -259,8 +260,17 @@ const char *String::toCString(bool unicode) const
|
||||
|
||||
std::string buffer = to8Bit(unicode);
|
||||
d->CString = new char[buffer.size() + 1];
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
|
||||
|
||||
strcpy_s(d->CString, buffer.size() + 1, buffer.c_str());
|
||||
|
||||
#else
|
||||
|
||||
strcpy(d->CString, buffer.c_str());
|
||||
|
||||
#endif
|
||||
|
||||
return d->CString;
|
||||
}
|
||||
|
||||
@@ -335,9 +345,6 @@ bool String::startsWith(const String &s) const
|
||||
|
||||
String String::substr(uint position, uint n) const
|
||||
{
|
||||
if(n > position + d->data.size())
|
||||
n = d->data.size() - position;
|
||||
|
||||
String s;
|
||||
s.d->data = d->data.substr(position, n);
|
||||
return s;
|
||||
@@ -779,9 +786,9 @@ void String::prepare(Type t)
|
||||
&target, targetBuffer + bufferSize,
|
||||
Unicode::lenientConversion);
|
||||
|
||||
if(result != Unicode::conversionOK)
|
||||
if(result != Unicode::conversionOK) {
|
||||
debug("String::prepare() - Unicode conversion error.");
|
||||
|
||||
}
|
||||
|
||||
int newSize = target != targetBuffer ? target - targetBuffer - 1 : 0;
|
||||
d->data.resize(newSize);
|
||||
|
||||
@@ -155,7 +155,7 @@ ConversionResult ConvertUTF16toUTF8 (
|
||||
case 4: *--target = (ch | byteMark) & byteMask; ch >>= 6;
|
||||
case 3: *--target = (ch | byteMark) & byteMask; ch >>= 6;
|
||||
case 2: *--target = (ch | byteMark) & byteMask; ch >>= 6;
|
||||
case 1: *--target = ch | firstByteMark[bytesToWrite];
|
||||
case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]);
|
||||
}
|
||||
target += bytesToWrite;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ ConversionResult ConvertUTF8toUTF16 (
|
||||
result = sourceIllegal;
|
||||
break;
|
||||
} else {
|
||||
*target++ = ch; /* normal case */
|
||||
*target++ = (UTF16)ch; /* normal case */
|
||||
}
|
||||
} else if (ch > UNI_MAX_UTF16) {
|
||||
if (flags == strictConversion) {
|
||||
@@ -270,7 +270,7 @@ ConversionResult ConvertUTF8toUTF16 (
|
||||
result = targetExhausted; break;
|
||||
}
|
||||
ch -= halfBase;
|
||||
*target++ = (ch >> halfShift) + UNI_SUR_HIGH_START;
|
||||
*target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START);
|
||||
*target++ = (ch & halfMask) + UNI_SUR_LOW_START;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,9 @@ namespace TagLib {
|
||||
* Contructs an TrueAudio 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
@@ -109,6 +112,9 @@ namespace TagLib {
|
||||
* file's audio properties will also be read using \a propertiesStyle. If
|
||||
* false, \a propertiesStyle is ignored. The frames will be created using
|
||||
* \a frameFactory.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
|
||||
bool readProperties = true,
|
||||
|
||||
@@ -86,14 +86,16 @@ WavPack::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
WavPack::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
|
||||
{
|
||||
d = new FilePrivate;
|
||||
read(readProperties, propertiesStyle);
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
}
|
||||
|
||||
WavPack::File::~File()
|
||||
|
||||
@@ -91,6 +91,9 @@ namespace TagLib {
|
||||
* Contructs an WavPack 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.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
@@ -359,7 +359,8 @@ XM::File::File(FileName file, bool readProperties,
|
||||
Mod::FileBase(file),
|
||||
d(new FilePrivate(propertiesStyle))
|
||||
{
|
||||
read(readProperties);
|
||||
if(isOpen())
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
XM::File::File(IOStream *stream, bool readProperties,
|
||||
@@ -367,7 +368,8 @@ XM::File::File(IOStream *stream, bool readProperties,
|
||||
Mod::FileBase(stream),
|
||||
d(new FilePrivate(propertiesStyle))
|
||||
{
|
||||
read(readProperties);
|
||||
if(isOpen())
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
XM::File::~File()
|
||||
@@ -443,7 +445,7 @@ bool XM::File::save()
|
||||
return false;
|
||||
|
||||
uint len = std::min(22UL, instrumentHeaderSize - 4U);
|
||||
if(i > lines.size())
|
||||
if(i >= lines.size())
|
||||
writeString(String::null, len);
|
||||
else
|
||||
writeString(lines[i], len);
|
||||
|
||||
@@ -48,6 +48,9 @@ namespace TagLib {
|
||||
* Contructs a Extended Module file from \a stream. If \a readProperties
|
||||
* is true the file's audio properties will also be read using
|
||||
* \a propertiesStyle. If false, \a propertiesStyle is ignored.
|
||||
*
|
||||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
AudioProperties::ReadStyle propertiesStyle =
|
||||
|
||||
@@ -16,6 +16,8 @@ INCLUDE_DIRECTORIES(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/vorbis
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/flac
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/speex
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/opus
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/flac
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/wavpack
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mod
|
||||
@@ -60,8 +62,11 @@ SET(test_runner_SRCS
|
||||
test_it.cpp
|
||||
test_xm.cpp
|
||||
test_mpc.cpp
|
||||
test_opus.cpp
|
||||
)
|
||||
|
||||
INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR})
|
||||
|
||||
ADD_EXECUTABLE(test_runner ${test_runner_SRCS})
|
||||
TARGET_LINK_LIBRARIES(test_runner tag ${CPPUNIT_LIBRARIES})
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,3 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <tag.h>
|
||||
@@ -7,6 +6,7 @@
|
||||
#include <tpropertymap.h>
|
||||
#include <apetag.h>
|
||||
#include <tdebug.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
APE::Item item3 = APE::Item("TRACKNUMBER", "29");
|
||||
tag.setItem("TRACKNUMBER", item3);
|
||||
properties = tag.properties();
|
||||
CPPUNIT_ASSERT_EQUAL(uint(2), properties["TRACKNUMBER"].size());
|
||||
CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), properties["TRACKNUMBER"].size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("17"), properties["TRACKNUMBER"][0]);
|
||||
CPPUNIT_ASSERT_EQUAL(String("29"), properties["TRACKNUMBER"][1]);
|
||||
|
||||
@@ -93,7 +93,7 @@ public:
|
||||
|
||||
APE::Tag tag;
|
||||
PropertyMap unsuccessful = tag.setProperties(properties);
|
||||
CPPUNIT_ASSERT_EQUAL(uint(2), unsuccessful.size());
|
||||
CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), unsuccessful.size());
|
||||
CPPUNIT_ASSERT(unsuccessful.contains("A"));
|
||||
CPPUNIT_ASSERT(unsuccessful.contains("MP+"));
|
||||
}
|
||||
|
||||
+38
-3
@@ -1,10 +1,11 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <tag.h>
|
||||
#include <tstringlist.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <asffile.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -13,7 +14,7 @@ using namespace TagLib;
|
||||
class TestASF : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TestASF);
|
||||
CPPUNIT_TEST(testProperties);
|
||||
CPPUNIT_TEST(testAudioProperties);
|
||||
CPPUNIT_TEST(testRead);
|
||||
CPPUNIT_TEST(testSaveMultipleValues);
|
||||
CPPUNIT_TEST(testSaveStream);
|
||||
@@ -22,11 +23,12 @@ class TestASF : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testSaveLargeValue);
|
||||
CPPUNIT_TEST(testSavePicture);
|
||||
CPPUNIT_TEST(testSaveMultiplePictures);
|
||||
CPPUNIT_TEST(testProperties);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
|
||||
void testProperties()
|
||||
void testAudioProperties()
|
||||
{
|
||||
ASF::File f(TEST_FILE_PATH_C("silence-1.wma"));
|
||||
CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->length());
|
||||
@@ -215,6 +217,39 @@ public:
|
||||
delete f;
|
||||
}
|
||||
|
||||
void testProperties()
|
||||
{
|
||||
ASF::File f(TEST_FILE_PATH_C("silence-1.wma"));
|
||||
|
||||
PropertyMap tags = f.properties();
|
||||
|
||||
tags["TRACKNUMBER"] = StringList("2");
|
||||
tags["DISCNUMBER"] = StringList("3");
|
||||
tags["BPM"] = StringList("123");
|
||||
tags["ARTIST"] = StringList("Foo Bar");
|
||||
f.setProperties(tags);
|
||||
|
||||
tags = f.properties();
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), f.tag()->artist());
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]);
|
||||
|
||||
CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/BeatsPerMinute"));
|
||||
CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/BeatsPerMinute"].size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("123"), f.tag()->attributeListMap()["WM/BeatsPerMinute"].front().toString());
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]);
|
||||
|
||||
CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/TrackNumber"));
|
||||
CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/TrackNumber"].size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("2"), f.tag()->attributeListMap()["WM/TrackNumber"].front().toString());
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("2"), tags["TRACKNUMBER"]);
|
||||
|
||||
CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/PartOfSet"));
|
||||
CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/PartOfSet"].size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("3"), f.tag()->attributeListMap()["WM/PartOfSet"].front().toString());
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["DISCNUMBER"]);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestASF);
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <tbytevector.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace TagLib;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <tbytevector.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace TagLib;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <tbytevectorstream.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace TagLib;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <tag.h>
|
||||
#include <fileref.h>
|
||||
#include <oggflacfile.h>
|
||||
#include <vorbisfile.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -29,6 +29,7 @@ class TestFileRef : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testMP4_3);
|
||||
CPPUNIT_TEST(testTrueAudio);
|
||||
CPPUNIT_TEST(testAPE);
|
||||
CPPUNIT_TEST(testWav);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -127,6 +128,11 @@ public:
|
||||
fileRefSave("no-tags", ".3g2");
|
||||
}
|
||||
|
||||
void testWav()
|
||||
{
|
||||
fileRefSave("empty", ".wav");
|
||||
}
|
||||
|
||||
void testOGA_FLAC()
|
||||
{
|
||||
FileRef *f = new FileRef(TEST_FILE_PATH_C("empty_flac.oga"));
|
||||
@@ -143,7 +149,7 @@ public:
|
||||
|
||||
void testAPE()
|
||||
{
|
||||
fileRefSave("mac-399.ape", ".ape");
|
||||
fileRefSave("mac-399", ".ape");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,3 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <tag.h>
|
||||
@@ -7,6 +6,7 @@
|
||||
#include <tpropertymap.h>
|
||||
#include <flacfile.h>
|
||||
#include <xiphcomment.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <tag.h>
|
||||
@@ -6,6 +5,7 @@
|
||||
#include <tbytevectorlist.h>
|
||||
#include <flacfile.h>
|
||||
#include <flacmetadatablock.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <tag.h>
|
||||
#include <tstringlist.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <flacunknownmetadatablock.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <id3v1tag.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace TagLib;
|
||||
|
||||
+74
-7
@@ -1,4 +1,3 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
// so evil :(
|
||||
@@ -15,8 +14,10 @@
|
||||
#include <relativevolumeframe.h>
|
||||
#include <popularimeterframe.h>
|
||||
#include <urllinkframe.h>
|
||||
#include <ownershipframe.h>
|
||||
#include <tdebug.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -61,6 +62,8 @@ class TestID3v2 : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testRenderUrlLinkFrame);
|
||||
CPPUNIT_TEST(testParseUserUrlLinkFrame);
|
||||
CPPUNIT_TEST(testRenderUserUrlLinkFrame);
|
||||
CPPUNIT_TEST(testParseOwnershipFrame);
|
||||
CPPUNIT_TEST(testRenderOwnershipFrame);
|
||||
CPPUNIT_TEST(testSaveUTF16Comment);
|
||||
CPPUNIT_TEST(testUpdateGenre23_1);
|
||||
CPPUNIT_TEST(testUpdateGenre23_2);
|
||||
@@ -69,6 +72,7 @@ class TestID3v2 : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testDowngradeTo23);
|
||||
// CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together
|
||||
CPPUNIT_TEST(testCompressedFrameWithBrokenLength);
|
||||
CPPUNIT_TEST(testW000);
|
||||
CPPUNIT_TEST(testPropertyInterface);
|
||||
CPPUNIT_TEST(testPropertyInterface2);
|
||||
CPPUNIT_TEST(testDeleteFrame);
|
||||
@@ -385,6 +389,38 @@ public:
|
||||
"http://example.com", 33), // URL
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testParseOwnershipFrame()
|
||||
{
|
||||
ID3v2::OwnershipFrame f(
|
||||
ByteVector("OWNE" // Frame ID
|
||||
"\x00\x00\x00\x19" // Frame size
|
||||
"\x00\x00" // Frame flags
|
||||
"\x00" // Text encoding
|
||||
"GBP1.99\x00" // Price paid
|
||||
"20120905" // Date of purchase
|
||||
"Beatport", 35)); // Seller
|
||||
CPPUNIT_ASSERT_EQUAL(String("GBP1.99"), f.pricePaid());
|
||||
CPPUNIT_ASSERT_EQUAL(String("20120905"), f.datePurchased());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Beatport"), f.seller());
|
||||
}
|
||||
|
||||
void testRenderOwnershipFrame()
|
||||
{
|
||||
ID3v2::OwnershipFrame f;
|
||||
f.setPricePaid("GBP1.99");
|
||||
f.setDatePurchased("20120905");
|
||||
f.setSeller("Beatport");
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
ByteVector("OWNE" // Frame ID
|
||||
"\x00\x00\x00\x19" // Frame size
|
||||
"\x00\x00" // Frame flags
|
||||
"\x00" // Text encoding
|
||||
"GBP1.99\x00" // Price paid
|
||||
"20120905" // Date of purchase
|
||||
"Beatport", 35), // URL
|
||||
f.render());
|
||||
}
|
||||
|
||||
void testItunes24FrameSize()
|
||||
{
|
||||
@@ -552,6 +588,16 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(String(""), frame->description());
|
||||
CPPUNIT_ASSERT_EQUAL(TagLib::uint(86414), frame->picture().size());
|
||||
}
|
||||
|
||||
void testW000()
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("w000.mp3"), false);
|
||||
CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("W000"));
|
||||
ID3v2::UrlLinkFrame *frame =
|
||||
dynamic_cast<TagLib::ID3v2::UrlLinkFrame*>(f.ID3v2Tag()->frameListMap()["W000"].front());
|
||||
CPPUNIT_ASSERT(frame);
|
||||
CPPUNIT_ASSERT_EQUAL(String("[email protected]____"), frame->url());
|
||||
}
|
||||
|
||||
void testPropertyInterface()
|
||||
{
|
||||
@@ -578,7 +624,7 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front());
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size());
|
||||
CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front());
|
||||
CPPUNIT_ASSERT_EQUAL(String("UFID/[email protected]"), dict.unsupportedData().front());
|
||||
}
|
||||
|
||||
void testPropertyInterface2()
|
||||
@@ -611,11 +657,23 @@ public:
|
||||
frame5->setText(tmclData);
|
||||
tag.addFrame(frame5);
|
||||
|
||||
ID3v2::UniqueFileIdentifierFrame *frame6 = new ID3v2::UniqueFileIdentifierFrame("http://musicbrainz.org", "152454b9-19ba-49f3-9fc9-8fc26545cf41");
|
||||
tag.addFrame(frame6);
|
||||
|
||||
ID3v2::UniqueFileIdentifierFrame *frame7 = new ID3v2::UniqueFileIdentifierFrame("http://example.com", "123");
|
||||
tag.addFrame(frame7);
|
||||
|
||||
ID3v2::UserTextIdentificationFrame *frame8 = new ID3v2::UserTextIdentificationFrame();
|
||||
frame8->setDescription("MusicBrainz Album Id");
|
||||
frame8->setText("95c454a5-d7e0-4d8f-9900-db04aca98ab3");
|
||||
tag.addFrame(frame8);
|
||||
|
||||
PropertyMap properties = tag.properties();
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(2u, properties.unsupportedData().size());
|
||||
CPPUNIT_ASSERT_EQUAL(3u, properties.unsupportedData().size());
|
||||
CPPUNIT_ASSERT(properties.unsupportedData().contains("TIPL"));
|
||||
CPPUNIT_ASSERT(properties.unsupportedData().contains("APIC"));
|
||||
CPPUNIT_ASSERT(properties.unsupportedData().contains("UFID/http://example.com"));
|
||||
|
||||
CPPUNIT_ASSERT(properties.contains("PERFORMER:VIOLIN"));
|
||||
CPPUNIT_ASSERT(properties.contains("PERFORMER:PIANO"));
|
||||
@@ -625,9 +683,17 @@ public:
|
||||
CPPUNIT_ASSERT(properties.contains("LYRICS"));
|
||||
CPPUNIT_ASSERT(properties.contains("LYRICS:TEST"));
|
||||
|
||||
CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_TRACKID"));
|
||||
CPPUNIT_ASSERT_EQUAL(String("152454b9-19ba-49f3-9fc9-8fc26545cf41"), properties["MUSICBRAINZ_TRACKID"].front());
|
||||
|
||||
CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_ALBUMID"));
|
||||
CPPUNIT_ASSERT_EQUAL(String("95c454a5-d7e0-4d8f-9900-db04aca98ab3"), properties["MUSICBRAINZ_ALBUMID"].front());
|
||||
|
||||
tag.removeUnsupportedProperties(properties.unsupportedData());
|
||||
CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty());
|
||||
CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty());
|
||||
CPPUNIT_ASSERT_EQUAL((ID3v2::UniqueFileIdentifierFrame *)0, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://example.com"));
|
||||
CPPUNIT_ASSERT_EQUAL(frame6, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://musicbrainz.org"));
|
||||
}
|
||||
|
||||
void testDeleteFrame()
|
||||
@@ -640,29 +706,30 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(1u, t->frameList("TCON").size());
|
||||
t->removeFrame(frame, true);
|
||||
f.save(MPEG::File::ID3v2);
|
||||
|
||||
|
||||
MPEG::File f2(newname.c_str());
|
||||
t = f2.ID3v2Tag();
|
||||
CPPUNIT_ASSERT(t->frameList("TCON").isEmpty());
|
||||
}
|
||||
|
||||
void testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2()
|
||||
{
|
||||
ScopedFileCopy copy("xing", ".mp3");
|
||||
string newname = copy.fileName();
|
||||
|
||||
|
||||
{
|
||||
MPEG::File foo(newname.c_str());
|
||||
foo.tag()->setArtist("Artist");
|
||||
foo.save(MPEG::File::ID3v1 | MPEG::File::ID3v2);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
MPEG::File bar(newname.c_str());
|
||||
bar.ID3v2Tag()->removeFrames("TPE1");
|
||||
// Should strip ID3v1 here and not add old values to ID3v2 again
|
||||
bar.save(MPEG::File::ID3v2, true);
|
||||
}
|
||||
|
||||
|
||||
MPEG::File f(newname.c_str());
|
||||
CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <infotag.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tdebug.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace TagLib;
|
||||
|
||||
class TestInfoTag : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TestInfoTag);
|
||||
CPPUNIT_TEST(testTitle);
|
||||
CPPUNIT_TEST(testNumericFields);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void testTitle()
|
||||
{
|
||||
RIFF::Info::Tag tag;
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(String(""), tag.title());
|
||||
tag.setTitle("Test title 1");
|
||||
CPPUNIT_ASSERT_EQUAL(String("Test title 1"), tag.title());
|
||||
}
|
||||
|
||||
void testNumericFields()
|
||||
{
|
||||
RIFF::Info::Tag tag;
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL((uint)0, tag.track());
|
||||
tag.setTrack(1234);
|
||||
CPPUNIT_ASSERT_EQUAL((uint)1234, tag.track());
|
||||
CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("IPRT"));
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL((uint)0, tag.year());
|
||||
tag.setYear(1234);
|
||||
CPPUNIT_ASSERT_EQUAL((uint)1234, tag.year());
|
||||
CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("ICRD"));
|
||||
}
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestInfoTag);
|
||||
+1
-1
@@ -19,9 +19,9 @@
|
||||
* MA 02110-1301 USA *
|
||||
***************************************************************************/
|
||||
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <itfile.h>
|
||||
#include <tstringlist.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
+1
-1
@@ -19,9 +19,9 @@
|
||||
* MA 02110-1301 USA *
|
||||
***************************************************************************/
|
||||
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <modfile.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
+52
-1
@@ -1,11 +1,12 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <tag.h>
|
||||
#include <mp4tag.h>
|
||||
#include <tbytevectorlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <mp4atom.h>
|
||||
#include <mp4file.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -25,6 +26,7 @@ class TestMP4 : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testCovrRead);
|
||||
CPPUNIT_TEST(testCovrWrite);
|
||||
CPPUNIT_TEST(testCovrRead2);
|
||||
CPPUNIT_TEST(testProperties);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@@ -224,6 +226,55 @@ public:
|
||||
delete f;
|
||||
}
|
||||
|
||||
void testProperties()
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("has-tags.m4a"));
|
||||
|
||||
PropertyMap tags = f.properties();
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("Test Artist"), tags["ARTIST"]);
|
||||
|
||||
tags["TRACKNUMBER"] = StringList("2/4");
|
||||
tags["DISCNUMBER"] = StringList("3/5");
|
||||
tags["BPM"] = StringList("123");
|
||||
tags["ARTIST"] = StringList("Foo Bar");
|
||||
tags["COMPILATION"] = StringList("1");
|
||||
f.setProperties(tags);
|
||||
|
||||
tags = f.properties();
|
||||
|
||||
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("trkn"));
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.tag()->itemListMap()["trkn"].toIntPair().first);
|
||||
CPPUNIT_ASSERT_EQUAL(4, f.tag()->itemListMap()["trkn"].toIntPair().second);
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("2/4"), tags["TRACKNUMBER"]);
|
||||
|
||||
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("disk"));
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.tag()->itemListMap()["disk"].toIntPair().first);
|
||||
CPPUNIT_ASSERT_EQUAL(5, f.tag()->itemListMap()["disk"].toIntPair().second);
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("3/5"), tags["DISCNUMBER"]);
|
||||
|
||||
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("tmpo"));
|
||||
CPPUNIT_ASSERT_EQUAL(123, f.tag()->itemListMap()["tmpo"].toInt());
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]);
|
||||
|
||||
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("\251ART"));
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), f.tag()->itemListMap()["\251ART"].toStringList());
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]);
|
||||
|
||||
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil"));
|
||||
CPPUNIT_ASSERT_EQUAL(true, f.tag()->itemListMap()["cpil"].toBool());
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["COMPILATION"]);
|
||||
|
||||
tags["COMPILATION"] = StringList("0");
|
||||
f.setProperties(tags);
|
||||
|
||||
tags = f.properties();
|
||||
|
||||
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil"));
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.tag()->itemListMap()["cpil"].toBool());
|
||||
CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["COMPILATION"]);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <tag.h>
|
||||
#include <mp4coverart.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user