NowPlaying: Add TagLib files for MP4 support

This commit is contained in:
Ivan Yu 2014-04-01 17:32:49 -04:00 committed by Birunthan Mohanathas
parent 4236bea52b
commit a2c1750b95
18 changed files with 2447 additions and 10 deletions

View File

@ -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<TagLib::MP4::File*>(fr.file()))
{
found = ExtractMP4(file, target);
}
else if (TagLib::ASF::File* file = dynamic_cast<TagLib::ASF::File*>(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.
**

View File

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

View File

@ -19,7 +19,7 @@
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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;.\SDKs\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<ExceptionHandling Condition="'$(Configuration)'=='Release'">false</ExceptionHandling>
<WholeProgramOptimization Condition="'$(Configuration)'=='Release'">false</WholeProgramOptimization>
</ClCompile>
@ -113,6 +113,12 @@
<ClInclude Include="taglib\toolkit\tmap.h" />
<ClInclude Include="taglib\toolkit\trefcounter.h" />
<ClInclude Include="taglib\toolkit\tdebuglistener.h" />
<ClInclude Include="taglib\mp4\mp4atom.h" />
<ClInclude Include="taglib\mp4\mp4coverart.h" />
<ClInclude Include="taglib\mp4\mp4file.h" />
<ClInclude Include="taglib\mp4\mp4item.h" />
<ClInclude Include="taglib\mp4\mp4properties.h" />
<ClInclude Include="taglib\mp4\mp4tag.h" />
<ClInclude Include="taglib\mpeg\mpegfile.h" />
<ClInclude Include="taglib\mpeg\mpegproperties.h" />
<ClInclude Include="taglib\mpeg\mpegheader.h" />
@ -165,6 +171,7 @@
<ClCompile Include="TagLibUnity_ape.cpp" />
<ClCompile Include="TagLibUnity_asf.cpp" />
<ClCompile Include="TagLibUnity_flac.cpp" />
<ClCompile Include="TagLibUnity_mp4.cpp" />
<ClCompile Include="TagLibUnity_mpeg.cpp" />
<ClCompile Include="TagLibUnity_mpeg_id3v2.cpp" />
<ClCompile Include="TagLibUnity_ogg.cpp" />

View File

@ -84,6 +84,9 @@
<ClCompile Include="TagLibUnity_flac.cpp">
<Filter>taglib</Filter>
</ClCompile>
<ClCompile Include="TagLibUnity_mp4.cpp">
<Filter>taglib</Filter>
</ClCompile>
<ClCompile Include="TagLibUnity_mpeg.cpp">
<Filter>taglib</Filter>
</ClCompile>
@ -329,6 +332,24 @@
<ClInclude Include="taglib\toolkit\tstringlist.h">
<Filter>taglib</Filter>
</ClInclude>
<ClInclude Include="taglib\mp4\mp4atom.h">
<Filter>taglib</Filter>
</ClInclude>
<ClInclude Include="taglib\mp4\mp4coverart.h">
<Filter>taglib</Filter>
</ClInclude>
<ClInclude Include="taglib\mp4\mp4file.h">
<Filter>taglib</Filter>
</ClInclude>
<ClInclude Include="taglib\mp4\mp4item.h">
<Filter>taglib</Filter>
</ClInclude>
<ClInclude Include="taglib\mp4\mp4properties.h">
<Filter>taglib</Filter>
</ClInclude>
<ClInclude Include="taglib\mp4\mp4tag.h">
<Filter>taglib</Filter>
</ClInclude>
<ClInclude Include="taglib\mpeg\id3v2\frames\uniquefileidentifierframe.h">
<Filter>taglib</Filter>
</ClInclude>

View File

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

View File

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

View File

@ -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 <tdebug.h>
#include <tstring.h>
#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;
}

View File

@ -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<Atom *> 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<AtomData> 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

View File

@ -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 <taglib.h>
#include <tdebug.h>
#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;
}

View File

@ -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<CoverArt> CoverArtList;
}
}
#endif

View File

@ -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 <tdebug.h>
#include <tstring.h>
#include <tpropertymap.h>
#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();
}

View File

@ -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 <b>is still</b> 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

View File

@ -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 <taglib.h>
#include <tdebug.h>
#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;
}

View File

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

View File

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

View File

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

View File

@ -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 <tdebug.h>
#include <tstring.h>
#include <tpropertymap.h>
#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<int>(data.toUInt(pos));
ByteVector name = data.mid(pos + 4, 4);
const int flags = static_cast<int>(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<int>(data.toUInt(pos));
ByteVector name = data.mid(pos + 4, 4);
const int flags = static_cast<int>(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<long>(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<String, String> keyMap;
if(keyMap.isEmpty()) {
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
for(int i = 0; i < numKeys; i++) {
keyMap[keyTranslation[i][0]] = keyTranslation[i][1];
}
}
PropertyMap props;
MP4::ItemListMap::ConstIterator it = d->items.begin();
for(; it != d->items.end(); ++it) {
if(keyMap.contains(it->first)) {
String key = keyMap[it->first];
if(key == "TRACKNUMBER" || key == "DISCNUMBER") {
MP4::Item::IntPair ip = it->second.toIntPair();
String value = String::number(ip.first);
if(ip.second) {
value += "/" + String::number(ip.second);
}
props[key] = value;
}
else if(key == "BPM") {
props[key] = String::number(it->second.toInt());
}
else if(key == "COMPILATION") {
props[key] = String::number(it->second.toBool());
}
else {
props[key] = it->second.toStringList();
}
}
else {
props.unsupportedData().append(it->first);
}
}
return props;
}
void MP4::Tag::removeUnsupportedProperties(const StringList &props)
{
StringList::ConstIterator it = props.begin();
for(; it != props.end(); ++it)
d->items.erase(*it);
}
PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
{
static Map<String, String> reverseKeyMap;
if(reverseKeyMap.isEmpty()) {
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
for(int i = 0; i < numKeys; i++) {
reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0];
}
}
PropertyMap origProps = properties();
PropertyMap::ConstIterator it = origProps.begin();
for(; it != origProps.end(); ++it) {
if(!props.contains(it->first) || props[it->first].isEmpty()) {
d->items.erase(reverseKeyMap[it->first]);
}
}
PropertyMap ignoredProps;
it = props.begin();
for(; it != props.end(); ++it) {
if(reverseKeyMap.contains(it->first)) {
String name = reverseKeyMap[it->first];
if(it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") {
int first = 0, second = 0;
StringList parts = StringList::split(it->second.front(), "/");
if(parts.size() > 0) {
first = parts[0].toInt();
if(parts.size() > 1) {
second = parts[1].toInt();
}
d->items[name] = MP4::Item(first, second);
}
}
else if(it->first == "BPM") {
int value = it->second.front().toInt();
d->items[name] = MP4::Item(value);
}
else if(it->first == "COMPILATION") {
bool value = (it->second.front().toInt() != 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 + "\"");
}
}

View File

@ -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<String, Item> 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