diff options
41 files changed, 3679 insertions, 3 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 3128948b..b21e9dc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,10 @@ OPTION(BUILD_TESTS "Build the test suite" OFF) OPTION(BUILD_EXAMPLES "Build the examples" OFF) OPTION(NO_ITUNES_HACKS "Disable workarounds for iTunes bugs" OFF) +OPTION(WITH_ASF "Enable ASF tag reading/writing code" OFF) +OPTION(WITH_MP4 "Enable MP4 tag reading/writing code" OFF) + +add_definitions(-DHAVE_CONFIG_H) #add some KDE specific stuff set(LIB_SUFFIX "" CACHE STRING "Define suffix of directory name (32/64)" ) diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 7dd5a164..3546bbc7 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -1,11 +1,13 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/toolkit + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/asf ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpeg ${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/flac ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpc + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mp4 ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpeg/id3v2 ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/wavpack ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/speex diff --git a/bindings/c/Makefile.am b/bindings/c/Makefile.am index 09e89703..8bb01102 100644 --- a/bindings/c/Makefile.am +++ b/bindings/c/Makefile.am @@ -1,6 +1,7 @@ INCLUDES = \ -I$(top_srcdir)/taglib \ -I$(top_srcdir)/taglib/toolkit \ + -I$(top_srcdir)/taglib/asf \ -I$(top_srcdir)/taglib/mpeg \ -I$(top_srcdir)/taglib/ogg \ -I$(top_srcdir)/taglib/ogg/vorbis \ @@ -8,6 +9,7 @@ INCLUDES = \ -I$(top_srcdir)/taglib/ogg/flac \ -I$(top_srcdir)/taglib/flac \ -I$(top_srcdir)/taglib/mpc \ + -I$(top_srcdir)/taglib/mp4 \ -I$(top_srcdir)/taglib/mpeg/id3v2 \ -I$(top_srcdir)/taglib/wavpack \ -I$(top_srcdir)/taglib/trueaudio \ diff --git a/bindings/c/tag_c.cpp b/bindings/c/tag_c.cpp index 377d92c7..f5f17656 100644 --- a/bindings/c/tag_c.cpp +++ b/bindings/c/tag_c.cpp @@ -19,11 +19,16 @@ * USA * ***************************************************************************/ +#ifdef HAVE_CONFIG_H +#include "../../config.h" +#endif + #include "tag_c.h" #include <stdlib.h> #include <fileref.h> #include <tfile.h> +#include <asffile.h> #include <vorbisfile.h> #include <mpegfile.h> #include <flacfile.h> @@ -32,6 +37,7 @@ #include <wavpackfile.h> #include <speexfile.h> #include <trueaudiofile.h> +#include <mp4file.h> #include <tag.h> #include <string.h> #include <id3v2framefactory.h> @@ -80,6 +86,14 @@ TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type) return reinterpret_cast<TagLib_File *>(new Ogg::Speex::File(filename)); case TagLib_File_TrueAudio: return reinterpret_cast<TagLib_File *>(new TrueAudio::File(filename)); +#ifdef WITH_MP4 + case TagLib_File_MP4: + return reinterpret_cast<TagLib_File *>(new MP4::File(filename)); +#endif +#ifdef WITH_ASF + case TagLib_File_ASF: + return reinterpret_cast<TagLib_File *>(new ASF::File(filename)); +#endif } return 0; diff --git a/bindings/c/tag_c.h b/bindings/c/tag_c.h index d70629ec..055da4e3 100644 --- a/bindings/c/tag_c.h +++ b/bindings/c/tag_c.h @@ -89,7 +89,9 @@ typedef enum { TagLib_File_OggFlac, TagLib_File_WavPack, TagLib_File_Speex, - TagLib_File_TrueAudio + TagLib_File_TrueAudio, + TagLib_File_MP4, + TagLib_File_ASF } TagLib_File_Type; /*! diff --git a/config-taglib.h.cmake b/config-taglib.h.cmake index 56674d6b..69c1adbb 100644 --- a/config-taglib.h.cmake +++ b/config-taglib.h.cmake @@ -7,3 +7,5 @@ #cmakedefine HAVE_ZLIB 1 #cmakedefine NO_ITUNES_HACKS 1 +#cmakedefine WITH_ASF 1 +#cmakedefine WITH_MP4 1 diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 4e9b7139..c0a18cbc 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -2,11 +2,13 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/toolkit + ${CMAKE_CURRENT_SOURCE_DIR}/asf ${CMAKE_CURRENT_SOURCE_DIR}/mpeg ${CMAKE_CURRENT_SOURCE_DIR}/ogg ${CMAKE_CURRENT_SOURCE_DIR}/ogg/flac ${CMAKE_CURRENT_SOURCE_DIR}/flac ${CMAKE_CURRENT_SOURCE_DIR}/mpc + ${CMAKE_CURRENT_SOURCE_DIR}/mp4 ${CMAKE_CURRENT_SOURCE_DIR}/ogg/vorbis ${CMAKE_CURRENT_SOURCE_DIR}/ogg/speex ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2 @@ -25,11 +27,13 @@ if(ZLIB_FOUND) endif(ZLIB_FOUND) ADD_SUBDIRECTORY( toolkit ) +ADD_SUBDIRECTORY( asf ) ADD_SUBDIRECTORY( mpeg ) ADD_SUBDIRECTORY( ogg ) ADD_SUBDIRECTORY( flac ) ADD_SUBDIRECTORY( ape ) ADD_SUBDIRECTORY( mpc ) +ADD_SUBDIRECTORY( mp4 ) ADD_SUBDIRECTORY( wavpack ) ADD_SUBDIRECTORY( trueaudio ) ADD_SUBDIRECTORY( riff ) @@ -101,6 +105,18 @@ mpc/mpcfile.cpp mpc/mpcproperties.cpp ) +IF(WITH_MP4) +SET(mp4_SRCS +mp4/mp4file.cpp +mp4/mp4atom.cpp +mp4/mp4tag.cpp +mp4/mp4item.cpp +mp4/mp4properties.cpp +) +ELSE(WITH_MP4) +SET(mp4_SRCS) +ENDIF(WITH_MP4) + SET(ape_SRCS ape/apetag.cpp ape/apefooter.cpp @@ -122,6 +138,17 @@ trueaudio/trueaudiofile.cpp trueaudio/trueaudioproperties.cpp ) +IF(WITH_ASF) +SET(asf_SRCS +asf/asftag.cpp +asf/asffile.cpp +asf/asfproperties.cpp +asf/asfattribute.cpp +) +ELSE(WITH_ASF) +SET(asf_SRCS) +ENDIF(WITH_ASF) + SET(riff_SRCS riff/rifffile.cpp ) @@ -149,6 +176,7 @@ toolkit/unicode.cpp 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} + ${mp4_SRCS} ${asf_SRCS} tag.cpp tagunion.cpp fileref.cpp diff --git a/taglib/Makefile.am b/taglib/Makefile.am index a75a5495..38f9d70e 100644 --- a/taglib/Makefile.am +++ b/taglib/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = toolkit mpeg ogg flac ape mpc wavpack trueaudio riff +SUBDIRS = toolkit mpeg ogg flac ape mpc wavpack trueaudio riff asf mp4 INCLUDES = \ -I$(top_srcdir)/taglib \ @@ -8,6 +8,8 @@ INCLUDES = \ -I$(top_srcdir)/taglib/ogg/flac \ -I$(top_srcdir)/taglib/flac \ -I$(top_srcdir)/taglib/mpc \ + -I$(top_srcdir)/taglib/asf \ + -I$(top_srcdir)/taglib/mp4 \ -I$(top_srcdir)/taglib/ogg/vorbis \ -I$(top_srcdir)/taglib/ogg/speex \ -I$(top_srcdir)/taglib/wavpack \ @@ -27,4 +29,5 @@ taglib_includedir = $(includedir)/taglib libtag_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 6:0:5 libtag_la_LIBADD = ./mpeg/libmpeg.la ./ogg/libogg.la ./flac/libflac.la ./mpc/libmpc.la \ ./ape/libape.la ./toolkit/libtoolkit.la ./wavpack/libwavpack.la \ - ./trueaudio/libtrueaudio.la ./riff/libriff.la + ./trueaudio/libtrueaudio.la ./riff/libriff.la \ + ./mp4/libmp4.la ./asf/libasf.la diff --git a/taglib/asf/CMakeLists.txt b/taglib/asf/CMakeLists.txt new file mode 100644 index 00000000..1cf3569a --- /dev/null +++ b/taglib/asf/CMakeLists.txt @@ -0,0 +1 @@ +INSTALL( FILES asffile.h asfproperties.h asftag.h asfattribute.h DESTINATION ${INCLUDE_INSTALL_DIR}/taglib) diff --git a/taglib/asf/Makefile.am b/taglib/asf/Makefile.am new file mode 100644 index 00000000..0147dca6 --- /dev/null +++ b/taglib/asf/Makefile.am @@ -0,0 +1,11 @@ +INCLUDES = \ + -I$(top_srcdir)/taglib \ + -I$(top_srcdir)/taglib/toolkit \ + $(all_includes) + +noinst_LTLIBRARIES = libasf.la + +libasf_la_SOURCES = asfattribute.cpp asffile.cpp asfproperties.cpp asftag.cpp + +taglib_include_HEADERS = asfattribute.h asffile.h asfproperties.h asftag.h +taglib_includedir = $(includedir)/taglib diff --git a/taglib/asf/asfattribute.cpp b/taglib/asf/asfattribute.cpp new file mode 100644 index 00000000..a4a4c178 --- /dev/null +++ b/taglib/asf/asfattribute.cpp @@ -0,0 +1,316 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WITH_ASF + +#include <taglib.h> +#include "asfattribute.h" +#include "asffile.h" + +using namespace TagLib; + +class ASF::Attribute::AttributePrivate : public RefCounter +{ +public: + AttributePrivate() + : stream(0), + language(0) {} + AttributeTypes type; + String stringValue; + ByteVector byteVectorValue; + union { + unsigned int intValue; + unsigned short shortValue; + unsigned long long longLongValue; + bool boolValue; + }; + int stream; + int language; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +ASF::Attribute::Attribute() +{ + d = new AttributePrivate; + d->type = UnicodeType; +} + +ASF::Attribute::Attribute(const ASF::Attribute &other) + : d(other.d) +{ + d->ref(); +} + +ASF::Attribute & +ASF::Attribute::operator=(const ASF::Attribute &other) +{ + if(d->deref()) + delete d; + d = other.d; + d->ref(); + return *this; +} + +ASF::Attribute::~Attribute() +{ + if(d->deref()) + delete d; +} + +ASF::Attribute::Attribute(const String &value) +{ + d = new AttributePrivate; + d->type = UnicodeType; + d->stringValue = value; +} + +ASF::Attribute::Attribute(const ByteVector &value) +{ + d = new AttributePrivate; + d->type = BytesType; + d->byteVectorValue = value; +} + +ASF::Attribute::Attribute(unsigned int value) +{ + d = new AttributePrivate; + d->type = DWordType; + d->intValue = value; +} + +ASF::Attribute::Attribute(unsigned long long value) +{ + d = new AttributePrivate; + d->type = QWordType; + d->longLongValue = value; +} + +ASF::Attribute::Attribute(unsigned short value) +{ + d = new AttributePrivate; + d->type = WordType; + d->shortValue = value; +} + +ASF::Attribute::Attribute(bool value) +{ + d = new AttributePrivate; + d->type = BoolType; + d->boolValue = value; +} + +ASF::Attribute::AttributeTypes +ASF::Attribute::type() const +{ + return d->type; +} + +String +ASF::Attribute::toString() const +{ + return d->stringValue; +} + +ByteVector +ASF::Attribute::toByteVector() const +{ + return d->byteVectorValue; +} + +unsigned short +ASF::Attribute::toBool() const +{ + return d->shortValue; +} + +unsigned short +ASF::Attribute::toUShort() const +{ + return d->shortValue; +} + +unsigned int +ASF::Attribute::toUInt() const +{ + return d->intValue; +} + +unsigned long long +ASF::Attribute::toULongLong() const +{ + return d->longLongValue; +} + +String +ASF::Attribute::parse(ASF::File &f, int kind) +{ + int size, nameLength; + String name; + + // extended content descriptor + if(kind == 0) { + nameLength = f.readWORD(); + name = f.readString(nameLength); + d->type = ASF::Attribute::AttributeTypes(f.readWORD()); + size = f.readWORD(); + } + // metadata & metadata library + else { + int temp = f.readWORD(); + // metadata library + if(kind == 2) { + d->language = temp; + } + d->stream = f.readWORD(); + nameLength = f.readWORD(); + d->type = ASF::Attribute::AttributeTypes(f.readWORD()); + size = f.readDWORD(); + name = f.readString(nameLength); + } + + switch(d->type) { + case WordType: + d->shortValue = f.readWORD(); + break; + + case BoolType: + if(kind == 0) { + d->boolValue = f.readDWORD() == 1; + } + else { + d->boolValue = f.readWORD() == 1; + } + break; + + case DWordType: + d->intValue = f.readDWORD(); + break; + + case QWordType: + d->longLongValue = f.readQWORD(); + break; + + case UnicodeType: + d->stringValue = f.readString(size); + break; + + case BytesType: + case GuidType: + d->byteVectorValue = f.readBlock(size); + break; + } + + return name; +} + +ByteVector +ASF::Attribute::render(const String &name, int kind) const +{ + ByteVector data; + + switch (d->type) { + case WordType: + data.append(ByteVector::fromShort(d->shortValue, false)); + break; + + case BoolType: + if(kind == 0) { + data.append(ByteVector::fromUInt(d->boolValue ? 1 : 0, false)); + } + else { + data.append(ByteVector::fromShort(d->boolValue ? 1 : 0, false)); + } + break; + + case DWordType: + data.append(ByteVector::fromUInt(d->intValue, false)); + break; + + case QWordType: + data.append(ByteVector::fromLongLong(d->longLongValue, false)); + break; + + case UnicodeType: + data.append(File::renderString(d->stringValue)); + break; + + case BytesType: + case GuidType: + data.append(d->byteVectorValue); + break; + } + + if(kind == 0) { + data = File::renderString(name, true) + + ByteVector::fromShort((int)d->type, false) + + ByteVector::fromShort(data.size(), false) + + data; + } + else { + ByteVector nameData = File::renderString(name); + data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) + + ByteVector::fromShort(d->stream, false) + + ByteVector::fromShort(nameData.size(), false) + + ByteVector::fromShort((int)d->type, false) + + ByteVector::fromUInt(data.size(), false) + + nameData + + data; + } + + return data; +} + +int +ASF::Attribute::language() const +{ + return d->language; +} + +void +ASF::Attribute::setLanguage(int value) +{ + d->language = value; +} + +int +ASF::Attribute::stream() const +{ + return d->stream; +} + +void +ASF::Attribute::setStream(int value) +{ + d->stream = value; +} + +#endif diff --git a/taglib/asf/asfattribute.h b/taglib/asf/asfattribute.h new file mode 100644 index 00000000..7278e90b --- /dev/null +++ b/taglib/asf/asfattribute.h @@ -0,0 +1,181 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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_ASFATTRIBUTE_H +#define TAGLIB_ASFATTRIBUTE_H + +#include <tstring.h> +#include <tbytevector.h> +#include "taglib_export.h" + +namespace TagLib +{ + + namespace ASF + { + + class File; + + class TAGLIB_EXPORT Attribute + { + public: + + /*! + * Enum of types an Attribute can have. + */ + enum AttributeTypes { + UnicodeType = 0, + BytesType = 1, + BoolType = 2, + DWordType = 3, + QWordType = 4, + WordType = 5, + GuidType = 6 + }; + + /*! + * Constructs an empty attribute. + */ + Attribute(); + + /*! + * Constructs an attribute with \a key and a UnicodeType \a value. + */ + Attribute(const String &value); + + /*! + * Constructs an attribute with \a key and a BytesType \a value. + */ + Attribute(const ByteVector &value); + + /*! + * Constructs an attribute with \a key and a DWordType \a value. + */ + Attribute(unsigned int value); + + /*! + * Constructs an attribute with \a key and a QWordType \a value. + */ + Attribute(unsigned long long value); + + /*! + * Constructs an attribute with \a key and a WordType \a value. + */ + Attribute(unsigned short value); + + /*! + * Constructs an attribute with \a key and a BoolType \a value. + */ + Attribute(bool value); + + /*! + * Construct an attribute as a copy of \a other. + */ + Attribute(const Attribute &item); + + /*! + * Copies the contents of \a other into this item. + */ + ASF::Attribute &operator=(const Attribute &other); + + /*! + * Destroys the attribute. + */ + virtual ~Attribute(); + + /*! + * Returns type of the value. + */ + AttributeTypes type() const; + + /*! + * Returns the BoolType \a value. + */ + unsigned short toBool() const; + + /*! + * Returns the WordType \a value. + */ + unsigned short toUShort() const; + + /*! + * Returns the DWordType \a value. + */ + unsigned int toUInt() const; + + /*! + * Returns the QWordType \a value. + */ + unsigned long long toULongLong() const; + + /*! + * Returns the UnicodeType \a value. + */ + String toString() const; + + /*! + * Returns the BytesType \a value. + */ + ByteVector toByteVector() const; + + /*! + * Returns the language number, or 0 is no stream number was set. + */ + int language() const; + + /*! + * Sets the language number. + */ + void setLanguage(int value); + + /*! + * Returns the stream number, or 0 is no stream number was set. + */ + int stream() const; + + /*! + * Sets the stream number. + */ + void setStream(int value); + +#ifndef DO_NOT_DOCUMENT + /* THIS IS PRIVATE, DON'T TOUCH IT! */ + String parse(ASF::File &file, int kind = 0); +#endif + + private: + friend class File; + + ByteVector render(const String &name, int kind = 0) const; + + class AttributePrivate; + AttributePrivate *d; + }; + + } + +} + +#endif diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp new file mode 100644 index 00000000..e403934b --- /dev/null +++ b/taglib/asf/asffile.cpp @@ -0,0 +1,562 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WITH_ASF + +#include <tdebug.h> +#include <tbytevectorlist.h> +#include <tstring.h> +#include "asffile.h" +#include "asftag.h" +#include "asfproperties.h" + +using namespace TagLib; + +class ASF::File::FilePrivate +{ +public: + FilePrivate(): + size(0), + tag(0), + properties(0), + contentDescriptionObject(0), + extendedContentDescriptionObject(0), + headerExtensionObject(0), + metadataObject(0), + metadataLibraryObject(0) {} + unsigned long long size; + ASF::Tag *tag; + ASF::Properties *properties; + List<ASF::File::BaseObject *> objects; + ASF::File::ContentDescriptionObject *contentDescriptionObject; + ASF::File::ExtendedContentDescriptionObject *extendedContentDescriptionObject; + ASF::File::HeaderExtensionObject *headerExtensionObject; + ASF::File::MetadataObject *metadataObject; + ASF::File::MetadataLibraryObject *metadataLibraryObject; +}; + +static ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); +static ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); +static ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); +static ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); +static ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); +static ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); +static ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); +static ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); + +class ASF::File::BaseObject +{ +public: + ByteVector data; + virtual ~BaseObject() {} + virtual ByteVector guid() = 0; + virtual void parse(ASF::File *file, unsigned int size); + virtual ByteVector render(ASF::File *file); +}; + +class ASF::File::UnknownObject : public ASF::File::BaseObject +{ + ByteVector myGuid; +public: + UnknownObject(const ByteVector &guid); + ByteVector guid(); +}; + +class ASF::File::FilePropertiesObject : public ASF::File::BaseObject +{ +public: + ByteVector guid(); + void parse(ASF::File *file, uint size); +}; + +class ASF::File::StreamPropertiesObject : public ASF::File::BaseObject +{ +public: + ByteVector guid(); + void parse(ASF::File *file, uint size); +}; + +class ASF::File::ContentDescriptionObject : public ASF::File::BaseObject +{ +public: + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +class ASF::File::ExtendedContentDescriptionObject : public ASF::File::BaseObject +{ +public: + ByteVectorList attributeData; + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +class ASF::File::MetadataObject : public ASF::File::BaseObject +{ +public: + ByteVectorList attributeData; + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +class ASF::File::MetadataLibraryObject : public ASF::File::BaseObject +{ +public: + ByteVectorList attributeData; + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject +{ +public: + List<ASF::File::BaseObject *> objects; + ByteVector guid(); + void parse(ASF::File *file, uint size); + ByteVector render(ASF::File *file); +}; + +void +ASF::File::BaseObject::parse(ASF::File *file, unsigned int size) +{ + data = file->readBlock(size - 24); +} + +ByteVector +ASF::File::BaseObject::render(ASF::File * /*file*/) +{ + return guid() + ByteVector::fromLongLong(data.size() + 24, false) + data; +} + +ASF::File::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid) +{ +} + +ByteVector +ASF::File::UnknownObject::guid() +{ + return myGuid; +} + +ByteVector +ASF::File::FilePropertiesObject::guid() +{ + return filePropertiesGuid; +} + +void +ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size) +{ + BaseObject::parse(file, size); + file->d->properties->setLength((int)(data.mid(40, 8).toLongLong(false) / 10000000L - data.mid(56, 8).toLongLong(false) / 1000L)); +} + +ByteVector +ASF::File::StreamPropertiesObject::guid() +{ + return streamPropertiesGuid; +} + +void +ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) +{ + BaseObject::parse(file, size); + file->d->properties->setChannels(data.mid(56, 2).toShort(false)); + file->d->properties->setSampleRate(data.mid(58, 4).toUInt(false)); + file->d->properties->setBitrate(data.mid(62, 4).toUInt(false) * 8 / 1000); +} + +ByteVector +ASF::File::ContentDescriptionObject::guid() +{ + return contentDescriptionGuid; +} + +void +ASF::File::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->contentDescriptionObject = this; + int titleLength = file->readWORD(); + int artistLength = file->readWORD(); + int copyrightLength = file->readWORD(); + int commentLength = file->readWORD(); + int ratingLength = file->readWORD(); + file->d->tag->setTitle(file->readString(titleLength)); + file->d->tag->setArtist(file->readString(artistLength)); + file->d->tag->setCopyright(file->readString(copyrightLength)); + file->d->tag->setComment(file->readString(commentLength)); + file->d->tag->setRating(file->readString(ratingLength)); +} + +ByteVector +ASF::File::ContentDescriptionObject::render(ASF::File *file) +{ + ByteVector v1 = file->renderString(file->d->tag->title()); + ByteVector v2 = file->renderString(file->d->tag->artist()); + ByteVector v3 = file->renderString(file->d->tag->copyright()); + ByteVector v4 = file->renderString(file->d->tag->comment()); + ByteVector v5 = file->renderString(file->d->tag->rating()); + data.clear(); + data.append(ByteVector::fromShort(v1.size(), false)); + data.append(ByteVector::fromShort(v2.size(), false)); + data.append(ByteVector::fromShort(v3.size(), false)); + data.append(ByteVector::fromShort(v4.size(), false)); + data.append(ByteVector::fromShort(v5.size(), false)); + data.append(v1); + data.append(v2); + data.append(v3); + data.append(v4); + data.append(v5); + return BaseObject::render(file); +} + +ByteVector +ASF::File::ExtendedContentDescriptionObject::guid() +{ + return extendedContentDescriptionGuid; +} + +void +ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->extendedContentDescriptionObject = this; + int count = file->readWORD(); + while(count--) { + ASF::Attribute attribute; + String name = attribute.parse(*file); + file->d->tag->addAttribute(name, attribute); + } +} + +ByteVector +ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file) +{ + data.clear(); + data.append(ByteVector::fromShort(attributeData.size(), false)); + data.append(attributeData.toByteVector(ByteVector::null)); + return BaseObject::render(file); +} + +ByteVector +ASF::File::MetadataObject::guid() +{ + return metadataGuid; +} + +void +ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->metadataObject = this; + int count = file->readWORD(); + while(count--) { + ASF::Attribute attribute; + String name = attribute.parse(*file, 1); + file->d->tag->addAttribute(name, attribute); + } +} + +ByteVector +ASF::File::MetadataObject::render(ASF::File *file) +{ + data.clear(); + data.append(ByteVector::fromShort(attributeData.size(), false)); + data.append(attributeData.toByteVector(ByteVector::null)); + return BaseObject::render(file); +} + +ByteVector +ASF::File::MetadataLibraryObject::guid() +{ + return metadataLibraryGuid; +} + +void +ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->metadataLibraryObject = this; + int count = file->readWORD(); + while(count--) { + ASF::Attribute attribute; + String name = attribute.parse(*file, 2); + file->d->tag->addAttribute(name, attribute); + } +} + +ByteVector +ASF::File::MetadataLibraryObject::render(ASF::File *file) +{ + data.clear(); + data.append(ByteVector::fromShort(attributeData.size(), false)); + data.append(attributeData.toByteVector(ByteVector::null)); + return BaseObject::render(file); +} + +ByteVector +ASF::File::HeaderExtensionObject::guid() +{ + return headerExtensionGuid; +} + +void +ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) +{ + file->d->headerExtensionObject = this; + file->seek(18, File::Current); + long long dataSize = file->readDWORD(); + long long dataPos = 0; + while(dataPos < dataSize) { + ByteVector guid = file->readBlock(16); + long long size = file->readQWORD(); + BaseObject *obj; + if(guid == metadataGuid) { + obj = new MetadataObject(); + } + else if(guid == metadataLibraryGuid) { + obj = new MetadataLibraryObject(); + } + else { + obj = new UnknownObject(guid); + } + obj->parse(file, size); + objects.append(obj); + dataPos += size; + } +} + +ByteVector +ASF::File::HeaderExtensionObject::render(ASF::File *file) +{ + data.clear(); + for(unsigned int i = 0; i < objects.size(); i++) { + data.append(objects[i]->render(file)); + } + data = ByteVector("\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65\x06\x00", 18) + ByteVector::fromUInt(data.size(), false) + data; + return BaseObject::render(file); +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) + : TagLib::File(file) +{ + d = new FilePrivate; + read(readProperties, propertiesStyle); +} + +ASF::File::~File() +{ + for(unsigned int i = 0; i < d->objects.size(); i++) { + delete d->objects[i]; + } + if(d->tag) { + delete d->tag; + } + if(d->properties) { + delete d->properties; + } + delete d; +} + +ASF::Tag *ASF::File::tag() const +{ + return d->tag; +} + +ASF::Properties *ASF::File::audioProperties() const +{ + return d->properties; +} + +void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*propertiesStyle*/) +{ + if(!isValid()) + return; + + ByteVector guid = readBlock(16); + if(guid != headerGuid) { + debug("ASF: Not an ASF file."); + return; + } + + d->tag = new ASF::Tag(); + d->properties = new ASF::Properties(); + + d->size = readQWORD(); + int numObjects = readDWORD(); + seek(2, Current); + + for(int i = 0; i < numObjects; i++) { + ByteVector guid = readBlock(16); + long size = (long)readQWORD(); + BaseObject *obj; + if(guid == filePropertiesGuid) { + obj = new FilePropertiesObject(); + } + else if(guid == streamPropertiesGuid) { + obj = new StreamPropertiesObject(); + } + else if(guid == contentDescriptionGuid) { + obj = new ContentDescriptionObject(); + } + else if(guid == extendedContentDescriptionGuid) { + obj = new ExtendedContentDescriptionObject(); + } + else if(guid == headerExtensionGuid) { + obj = new HeaderExtensionObject(); + } + else { + obj = new UnknownObject(guid); + } + obj->parse(this, size); + d->objects.append(obj); + } +} + +bool ASF::File::save() +{ + if(readOnly()) { + debug("ASF: File is read-only."); + return false; + } + + if(!d->contentDescriptionObject) { + d->contentDescriptionObject = new ContentDescriptionObject(); + d->objects.append(d->contentDescriptionObject); + } + if(!d->extendedContentDescriptionObject) { + d->extendedContentDescriptionObject = new ExtendedContentDescriptionObject(); + d->objects.append(d->extendedContentDescriptionObject); + } + if(!d->headerExtensionObject) { + d->headerExtensionObject = new HeaderExtensionObject(); + d->objects.append(d->headerExtensionObject); + } + if(!d->metadataObject) { + d->metadataObject = new MetadataObject(); + d->headerExtensionObject->objects.append(d->metadataObject); + } + if(!d->metadataLibraryObject) { + d->metadataLibraryObject = new MetadataLibraryObject(); + d->headerExtensionObject->objects.append(d->metadataLibraryObject); + } + + ASF::AttributeListMap::ConstIterator it = d->tag->attributeListMap().begin(); + for(; it != d->tag->attributeListMap().end(); it++) { + const String &name = it->first; + const AttributeList &attributes = it->second; + bool inExtendedContentDescriptionObject = false; + bool inMetadataObject = false; + for(unsigned int j = 0; j < attributes.size(); j++) { + const Attribute &attribute = attributes[j]; + if(!inExtendedContentDescriptionObject && attribute.language() == 0 && attribute.stream() == 0) { + d->extendedContentDescriptionObject->attributeData.append(attribute.render(name)); + inExtendedContentDescriptionObject = true; + } + else if(!inMetadataObject && attribute.language() == 0 && attribute.stream() != 0) { + d->metadataObject->attributeData.append(attribute.render(name, 1)); + inMetadataObject = true; + } + else { + d->metadataLibraryObject->attributeData.append(attribute.render(name, 2)); + } + } + } + + ByteVector data; + for(unsigned int i = 0; i < d->objects.size(); i++) { + 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); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +int ASF::File::readBYTE() +{ + ByteVector v = readBlock(1); + return v[0]; +} + +int ASF::File::readWORD() +{ + ByteVector v = readBlock(2); + return v.toShort(false); +} + +unsigned int ASF::File::readDWORD() +{ + ByteVector v = readBlock(4); + return v.toUInt(false); +} + +long long ASF::File::readQWORD() +{ + ByteVector v = readBlock(8); + return v.toLongLong(false); +} + +String +ASF::File::readString(int length) +{ + ByteVector data = readBlock(length); + unsigned int size = data.size(); + while (size >= 2) { + if(data[size - 1] != '\0' || data[size - 2] != '\0') { + break; + } + size -= 2; + } + if(size != data.size()) { + data.resize(size); + } + return String(data, String::UTF16LE); +} + +ByteVector +ASF::File::renderString(const String &str, bool includeLength) +{ + ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false); + if(includeLength) { + data = ByteVector::fromShort(data.size(), false) + data; + } + return data; +} + +#endif diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h new file mode 100644 index 00000000..9d2913d5 --- /dev/null +++ b/taglib/asf/asffile.h @@ -0,0 +1,119 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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_ASFFILE_H +#define TAGLIB_ASFFILE_H + +#include <tag.h> +#include <tfile.h> +#include "taglib_export.h" +#include "asfproperties.h" +#include "asftag.h" + +namespace TagLib { + + //! An implementation of ASF (WMA) metadata + namespace ASF { + + /*! + * This implements and provides an interface for ASF files to the + * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing + * the abstract TagLib::File API as well as providing some additional + * information specific to ASF files. + */ + class TAGLIB_EXPORT File : public TagLib::File + { + public: + + /*! + * Contructs an ASF 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 In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. + */ + File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns a pointer to the ASF tag of the file. + * + * ASF::Tag implements the tag interface, so this serves as the + * reimplementation of TagLib::File::tag(). + * + * \note The Tag <b>is still</b> owned by the ASF::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + */ + virtual Tag *tag() const; + + /*! + * Returns the ASF audio properties for this file. + */ + virtual Properties *audioProperties() const; + + /*! + * Save the file. + * + * This returns true if the save was successful. + */ + virtual bool save(); + + private: + + int readBYTE(); + int readWORD(); + unsigned int readDWORD(); + long long readQWORD(); + static ByteVector renderString(const String &str, bool includeLength = false); + String readString(int len); + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + + friend class Attribute; + + class BaseObject; + class UnknownObject; + class FilePropertiesObject; + class StreamPropertiesObject; + class ContentDescriptionObject; + class ExtendedContentDescriptionObject; + class HeaderExtensionObject; + class MetadataObject; + class MetadataLibraryObject; + + class FilePrivate; + FilePrivate *d; + }; + + } + +} + +#endif diff --git a/taglib/asf/asfproperties.cpp b/taglib/asf/asfproperties.cpp new file mode 100644 index 00000000..68e2d4ca --- /dev/null +++ b/taglib/asf/asfproperties.cpp @@ -0,0 +1,107 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WITH_ASF + +#include <tdebug.h> +#include <tstring.h> +#include "asfproperties.h" + +using namespace TagLib; + +class ASF::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate(): length(0), bitrate(0), sampleRate(0), channels(0) {} + int length; + int bitrate; + int sampleRate; + int channels; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +ASF::Properties::Properties() : AudioProperties(AudioProperties::Average) +{ + d = new PropertiesPrivate; +} + +ASF::Properties::~Properties() +{ + if(d) + delete d; +} + +int ASF::Properties::length() const +{ + return d->length; +} + +int ASF::Properties::bitrate() const +{ + return d->bitrate; +} + +int ASF::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int ASF::Properties::channels() const +{ + return d->channels; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void ASF::Properties::setLength(int length) +{ + d->length = length; +} + +void ASF::Properties::setBitrate(int length) +{ + d->bitrate = length; +} + +void ASF::Properties::setSampleRate(int length) +{ + d->sampleRate = length; +} + +void ASF::Properties::setChannels(int length) +{ + d->channels = length; +} + +#endif diff --git a/taglib/asf/asfproperties.h b/taglib/asf/asfproperties.h new file mode 100644 index 00000000..938b0c9d --- /dev/null +++ b/taglib/asf/asfproperties.h @@ -0,0 +1,74 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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_ASFPROPERTIES_H +#define TAGLIB_ASFPROPERTIES_H + +#include <audioproperties.h> +#include <tstring.h> +#include "taglib_export.h" + +namespace TagLib { + + namespace ASF { + + //! An implementation of ASF audio properties + class TAGLIB_EXPORT Properties : public AudioProperties + { + public: + + /*! + * Create an instance of ASF::Properties. + */ + Properties(); + + /*! + * Destroys this ASF::Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + +#ifndef DO_NOT_DOCUMENT + void setLength(int value); + void setBitrate(int value); + void setSampleRate(int value); + void setChannels(int value); +#endif + + private: + class PropertiesPrivate; + PropertiesPrivate *d; + }; + + } + +} + +#endif diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp new file mode 100644 index 00000000..6bea247f --- /dev/null +++ b/taglib/asf/asftag.cpp @@ -0,0 +1,214 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WITH_ASF + +#include "asftag.h" + +using namespace TagLib; + +class ASF::Tag::TagPrivate +{ +public: + String title; + String artist; + String copyright; + String comment; + String rating; + AttributeListMap attributeListMap; +}; + +ASF::Tag::Tag() +: TagLib::Tag() +{ + d = new TagPrivate; +} + +ASF::Tag::~Tag() +{ + if(d) + delete d; +} + +String +ASF::Tag::title() const +{ + return d->title; +} + +String +ASF::Tag::artist() const +{ + return d->artist; +} + +String +ASF::Tag::album() const +{ + if(d->attributeListMap.contains("WM/AlbumTitle")) + return d->attributeListMap["WM/AlbumTitle"][0].toString(); + return String::null; +} + +String +ASF::Tag::copyright() const +{ + return d->copyright; +} + +String +ASF::Tag::comment() const +{ + return d->comment; +} + +String +ASF::Tag::rating() const +{ + return d->rating; +} + +unsigned int +ASF::Tag::year() const +{ + if(d->attributeListMap.contains("WM/Year")) + return d->attributeListMap["WM/Year"][0].toString().toInt(); + return 0; +} + +unsigned int +ASF::Tag::track() const +{ + if(d->attributeListMap.contains("WM/TrackNumber")) + return d->attributeListMap["WM/TrackNumber"][0].toString().toInt(); + if(d->attributeListMap.contains("WM/Track")) + return d->attributeListMap["WM/Track"][0].toUInt(); + return 0; +} + +String +ASF::Tag::genre() const +{ + if(d->attributeListMap.contains("WM/Genre")) + return d->attributeListMap["WM/Genre"][0].toString(); + return String::null; +} + +void +ASF::Tag::setTitle(const String &value) +{ + d->title = value; +} + +void +ASF::Tag::setArtist(const String &value) +{ + d->artist = value; +} + +void +ASF::Tag::setCopyright(const String &value) +{ + d->copyright = value; +} + +void +ASF::Tag::setComment(const String &value) +{ + d->comment = value; +} + +void +ASF::Tag::setRating(const String &value) +{ + d->rating = value; +} + +void +ASF::Tag::setAlbum(const String &value) +{ + setAttribute("WM/AlbumTitle", value); +} + +void +ASF::Tag::setGenre(const String &value) +{ + setAttribute("WM/Genre", value); +} + +void +ASF::Tag::setYear(uint value) +{ + setAttribute("WM/Year", String::number(value)); +} + +void +ASF::Tag::setTrack(uint value) +{ + setAttribute("WM/TrackNumber", String::number(value)); +} + +ASF::AttributeListMap& +ASF::Tag::attributeListMap() +{ + return d->attributeListMap; +} + +void ASF::Tag::removeItem(const String &key) +{ + AttributeListMap::Iterator it = d->attributeListMap.find(key); + if(it != d->attributeListMap.end()) + d->attributeListMap.erase(it); +} + +void ASF::Tag::setAttribute(const String &name, const Attribute &attribute) +{ + AttributeList value; + value.append(attribute); + d->attributeListMap.insert(name, value); +} + +void ASF::Tag::addAttribute(const String &name, const Attribute &attribute) +{ + if(d->attributeListMap.contains(name)) { + d->attributeListMap[name].append(attribute); + } + else { + setAttribute(name, attribute); + } +} + +bool ASF::Tag::isEmpty() const { + return TagLib::Tag::isEmpty() && + copyright().isEmpty() && + rating().isEmpty() && + d->attributeListMap.isEmpty(); +} + +#endif diff --git a/taglib/asf/asftag.h b/taglib/asf/asftag.h new file mode 100644 index 00000000..78c90fc2 --- /dev/null +++ b/taglib/asf/asftag.h @@ -0,0 +1,186 @@ +/************************************************************************** + copyright : (C) 2005-2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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_ASFTAG_H +#define TAGLIB_ASFTAG_H + +#include <tag.h> +#include <tlist.h> +#include <tmap.h> +#include "taglib_export.h" +#include "asfattribute.h" + +namespace TagLib { + + namespace ASF { + + typedef List<Attribute> AttributeList; + typedef Map<String, AttributeList> AttributeListMap; + + class TAGLIB_EXPORT Tag : public TagLib::Tag { + + friend class File; + + public: + + Tag(); + + virtual ~Tag(); + + /*! + * Returns the track name. + */ + virtual String title() const; + + /*! + * Returns the artist name. + */ + virtual String artist() const; + + /*! + * Returns the album name; if no album name is present in the tag + * String::null will be returned. + */ + virtual String album() const; + + /*! + * Returns the track comment. + */ + virtual String comment() const; + + /*! + * Returns the genre name; if no genre is present in the tag String::null + * will be returned. + */ + virtual String genre() const; + + /*! + * Returns the rating. + */ + virtual String rating() const; + + /*! + * Returns the genre name; if no genre is present in the tag String::null + * will be returned. + */ + virtual String copyright() const; + + /*! + * Returns the year; if there is no year set, this will return 0. + */ + virtual uint year() const; + + /*! + * Returns the track number; if there is no track number set, this will + * return 0. + */ + virtual uint track() const; + + /*! + * Sets the title to \a s. + */ + virtual void setTitle(const String &s); + + /*! + * Sets the artist to \a s. + */ + virtual void setArtist(const String &s); + + /*! + * Sets the album to \a s. If \a s is String::null then this value will be + * cleared. + */ + virtual void setAlbum(const String &s); + + /*! + * Sets the comment to \a s. + */ + virtual void setComment(const String &s); + + /*! + * Sets the rating to \a s. + */ + virtual void setRating(const String &s); + + /*! + * Sets the copyright to \a s. + */ + virtual void setCopyright(const String &s); + + /*! + * Sets the genre to \a s. + */ + virtual void setGenre(const String &s); + + /*! + * Sets the year to \a i. If \a s is 0 then this value will be cleared. + */ + virtual void setYear(uint i); + + /*! + * Sets the track to \a i. If \a s is 0 then this value will be cleared. + */ + virtual void setTrack(uint i); + + /*! + * Returns true if the tag does not contain any data. This should be + * reimplemented in subclasses that provide more than the basic tagging + * abilities in this class. + */ + virtual bool isEmpty() const; + + /*! + * Returns a reference to the item list map. This is an AttributeListMap of + * all of the items in the tag. + * + * This is the most powerfull structure for accessing the items of the tag. + */ + AttributeListMap &attributeListMap(); + + /*! + * Removes the \a key attribute from the tag + */ + void removeItem(const String &name); + + /*! + * Sets the \a key attribute to the value of \a attribute. If an attribute + * with the \a key is already present, it will be replaced. + */ + void setAttribute(const String &name, const Attribute &attribute); + + /*! + * Sets the \a key attribute to the value of \a attribute. If an attribute + * with the \a key is already present, it will be added to the list. + */ + void addAttribute(const String &name, const Attribute &attribute); + + private: + + class TagPrivate; + TagPrivate *d; + }; + } +} +#endif diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index ce6f649b..b326bd22 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -23,15 +23,21 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + #include <tfile.h> #include <tstring.h> #include "fileref.h" +#include "asffile.h" #include "mpegfile.h" #include "vorbisfile.h" #include "flacfile.h" #include "oggflacfile.h" #include "mpcfile.h" +#include "mp4file.h" #include "wavpackfile.h" #include "speexfile.h" #include "trueaudiofile.h" @@ -123,6 +129,15 @@ StringList FileRef::defaultFileExtensions() l.append("wv"); l.append("spx"); l.append("tta"); +#ifdef WITH_MP4 + l.append("m4a"); + l.append("m4b"); + l.append("m4p"); + l.append("3g2"); +#endif +#ifdef WITH_ASF + l.append("wma"); +#endif l.append("aif"); l.append("aiff"); l.append("wav"); @@ -202,6 +217,17 @@ File *FileRef::create(FileName fileName, bool readAudioProperties, return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle); if(s.substr(s.size() - 4, 4).upper() == ".TTA") return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle); +#ifdef WITH_MP4 + if(s.substr(s.size() - 4, 4).upper() == ".M4A" || + s.substr(s.size() - 4, 4).upper() == ".M4B" || + s.substr(s.size() - 4, 4).upper() == ".M4P" || + s.substr(s.size() - 4, 4).upper() == ".3G2") + return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle); +#endif +#ifdef WITH_ASF + if(s.substr(s.size() - 4, 4).upper() == ".WMA") + return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle); +#endif if(s.substr(s.size() - 4, 4).upper() == ".AIF") return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle); if(s.substr(s.size() - 4, 4).upper() == ".WAV") diff --git a/taglib/mp4/CMakeLists.txt b/taglib/mp4/CMakeLists.txt new file mode 100644 index 00000000..803a9d52 --- /dev/null +++ b/taglib/mp4/CMakeLists.txt @@ -0,0 +1 @@ +INSTALL( FILES mp4file.h mp4atom.h mp4tag.h mp4item.h mp4properties.h DESTINATION ${INCLUDE_INSTALL_DIR}/taglib) diff --git a/taglib/mp4/Makefile.am b/taglib/mp4/Makefile.am new file mode 100644 index 00000000..4ff5a305 --- /dev/null +++ b/taglib/mp4/Makefile.am @@ -0,0 +1,11 @@ +INCLUDES = \ + -I$(top_srcdir)/taglib \ + -I$(top_srcdir)/taglib/toolkit \ + $(all_includes) + +noinst_LTLIBRARIES = libmp4.la + +libmp4_la_SOURCES = mp4atom.cpp mp4file.cpp mp4item.cpp mp4properties.cpp mp4tag.cpp + +taglib_include_HEADERS = mp4atom.h mp4file.h mp4item.h mp4properties.h mp4tag.h +taglib_includedir = $(includedir)/taglib diff --git a/taglib/mp4/mp4atom.cpp b/taglib/mp4/mp4atom.cpp new file mode 100644 index 00000000..e1a36828 --- /dev/null +++ b/taglib/mp4/mp4atom.cpp @@ -0,0 +1,175 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WITH_MP4 + +#include <tdebug.h> +#include <tstring.h> +#include "mp4atom.h" + +using namespace TagLib; + +const char *MP4::Atom::containers[10] = { + "moov", "udta", "mdia", "meta", "ilst", + "stbl", "minf", "moof", "traf", "trak", +}; + +MP4::Atom::Atom(File *file) +{ + offset = file->tell(); + ByteVector header = file->readBlock(8); + length = header.mid(0, 4).toUInt(); + + if (length == 1) { + debug("MP4: 64-bit atoms are not supported"); + length = 0; + file->seek(0, File::End); + return; + } + if (length < 8) { + debug("MP4: Invalid atom size"); + length = 0; + file->seek(0, File::End); + return; + } + + name = header.mid(4, 4); + + for(int i = 0; i < numContainers; i++) { + if(name == containers[i]) { + if(name == "meta") { + file->seek(4, File::Current); + } + while(file->tell() < offset + length) { + children.append(new MP4::Atom(file)); + } + return; + } + } + + file->seek(offset + length); +} + +MP4::Atom::~Atom() +{ + for(unsigned int i = 0; i < children.size(); i++) { + delete children[i]; + } + children.clear(); +} + +MP4::Atom * +MP4::Atom::find(const char *name1, const char *name2, const char *name3, const char *name4) +{ + if(name1 == 0) { + return this; + } + for(unsigned int i = 0; i < children.size(); i++) { + if(children[i]->name == name1) { + return children[i]->find(name2, name3, name4); + } + } + return 0; +} + +MP4::AtomList +MP4::Atom::findall(const char *name, bool recursive) +{ + MP4::AtomList result; + for(unsigned int i = 0; i < children.size(); i++) { + if(children[i]->name == name) { + result.append(children[i]); + } + if(recursive) { + result.append(children[i]->findall(name, recursive)); + } + } + return result; +} + +bool +MP4::Atom::path(MP4::AtomList &path, const char *name1, const char *name2, const char *name3) +{ + path.append(this); + if(name1 == 0) { + return true; + } + for(unsigned int i = 0; i < children.size(); i++) { + if(children[i]->name == name1) { + return children[i]->path(path, name2, name3); + } + } + return false; +} + +MP4::Atoms::Atoms(File *file) +{ + file->seek(0, File::End); + long end = file->tell(); + file->seek(0); + while(file->tell() + 8 <= end) { + atoms.append(new MP4::Atom(file)); + } +} + +MP4::Atoms::~Atoms() +{ + for(unsigned int i = 0; i < atoms.size(); i++) { + delete atoms[i]; + } + atoms.clear(); +} + +MP4::Atom * +MP4::Atoms::find(const char *name1, const char *name2, const char *name3, const char *name4) +{ + for(unsigned int i = 0; i < atoms.size(); i++) { + if(atoms[i]->name == name1) { + return atoms[i]->find(name2, name3, name4); + } + } + return 0; +} + +MP4::AtomList +MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const char *name4) +{ + MP4::AtomList path; + for(unsigned int i = 0; i < atoms.size(); i++) { + if(atoms[i]->name == name1) { + if(!atoms[i]->path(path, name2, name3, name4)) { + path.clear(); + } + return path; + } + } + return path; +} + +#endif diff --git a/taglib/mp4/mp4atom.h b/taglib/mp4/mp4atom.h new file mode 100644 index 00000000..702566b9 --- /dev/null +++ b/taglib/mp4/mp4atom.h @@ -0,0 +1,77 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +// This file is not part of the public API! + +#ifndef DO_NOT_DOCUMENT + +#ifndef TAGLIB_MP4ATOM_H +#define TAGLIB_MP4ATOM_H + +#include <tfile.h> +#include <tlist.h> + +namespace TagLib { + + namespace MP4 { + + class Atom; + typedef TagLib::List<Atom *> AtomList; + + class Atom + { + public: + Atom(File *file); + ~Atom(); + Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0); + bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0); + AtomList findall(const char *name, bool recursive = false); + long offset; + long length; + TagLib::ByteVector name; + AtomList children; + private: + static const int numContainers = 10; + static const char *containers[10]; + }; + + //! Root-level atoms + class Atoms + { + public: + Atoms(File *file); + ~Atoms(); + Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0); + AtomList path(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0); + AtomList atoms; + }; + + } + +} + +#endif + +#endif diff --git a/taglib/mp4/mp4file.cpp b/taglib/mp4/mp4file.cpp new file mode 100644 index 00000000..bcb88afa --- /dev/null +++ b/taglib/mp4/mp4file.cpp @@ -0,0 +1,111 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WITH_MP4 + +#include <tdebug.h> +#include <tstring.h> +#include "mp4atom.h" +#include "mp4tag.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::File::FilePrivate +{ +public: + FilePrivate() : tag(0), atoms(0) + { + } + + ~FilePrivate() + { + if(atoms) { + delete atoms; + atoms = 0; + } + if(tag) { + delete tag; + tag = 0; + } + if(properties) { + delete properties; + properties = 0; + } + } + + MP4::Tag *tag; + MP4::Atoms *atoms; + MP4::Properties *properties; +}; + +MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) + : TagLib::File(file) +{ + d = new FilePrivate; + read(readProperties, audioPropertiesStyle); +} + +MP4::File::~File() +{ + delete d; +} + +MP4::Tag * +MP4::File::tag() const +{ + return d->tag; +} + +MP4::Properties * +MP4::File::audioProperties() const +{ + return d->properties; +} + +void +MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle) +{ + if(!isValid()) + return; + + d->atoms = new Atoms(this); + d->tag = new Tag(this, d->atoms); + if(readProperties) { + d->properties = new Properties(this, d->atoms, audioPropertiesStyle); + } +} + +bool +MP4::File::save() +{ + return d->tag->save(); +} + +#endif diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h new file mode 100644 index 00000000..3bd15e3a --- /dev/null +++ b/taglib/mp4/mp4file.h @@ -0,0 +1,102 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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_MP4FILE_H +#define TAGLIB_MP4FILE_H + +#include <tag.h> +#include <tfile.h> +#include "taglib_export.h" +#include "mp4properties.h" +#include "mp4tag.h" + +namespace TagLib { + + //! An implementation of MP4 (AAC, ALAC, ...) metadata + namespace MP4 { + + class Atoms; + + /*! + * This implements and provides an interface for MP4 files to the + * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing + * the abstract TagLib::File API as well as providing some additional + * information specific to MP4 files. + */ + class TAGLIB_EXPORT File : public TagLib::File + { + public: + /*! + * Contructs an ASF 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 In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. + */ + File(FileName file, bool readProperties = true, Properties::ReadStyle audioPropertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns a pointer to the MP4 tag of the file. + * + * MP4::Tag implements the tag interface, so this serves as the + * reimplementation of TagLib::File::tag(). + * + * \note The Tag <b>is still</b> owned by the ASF::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + */ + Tag *tag() const; + + /*! + * Returns the MP4 audio properties for this file. + */ + Properties *audioProperties() const; + + /*! + * Save the file. + * + * This returns true if the save was successful. + */ + bool save(); + + private: + + void read(bool readProperties, Properties::ReadStyle audioPropertiesStyle); + + class FilePrivate; + FilePrivate *d; + }; + + } + +} + +#endif diff --git a/taglib/mp4/mp4item.cpp b/taglib/mp4/mp4item.cpp new file mode 100644 index 00000000..2b5613ad --- /dev/null +++ b/taglib/mp4/mp4item.cpp @@ -0,0 +1,136 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WITH_MP4 + +#include <taglib.h> +#include <tdebug.h> +#include "mp4item.h" + +using namespace TagLib; + +class MP4::Item::ItemPrivate : public RefCounter +{ +public: + ItemPrivate() : RefCounter(), valid(true) {} + + bool valid; + union { + bool m_bool; + int m_int; + IntPair m_intPair; + }; + StringList m_stringList; +}; + +MP4::Item::Item() +{ + d = new ItemPrivate; + d->valid = false; +} + +MP4::Item::Item(const Item &item) : d(item.d) +{ + d->ref(); +} + +MP4::Item & +MP4::Item::operator=(const Item &item) +{ + if(d->deref()) { + delete d; + } + d = item.d; + d->ref(); + return *this; +} + +MP4::Item::~Item() +{ + if(d->deref()) { + delete d; + } +} + +MP4::Item::Item(bool value) +{ + d = new ItemPrivate; + d->m_bool = value; +} + +MP4::Item::Item(int value) +{ + d = new ItemPrivate; + d->m_int = value; +} + +MP4::Item::Item(int value1, int value2) +{ + d = new ItemPrivate; + d->m_intPair.first = value1; + d->m_intPair.second = value2; +} + +MP4::Item::Item(const StringList &value) +{ + d = new ItemPrivate; + d->m_stringList = value; +} + +bool +MP4::Item::toBool() const +{ + return d->m_bool; +} + +int +MP4::Item::toInt() const +{ + return d->m_int; +} + +MP4::Item::IntPair +MP4::Item::toIntPair() const +{ + return d->m_intPair; +} + +StringList +MP4::Item::toStringList() const +{ + return d->m_stringList; +} + +bool +MP4::Item::isValid() const +{ + return d->valid; +} + +#endif diff --git a/taglib/mp4/mp4item.h b/taglib/mp4/mp4item.h new file mode 100644 index 00000000..d53c41c3 --- /dev/null +++ b/taglib/mp4/mp4item.h @@ -0,0 +1,69 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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_MP4ITEM_H +#define TAGLIB_MP4ITEM_H + +#include <tstringlist.h> +#include "taglib_export.h" + +namespace TagLib { + + namespace MP4 { + + class TAGLIB_EXPORT Item + { + public: + struct IntPair { + int first, second; + }; + + Item(); + Item(const Item &item); + Item &operator=(const Item &item); + ~Item(); + + Item(int value); + Item(bool value); + Item(int first, int second); + Item(const StringList &value); + + int toInt() const; + bool toBool() const; + IntPair toIntPair() const; + StringList toStringList() const; + + bool isValid() const; + + private: + class ItemPrivate; + ItemPrivate *d; + }; + + } + +} + +#endif diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp new file mode 100644 index 00000000..c973d3a2 --- /dev/null +++ b/taglib/mp4/mp4properties.cpp @@ -0,0 +1,169 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WITH_MP4 + +#include <tdebug.h> +#include <tstring.h> +#include "mp4file.h" +#include "mp4atom.h" +#include "mp4properties.h" + +using namespace TagLib; + +class MP4::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), bitsPerSample(0) {} + + int length; + int bitrate; + int sampleRate; + int channels; + int bitsPerSample; +}; + +MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) + : AudioProperties(style) +{ + d = new PropertiesPrivate; + + MP4::Atom *moov = atoms->find("moov"); + if(!moov) { + debug("MP4: Atom 'moov' not found"); + return; + } + + MP4::Atom *trak = 0; + ByteVector data; + + MP4::AtomList trakList = moov->findall("trak"); + for (unsigned int i = 0; i < trakList.size(); i++) { + trak = trakList[i]; + MP4::Atom *hdlr = trak->find("mdia", "hdlr"); + if(!hdlr) { + debug("MP4: Atom 'trak.mdia.hdlr' not found"); + return; + } + file->seek(hdlr->offset); + data = file->readBlock(hdlr->length); + if(data.mid(16, 4) == "soun") { + break; + } + trak = 0; + } + if (!trak) { + debug("MP4: No audio tracks"); + return; + } + + MP4::Atom *mdhd = trak->find("mdia", "mdhd"); + if(!mdhd) { + debug("MP4: Atom 'trak.mdia.mdhd' not found"); + return; + } + + file->seek(mdhd->offset); + data = file->readBlock(mdhd->length); + if(data[8] == 0) { + unsigned int unit = data.mid(20, 4).toUInt(); + unsigned int length = data.mid(24, 4).toUInt(); + d->length = length / unit; + } + else { + long long unit = data.mid(28, 8).toLongLong(); + long long length = data.mid(36, 8).toLongLong(); + d->length = int(length / unit); + } + + MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd"); + if(!atom) { + return; + } + + file->seek(atom->offset); + data = file->readBlock(atom->length); + if(data.mid(20, 4) == "mp4a") { + d->channels = data.mid(40, 2).toShort(); + d->bitsPerSample = data.mid(42, 2).toShort(); + d->sampleRate = data.mid(46, 4).toUInt(); + if(data.mid(56, 4) == "esds" && data[64] == 0x03) { + long pos = 65; + if(data.mid(pos, 3) == "\x80\x80\x80") { + pos += 3; + } + pos += 4; + if(data[pos] == 0x04) { + pos += 1; + if(data.mid(pos, 3) == "\x80\x80\x80") { + pos += 3; + } + pos += 10; + d->bitrate = (data.mid(pos, 4).toUInt() + 500) / 1000; + } + } + } +} + +MP4::Properties::~Properties() +{ + delete d; +} + +int +MP4::Properties::channels() const +{ + return d->channels; +} + +int +MP4::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int +MP4::Properties::length() const +{ + return d->length; +} + +int +MP4::Properties::bitrate() const +{ + return d->bitrate; +} + +int +MP4::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + +#endif diff --git a/taglib/mp4/mp4properties.h b/taglib/mp4/mp4properties.h new file mode 100644 index 00000000..fb76c8ab --- /dev/null +++ b/taglib/mp4/mp4properties.h @@ -0,0 +1,61 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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_MP4PROPERTIES_H +#define TAGLIB_MP4PROPERTIES_H + +#include "taglib_export.h" +#include "audioproperties.h" + +namespace TagLib { + + namespace MP4 { + + class Atoms; + class File; + + //! An implementation of MP4 audio properties + class TAGLIB_EXPORT Properties : public AudioProperties + { + public: + Properties(File *file, Atoms *atoms, ReadStyle style = Average); + virtual ~Properties(); + + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + virtual int bitsPerSample() const; + + private: + class PropertiesPrivate; + PropertiesPrivate *d; + }; + + } + +} + +#endif diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp new file mode 100644 index 00000000..229c4755 --- /dev/null +++ b/taglib/mp4/mp4tag.cpp @@ -0,0 +1,558 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WITH_MP4 + +#include <tdebug.h> +#include <tstring.h> +#include "mp4atom.h" +#include "mp4tag.h" + +using namespace TagLib; + +class MP4::Tag::TagPrivate +{ +public: + TagPrivate() : file(0), atoms(0) {} + ~TagPrivate() {} + File *file; + Atoms *atoms; + ItemListMap items; +}; + +MP4::Tag::Tag(File *file, MP4::Atoms *atoms) +{ + d = new TagPrivate; + d->file = file; + d->atoms = atoms; + + MP4::Atom *ilst = atoms->find("moov", "udta", "meta", "ilst"); + if(!ilst) { + //debug("Atom moov.udta.meta.ilst not found."); + return; + } + + for(unsigned int i = 0; i < ilst->children.size(); i++) { + MP4::Atom *atom = ilst->children[i]; + file->seek(atom->offset + 8); + if(atom->name == "----") { + parseFreeForm(atom, file); + } + else if(atom->name == "trkn" || atom->name == "disk") { + parseIntPair(atom, file); + } + else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst") { + parseBool(atom, file); + } + else if(atom->name == "tmpo") { + parseInt(atom, file); + } + else { + parseText(atom, file); + } + } +} + +ByteVectorList +MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +{ + ByteVectorList result; + ByteVector data = file->readBlock(atom->length - 8); + int i = 0; + unsigned int pos = 0; + while(pos < data.size()) { + int length = data.mid(pos, 4).toUInt(); + ByteVector name = data.mid(pos + 4, 4); + int flags = data.mid(pos + 8, 4).toUInt(); + if(freeForm && i < 2) { + if(i == 0 && name != "mean") { + debug("MP4: Unexpected atom \"" + name + "\", expecting \"mean\""); + return result; + } + else if(i == 1 && name != "name") { + debug("MP4: Unexpected atom \"" + name + "\", expecting \"name\""); + return result; + } + result.append(data.mid(pos + 12, length - 12)); + } + else { + if(name != "data") { + debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); + return result; + } + if(expectedFlags == -1 || flags == expectedFlags) { + result.append(data.mid(pos + 16, length - 16)); + } + } + pos += length; + i++; + } + return result; +} + +void +MP4::Tag::parseInt(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + d->items.insert(atom->name, (int)data[0].toShort()); + } +} + +void +MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + int a = data[0].mid(2, 2).toShort(); + int b = data[0].mid(4, 2).toShort(); + d->items.insert(atom->name, MP4::Item(a, b)); + } +} + +void +MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + d->items.insert(atom->name, data[0][0] != '\0'); + } +} + +void +MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags) +{ + ByteVectorList data = parseData(atom, file, expectedFlags); + if(data.size()) { + StringList value; + for(unsigned int i = 0; i < data.size(); i++) { + value.append(String(data[i], String::UTF8)); + } + d->items.insert(atom->name, value); + } +} + +void +MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file, 1, true); + if(data.size() > 2) { + StringList value; + for(unsigned int i = 2; i < data.size(); i++) { + value.append(String(data[i], String::UTF8)); + } + String name = "----:" + data[0] + ":" + data[1]; + d->items.insert(name, value); + } +} + +ByteVector +MP4::Tag::padIlst(const ByteVector &data, int length) +{ + if (length == -1) { + length = ((data.size() + 1023) & ~1023) - data.size(); + } + return renderAtom("free", ByteVector(length, '\1')); +} + +ByteVector +MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) +{ + return ByteVector::fromUInt(data.size() + 8) + name + data; +} + +ByteVector +MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) +{ + ByteVector result; + for(unsigned int i = 0; i < data.size(); i++) { + result.append(renderAtom("data", ByteVector::fromUInt(flags) + ByteVector(4, '\0') + data[i])); + } + return renderAtom(name, result); +} + +ByteVector +MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item) +{ + ByteVectorList data; + data.append(ByteVector(1, item.toBool() ? '\1' : '\0')); + return renderData(name, 0x15, data); +} + +ByteVector +MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item) +{ + ByteVectorList data; + data.append(ByteVector::fromShort(item.toInt())); + return renderData(name, 0x15, data); +} + +ByteVector +MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item) +{ + ByteVectorList data; + data.append(ByteVector(2, '\0') + + ByteVector::fromShort(item.toIntPair().first) + + ByteVector::fromShort(item.toIntPair().second) + + ByteVector(2, '\0')); + return renderData(name, 0x15, data); +} + +ByteVector +MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item) +{ + ByteVectorList data; + data.append(ByteVector(2, '\0') + + ByteVector::fromShort(item.toIntPair().first) + + ByteVector::fromShort(item.toIntPair().second)); + return renderData(name, 0x15, data); +} + +ByteVector +MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags) +{ + ByteVectorList data; + StringList value = item.toStringList(); + for(unsigned int i = 0; i < value.size(); i++) { + data.append(value[i].data(String::UTF8)); + } + return renderData(name, flags, data); +} + +ByteVector +MP4::Tag::renderFreeForm(const String &name, MP4::Item &item) +{ + StringList header = StringList::split(name, ":"); + if (header.size() != 3) { + debug("MP4: Invalid free-form item name \"" + name + "\""); + return ByteVector::null; + } + ByteVector data; + data.append(renderAtom("mean", ByteVector::fromUInt(0) + header[1].data(String::UTF8))); + data.append(renderAtom("name", ByteVector::fromUInt(0) + header[2].data(String::UTF8))); + StringList value = item.toStringList(); + for(unsigned int i = 0; i < value.size(); i++) { + data.append(renderAtom("data", ByteVector::fromUInt(1) + ByteVector(4, '\0') + value[i].data(String::UTF8))); + } + return renderAtom("----", data); +} + +bool +MP4::Tag::save() +{ + ByteVector data; + for(MP4::ItemListMap::Iterator i = d->items.begin(); i != d->items.end(); i++) { + const String name = i->first; + if(name.startsWith("----")) { + data.append(renderFreeForm(name, i->second)); + } + else if(name == "trkn") { + data.append(renderIntPair(name.data(String::Latin1), i->second)); + } + else if(name == "disk") { + data.append(renderIntPairNoTrailing(name.data(String::Latin1), i->second)); + } + else if(name == "cpil" || name == "pgap" || name == "pcst") { + data.append(renderBool(name.data(String::Latin1), i->second)); + } + else if(name == "tmpo") { + data.append(renderInt(name.data(String::Latin1), i->second)); + } + else if(name.size() == 4){ + data.append(renderText(name.data(String::Latin1), i->second)); + } + else { + debug("MP4: Unknown item name \"" + name + "\""); + } + } + data = renderAtom("ilst", data); + + AtomList path = d->atoms->path("moov", "udta", "meta", "ilst"); + if(path.size() == 4) { + saveExisting(data, path); + } + else { + saveNew(data); + } + + return true; +} + +void +MP4::Tag::updateParents(AtomList &path, long delta, int ignore) +{ + for(unsigned int i = 0; i < path.size() - ignore; i++) { + d->file->seek(path[i]->offset); + long size = d->file->readBlock(4).toUInt() + delta; + d->file->seek(path[i]->offset); + d->file->writeBlock(ByteVector::fromUInt(size)); + } +} + +void +MP4::Tag::updateOffsets(long delta, long offset) +{ + MP4::Atom *moov = d->atoms->find("moov"); + if(moov) { + MP4::AtomList stco = moov->findall("stco", true); + for(unsigned int i = 0; i < stco.size(); i++) { + MP4::Atom *atom = stco[i]; + if(atom->offset > offset) { + atom->offset += delta; + } + d->file->seek(atom->offset + 12); + ByteVector data = d->file->readBlock(atom->length - 12); + unsigned int count = data.mid(0, 4).toUInt(); + d->file->seek(atom->offset + 16); + int pos = 4; + while(count--) { + long o = data.mid(pos, 4).toUInt(); + if(o > offset) { + o += delta; + } + d->file->writeBlock(ByteVector::fromUInt(o)); + pos += 4; + } + } + + MP4::AtomList co64 = moov->findall("co64", true); + for(unsigned int i = 0; i < co64.size(); i++) { + MP4::Atom *atom = co64[i]; + if(atom->offset > offset) { + atom->offset += delta; + } + d->file->seek(atom->offset + 12); + ByteVector data = d->file->readBlock(atom->length - 12); + unsigned int count = data.mid(0, 4).toUInt(); + d->file->seek(atom->offset + 16); + int pos = 4; + while(count--) { + long long o = data.mid(pos, 8).toLongLong(); + if(o > offset) { + o += delta; + } + d->file->writeBlock(ByteVector::fromLongLong(o)); + pos += 8; + } + } + } + + MP4::Atom *moof = d->atoms->find("moof"); + if(moof) { + MP4::AtomList tfhd = moof->findall("tfhd", true); + for(unsigned int i = 0; i < tfhd.size(); i++) { + MP4::Atom *atom = tfhd[i]; + if(atom->offset > offset) { + atom->offset += delta; + } + d->file->seek(atom->offset + 9); + ByteVector data = d->file->readBlock(atom->offset - 9); + unsigned int flags = (ByteVector(1, '\0') + data.mid(0, 3)).toUInt(); + if(flags & 1) { + long long o = data.mid(7, 8).toLongLong(); + if(o > offset) { + o += delta; + } + d->file->seek(atom->offset + 16); + d->file->writeBlock(ByteVector::fromLongLong(o)); + } + } + } +} + +void +MP4::Tag::saveNew(ByteVector &data) +{ + data = renderAtom("meta", TagLib::ByteVector(4, '\0') + + renderAtom("hdlr", TagLib::ByteVector(8, '\0') + TagLib::ByteVector("mdirappl") + TagLib::ByteVector(9, '\0')) + + data + padIlst(data)); + + AtomList path = d->atoms->path("moov", "udta"); + if(path.size() != 2) { + path = d->atoms->path("moov"); + data = renderAtom("udta", data); + } + + long offset = path[path.size() - 1]->offset + 8; + d->file->insert(data, offset, 0); + + updateParents(path, data.size()); + updateOffsets(data.size(), offset); +} + +void +MP4::Tag::saveExisting(ByteVector &data, AtomList &path) +{ + MP4::Atom *ilst = path[path.size() - 1]; + long offset = ilst->offset; + long length = ilst->length; + + MP4::Atom *meta = path[path.size() - 2]; + AtomList::Iterator index = meta->children.find(ilst); + if(index != meta->children.begin()) { + AtomList::Iterator prevIndex = index; + prevIndex--; + MP4::Atom *prev = *prevIndex; + if(prev->name == "free") { + offset = prev->offset; + length += prev->length; + } + } + if(index != meta->children.end()) { + AtomList::Iterator nextIndex = index; + nextIndex++; + MP4::Atom *next = *nextIndex; + if(next->name == "free") { + length += next->length; + } + } + + long delta = data.size() - length; + if(delta > 0 || (delta < 0 && delta > -8)) { + data.append(padIlst(data)); + delta = data.size() - length; + } + else if(delta < 0) { + data.append(padIlst(data, -delta - 8)); + delta = 0; + } + + d->file->insert(data, offset, length); + + if(delta) { + updateParents(path, delta, 1); + updateOffsets(delta, offset); + } +} + +String +MP4::Tag::title() const +{ + if(d->items.contains("\251nam")) + return d->items["\251nam"].toStringList().toString(", "); + return String::null; +} + +String +MP4::Tag::artist() const +{ + if(d->items.contains("\251ART")) + return d->items["\251ART"].toStringList().toString(", "); + return String::null; +} + +String +MP4::Tag::album() const +{ + if(d->items.contains("\251alb")) + return d->items["\251alb"].toStringList().toString(", "); + return String::null; +} + +String +MP4::Tag::comment() const +{ + if(d->items.contains("\251cmt")) + return d->items["\251cmt"].toStringList().toString(", "); + return String::null; +} + +String +MP4::Tag::genre() const +{ + if(d->items.contains("\251gen")) + return d->items["\251gen"].toStringList().toString(", "); + return String::null; +} + +unsigned int +MP4::Tag::year() const +{ + if(d->items.contains("\251day")) + return d->items["\251day"].toStringList().toString().toInt(); + return 0; +} + +unsigned int +MP4::Tag::track() const +{ + if(d->items.contains("trkn")) + return d->items["trkn"].toIntPair().first; + return 0; +} + +void +MP4::Tag::setTitle(const String &value) +{ + d->items["\251nam"] = StringList(value); +} + +void +MP4::Tag::setArtist(const String &value) +{ + d->items["\251ART"] = StringList(value); +} + +void +MP4::Tag::setAlbum(const String &value) +{ + d->items["\251alb"] = StringList(value); +} + +void +MP4::Tag::setComment(const String &value) +{ + d->items["\251cmt"] = StringList(value); +} + +void +MP4::Tag::setGenre(const String &value) +{ + d->items["\251gen"] = StringList(value); +} + +void +MP4::Tag::setYear(uint value) +{ + d->items["\251day"] = StringList(String::number(value)); +} + +void +MP4::Tag::setTrack(uint value) +{ + d->items["trkn"] = MP4::Item(value, 0); +} + +MP4::ItemListMap & +MP4::Tag::itemListMap() +{ + return d->items; +} + +#endif diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h new file mode 100644 index 00000000..4671d16c --- /dev/null +++ b/taglib/mp4/mp4tag.h @@ -0,0 +1,100 @@ +/************************************************************************** + copyright : (C) 2007 by Lukáš Lalinský + email : lalinsky@gmail.com + **************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * 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_MP4TAG_H +#define TAGLIB_MP4TAG_H + +#include <tag.h> +#include <tbytevectorlist.h> +#include <tfile.h> +#include <tmap.h> +#include <tstringlist.h> +#include "taglib_export.h" +#include "mp4atom.h" +#include "mp4item.h" + +namespace TagLib { + + namespace MP4 { + + typedef TagLib::Map<String, Item> ItemListMap; + + class TAGLIB_EXPORT Tag: public TagLib::Tag + { + public: + Tag(TagLib::File *file, Atoms *atoms); + bool save(); + + String title() const; + String artist() const; + String album() const; + String comment() const; + String genre() const; + uint year() const; + uint track() const; + + void setTitle(const String &value); + void setArtist(const String &value); + void setAlbum(const String &value); + void setComment(const String &value); + void setGenre(const String &value); + void setYear(uint value); + void setTrack(uint value); + + ItemListMap &itemListMap(); + + private: + TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); + void parseText(Atom *atom, TagLib::File *file, int expectedFlags = 1); + void parseFreeForm(Atom *atom, TagLib::File *file); + void parseInt(Atom *atom, TagLib::File *file); + void parseIntPair(Atom *atom, TagLib::File *file); + void parseBool(Atom *atom, TagLib::File *file); + + TagLib::ByteVector padIlst(const ByteVector &data, int length = -1); + TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data); + TagLib::ByteVector renderData(const ByteVector &name, int flags, const TagLib::ByteVectorList &data); + TagLib::ByteVector renderText(const ByteVector &name, Item &item, int flags = 1); + TagLib::ByteVector renderFreeForm(const String &name, Item &item); + TagLib::ByteVector renderBool(const ByteVector &name, Item &item); + TagLib::ByteVector renderInt(const ByteVector &name, Item &item); + TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item); + TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item); + + void updateParents(AtomList &path, long delta, int ignore = 0); + void updateOffsets(long delta, long offset); + + void saveNew(TagLib::ByteVector &data); + void saveExisting(TagLib::ByteVector &data, AtomList &path); + + class TagPrivate; + TagPrivate *d; + }; + + } + +} + +#endif diff --git a/taglib/taglib.pro b/taglib/taglib.pro index 76be4c45..188179a4 100644 --- a/taglib/taglib.pro +++ b/taglib/taglib.pro @@ -14,6 +14,7 @@ DEPENDPATH += . \ ape \ flac \ mpc \ + mp4 \ mpeg \ ogg \ ogg/speex \ @@ -33,6 +34,7 @@ INCLUDEPATH += . \ flac \ ogg/flac \ mpc \ + mp4 \ wavpack \ ogg/speex \ trueaudio \ @@ -54,6 +56,10 @@ HEADERS += audioproperties.h \ flac/flacproperties.h \ mpc/mpcfile.h \ mpc/mpcproperties.h \ + mp4/mp4atom.h \ + mp4/mp4item.h \ + mp4/mp4file.h \ + mp4/mp4properties.h \ mpeg/mpegfile.h \ mpeg/mpegheader.h \ mpeg/mpegproperties.h \ @@ -111,6 +117,10 @@ SOURCES += audioproperties.cpp \ ape/apetag.cpp \ flac/flacfile.cpp \ flac/flacproperties.cpp \ + mp4/mp4atom.cpp \ + mp4/mp4item.cpp \ + mp4/mp4file.cpp \ + mp4/mp4properties.cpp \ mpc/mpcfile.cpp \ mpc/mpcproperties.cpp \ mpeg/mpegfile.cpp \ @@ -168,6 +178,10 @@ SOURCES += audioproperties.cpp \ ape/apetag.h \ flac/flacfile.h \ flac/flacproperties.h \ + mp4/mp4atom.h \ + mp4/mp4item.h \ + mp4/mp4file.h \ + mp4/mp4properties.h \ mpc/mpcfile.h \ mpc/mpcproperties.h \ mpeg/mpegfile.h \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 88c08098..99d648d0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,10 +3,12 @@ if(BUILD_TESTS) INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/../taglib ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/toolkit + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/asf ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v1 ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2 ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2/frames ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mp4 ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/trueaudio ) @@ -23,6 +25,12 @@ SET(test_runner_SRCS test_id3v1.cpp test_id3v2.cpp ) +IF(WITH_MP4) +SET(test_runner_SRCS ${test_runner_SRCS} test_mp4.cpp) +ENDIF(WITH_MP4) +IF(WITH_ASF) +SET(test_runner_SRCS ${test_runner_SRCS} test_asf.cpp) +ENDIF(WITH_ASF) ADD_EXECUTABLE(test_runner ${test_runner_SRCS}) TARGET_LINK_LIBRARIES(test_runner tag ${CPPUNIT_LIBRARIES}) diff --git a/tests/data/click.wv b/tests/data/click.wv Binary files differnew file mode 100644 index 00000000..f8bd1a85 --- /dev/null +++ b/tests/data/click.wv diff --git a/tests/data/has-tags.m4a b/tests/data/has-tags.m4a Binary files differnew file mode 100644 index 00000000..f48a28b5 --- /dev/null +++ b/tests/data/has-tags.m4a diff --git a/tests/data/no-tags.3g2 b/tests/data/no-tags.3g2 Binary files differnew file mode 100644 index 00000000..d31a6ce9 --- /dev/null +++ b/tests/data/no-tags.3g2 diff --git a/tests/data/no-tags.m4a b/tests/data/no-tags.m4a Binary files differnew file mode 100644 index 00000000..ba4e92ba --- /dev/null +++ b/tests/data/no-tags.m4a diff --git a/tests/data/silence-1.wma b/tests/data/silence-1.wma Binary files differnew file mode 100644 index 00000000..e06f9176 --- /dev/null +++ b/tests/data/silence-1.wma diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp new file mode 100644 index 00000000..2b1e7aa4 --- /dev/null +++ b/tests/test_asf.cpp @@ -0,0 +1,103 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <asffile.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestASF : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestASF); + CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testRead); + CPPUNIT_TEST(testSaveMultipleValues); + CPPUNIT_TEST(testSaveStream); + CPPUNIT_TEST(testSaveLanguage); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testProperties() + { + ASF::File f("data/silence-1.wma"); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); + } + + void testRead() + { + ASF::File f("data/silence-1.wma"); + CPPUNIT_ASSERT_EQUAL(String("test"), f.tag()->title()); + } + + void testSaveMultipleValues() + { + string newname = copyFile("silence-1", ".wma"); + + ASF::File *f = new ASF::File(newname.c_str()); + ASF::AttributeList values; + values.append("Foo"); + values.append("Bar"); + f->tag()->attributeListMap()["WM/AlbumTitle"] = values; + f->save(); + delete f; + + f = new ASF::File(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(2, (int)f->tag()->attributeListMap()["WM/AlbumTitle"].size()); + delete f; + + deleteFile(newname); + } + + void testSaveStream() + { + string newname = copyFile("silence-1", ".wma"); + + ASF::File *f = new ASF::File(newname.c_str()); + ASF::AttributeList values; + ASF::Attribute attr("Foo"); + attr.setStream(43); + values.append(attr); + f->tag()->attributeListMap()["WM/AlbumTitle"] = values; + f->save(); + delete f; + + f = new ASF::File(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(43, f->tag()->attributeListMap()["WM/AlbumTitle"][0].stream()); + delete f; + + deleteFile(newname); + } + + void testSaveLanguage() + { + string newname = copyFile("silence-1", ".wma"); + + ASF::File *f = new ASF::File(newname.c_str()); + ASF::AttributeList values; + ASF::Attribute attr("Foo"); + attr.setStream(32); + attr.setLanguage(56); + values.append(attr); + f->tag()->attributeListMap()["WM/AlbumTitle"] = values; + f->save(); + delete f; + + f = new ASF::File(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(32, f->tag()->attributeListMap()["WM/AlbumTitle"][0].stream()); + CPPUNIT_ASSERT_EQUAL(56, f->tag()->attributeListMap()["WM/AlbumTitle"][0].language()); + delete f; + + deleteFile(newname); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestASF); diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index 0752c08a..c2cba179 100644 --- a/tests/test_fileref.cpp +++ b/tests/test_fileref.cpp @@ -11,11 +11,15 @@ using namespace TagLib; class TestFileRef : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestFileRef); + CPPUNIT_TEST(testASF); CPPUNIT_TEST(testMusepack); CPPUNIT_TEST(testVorbis); CPPUNIT_TEST(testSpeex); CPPUNIT_TEST(testFLAC); CPPUNIT_TEST(testMP3); + CPPUNIT_TEST(testMP4_1); + CPPUNIT_TEST(testMP4_2); + CPPUNIT_TEST(testMP4_3); CPPUNIT_TEST(testTrueAudio); CPPUNIT_TEST_SUITE_END(); @@ -71,6 +75,11 @@ public: fileRefSave("click", ".mpc"); } + void testASF() + { + fileRefSave("silence-1", ".wma"); + } + void testVorbis() { fileRefSave("empty", ".ogg"); @@ -96,6 +105,21 @@ public: fileRefSave("empty", ".tta"); } + void testMP4_1() + { + fileRefSave("has-tags", ".m4a"); + } + + void testMP4_2() + { + fileRefSave("no-tags", ".m4a"); + } + + void testMP4_3() + { + fileRefSave("no-tags", ".3g2"); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFileRef); diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp new file mode 100644 index 00000000..2bca203b --- /dev/null +++ b/tests/test_mp4.cpp @@ -0,0 +1,103 @@ +#include <cppunit/extensions/HelperMacros.h> +#include <string> +#include <stdio.h> +#include <tag.h> +#include <mp4tag.h> +#include <tbytevectorlist.h> +#include <mp4atom.h> +#include <mp4file.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestMP4 : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMP4); + CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testFreeForm); + CPPUNIT_TEST(testUpdateStco); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testProperties() + { + MP4::File f("data/has-tags.m4a"); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, ((MP4::Properties *)f.audioProperties())->bitsPerSample()); + } + + void testUpdateStco() + { + string filename = copyFile("no-tags", ".3g2"); + + MP4::File *f = new MP4::File(filename.c_str()); + f->tag()->setArtist(ByteVector(3000, 'x')); + + ByteVectorList data1; + { + MP4::Atoms a(f); + MP4::Atom *stco = a.find("moov")->findall("stco", true)[0]; + f->seek(stco->offset + 12); + ByteVector data = f->readBlock(stco->length - 12); + unsigned int count = data.mid(0, 4).toUInt(); + int pos = 4; + while (count--) { + unsigned int offset = data.mid(pos, 4).toUInt(); + f->seek(offset); + data1.append(f->readBlock(20)); + pos += 4; + } + } + + f->save(); + delete f; + f = new MP4::File(filename.c_str()); + + { + MP4::Atoms a(f); + MP4::Atom *stco = a.find("moov")->findall("stco", true)[0]; + f->seek(stco->offset + 12); + ByteVector data = f->readBlock(stco->length - 12); + unsigned int count = data.mid(0, 4).toUInt(); + int pos = 4, i = 0; + while (count--) { + unsigned int offset = data.mid(pos, 4).toUInt(); + f->seek(offset); + CPPUNIT_ASSERT_EQUAL(data1[i], f->readBlock(20)); + pos += 4; + i++; + } + } + + delete f; + + deleteFile(filename); + } + + void testFreeForm() + { + string filename = copyFile("has-tags", ".m4a"); + + MP4::File *f = new MP4::File(filename.c_str()); + CPPUNIT_ASSERT(f->tag()->itemListMap().contains("----:com.apple.iTunes:iTunNORM")); + f->tag()->itemListMap()["----:org.kde.TagLib:Foo"] = StringList("Bar"); + f->save(); + delete f; + + f = new MP4::File(filename.c_str()); + CPPUNIT_ASSERT(f->tag()->itemListMap().contains("----:org.kde.TagLib:Foo")); + CPPUNIT_ASSERT_EQUAL(String("Bar"), f->tag()->itemListMap()["----:org.kde.TagLib:Foo"].toStringList()[0]); + f->save(); + delete f; + + deleteFile(filename); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4); |