From a2c1750b950b563fbb834348cfa7792584670c6d Mon Sep 17 00:00:00 2001 From: Ivan Yu Date: Tue, 1 Apr 2014 17:32:49 -0400 Subject: [PATCH] NowPlaying: Add TagLib files for MP4 support --- Plugins/PluginNowPlaying/Cover.cpp | 25 + Plugins/PluginNowPlaying/Cover.h | 2 + .../PluginNowPlaying/PluginNowPlaying.vcxproj | 9 +- .../PluginNowPlaying.vcxproj.filters | 21 + Plugins/PluginNowPlaying/TagLibUnity_mp4.cpp | 32 + Plugins/PluginNowPlaying/taglib/fileref.cpp | 18 +- .../PluginNowPlaying/taglib/mp4/mp4atom.cpp | 194 ++++ Plugins/PluginNowPlaying/taglib/mp4/mp4atom.h | 111 +++ .../taglib/mp4/mp4coverart.cpp | 83 ++ .../PluginNowPlaying/taglib/mp4/mp4coverart.h | 75 ++ .../PluginNowPlaying/taglib/mp4/mp4file.cpp | 163 ++++ Plugins/PluginNowPlaying/taglib/mp4/mp4file.h | 130 +++ .../PluginNowPlaying/taglib/mp4/mp4item.cpp | 206 ++++ Plugins/PluginNowPlaying/taglib/mp4/mp4item.h | 83 ++ .../taglib/mp4/mp4properties.cpp | 199 ++++ .../taglib/mp4/mp4properties.h | 71 ++ .../PluginNowPlaying/taglib/mp4/mp4tag.cpp | 917 ++++++++++++++++++ Plugins/PluginNowPlaying/taglib/mp4/mp4tag.h | 118 +++ 18 files changed, 2447 insertions(+), 10 deletions(-) create mode 100644 Plugins/PluginNowPlaying/TagLibUnity_mp4.cpp create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4atom.cpp create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4atom.h create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4coverart.cpp create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4coverart.h create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4file.cpp create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4file.h create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4item.cpp create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4item.h create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4properties.cpp create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4properties.h create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4tag.cpp create mode 100644 Plugins/PluginNowPlaying/taglib/mp4/mp4tag.h diff --git a/Plugins/PluginNowPlaying/Cover.cpp b/Plugins/PluginNowPlaying/Cover.cpp index cc9c2b84..45cde3c4 100644 --- a/Plugins/PluginNowPlaying/Cover.cpp +++ b/Plugins/PluginNowPlaying/Cover.cpp @@ -88,6 +88,10 @@ bool CCover::GetEmbedded(const TagLib::FileRef& fr, const std::wstring& target) found = ExtractID3(file->ID3v2Tag(), target); } } + else if (TagLib::MP4::File* file = dynamic_cast(fr.file())) + { + found = ExtractMP4(file, target); + } else if (TagLib::ASF::File* file = dynamic_cast(fr.file())) { found = ExtractASF(file, target); @@ -208,6 +212,27 @@ bool CCover::ExtractFLAC(TagLib::FLAC::File* file, const std::wstring& target) return false; } +/* +** Extracts cover art embedded in MP4 files. +** +*/ +bool CCover::ExtractMP4(TagLib::MP4::File* file, const std::wstring& target) +{ + TagLib::MP4::Tag* tag = file->tag(); + const TagLib::MP4::ItemListMap& itemListMap = tag->itemListMap(); + if (itemListMap.contains("covr")) + { + const TagLib::MP4::CoverArtList& coverArtList = itemListMap["covr"].toCoverArtList(); + if (!coverArtList.isEmpty()) + { + const TagLib::MP4::CoverArt* pic = &(coverArtList.front()); + return WriteCover(pic->data(), target); + } + } + + return false; +} + /* ** Write cover data to file. ** diff --git a/Plugins/PluginNowPlaying/Cover.h b/Plugins/PluginNowPlaying/Cover.h index d7670de7..d08da415 100644 --- a/Plugins/PluginNowPlaying/Cover.h +++ b/Plugins/PluginNowPlaying/Cover.h @@ -31,6 +31,7 @@ #include "id3v2tag.h" #include "mpcfile.h" #include "mpegfile.h" +#include "mp4file.h" #include "tag.h" #include "taglib.h" #include "textidentificationframe.h" @@ -50,6 +51,7 @@ private: static bool ExtractID3(TagLib::ID3v2::Tag* tag, const std::wstring& target); static bool ExtractASF(TagLib::ASF::File* file, const std::wstring& target); static bool ExtractFLAC(TagLib::FLAC::File* file, const std::wstring& target); + static bool ExtractMP4(TagLib::MP4::File* file, const std::wstring& target); static bool WriteCover(const TagLib::ByteVector& data, const std::wstring& target); }; diff --git a/Plugins/PluginNowPlaying/PluginNowPlaying.vcxproj b/Plugins/PluginNowPlaying/PluginNowPlaying.vcxproj index 8d0da378..40ff376c 100644 --- a/Plugins/PluginNowPlaying/PluginNowPlaying.vcxproj +++ b/Plugins/PluginNowPlaying/PluginNowPlaying.vcxproj @@ -19,7 +19,7 @@ _USRDLL;%(PreprocessorDefinitions) - ./taglib;./taglib/toolkit;./taglib/asf;./taglib/mpeg;./taglib/ogg;./taglib/ogg/flac;./taglib/flac;./taglib/mpc;./taglib/ogg/vorbis;./taglib/mpeg/id3v2;./taglib/mpeg/id3v2/frames;./taglib/mpeg/id3v1;./taglib/ape;./taglib/wavpack;.\SDKs\;%(AdditionalIncludeDirectories) + ./taglib;./taglib/toolkit;./taglib/asf;./taglib/mpeg;./taglib/ogg;./taglib/ogg/flac;./taglib/flac;./taglib/mpc;./taglib/ogg/vorbis;./taglib/mpeg/id3v2;./taglib/mpeg/id3v2/frames;./taglib/mpeg/id3v1;./taglib/ape;./taglib/wavpack;./taglib/mp4;.\SDKs\;%(AdditionalIncludeDirectories) false false @@ -113,6 +113,12 @@ + + + + + + @@ -165,6 +171,7 @@ + diff --git a/Plugins/PluginNowPlaying/PluginNowPlaying.vcxproj.filters b/Plugins/PluginNowPlaying/PluginNowPlaying.vcxproj.filters index 05d1e41e..6a81dce4 100644 --- a/Plugins/PluginNowPlaying/PluginNowPlaying.vcxproj.filters +++ b/Plugins/PluginNowPlaying/PluginNowPlaying.vcxproj.filters @@ -84,6 +84,9 @@ taglib + + taglib + taglib @@ -329,6 +332,24 @@ taglib + + taglib + + + taglib + + + taglib + + + taglib + + + taglib + + + taglib + taglib diff --git a/Plugins/PluginNowPlaying/TagLibUnity_mp4.cpp b/Plugins/PluginNowPlaying/TagLibUnity_mp4.cpp new file mode 100644 index 00000000..aec62e7d --- /dev/null +++ b/Plugins/PluginNowPlaying/TagLibUnity_mp4.cpp @@ -0,0 +1,32 @@ +/* +Copyright (C) 2014 Birunthan Mohanathas + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +// TagLib unity build: This file includes several TagLib source files. By compiling all of them +// together, the build performance is greatly increased. + +#pragma warning(push) +#pragma warning(disable: 4244; disable: 4267) + +#include "taglib\mp4\mp4atom.cpp" +#include "taglib\mp4\mp4coverart.cpp" +#include "taglib\mp4\mp4file.cpp" +#include "taglib\mp4\mp4item.cpp" +#include "taglib\mp4\mp4properties.cpp" +#include "taglib\mp4\mp4tag.cpp" + +#pragma warning(pop) diff --git a/Plugins/PluginNowPlaying/taglib/fileref.cpp b/Plugins/PluginNowPlaying/taglib/fileref.cpp index 01d3bec0..d54bcebe 100644 --- a/Plugins/PluginNowPlaying/taglib/fileref.cpp +++ b/Plugins/PluginNowPlaying/taglib/fileref.cpp @@ -39,7 +39,7 @@ #include "flacfile.h" #include "oggflacfile.h" #include "mpcfile.h" -//#include "mp4file.h" +#include "mp4file.h" //#include "wavpackfile.h" //#include "speexfile.h" //#include "trueaudiofile.h" @@ -148,12 +148,12 @@ StringList FileRef::defaultFileExtensions() // l.append("wv"); // l.append("spx"); // l.append("tta"); -// l.append("m4a"); -// l.append("m4r"); -// l.append("m4b"); -// l.append("m4p"); -// l.append("3g2"); -// l.append("mp4"); + l.append("m4a"); + l.append("m4r"); + l.append("m4b"); + l.append("m4p"); + l.append("3g2"); + l.append("mp4"); l.append("wma"); l.append("asf"); // l.append("aif"); @@ -260,8 +260,8 @@ File *FileRef::create(FileName fileName, bool readAudioProperties, // return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle); // if(ext == "TTA") // return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle); -// if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2") -// return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2") + return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "WMA" || ext == "ASF") return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle); // if(ext == "AIF" || ext == "AIFF") diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4atom.cpp b/Plugins/PluginNowPlaying/taglib/mp4/mp4atom.cpp new file mode 100644 index 00000000..7b87a479 --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4atom.cpp @@ -0,0 +1,194 @@ +/************************************************************************** + 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include "mp4atom.h" + +using namespace TagLib; + +const char *MP4::Atom::containers[11] = { + "moov", "udta", "mdia", "meta", "ilst", + "stbl", "minf", "moof", "traf", "trak", + "stsd" +}; + +MP4::Atom::Atom(File *file) +{ + offset = file->tell(); + ByteVector header = file->readBlock(8); + if (header.size() != 8) { + // The atom header must be 8 bytes long, otherwise there is either + // trailing garbage or the file is truncated + debug("MP4: Couldn't read 8 bytes of data for atom header"); + length = 0; + file->seek(0, File::End); + return; + } + + length = header.toUInt(); + + if (length == 1) { + const long long longLength = file->readBlock(8).toLongLong(); + if (longLength >= 8 && longLength <= 0xFFFFFFFF) { + // The atom has a 64-bit length, but it's actually a 32-bit value + length = (long)longLength; + } + else { + 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); + } + else if(name == "stsd") { + file->seek(8, File::Current); + } + while(file->tell() < offset + length) { + MP4::Atom *child = new MP4::Atom(file); + children.append(child); + if (child->length == 0) + return; + } + 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) { + MP4::Atom *atom = new MP4::Atom(file); + atoms.append(atom); + if (atom->length == 0) + break; + } +} + +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; +} + diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4atom.h b/Plugins/PluginNowPlaying/taglib/mp4/mp4atom.h new file mode 100644 index 00000000..ea5091a8 --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4atom.h @@ -0,0 +1,111 @@ +/************************************************************************** + copyright : (C) 2007,2011 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +// 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 AtomList; + + enum AtomDataType + { + TypeImplicit = 0, // for use with tags for which no type needs to be indicated because only one type is allowed + TypeUTF8 = 1, // without any count or null terminator + TypeUTF16 = 2, // also known as UTF-16BE + TypeSJIS = 3, // deprecated unless it is needed for special Japanese characters + TypeHTML = 6, // the HTML file header specifies which HTML version + TypeXML = 7, // the XML header must identify the DTD or schemas + TypeUUID = 8, // also known as GUID; stored as 16 bytes in binary (valid as an ID) + TypeISRC = 9, // stored as UTF-8 text (valid as an ID) + TypeMI3P = 10, // stored as UTF-8 text (valid as an ID) + TypeGIF = 12, // (deprecated) a GIF image + TypeJPEG = 13, // a JPEG image + TypePNG = 14, // a PNG image + TypeURL = 15, // absolute, in UTF-8 characters + TypeDuration = 16, // in milliseconds, 32-bit integer + TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits + TypeGenred = 18, // a list of enumerated values + TypeInteger = 21, // a signed big-endian integer with length one of { 1,2,3,4,8 } bytes + TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit ingteger + TypeUPC = 25, // Universal Product Code, in text UTF-8 format (valid as an ID) + TypeBMP = 27, // Windows bitmap image + TypeUndefined = 255 // undefined + }; + + struct AtomData { + AtomData(AtomDataType type, ByteVector data) : type(type), locale(0), data(data) {} + AtomDataType type; + int locale; + ByteVector data; + }; + + typedef TagLib::List AtomDataList; + + 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 = 11; + static const char *containers[11]; + }; + + //! 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/Plugins/PluginNowPlaying/taglib/mp4/mp4coverart.cpp b/Plugins/PluginNowPlaying/taglib/mp4/mp4coverart.cpp new file mode 100644 index 00000000..2746469d --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4coverart.cpp @@ -0,0 +1,83 @@ +/************************************************************************** + copyright : (C) 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include "trefcounter.h" +#include "mp4coverart.h" + +using namespace TagLib; + +class MP4::CoverArt::CoverArtPrivate : public RefCounter +{ +public: + CoverArtPrivate() : RefCounter(), format(MP4::CoverArt::JPEG) {} + + Format format; + ByteVector data; +}; + +MP4::CoverArt::CoverArt(Format format, const ByteVector &data) +{ + d = new CoverArtPrivate; + d->format = format; + d->data = data; +} + +MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d) +{ + d->ref(); +} + +MP4::CoverArt & +MP4::CoverArt::operator=(const CoverArt &item) +{ + if(d->deref()) { + delete d; + } + d = item.d; + d->ref(); + return *this; +} + +MP4::CoverArt::~CoverArt() +{ + if(d->deref()) { + delete d; + } +} + +MP4::CoverArt::Format +MP4::CoverArt::format() const +{ + return d->format; +} + +ByteVector +MP4::CoverArt::data() const +{ + return d->data; +} + diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4coverart.h b/Plugins/PluginNowPlaying/taglib/mp4/mp4coverart.h new file mode 100644 index 00000000..64115b45 --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4coverart.h @@ -0,0 +1,75 @@ +/************************************************************************** + copyright : (C) 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MP4COVERART_H +#define TAGLIB_MP4COVERART_H + +#include "tlist.h" +#include "tbytevector.h" +#include "taglib_export.h" +#include "mp4atom.h" + +namespace TagLib { + + namespace MP4 { + + class TAGLIB_EXPORT CoverArt + { + public: + /*! + * This describes the image type. + */ + enum Format { + JPEG = TypeJPEG, + PNG = TypePNG, + BMP = TypeBMP, + GIF = TypeGIF, + Unknown = TypeImplicit, + }; + + CoverArt(Format format, const ByteVector &data); + ~CoverArt(); + + CoverArt(const CoverArt &item); + CoverArt &operator=(const CoverArt &item); + + //! Format of the image + Format format() const; + + //! The image data + ByteVector data() const; + + private: + class CoverArtPrivate; + CoverArtPrivate *d; + }; + + typedef List CoverArtList; + + } + +} + +#endif diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4file.cpp b/Plugins/PluginNowPlaying/taglib/mp4/mp4file.cpp new file mode 100644 index 00000000..aab1a2be --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4file.cpp @@ -0,0 +1,163 @@ +/************************************************************************** + 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include +#include "mp4atom.h" +#include "mp4tag.h" +#include "mp4file.h" + +using namespace TagLib; + +class MP4::File::FilePrivate +{ +public: + FilePrivate() : tag(0), atoms(0), properties(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; + if(isOpen()) + read(readProperties, audioPropertiesStyle); +} + +MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) + : TagLib::File(stream) +{ + d = new FilePrivate; + if(isOpen()) + read(readProperties, audioPropertiesStyle); +} + +MP4::File::~File() +{ + delete d; +} + +MP4::Tag * +MP4::File::tag() const +{ + return d->tag; +} + +PropertyMap MP4::File::properties() const +{ + return d->tag->properties(); +} + +void MP4::File::removeUnsupportedProperties(const StringList &properties) +{ + d->tag->removeUnsupportedProperties(properties); +} + +PropertyMap MP4::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + +MP4::Properties * +MP4::File::audioProperties() const +{ + return d->properties; +} + +bool +MP4::File::checkValid(const MP4::AtomList &list) +{ + for(uint i = 0; i < list.size(); i++) { + if(list[i]->length == 0) + return false; + if(!checkValid(list[i]->children)) + return false; + } + return true; +} + +void +MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle) +{ + if(!isValid()) + return; + + d->atoms = new Atoms(this); + if (!checkValid(d->atoms->atoms)) { + setValid(false); + return; + } + + // must have a moov atom, otherwise consider it invalid + MP4::Atom *moov = d->atoms->find("moov"); + if(!moov) { + setValid(false); + return; + } + + d->tag = new Tag(this, d->atoms); + if(readProperties) { + d->properties = new Properties(this, d->atoms, audioPropertiesStyle); + } +} + +bool +MP4::File::save() +{ + if(readOnly()) { + debug("MP4::File::save() -- File is read only."); + return false; + } + + if(!isValid()) { + debug("MP4::File::save() -- Trying to save invalid file."); + return false; + } + + return d->tag->save(); +} + diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4file.h b/Plugins/PluginNowPlaying/taglib/mp4/mp4file.h new file mode 100644 index 00000000..39693d36 --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4file.h @@ -0,0 +1,130 @@ +/************************************************************************** + 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_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: + /*! + * Constructs an MP4 file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(FileName file, bool readProperties = true, + Properties::ReadStyle audioPropertiesStyle = Properties::Average); + + /*! + * Constructs an MP4 file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle 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 is still owned by the MP4::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + */ + Tag *tag() const; + + /*! + * Implements the unified property interface -- export function. + */ + PropertyMap properties() const; + + /*! + * Removes unsupported properties. Forwards to the actual Tag's + * removeUnsupportedProperties() function. + */ + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + */ + PropertyMap setProperties(const PropertyMap &); + + /*! + * Returns the MP4 audio properties for this file. + */ + Properties *audioProperties() const; + + /*! + * Save the file. + * + * This returns true if the save was successful. + */ + bool save(); + + private: + + void read(bool readProperties, Properties::ReadStyle audioPropertiesStyle); + bool checkValid(const MP4::AtomList &list); + + class FilePrivate; + FilePrivate *d; + }; + + } + +} + +#endif diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4item.cpp b/Plugins/PluginNowPlaying/taglib/mp4/mp4item.cpp new file mode 100644 index 00000000..671f26b4 --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4item.cpp @@ -0,0 +1,206 @@ +/************************************************************************** + 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include "trefcounter.h" +#include "mp4item.h" + +using namespace TagLib; + +class MP4::Item::ItemPrivate : public RefCounter +{ +public: + ItemPrivate() : RefCounter(), valid(true), atomDataType(TypeUndefined) {} + + bool valid; + AtomDataType atomDataType; + union { + bool m_bool; + int m_int; + IntPair m_intPair; + uchar m_byte; + uint m_uint; + long long m_longlong; + }; + StringList m_stringList; + ByteVectorList m_byteVectorList; + MP4::CoverArtList m_coverArtList; +}; + +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(uchar value) +{ + d = new ItemPrivate; + d->m_byte = value; +} + +MP4::Item::Item(uint value) +{ + d = new ItemPrivate; + d->m_uint = value; +} + +MP4::Item::Item(long long value) +{ + d = new ItemPrivate; + d->m_longlong = 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 ByteVectorList &value) +{ + d = new ItemPrivate; + d->m_byteVectorList = value; +} + +MP4::Item::Item(const StringList &value) +{ + d = new ItemPrivate; + d->m_stringList = value; +} + +MP4::Item::Item(const MP4::CoverArtList &value) +{ + d = new ItemPrivate; + d->m_coverArtList = value; +} + +void MP4::Item::setAtomDataType(MP4::AtomDataType type) +{ + d->atomDataType = type; +} + +MP4::AtomDataType MP4::Item::atomDataType() const +{ + return d->atomDataType; +} + +bool +MP4::Item::toBool() const +{ + return d->m_bool; +} + +int +MP4::Item::toInt() const +{ + return d->m_int; +} + +uchar +MP4::Item::toByte() const +{ + return d->m_byte; +} + +TagLib::uint +MP4::Item::toUInt() const +{ + return d->m_uint; +} + +long long +MP4::Item::toLongLong() const +{ + return d->m_longlong; +} + +MP4::Item::IntPair +MP4::Item::toIntPair() const +{ + return d->m_intPair; +} + +StringList +MP4::Item::toStringList() const +{ + return d->m_stringList; +} + +ByteVectorList +MP4::Item::toByteVectorList() const +{ + return d->m_byteVectorList; +} + +MP4::CoverArtList +MP4::Item::toCoverArtList() const +{ + return d->m_coverArtList; +} + +bool +MP4::Item::isValid() const +{ + return d->valid; +} + diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4item.h b/Plugins/PluginNowPlaying/taglib/mp4/mp4item.h new file mode 100644 index 00000000..be7aa1a1 --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4item.h @@ -0,0 +1,83 @@ +/************************************************************************** + 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MP4ITEM_H +#define TAGLIB_MP4ITEM_H + +#include "tstringlist.h" +#include "mp4coverart.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(uchar value); + Item(uint value); + Item(long long value); + Item(bool value); + Item(int first, int second); + Item(const StringList &value); + Item(const ByteVectorList &value); + Item(const CoverArtList &value); + + void setAtomDataType(AtomDataType type); + AtomDataType atomDataType() const; + + int toInt() const; + uchar toByte() const; + uint toUInt() const; + long long toLongLong() const; + bool toBool() const; + IntPair toIntPair() const; + StringList toStringList() const; + ByteVectorList toByteVectorList() const; + CoverArtList toCoverArtList() const; + + bool isValid() const; + + private: + class ItemPrivate; + ItemPrivate *d; + }; + + } + +} + +#endif diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4properties.cpp b/Plugins/PluginNowPlaying/taglib/mp4/mp4properties.cpp new file mode 100644 index 00000000..5a41c081 --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4properties.cpp @@ -0,0 +1,199 @@ +/************************************************************************** + 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#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), encrypted(false), codec(MP4::Properties::Unknown) {} + + int length; + int bitrate; + int sampleRate; + int channels; + int bitsPerSample; + bool encrypted; + Codec codec; +}; + +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); + uint version = data[8]; + if(version == 1) { + if (data.size() < 36 + 8) { + debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); + return; + } + const long long unit = data.toLongLong(28U); + const long long length = data.toLongLong(36U); + d->length = unit ? int(length / unit) : 0; + } + else { + if (data.size() < 24 + 4) { + debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); + return; + } + const unsigned int unit = data.toUInt(20U); + const unsigned int length = data.toUInt(24U); + d->length = unit ? length / unit : 0; + } + + 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->codec = AAC; + d->channels = data.toShort(40U); + d->bitsPerSample = data.toShort(42U); + d->sampleRate = data.toUInt(46U); + if(data.mid(56, 4) == "esds" && data[64] == 0x03) { + uint 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.toUInt(pos) + 500) / 1000; + } + } + } + else if (data.mid(20, 4) == "alac") { + if (atom->length == 88 && data.mid(56, 4) == "alac") { + d->codec = ALAC; + d->bitsPerSample = data.at(69); + d->channels = data.at(73); + d->bitrate = data.toUInt(80U) / 1000; + d->sampleRate = data.toUInt(84U); + } + } + + MP4::Atom *drms = atom->find("drms"); + if(drms) { + d->encrypted = true; + } +} + +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; +} + +bool +MP4::Properties::isEncrypted() const +{ + return d->encrypted; +} + +MP4::Properties::Codec MP4::Properties::codec() const +{ + return d->codec; +} + diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4properties.h b/Plugins/PluginNowPlaying/taglib/mp4/mp4properties.h new file mode 100644 index 00000000..2607c366 --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4properties.h @@ -0,0 +1,71 @@ +/************************************************************************** + 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_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: + enum Codec { + Unknown = 0, + AAC, + ALAC + }; + + 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; + bool isEncrypted() const; + + //! Audio codec used in the MP4 file + Codec codec() const; + + private: + class PropertiesPrivate; + PropertiesPrivate *d; + }; + + } + +} + +#endif diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4tag.cpp b/Plugins/PluginNowPlaying/taglib/mp4/mp4tag.cpp new file mode 100644 index 00000000..bbe3301d --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4tag.cpp @@ -0,0 +1,917 @@ +/************************************************************************** + copyright : (C) 2007,2011 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include +#include +#include "mp4atom.h" +#include "mp4tag.h" +#include "id3v1genres.h" + +using namespace TagLib; + +class MP4::Tag::TagPrivate +{ +public: + TagPrivate() : file(0), atoms(0) {} + ~TagPrivate() {} + TagLib::File *file; + Atoms *atoms; + ItemListMap items; +}; + +MP4::Tag::Tag() +{ + d = new TagPrivate; +} + +MP4::Tag::Tag(TagLib::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" || + atom->name == "hdvd") { + parseBool(atom, file); + } + else if(atom->name == "tmpo") { + parseInt(atom, file); + } + else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" || + atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") { + parseUInt(atom, file); + } + else if(atom->name == "plID") { + parseLongLong(atom, file); + } + else if(atom->name == "stik" || atom->name == "rtng" || atom->name == "akID") { + parseByte(atom, file); + } + else if(atom->name == "gnre") { + parseGnre(atom, file); + } + else if(atom->name == "covr") { + parseCovr(atom, file); + } + else { + parseText(atom, file); + } + } +} + +MP4::Tag::~Tag() +{ + delete d; +} + +MP4::AtomDataList +MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +{ + AtomDataList result; + ByteVector data = file->readBlock(atom->length - 8); + int i = 0; + unsigned int pos = 0; + while(pos < data.size()) { + const int length = static_cast(data.toUInt(pos)); + ByteVector name = data.mid(pos + 4, 4); + const int flags = static_cast(data.toUInt(pos + 8)); + 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(AtomData(AtomDataType(flags), 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(AtomData(AtomDataType(flags), data.mid(pos + 16, length - 16))); + } + } + pos += length; + i++; + } + return result; +} + +ByteVectorList +MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +{ + AtomDataList data = parseData2(atom, file, expectedFlags, freeForm); + ByteVectorList result; + for(uint i = 0; i < data.size(); i++) { + result.append(data[i].data); + } + return result; +} + +void +MP4::Tag::parseInt(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + addItem(atom->name, (int)data[0].toShort()); + } +} + +void +MP4::Tag::parseUInt(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + addItem(atom->name, data[0].toUInt()); + } +} + +void +MP4::Tag::parseLongLong(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + addItem(atom->name, data[0].toLongLong()); + } +} + +void +MP4::Tag::parseByte(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + addItem(atom->name, (uchar)data[0].at(0)); + } +} + +void +MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + int idx = (int)data[0].toShort(); + if(idx > 0) { + addItem("\251gen", StringList(ID3v1::genre(idx - 1))); + } + } +} + +void +MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + const int a = data[0].toShort(2U); + const int b = data[0].toShort(4U); + addItem(atom->name, MP4::Item(a, b)); + } +} + +void +MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file) +{ + ByteVectorList data = parseData(atom, file); + if(data.size()) { + bool value = data[0].size() ? data[0][0] != '\0' : false; + addItem(atom->name, value); + } +} + +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)); + } + addItem(atom->name, value); + } +} + +void +MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file) +{ + AtomDataList data = parseData2(atom, file, -1, true); + if(data.size() > 2) { + String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8); + AtomDataType type = data[2].type; + for(uint i = 2; i < data.size(); i++) { + if(data[i].type != type) { + debug("MP4: We currently don't support values with multiple types"); + break; + } + } + if(type == TypeUTF8) { + StringList value; + for(uint i = 2; i < data.size(); i++) { + value.append(String(data[i].data, String::UTF8)); + } + Item item(value); + item.setAtomDataType(type); + addItem(name, item); + } + else { + ByteVectorList value; + for(uint i = 2; i < data.size(); i++) { + value.append(data[i].data); + } + Item item(value); + item.setAtomDataType(type); + addItem(name, item); + } + } +} + +void +MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) +{ + MP4::CoverArtList value; + ByteVector data = file->readBlock(atom->length - 8); + unsigned int pos = 0; + while(pos < data.size()) { + const int length = static_cast(data.toUInt(pos)); + ByteVector name = data.mid(pos + 4, 4); + const int flags = static_cast(data.toUInt(pos + 8)); + if(name != "data") { + debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); + break; + } + if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF || flags == TypeImplicit) { + value.append(MP4::CoverArt(MP4::CoverArt::Format(flags), + data.mid(pos + 16, length - 16))); + } + else { + debug("MP4: Unknown covr format " + String::number(flags)); + } + pos += length; + } + if(value.size() > 0) + addItem(atom->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, TypeInteger, data); +} + +ByteVector +MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item) +{ + ByteVectorList data; + data.append(ByteVector::fromShort(item.toInt())); + return renderData(name, TypeInteger, data); +} + +ByteVector +MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item) +{ + ByteVectorList data; + data.append(ByteVector::fromUInt(item.toUInt())); + return renderData(name, TypeInteger, data); +} + +ByteVector +MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item) +{ + ByteVectorList data; + data.append(ByteVector::fromLongLong(item.toLongLong())); + return renderData(name, TypeInteger, data); +} + +ByteVector +MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item) +{ + ByteVectorList data; + data.append(ByteVector(1, item.toByte())); + return renderData(name, TypeInteger, 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, TypeImplicit, 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, TypeImplicit, 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::renderCovr(const ByteVector &name, MP4::Item &item) +{ + ByteVector data; + MP4::CoverArtList value = item.toCoverArtList(); + for(unsigned int i = 0; i < value.size(); i++) { + data.append(renderAtom("data", ByteVector::fromUInt(value[i].format()) + + ByteVector(4, '\0') + value[i].data())); + } + return renderAtom(name, 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))); + AtomDataType type = item.atomDataType(); + if(type == TypeUndefined) { + if(!item.toStringList().isEmpty()) { + type = TypeUTF8; + } + else { + type = TypeImplicit; + } + } + if(type == TypeUTF8) { + StringList value = item.toStringList(); + for(unsigned int i = 0; i < value.size(); i++) { + data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + value[i].data(String::UTF8))); + } + } + else { + ByteVectorList value = item.toByteVectorList(); + for(unsigned int i = 0; i < value.size(); i++) { + data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + value[i])); + } + } + 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" || name == "hdvd") { + 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 == "tvsn" || name == "tves" || name == "cnID" || + name == "sfID" || name == "atID" || name == "geID") { + data.append(renderUInt(name.data(String::Latin1), i->second)); + } + else if(name == "plID") { + data.append(renderLongLong(name.data(String::Latin1), i->second)); + } + else if(name == "stik" || name == "rtng" || name == "akID") { + data.append(renderByte(name.data(String::Latin1), i->second)); + } + else if(name == "covr") { + data.append(renderCovr(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(); + // 64-bit + if (size == 1) { + d->file->seek(4, File::Current); // Skip name + long long longSize = d->file->readBlock(8).toLongLong(); + // Seek the offset of the 64-bit size + d->file->seek(path[i]->offset + 8); + d->file->writeBlock(ByteVector::fromLongLong(longSize + delta)); + } + // 32-bit + else { + d->file->seek(path[i]->offset); + d->file->writeBlock(ByteVector::fromUInt(size + delta)); + } + } +} + +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.toUInt(); + d->file->seek(atom->offset + 16); + uint pos = 4; + while(count--) { + long o = static_cast(data.toUInt(pos)); + 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.toUInt(); + d->file->seek(atom->offset + 16); + uint pos = 4; + while(count--) { + long long o = data.toLongLong(pos); + 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->length - 9); + const unsigned int flags = data.toUInt(0, 3, true); + if(flags & 1) { + long long o = data.toLongLong(7U); + 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); + + // check if there is an atom before 'ilst', and possibly use it as padding + if(index != meta->children.begin()) { + AtomList::Iterator prevIndex = index; + prevIndex--; + MP4::Atom *prev = *prevIndex; + if(prev->name == "free") { + offset = prev->offset; + length += prev->length; + } + } + // check if there is an atom after 'ilst', and possibly use it as padding + AtomList::Iterator nextIndex = index; + nextIndex++; + if(nextIndex != meta->children.end()) { + 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; +} + +static const char *keyTranslation[][2] = { + { "\251nam", "TITLE" }, + { "\251ART", "ARTIST" }, + { "\251alb", "ALBUM" }, + { "\251cmt", "COMMENT" }, + { "\251gen", "GENRE" }, + { "\251day", "DATE" }, + { "\251wrt", "COMPOSER" }, + { "\251grp", "GROUPING" }, + { "trkn", "TRACKNUMBER" }, + { "disk", "DISCNUMBER" }, + { "cpil", "COMPILATION" }, + { "tmpo", "BPM" }, + { "cprt", "COPYRIGHT" }, + { "\251lyr", "LYRICS" }, + { "\251too", "ENCODEDBY" }, + { "soal", "ALBUMSORT" }, + { "soaa", "ALBUMARTISTSORT" }, + { "soar", "ARTISTSORT" }, + { "sonm", "TITLESORT" }, + { "soco", "COMPOSERSORT" }, + { "sosn", "SHOWSORT" }, + { "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" }, + { "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, + { "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, + { "----:com.apple.iTunes:ASIN", "ASIN" }, + { "----:com.apple.iTunes:LABEL", "LABEL" }, + { "----:com.apple.iTunes:LYRICIST", "LYRICIST" }, + { "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" }, + { "----:com.apple.iTunes:REMIXER", "REMIXER" }, + { "----:com.apple.iTunes:ENGINEER", "ENGINEER" }, + { "----:com.apple.iTunes:PRODUCER", "PRODUCER" }, + { "----:com.apple.iTunes:DJMIXER", "DJMIXER" }, + { "----:com.apple.iTunes:MIXER", "MIXER" }, + { "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" }, + { "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" }, + { "----:com.apple.iTunes:MOOD", "MOOD" }, + { "----:com.apple.iTunes:ISRC", "ISRC" }, + { "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" }, + { "----:com.apple.iTunes:BARCODE", "BARCODE" }, + { "----:com.apple.iTunes:SCRIPT", "SCRIPT" }, + { "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" }, + { "----:com.apple.iTunes:LICENSE", "LICENSE" }, + { "----:com.apple.iTunes:MEDIA", "MEDIA" }, +}; + +PropertyMap MP4::Tag::properties() const +{ + static Map keyMap; + if(keyMap.isEmpty()) { + int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + for(int i = 0; i < numKeys; i++) { + keyMap[keyTranslation[i][0]] = keyTranslation[i][1]; + } + } + + PropertyMap props; + MP4::ItemListMap::ConstIterator it = d->items.begin(); + for(; it != d->items.end(); ++it) { + if(keyMap.contains(it->first)) { + String key = keyMap[it->first]; + if(key == "TRACKNUMBER" || key == "DISCNUMBER") { + MP4::Item::IntPair ip = it->second.toIntPair(); + String value = String::number(ip.first); + if(ip.second) { + value += "/" + String::number(ip.second); + } + props[key] = value; + } + else if(key == "BPM") { + props[key] = String::number(it->second.toInt()); + } + else if(key == "COMPILATION") { + props[key] = String::number(it->second.toBool()); + } + else { + props[key] = it->second.toStringList(); + } + } + else { + props.unsupportedData().append(it->first); + } + } + return props; +} + +void MP4::Tag::removeUnsupportedProperties(const StringList &props) +{ + StringList::ConstIterator it = props.begin(); + for(; it != props.end(); ++it) + d->items.erase(*it); +} + +PropertyMap MP4::Tag::setProperties(const PropertyMap &props) +{ + static Map reverseKeyMap; + if(reverseKeyMap.isEmpty()) { + int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + for(int i = 0; i < numKeys; i++) { + reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0]; + } + } + + PropertyMap origProps = properties(); + PropertyMap::ConstIterator it = origProps.begin(); + for(; it != origProps.end(); ++it) { + if(!props.contains(it->first) || props[it->first].isEmpty()) { + d->items.erase(reverseKeyMap[it->first]); + } + } + + PropertyMap ignoredProps; + it = props.begin(); + for(; it != props.end(); ++it) { + if(reverseKeyMap.contains(it->first)) { + String name = reverseKeyMap[it->first]; + if(it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") { + int first = 0, second = 0; + StringList parts = StringList::split(it->second.front(), "/"); + if(parts.size() > 0) { + first = parts[0].toInt(); + if(parts.size() > 1) { + second = parts[1].toInt(); + } + d->items[name] = MP4::Item(first, second); + } + } + else if(it->first == "BPM") { + int value = it->second.front().toInt(); + d->items[name] = MP4::Item(value); + } + else if(it->first == "COMPILATION") { + bool value = (it->second.front().toInt() != 0); + d->items[name] = MP4::Item(value); + } + else { + d->items[name] = it->second; + } + } + else { + ignoredProps.insert(it->first, it->second); + } + } + + return ignoredProps; +} + +void MP4::Tag::addItem(const String &name, const Item &value) +{ + if(!d->items.contains(name)) { + d->items.insert(name, value); + } + else { + debug("MP4: Ignoring duplicate atom \"" + name + "\""); + } +} diff --git a/Plugins/PluginNowPlaying/taglib/mp4/mp4tag.h b/Plugins/PluginNowPlaying/taglib/mp4/mp4tag.h new file mode 100644 index 00000000..48d71fcb --- /dev/null +++ b/Plugins/PluginNowPlaying/taglib/mp4/mp4tag.h @@ -0,0 +1,118 @@ +/************************************************************************** + copyright : (C) 2007,2011 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_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 ItemListMap; + + class TAGLIB_EXPORT Tag: public TagLib::Tag + { + public: + Tag(); + Tag(TagLib::File *file, Atoms *atoms); + ~Tag(); + 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(); + + PropertyMap properties() const; + void removeUnsupportedProperties(const StringList& properties); + PropertyMap setProperties(const PropertyMap &properties); + + private: + AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); + TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); + 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 parseByte(Atom *atom, TagLib::File *file); + void parseUInt(Atom *atom, TagLib::File *file); + void parseLongLong(Atom *atom, TagLib::File *file); + void parseGnre(Atom *atom, TagLib::File *file); + void parseIntPair(Atom *atom, TagLib::File *file); + void parseBool(Atom *atom, TagLib::File *file); + void parseCovr(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 = TypeUTF8); + 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 renderByte(const ByteVector &name, Item &item); + TagLib::ByteVector renderUInt(const ByteVector &name, Item &item); + TagLib::ByteVector renderLongLong(const ByteVector &name, Item &item); + TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item); + TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item); + TagLib::ByteVector renderCovr(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); + + void addItem(const String &name, const Item &value); + + class TagPrivate; + TagPrivate *d; + }; + + } + +} + +#endif