Merge remote-tracking branch 'upstream/master' into qt5

This commit is contained in:
Chocobozzz 2015-12-13 20:05:12 +01:00
commit ff7026c9fe
654 changed files with 33248 additions and 32716 deletions

View File

@ -0,0 +1,128 @@
/* Copyright 2014, Uwe L. Korn <uwelk@xhochy.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "Json.h"
// Qt version specific includes
#if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
#include <QJsonDocument>
#include <QMetaProperty>
#else
#include <qjson/parser.h>
#include <qjson/qobjecthelper.h>
#include <qjson/serializer.h>
#endif
namespace QJsonWrapper
{
QVariantMap
qobject2qvariant( const QObject* object )
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
QVariantMap map;
if ( object == NULL )
{
return map;
}
const QMetaObject* metaObject = object->metaObject();
for ( int i = 0; i < metaObject->propertyCount(); ++i )
{
QMetaProperty metaproperty = metaObject->property( i );
if ( metaproperty.isReadable() )
{
map[ QLatin1String( metaproperty.name() ) ] = object->property( metaproperty.name() );
}
}
return map;
#else
return QJson::QObjectHelper::qobject2qvariant( object );
#endif
}
void
qvariant2qobject( const QVariantMap& variant, QObject* object )
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
for ( QVariantMap::const_iterator iter = variant.begin(); iter != variant.end(); ++iter )
{
QVariant property = object->property( iter.key().toLatin1() );
Q_ASSERT( property.isValid() );
if ( property.isValid() )
{
QVariant value = iter.value();
if ( value.canConvert( property.type() ) )
{
value.convert( property.type() );
object->setProperty( iter.key().toLatin1(), value );
} else if ( QString( QLatin1String("QVariant") ).compare( QLatin1String( property.typeName() ) ) == 0 ) {
object->setProperty( iter.key().toLatin1(), value );
}
}
}
#else
QJson::QObjectHelper::qvariant2qobject( variant, object );
#endif
}
QVariant
parseJson( const QByteArray& jsonData, bool* ok )
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson( jsonData, &error );
if ( ok != NULL )
{
*ok = ( error.error == QJsonParseError::NoError );
}
return doc.toVariant();
#else
QJson::Parser p;
return p.parse( jsonData, ok );
#endif
}
QByteArray
toJson( const QVariant &variant, bool* ok )
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
QJsonDocument doc = QJsonDocument::fromVariant( variant );
if ( ok != NULL )
{
*ok = !doc.isNull();
}
return doc.toJson( QJsonDocument::Compact );
#else
QJson::Serializer serializer;
QByteArray ret = serializer.serialize(variant);
if ( ok != NULL )
{
*ok = !ret.isNull();
}
return ret;
#endif
}
}

View File

@ -0,0 +1,36 @@
/* Copyright 2014, Uwe L. Korn <uwelk@xhochy.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#ifndef QJSONWRAPPER_JSON_H
#define QJSONWRAPPER_JSON_H
#include <QVariant>
namespace QJsonWrapper
{
QVariantMap qobject2qvariant( const QObject* object );
void qvariant2qobject( const QVariantMap& variant, QObject* object );
QVariant parseJson( const QByteArray& jsonData, bool* ok = 0 );
QByteArray toJson( const QVariant &variant, bool* ok = 0 );
}
#endif // QJSONWRAPPER_JSON_H

View File

@ -1,3 +1,9 @@
# Extra bits for Clementine.
set( MYGPO_QT_VERSION_MAJOR "1" )
set( MYGPO_QT_VERSION_MINOR "0" )
set( MYGPO_QT_VERSION_PATCH "9" )
configure_file( Version.h.in ${CMAKE_CURRENT_BINARY_DIR}/Version.h )
include_directories("${QJSON_INCLUDEDIR}/qjson")
include_directories( ${QJSON_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${QJSON_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} )

View File

@ -29,7 +29,7 @@ using namespace mygpo;
Config* Config::s_instance = 0; Config* Config::s_instance = 0;
ConfigPrivate::ConfigPrivate( Config* qq ) : q( qq ), m_mygpoBaseUrl( QUrl( QLatin1String( "http://gpodder.net" ) ) ), m_userAgentPrefix( QString() ) ConfigPrivate::ConfigPrivate( Config* qq ) : q( qq ), m_mygpoBaseUrl( QUrl( QLatin1String( "https://gpodder.net" ) ) ), m_userAgentPrefix( QString() )
{ {
} }

View File

@ -59,10 +59,18 @@ public:
} }
} }
void escapePressed() void keyDownPressed()
{ {
if (qSearchField) { if (qSearchField) {
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier);
QApplication::postEvent(qSearchField, event);
}
}
void keyUpPressed()
{
if (qSearchField) {
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
QApplication::postEvent(qSearchField, event); QApplication::postEvent(qSearchField, event);
} }
} }
@ -87,6 +95,22 @@ public:
pimpl->textDidChange(toQString([[notification object] stringValue])); pimpl->textDidChange(toQString([[notification object] stringValue]));
} }
-(BOOL)control: (NSControl *)control textView:
(NSTextView *)textView doCommandBySelector:
(SEL)commandSelector {
Q_ASSERT(pimpl);
if (!pimpl) return NO;
if (commandSelector == @selector(moveDown:)) {
pimpl->keyDownPressed();
return YES;
} else if (commandSelector == @selector(moveUp:)) {
pimpl->keyUpPressed();
return YES;
}
return NO;
}
-(void)controlTextDidEndEditing:(NSNotification*)notification { -(void)controlTextDidEndEditing:(NSNotification*)notification {
// No Q_ASSERT here as it is called on destruction. // No Q_ASSERT here as it is called on destruction.
if (!pimpl) return; if (!pimpl) return;
@ -95,8 +119,6 @@ public:
if ([[[notification userInfo] objectForKey:@"NSTextMovement"] intValue] == NSReturnTextMovement) if ([[[notification userInfo] objectForKey:@"NSTextMovement"] intValue] == NSReturnTextMovement)
pimpl->returnPressed(); pimpl->returnPressed();
else if ([[[notification userInfo] objectForKey:@"NSTextMovement"] intValue] == NSOtherTextMovement)
pimpl->escapePressed();
} }
@end @end
@ -124,21 +146,17 @@ public:
} }
else if (keyString == "c") // Cmd+c else if (keyString == "c") // Cmd+c
{ {
QClipboard* clipboard = QApplication::clipboard(); [[self currentEditor] copy: nil];
clipboard->setText(toQString([self stringValue]));
return YES; return YES;
} }
else if (keyString == "v") // Cmd+v else if (keyString == "v") // Cmd+v
{ {
QClipboard* clipboard = QApplication::clipboard(); [[self currentEditor] paste: nil];
[self setStringValue:fromQString(clipboard->text())];
return YES; return YES;
} }
else if (keyString == "x") // Cmd+x else if (keyString == "x") // Cmd+x
{ {
QClipboard* clipboard = QApplication::clipboard(); [[self currentEditor] cut: nil];
clipboard->setText(toQString([self stringValue]));
[self setStringValue:@""];
return YES; return YES;
} }
} }
@ -176,6 +194,10 @@ void QSearchField::setText(const QString &text)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pimpl->nsSearchField setStringValue:fromQString(text)]; [pimpl->nsSearchField setStringValue:fromQString(text)];
if (!text.isEmpty()) {
[pimpl->nsSearchField selectText:pimpl->nsSearchField];
[[pimpl->nsSearchField currentEditor] setSelectedRange:NSMakeRange([[pimpl->nsSearchField stringValue] length], 0)];
}
[pool drain]; [pool drain];
} }
@ -198,12 +220,25 @@ QString QSearchField::placeholderText() const {
void QSearchField::setFocus(Qt::FocusReason reason) void QSearchField::setFocus(Qt::FocusReason reason)
{ {
Q_ASSERT(pimpl); /* Do nothing: we were previously using makeFirstResponder on search field, but
if (!pimpl) * that resulted in having the text being selected (and I didn't find any way to
return; * deselect it) which would result in the user erasing the first letter he just
* typed, after using setText (e.g. if the user typed a letter while having
* focus on the playlist, which means we call setText and give focus to the
* search bar).
* Instead now the focus will take place when calling selectText in setText.
* This obviously breaks the purpose of this function, but we never call only
* setFocus on a search box in Clementine (i.e. without a call to setText
* shortly after).
*/
if ([pimpl->nsSearchField acceptsFirstResponder]) // Q_ASSERT(pimpl);
[[pimpl->nsSearchField window] makeFirstResponder: pimpl->nsSearchField]; // if (!pimpl)
// return;
// if ([pimpl->nsSearchField acceptsFirstResponder]) {
// [[pimpl->nsSearchField window] makeFirstResponder: pimpl->nsSearchField];
// }
} }
void QSearchField::setFocus() void QSearchField::setFocus()

View File

@ -63,7 +63,7 @@ QSearchField::QSearchField(QWidget *parent) : QWidget(parent)
connect(lineEdit, SIGNAL(textChanged(QString)), connect(lineEdit, SIGNAL(textChanged(QString)),
this, SLOT(setText(QString))); this, SLOT(setText(QString)));
QIcon clearIcon(IconLoader::Load("edit-clear-locationbar-ltr")); QIcon clearIcon(IconLoader::Load("edit-clear-locationbar-ltr", IconLoader::Base));
QToolButton *clearButton = new QToolButton(this); QToolButton *clearButton = new QToolButton(this);
clearButton->setIcon(clearIcon); clearButton->setIcon(clearIcon);

View File

@ -1,8 +1,8 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-delete-non-virtual-dtor") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-delete-non-virtual-dtor")
set(TAGLIB_SOVERSION_CURRENT 13) set(TAGLIB_SOVERSION_CURRENT 16)
set(TAGLIB_SOVERSION_REVISION 0) set(TAGLIB_SOVERSION_REVISION 1)
set(TAGLIB_SOVERSION_AGE 12) set(TAGLIB_SOVERSION_AGE 15)
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}") math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}") math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")
math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}") math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}")
@ -48,6 +48,8 @@ include_directories(
if(ZLIB_FOUND) if(ZLIB_FOUND)
include_directories(${ZLIB_INCLUDE_DIR}) include_directories(${ZLIB_INCLUDE_DIR})
elseif(HAVE_ZLIB_SOURCE)
include_directories(${ZLIB_SOURCE})
endif() endif()
set(tag_HDRS set(tag_HDRS
@ -87,16 +89,20 @@ set(tag_HDRS
mpeg/id3v2/id3v2tag.h mpeg/id3v2/id3v2tag.h
mpeg/id3v2/frames/attachedpictureframe.h mpeg/id3v2/frames/attachedpictureframe.h
mpeg/id3v2/frames/commentsframe.h mpeg/id3v2/frames/commentsframe.h
mpeg/id3v2/frames/eventtimingcodesframe.h
mpeg/id3v2/frames/generalencapsulatedobjectframe.h mpeg/id3v2/frames/generalencapsulatedobjectframe.h
mpeg/id3v2/frames/ownershipframe.h mpeg/id3v2/frames/ownershipframe.h
mpeg/id3v2/frames/popularimeterframe.h mpeg/id3v2/frames/popularimeterframe.h
mpeg/id3v2/frames/privateframe.h mpeg/id3v2/frames/privateframe.h
mpeg/id3v2/frames/relativevolumeframe.h mpeg/id3v2/frames/relativevolumeframe.h
mpeg/id3v2/frames/synchronizedlyricsframe.h
mpeg/id3v2/frames/textidentificationframe.h mpeg/id3v2/frames/textidentificationframe.h
mpeg/id3v2/frames/uniquefileidentifierframe.h mpeg/id3v2/frames/uniquefileidentifierframe.h
mpeg/id3v2/frames/unknownframe.h mpeg/id3v2/frames/unknownframe.h
mpeg/id3v2/frames/unsynchronizedlyricsframe.h mpeg/id3v2/frames/unsynchronizedlyricsframe.h
mpeg/id3v2/frames/urllinkframe.h mpeg/id3v2/frames/urllinkframe.h
mpeg/id3v2/frames/chapterframe.h
mpeg/id3v2/frames/tableofcontentsframe.h
ogg/oggfile.h ogg/oggfile.h
ogg/oggpage.h ogg/oggpage.h
ogg/oggpageheader.h ogg/oggpageheader.h
@ -177,16 +183,20 @@ set(id3v2_SRCS
set(frames_SRCS set(frames_SRCS
mpeg/id3v2/frames/attachedpictureframe.cpp mpeg/id3v2/frames/attachedpictureframe.cpp
mpeg/id3v2/frames/commentsframe.cpp mpeg/id3v2/frames/commentsframe.cpp
mpeg/id3v2/frames/eventtimingcodesframe.cpp
mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp
mpeg/id3v2/frames/ownershipframe.cpp mpeg/id3v2/frames/ownershipframe.cpp
mpeg/id3v2/frames/popularimeterframe.cpp mpeg/id3v2/frames/popularimeterframe.cpp
mpeg/id3v2/frames/privateframe.cpp mpeg/id3v2/frames/privateframe.cpp
mpeg/id3v2/frames/relativevolumeframe.cpp mpeg/id3v2/frames/relativevolumeframe.cpp
mpeg/id3v2/frames/synchronizedlyricsframe.cpp
mpeg/id3v2/frames/textidentificationframe.cpp mpeg/id3v2/frames/textidentificationframe.cpp
mpeg/id3v2/frames/uniquefileidentifierframe.cpp mpeg/id3v2/frames/uniquefileidentifierframe.cpp
mpeg/id3v2/frames/unknownframe.cpp mpeg/id3v2/frames/unknownframe.cpp
mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp
mpeg/id3v2/frames/urllinkframe.cpp mpeg/id3v2/frames/urllinkframe.cpp
mpeg/id3v2/frames/chapterframe.cpp
mpeg/id3v2/frames/tableofcontentsframe.cpp
) )
set(ogg_SRCS set(ogg_SRCS
@ -313,14 +323,31 @@ set(toolkit_SRCS
toolkit/tpropertymap.cpp toolkit/tpropertymap.cpp
toolkit/trefcounter.cpp toolkit/trefcounter.cpp
toolkit/tdebuglistener.cpp toolkit/tdebuglistener.cpp
toolkit/unicode.cpp
) )
if(NOT WIN32)
set(unicode_SRCS
toolkit/unicode.cpp
)
endif()
if(HAVE_ZLIB_SOURCE)
set(zlib_SRCS
${ZLIB_SOURCE}/adler32.c
${ZLIB_SOURCE}/crc32.c
${ZLIB_SOURCE}/inffast.c
${ZLIB_SOURCE}/inflate.c
${ZLIB_SOURCE}/inftrees.c
${ZLIB_SOURCE}/zutil.c
)
endif()
set(tag_LIB_SRCS set(tag_LIB_SRCS
${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS}
${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS}
${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS}
${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS}
${unicode_SRCS} ${zlib_SRCS}
tag.cpp tag.cpp
tagunion.cpp tagunion.cpp
fileref.cpp fileref.cpp
@ -345,6 +372,7 @@ foreach(header ${tag_HDRS})
configure_file( configure_file(
"${header}" "${header}"
"${CMAKE_CURRENT_BINARY_DIR}/headers/taglib/${header_name}" "${CMAKE_CURRENT_BINARY_DIR}/headers/taglib/${header_name}"
COPY_ONLY COPYONLY
) )
endforeach() endforeach()

View File

@ -36,6 +36,7 @@
#include <tdebug.h> #include <tdebug.h>
#include <tagunion.h> #include <tagunion.h>
#include <id3v1tag.h> #include <id3v1tag.h>
#include <id3v2header.h>
#include <tpropertymap.h> #include <tpropertymap.h>
#include "apefile.h" #include "apefile.h"
@ -57,12 +58,17 @@ public:
APELocation(-1), APELocation(-1),
APESize(0), APESize(0),
ID3v1Location(-1), ID3v1Location(-1),
ID3v2Header(0),
ID3v2Location(-1),
ID3v2Size(0),
properties(0), properties(0),
hasAPE(false), hasAPE(false),
hasID3v1(false) {} hasID3v1(false),
hasID3v2(false) {}
~FilePrivate() ~FilePrivate()
{ {
delete ID3v2Header;
delete properties; delete properties;
} }
@ -71,6 +77,10 @@ public:
long ID3v1Location; long ID3v1Location;
ID3v2::Header *ID3v2Header;
long ID3v2Location;
uint ID3v2Size;
TagUnion tag; TagUnion tag;
Properties *properties; Properties *properties;
@ -80,26 +90,27 @@ public:
bool hasAPE; bool hasAPE;
bool hasID3v1; bool hasID3v1;
bool hasID3v2;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
APE::File::File(FileName file, bool readProperties, APE::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
Properties::ReadStyle propertiesStyle) : TagLib::File(file) TagLib::File(file),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
APE::File::File(IOStream *stream, bool readProperties, APE::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
Properties::ReadStyle propertiesStyle) : TagLib::File(stream) TagLib::File(stream),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
APE::File::~File() APE::File::~File()
@ -249,8 +260,19 @@ bool APE::File::hasID3v1Tag() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void APE::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) void APE::File::read(bool readProperties)
{ {
// Look for an ID3v2 tag
d->ID3v2Location = findID3v2();
if(d->ID3v2Location >= 0) {
seek(d->ID3v2Location);
d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size()));
d->ID3v2Size = d->ID3v2Header->completeTagSize();
d->hasID3v2 = true;
}
// Look for an ID3v1 tag // Look for an ID3v1 tag
d->ID3v1Location = findID3v1(); d->ID3v1Location = findID3v1();
@ -277,7 +299,25 @@ void APE::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
// Look for APE audio properties // Look for APE audio properties
if(readProperties) { if(readProperties) {
d->properties = new Properties(this);
long streamLength;
if(d->hasAPE)
streamLength = d->APELocation;
else if(d->hasID3v1)
streamLength = d->ID3v1Location;
else
streamLength = length();
if(d->hasID3v2) {
seek(d->ID3v2Location + d->ID3v2Size);
streamLength -= (d->ID3v2Location + d->ID3v2Size);
}
else {
seek(0);
}
d->properties = new Properties(this, streamLength);
} }
} }
@ -312,3 +352,16 @@ long APE::File::findID3v1()
return -1; return -1;
} }
long APE::File::findID3v2()
{
if(!isValid())
return -1;
seek(0);
if(readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}

View File

@ -130,7 +130,7 @@ namespace TagLib {
/*! /*!
* Implements the unified property interface -- import function. * Implements the unified property interface -- import function.
* Creates an APEv2 tag if necessary. A pontentially existing ID3v1 * Creates an APEv2 tag if necessary. A potentially existing ID3v1
* tag will be updated as well. * tag will be updated as well.
*/ */
PropertyMap setProperties(const PropertyMap &); PropertyMap setProperties(const PropertyMap &);
@ -146,6 +146,9 @@ namespace TagLib {
* *
* \note According to the official Monkey's Audio SDK, an APE file * \note According to the official Monkey's Audio SDK, an APE file
* can only have either ID3V1 or APE tags, so a parameter is used here. * can only have either ID3V1 or APE tags, so a parameter is used here.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
virtual bool save(); virtual bool save();
@ -156,8 +159,8 @@ namespace TagLib {
* if there is no valid ID3v1 tag. If \a create is true it will create * if there is no valid ID3v1 tag. If \a create is true it will create
* an ID3v1 tag if one does not exist and returns a valid pointer. * an ID3v1 tag if one does not exist and returns a valid pointer.
* *
* \note This may return a valid pointer regardless of whether or not the * \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
* on disk actually has an ID3v1 tag. * on disk actually has an ID3v1 tag.
* *
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be * \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -175,8 +178,8 @@ namespace TagLib {
* if there is no valid APE tag. If \a create is true it will create * if there is no valid APE tag. If \a create is true it will create
* an APE tag if one does not exist and returns a valid pointer. * an APE tag if one does not exist and returns a valid pointer.
* *
* \note This may return a valid pointer regardless of whether or not the * \note This may return a valid pointer regardless of whether or not the
* file on disk has an APE tag. Use hasAPETag() to check if the file * file on disk has an APE tag. Use hasAPETag() to check if the file
* on disk actually has an APE tag. * on disk actually has an APE tag.
* *
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be * \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -215,10 +218,10 @@ namespace TagLib {
File(const File &); File(const File &);
File &operator=(const File &); File &operator=(const File &);
void read(bool readProperties, Properties::ReadStyle propertiesStyle); void read(bool readProperties);
void scan();
long findID3v1();
long findAPE(); long findAPE();
long findID3v1();
long findID3v2();
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;

View File

@ -37,7 +37,7 @@ namespace TagLib {
/*! /*!
* This class implements APE footers (and headers). It attempts to follow, both * This class implements APE footers (and headers). It attempts to follow, both
* semantically and programatically, the structure specified in * semantically and programmatically, the structure specified in
* the APE v2.0 standard. The API is based on the properties of APE footer and * the APE v2.0 standard. The API is based on the properties of APE footer and
* headers specified there. * headers specified there.
*/ */

View File

@ -86,8 +86,10 @@ APE::Item::~Item()
Item &APE::Item::operator=(const Item &item) Item &APE::Item::operator=(const Item &item)
{ {
delete d; if(&item != this) {
d = new ItemPrivate(*item.d); delete d;
d = new ItemPrivate(*item.d);
}
return *this; return *this;
} }

View File

@ -153,7 +153,7 @@ namespace TagLib {
/*! /*!
* Returns the value as a single string. In case of multiple strings, * Returns the value as a single string. In case of multiple strings,
* the first is returned. If the data type is not \a Text, always returns * the first is returned. If the data type is not \a Text, always returns
* an empty String. * an empty String.
*/ */
String toString() const; String toString() const;
@ -164,7 +164,7 @@ namespace TagLib {
#endif #endif
/*! /*!
* Returns the list of text values. If the data type is not \a Text, always * Returns the list of text values. If the data type is not \a Text, always
* returns an empty StringList. * returns an empty StringList.
*/ */
StringList values() const; StringList values() const;

View File

@ -33,22 +33,22 @@
#include "id3v2tag.h" #include "id3v2tag.h"
#include "apeproperties.h" #include "apeproperties.h"
#include "apefile.h" #include "apefile.h"
#include "apetag.h"
#include "apefooter.h"
using namespace TagLib; using namespace TagLib;
class APE::Properties::PropertiesPrivate class APE::Properties::PropertiesPrivate
{ {
public: public:
PropertiesPrivate(File *file, long streamLength) : PropertiesPrivate() :
length(0), length(0),
bitrate(0), bitrate(0),
sampleRate(0), sampleRate(0),
channels(0), channels(0),
version(0), version(0),
bitsPerSample(0), bitsPerSample(0),
sampleFrames(0), sampleFrames(0) {}
file(file),
streamLength(streamLength) {}
int length; int length;
int bitrate; int bitrate;
@ -57,18 +57,24 @@ public:
int version; int version;
int bitsPerSample; int bitsPerSample;
uint sampleFrames; uint sampleFrames;
File *file;
long streamLength;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
APE::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) APE::Properties::Properties(File *, ReadStyle style) :
AudioProperties(style),
d(new PropertiesPrivate())
{ {
d = new PropertiesPrivate(file, file->length()); debug("APE::Properties::Properties() -- This constructor is no longer used.");
read(); }
APE::Properties::Properties(File *file, long streamLength, ReadStyle style) :
AudioProperties(style),
d(new PropertiesPrivate())
{
read(file, streamLength);
} }
APE::Properties::~Properties() APE::Properties::~Properties()
@ -77,6 +83,16 @@ APE::Properties::~Properties()
} }
int APE::Properties::length() const int APE::Properties::length() const
{
return lengthInSeconds();
}
int APE::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int APE::Properties::lengthInMilliseconds() const
{ {
return d->length; return d->length;
} }
@ -115,98 +131,93 @@ TagLib::uint APE::Properties::sampleFrames() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
namespace
void APE::Properties::read()
{ {
// First we are searching the descriptor inline int headerVersion(const ByteVector &header)
long offset = findDescriptor(); {
if(offset < 0) if(header.size() < 6 || !header.startsWith("MAC "))
return; return -1;
// Then we read the header common for all versions of APE return header.toUShort(4, false);
d->file->seek(offset);
ByteVector commonHeader = d->file->readBlock(6);
if(!commonHeader.startsWith("MAC "))
return;
d->version = commonHeader.toUShort(4, false);
if(d->version >= 3980) {
analyzeCurrent();
}
else {
analyzeOld();
} }
} }
long APE::Properties::findDescriptor() void APE::Properties::read(File *file, long streamLength)
{ {
long ID3v2Location = findID3v2(); // First, we assume that the file pointer is set at the first descriptor.
long ID3v2OriginalSize = 0; long offset = file->tell();
bool hasID3v2 = false; int version = headerVersion(file->readBlock(6));
if(ID3v2Location >= 0) {
ID3v2::Tag tag(d->file, ID3v2Location); // Next, we look for the descriptor.
ID3v2OriginalSize = tag.header()->completeTagSize(); if(version < 0) {
if(tag.header()->tagSize() > 0) offset = file->find("MAC ", offset);
hasID3v2 = true; file->seek(offset);
version = headerVersion(file->readBlock(6));
} }
long offset = 0; if(version < 0) {
if(hasID3v2) debug("APE::Properties::read() -- APE descriptor not found");
offset = d->file->find("MAC ", ID3v2Location + ID3v2OriginalSize); return;
}
d->version = version;
if(d->version >= 3980)
analyzeCurrent(file);
else else
offset = d->file->find("MAC "); analyzeOld(file);
if(offset < 0) { if(d->sampleFrames > 0 && d->sampleRate > 0) {
debug("APE::Properties::findDescriptor() -- APE descriptor not found"); const double length = d->sampleFrames * 1000.0 / d->sampleRate;
return -1; d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
} }
return offset;
} }
long APE::Properties::findID3v2() void APE::Properties::analyzeCurrent(File *file)
{
if(!d->file->isValid())
return -1;
d->file->seek(0);
if(d->file->readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}
void APE::Properties::analyzeCurrent()
{ {
// Read the descriptor // Read the descriptor
d->file->seek(2, File::Current); file->seek(2, File::Current);
ByteVector descriptor = d->file->readBlock(44); const ByteVector descriptor = file->readBlock(44);
if(descriptor.size() < 44) {
debug("APE::Properties::analyzeCurrent() -- descriptor is too short.");
return;
}
const uint descriptorBytes = descriptor.toUInt(0, false); const uint descriptorBytes = descriptor.toUInt(0, false);
if ((descriptorBytes - 52) > 0) if((descriptorBytes - 52) > 0)
d->file->seek(descriptorBytes - 52, File::Current); file->seek(descriptorBytes - 52, File::Current);
// Read the header // Read the header
ByteVector header = d->file->readBlock(24); const ByteVector header = file->readBlock(24);
if(header.size() < 24) {
debug("APE::Properties::analyzeCurrent() -- MAC header is too short.");
return;
}
// Get the APE info // Get the APE info
d->channels = header.toShort(18, false); d->channels = header.toShort(18, false);
d->sampleRate = header.toUInt(20, false); d->sampleRate = header.toUInt(20, false);
d->bitsPerSample = header.toShort(16, false); d->bitsPerSample = header.toShort(16, false);
//d->compressionLevel =
const uint totalFrames = header.toUInt(12, false); const uint totalFrames = header.toUInt(12, false);
if(totalFrames == 0)
return;
const uint blocksPerFrame = header.toUInt(4, false); const uint blocksPerFrame = header.toUInt(4, false);
const uint finalFrameBlocks = header.toUInt(8, false); const uint finalFrameBlocks = header.toUInt(8, false);
d->sampleFrames = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0; d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0;
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
} }
void APE::Properties::analyzeOld() void APE::Properties::analyzeOld(File *file)
{ {
ByteVector header = d->file->readBlock(26); const ByteVector header = file->readBlock(26);
if(header.size() < 26) {
debug("APE::Properties::analyzeOld() -- MAC header is too short.");
return;
}
const uint totalFrames = header.toUInt(18, false); const uint totalFrames = header.toUInt(18, false);
// Fail on 0 length APE files (catches non-finalized APE files) // Fail on 0 length APE files (catches non-finalized APE files)
@ -221,12 +232,21 @@ void APE::Properties::analyzeOld()
blocksPerFrame = 73728; blocksPerFrame = 73728;
else else
blocksPerFrame = 9216; blocksPerFrame = 9216;
// Get the APE info
d->channels = header.toShort(4, false); d->channels = header.toShort(4, false);
d->sampleRate = header.toUInt(6, false); d->sampleRate = header.toUInt(6, false);
const uint finalFrameBlocks = header.toUInt(22, false);
const uint totalBlocks
= totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0;
d->length = totalBlocks / d->sampleRate;
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
}
const uint finalFrameBlocks = header.toUInt(22, false);
d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
// Get the bit depth from the RIFF-fmt chunk.
file->seek(16, File::Current);
const ByteVector fmt = file->readBlock(28);
if(fmt.size() < 28 || !fmt.startsWith("WAVEfmt ")) {
debug("APE::Properties::analyzeOld() -- fmt header is too short.");
return;
}
d->bitsPerSample = fmt.toShort(26, false);
}

View File

@ -51,26 +51,73 @@ namespace TagLib {
public: public:
/*! /*!
* Create an instance of APE::Properties with the data read from the * Create an instance of APE::Properties with the data read from the
* ByteVector \a data. * APE::File \a file.
*
* \deprecated
*/ */
Properties(File *f, ReadStyle style = Average); Properties(File *file, ReadStyle style = Average);
/*!
* Create an instance of APE::Properties with the data read from the
* APE::File \a file.
*/
Properties(File *file, long streamLength, ReadStyle style = Average);
/*! /*!
* Destroys this APE::Properties instance. * Destroys this APE::Properties instance.
*/ */
virtual ~Properties(); virtual ~Properties();
// Reimplementations. /*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const; virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const; virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const; virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const; virtual int channels() const;
/*! /*!
* Returns number of bits per sample. * Returns the number of bits per audio sample.
*/ */
int bitsPerSample() const; int bitsPerSample() const;
/*!
* Returns the total number of audio samples in file.
*/
uint sampleFrames() const; uint sampleFrames() const;
/*! /*!
@ -82,13 +129,10 @@ namespace TagLib {
Properties(const Properties &); Properties(const Properties &);
Properties &operator=(const Properties &); Properties &operator=(const Properties &);
void read(); void read(File *file, long streamLength);
long findDescriptor(); void analyzeCurrent(File *file);
long findID3v2(); void analyzeOld(File *file);
void analyzeCurrent();
void analyzeOld();
class PropertiesPrivate; class PropertiesPrivate;
PropertiesPrivate *d; PropertiesPrivate *d;

View File

@ -46,11 +46,12 @@ using namespace APE;
class APE::Tag::TagPrivate class APE::Tag::TagPrivate
{ {
public: public:
TagPrivate() : file(0), footerLocation(-1), tagLength(0) {} TagPrivate() :
file(0),
footerLocation(-1) {}
TagLib::File *file; TagLib::File *file;
long footerLocation; long footerLocation;
long tagLength;
Footer footer; Footer footer;
@ -61,14 +62,16 @@ public:
// public methods // public methods
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
APE::Tag::Tag() : TagLib::Tag() APE::Tag::Tag() :
TagLib::Tag(),
d(new TagPrivate())
{ {
d = new TagPrivate;
} }
APE::Tag::Tag(TagLib::File *file, long footerLocation) : TagLib::Tag() APE::Tag::Tag(TagLib::File *file, long footerLocation) :
TagLib::Tag(),
d(new TagPrivate())
{ {
d = new TagPrivate;
d->file = file; d->file = file;
d->footerLocation = footerLocation; d->footerLocation = footerLocation;
@ -89,35 +92,35 @@ String APE::Tag::title() const
{ {
if(d->itemListMap["TITLE"].isEmpty()) if(d->itemListMap["TITLE"].isEmpty())
return String::null; return String::null;
return d->itemListMap["TITLE"].toString(); return d->itemListMap["TITLE"].values().toString();
} }
String APE::Tag::artist() const String APE::Tag::artist() const
{ {
if(d->itemListMap["ARTIST"].isEmpty()) if(d->itemListMap["ARTIST"].isEmpty())
return String::null; return String::null;
return d->itemListMap["ARTIST"].toString(); return d->itemListMap["ARTIST"].values().toString();
} }
String APE::Tag::album() const String APE::Tag::album() const
{ {
if(d->itemListMap["ALBUM"].isEmpty()) if(d->itemListMap["ALBUM"].isEmpty())
return String::null; return String::null;
return d->itemListMap["ALBUM"].toString(); return d->itemListMap["ALBUM"].values().toString();
} }
String APE::Tag::comment() const String APE::Tag::comment() const
{ {
if(d->itemListMap["COMMENT"].isEmpty()) if(d->itemListMap["COMMENT"].isEmpty())
return String::null; return String::null;
return d->itemListMap["COMMENT"].toString(); return d->itemListMap["COMMENT"].values().toString();
} }
String APE::Tag::genre() const String APE::Tag::genre() const
{ {
if(d->itemListMap["GENRE"].isEmpty()) if(d->itemListMap["GENRE"].isEmpty())
return String::null; return String::null;
return d->itemListMap["GENRE"].toString(); return d->itemListMap["GENRE"].values().toString();
} }
TagLib::uint APE::Tag::year() const TagLib::uint APE::Tag::year() const
@ -233,7 +236,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
toRemove.append(remIt->first); toRemove.append(remIt->first);
} }
for (StringList::Iterator removeIt = toRemove.begin(); removeIt != toRemove.end(); removeIt++) for(StringList::ConstIterator removeIt = toRemove.begin(); removeIt != toRemove.end(); removeIt++)
removeItem(*removeIt); removeItem(*removeIt);
// now sync in the "forward direction" // now sync in the "forward direction"
@ -284,9 +287,7 @@ const APE::ItemListMap& APE::Tag::itemListMap() const
void APE::Tag::removeItem(const String &key) void APE::Tag::removeItem(const String &key)
{ {
Map<const String, Item>::Iterator it = d->itemListMap.find(key.upper()); d->itemListMap.erase(key.upper());
if(it != d->itemListMap.end())
d->itemListMap.erase(it);
} }
void APE::Tag::addValue(const String &key, const String &value, bool replace) void APE::Tag::addValue(const String &key, const String &value, bool replace)
@ -368,10 +369,13 @@ ByteVector APE::Tag::render() const
void APE::Tag::parse(const ByteVector &data) void APE::Tag::parse(const ByteVector &data)
{ {
uint pos = 0;
// 11 bytes is the minimum size for an APE item // 11 bytes is the minimum size for an APE item
if(data.size() < 11)
return;
uint pos = 0;
for(uint i = 0; i < d->footer.itemCount() && pos <= data.size() - 11; i++) { for(uint i = 0; i < d->footer.itemCount() && pos <= data.size() - 11; i++) {
APE::Item item; APE::Item item;
item.parse(data.mid(pos)); item.parse(data.mid(pos));

View File

@ -143,7 +143,7 @@ namespace TagLib {
* Returns a reference to the item list map. This is an ItemListMap of * Returns a reference to the item list map. This is an ItemListMap of
* all of the items in the tag. * all of the items in the tag.
* *
* This is the most powerfull structure for accessing the items of the tag. * This is the most powerful structure for accessing the items of the tag.
* *
* APE tags are case-insensitive, all keys in this map have been converted * APE tags are case-insensitive, all keys in this map have been converted
* to upper case. * to upper case.

View File

@ -25,9 +25,11 @@
#include <taglib.h> #include <taglib.h>
#include <tdebug.h> #include <tdebug.h>
#include "trefcounter.h" #include <trefcounter.h>
#include "asfattribute.h" #include "asfattribute.h"
#include "asffile.h" #include "asffile.h"
#include "asfutils.h"
using namespace TagLib; using namespace TagLib;
@ -70,10 +72,12 @@ ASF::Attribute::Attribute(const ASF::Attribute &other)
ASF::Attribute &ASF::Attribute::operator=(const ASF::Attribute &other) ASF::Attribute &ASF::Attribute::operator=(const ASF::Attribute &other)
{ {
if(d->deref()) if(&other != this) {
delete d; if(d->deref())
d = other.d; delete d;
d->ref(); d = other.d;
d->ref();
}
return *this; return *this;
} }
@ -181,23 +185,23 @@ String ASF::Attribute::parse(ASF::File &f, int kind)
d->pictureValue = Picture::fromInvalid(); d->pictureValue = Picture::fromInvalid();
// extended content descriptor // extended content descriptor
if(kind == 0) { if(kind == 0) {
nameLength = f.readWORD(); nameLength = readWORD(&f);
name = f.readString(nameLength); name = readString(&f, nameLength);
d->type = ASF::Attribute::AttributeTypes(f.readWORD()); d->type = ASF::Attribute::AttributeTypes(readWORD(&f));
size = f.readWORD(); size = readWORD(&f);
} }
// metadata & metadata library // metadata & metadata library
else { else {
int temp = f.readWORD(); int temp = readWORD(&f);
// metadata library // metadata library
if(kind == 2) { if(kind == 2) {
d->language = temp; d->language = temp;
} }
d->stream = f.readWORD(); d->stream = readWORD(&f);
nameLength = f.readWORD(); nameLength = readWORD(&f);
d->type = ASF::Attribute::AttributeTypes(f.readWORD()); d->type = ASF::Attribute::AttributeTypes(readWORD(&f));
size = f.readDWORD(); size = readDWORD(&f);
name = f.readString(nameLength); name = readString(&f, nameLength);
} }
if(kind != 2 && size > 65535) { if(kind != 2 && size > 65535) {
@ -206,28 +210,28 @@ String ASF::Attribute::parse(ASF::File &f, int kind)
switch(d->type) { switch(d->type) {
case WordType: case WordType:
d->shortValue = f.readWORD(); d->shortValue = readWORD(&f);
break; break;
case BoolType: case BoolType:
if(kind == 0) { if(kind == 0) {
d->boolValue = f.readDWORD() == 1; d->boolValue = (readDWORD(&f) == 1);
} }
else { else {
d->boolValue = f.readWORD() == 1; d->boolValue = (readWORD(&f) == 1);
} }
break; break;
case DWordType: case DWordType:
d->intValue = f.readDWORD(); d->intValue = readDWORD(&f);
break; break;
case QWordType: case QWordType:
d->longLongValue = f.readQWORD(); d->longLongValue = readQWORD(&f);
break; break;
case UnicodeType: case UnicodeType:
d->stringValue = f.readString(size); d->stringValue = readString(&f, size);
break; break;
case BytesType: case BytesType:
@ -295,7 +299,7 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
break; break;
case UnicodeType: case UnicodeType:
data.append(File::renderString(d->stringValue)); data.append(renderString(d->stringValue));
break; break;
case BytesType: case BytesType:
@ -309,13 +313,13 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
} }
if(kind == 0) { if(kind == 0) {
data = File::renderString(name, true) + data = renderString(name, true) +
ByteVector::fromShort((int)d->type, false) + ByteVector::fromShort((int)d->type, false) +
ByteVector::fromShort(data.size(), false) + ByteVector::fromShort(data.size(), false) +
data; data;
} }
else { else {
ByteVector nameData = File::renderString(name); ByteVector nameData = renderString(name);
data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) + data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) +
ByteVector::fromShort(d->stream, false) + ByteVector::fromShort(d->stream, false) +
ByteVector::fromShort(nameData.size(), false) + ByteVector::fromShort(nameData.size(), false) +

View File

@ -27,15 +27,28 @@
#include <tbytevectorlist.h> #include <tbytevectorlist.h>
#include <tpropertymap.h> #include <tpropertymap.h>
#include <tstring.h> #include <tstring.h>
#include "asffile.h" #include "asffile.h"
#include "asftag.h" #include "asftag.h"
#include "asfproperties.h" #include "asfproperties.h"
#include "asfutils.h"
using namespace TagLib; using namespace TagLib;
class ASF::File::FilePrivate class ASF::File::FilePrivate
{ {
public: public:
class BaseObject;
class UnknownObject;
class FilePropertiesObject;
class StreamPropertiesObject;
class ContentDescriptionObject;
class ExtendedContentDescriptionObject;
class HeaderExtensionObject;
class CodecListObject;
class MetadataObject;
class MetadataLibraryObject;
FilePrivate(): FilePrivate():
size(0), size(0),
tag(0), tag(0),
@ -44,189 +57,227 @@ public:
extendedContentDescriptionObject(0), extendedContentDescriptionObject(0),
headerExtensionObject(0), headerExtensionObject(0),
metadataObject(0), metadataObject(0),
metadataLibraryObject(0) {} metadataLibraryObject(0)
{
objects.setAutoDelete(true);
}
~FilePrivate()
{
delete tag;
delete properties;
}
unsigned long long size; unsigned long long size;
ASF::Tag *tag; ASF::Tag *tag;
ASF::Properties *properties; ASF::Properties *properties;
List<ASF::File::BaseObject *> objects;
ASF::File::ContentDescriptionObject *contentDescriptionObject; List<BaseObject *> objects;
ASF::File::ExtendedContentDescriptionObject *extendedContentDescriptionObject;
ASF::File::HeaderExtensionObject *headerExtensionObject; ContentDescriptionObject *contentDescriptionObject;
ASF::File::MetadataObject *metadataObject; ExtendedContentDescriptionObject *extendedContentDescriptionObject;
ASF::File::MetadataLibraryObject *metadataLibraryObject; HeaderExtensionObject *headerExtensionObject;
MetadataObject *metadataObject;
MetadataLibraryObject *metadataLibraryObject;
}; };
static ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); namespace
static ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); {
static ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); const ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16);
static ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); const ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16);
static ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); const ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16);
static ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); const ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16);
static ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); const ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16);
static ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16);
static ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16);
static ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16);
static ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); const ByteVector codecListGuid("\x40\x52\xd1\x86\x1d\x31\xd0\x11\xa3\xa4\x00\xa0\xc9\x03\x48\xf6", 16);
const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16);
const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16);
const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16);
}
class ASF::File::BaseObject class ASF::File::FilePrivate::BaseObject
{ {
public: public:
ByteVector data; ByteVector data;
virtual ~BaseObject() {} virtual ~BaseObject() {}
virtual ByteVector guid() = 0; virtual ByteVector guid() const = 0;
virtual void parse(ASF::File *file, unsigned int size); virtual void parse(ASF::File *file, unsigned int size);
virtual ByteVector render(ASF::File *file); virtual ByteVector render(ASF::File *file);
}; };
class ASF::File::UnknownObject : public ASF::File::BaseObject class ASF::File::FilePrivate::UnknownObject : public ASF::File::FilePrivate::BaseObject
{ {
ByteVector myGuid; ByteVector myGuid;
public: public:
UnknownObject(const ByteVector &guid); UnknownObject(const ByteVector &guid);
ByteVector guid(); ByteVector guid() const;
}; };
class ASF::File::FilePropertiesObject : public ASF::File::BaseObject class ASF::File::FilePrivate::FilePropertiesObject : public ASF::File::FilePrivate::BaseObject
{ {
public: public:
ByteVector guid(); ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, uint size);
}; };
class ASF::File::StreamPropertiesObject : public ASF::File::BaseObject class ASF::File::FilePrivate::StreamPropertiesObject : public ASF::File::FilePrivate::BaseObject
{ {
public: public:
ByteVector guid(); ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, uint size);
}; };
class ASF::File::ContentDescriptionObject : public ASF::File::BaseObject class ASF::File::FilePrivate::ContentDescriptionObject : public ASF::File::FilePrivate::BaseObject
{ {
public: public:
ByteVector guid(); ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
class ASF::File::ExtendedContentDescriptionObject : public ASF::File::BaseObject class ASF::File::FilePrivate::ExtendedContentDescriptionObject : public ASF::File::FilePrivate::BaseObject
{ {
public: public:
ByteVectorList attributeData; ByteVectorList attributeData;
ByteVector guid(); ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
class ASF::File::MetadataObject : public ASF::File::BaseObject class ASF::File::FilePrivate::MetadataObject : public ASF::File::FilePrivate::BaseObject
{ {
public: public:
ByteVectorList attributeData; ByteVectorList attributeData;
ByteVector guid(); ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
class ASF::File::MetadataLibraryObject : public ASF::File::BaseObject class ASF::File::FilePrivate::MetadataLibraryObject : public ASF::File::FilePrivate::BaseObject
{ {
public: public:
ByteVectorList attributeData; ByteVectorList attributeData;
ByteVector guid(); ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject class ASF::File::FilePrivate::HeaderExtensionObject : public ASF::File::FilePrivate::BaseObject
{ {
public: public:
List<ASF::File::BaseObject *> objects; List<ASF::File::FilePrivate::BaseObject *> objects;
~HeaderExtensionObject(); HeaderExtensionObject();
ByteVector guid(); ByteVector guid() const;
void parse(ASF::File *file, uint size); void parse(ASF::File *file, uint size);
ByteVector render(ASF::File *file); ByteVector render(ASF::File *file);
}; };
ASF::File::HeaderExtensionObject::~HeaderExtensionObject() class ASF::File::FilePrivate::CodecListObject : public ASF::File::FilePrivate::BaseObject
{ {
for(unsigned int i = 0; i < objects.size(); i++) { public:
delete objects[i]; ByteVector guid() const;
} void parse(ASF::File *file, uint size);
}
void ASF::File::BaseObject::parse(ASF::File *file, unsigned int size) private:
enum CodecType
{
Video = 0x0001,
Audio = 0x0002,
Unknown = 0xFFFF
};
};
void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int size)
{ {
data.clear(); data.clear();
if (size > 24 && size <= (unsigned int)(file->length())) if(size > 24 && size <= (unsigned int)(file->length()))
data = file->readBlock(size - 24); data = file->readBlock(size - 24);
else else
data = ByteVector::null; data = ByteVector::null;
} }
ByteVector ASF::File::BaseObject::render(ASF::File * /*file*/) ByteVector ASF::File::FilePrivate::BaseObject::render(ASF::File * /*file*/)
{ {
return guid() + ByteVector::fromLongLong(data.size() + 24, false) + data; return guid() + ByteVector::fromLongLong(data.size() + 24, false) + data;
} }
ASF::File::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid) ASF::File::FilePrivate::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid)
{ {
} }
ByteVector ASF::File::UnknownObject::guid() ByteVector ASF::File::FilePrivate::UnknownObject::guid() const
{ {
return myGuid; return myGuid;
} }
ByteVector ASF::File::FilePropertiesObject::guid() ByteVector ASF::File::FilePrivate::FilePropertiesObject::guid() const
{ {
return filePropertiesGuid; return filePropertiesGuid;
} }
void ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size) void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, uint size)
{ {
BaseObject::parse(file, size); BaseObject::parse(file, size);
file->d->properties->setLength( if(data.size() < 64) {
(int)(data.toLongLong(40, false) / 10000000L - data.toLongLong(56, false) / 1000L)); debug("ASF::File::FilePrivate::FilePropertiesObject::parse() -- data is too short.");
return;
}
const long long duration = data.toLongLong(40, false);
const long long preroll = data.toLongLong(56, false);
file->d->properties->setLengthInMilliseconds(static_cast<int>(duration / 10000.0 - preroll + 0.5));
} }
ByteVector ASF::File::StreamPropertiesObject::guid() ByteVector ASF::File::FilePrivate::StreamPropertiesObject::guid() const
{ {
return streamPropertiesGuid; return streamPropertiesGuid;
} }
void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) void ASF::File::FilePrivate::StreamPropertiesObject::parse(ASF::File *file, uint size)
{ {
BaseObject::parse(file, size); BaseObject::parse(file, size);
file->d->properties->setChannels(data.toShort(56, false)); if(data.size() < 70) {
debug("ASF::File::FilePrivate::StreamPropertiesObject::parse() -- data is too short.");
return;
}
file->d->properties->setCodec(data.toUShort(54, false));
file->d->properties->setChannels(data.toUShort(56, false));
file->d->properties->setSampleRate(data.toUInt(58, false)); file->d->properties->setSampleRate(data.toUInt(58, false));
file->d->properties->setBitrate(data.toUInt(62, false) * 8 / 1000); file->d->properties->setBitrate(static_cast<int>(data.toUInt(62, false) * 8.0 / 1000.0 + 0.5));
file->d->properties->setBitsPerSample(data.toUShort(68, false));
} }
ByteVector ASF::File::ContentDescriptionObject::guid() ByteVector ASF::File::FilePrivate::ContentDescriptionObject::guid() const
{ {
return contentDescriptionGuid; return contentDescriptionGuid;
} }
void ASF::File::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/)
{ {
file->d->contentDescriptionObject = this; file->d->contentDescriptionObject = this;
int titleLength = file->readWORD(); const int titleLength = readWORD(file);
int artistLength = file->readWORD(); const int artistLength = readWORD(file);
int copyrightLength = file->readWORD(); const int copyrightLength = readWORD(file);
int commentLength = file->readWORD(); const int commentLength = readWORD(file);
int ratingLength = file->readWORD(); const int ratingLength = readWORD(file);
file->d->tag->setTitle(file->readString(titleLength)); file->d->tag->setTitle(readString(file,titleLength));
file->d->tag->setArtist(file->readString(artistLength)); file->d->tag->setArtist(readString(file,artistLength));
file->d->tag->setCopyright(file->readString(copyrightLength)); file->d->tag->setCopyright(readString(file,copyrightLength));
file->d->tag->setComment(file->readString(commentLength)); file->d->tag->setComment(readString(file,commentLength));
file->d->tag->setRating(file->readString(ratingLength)); file->d->tag->setRating(readString(file,ratingLength));
} }
ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file) ByteVector ASF::File::FilePrivate::ContentDescriptionObject::render(ASF::File *file)
{ {
ByteVector v1 = file->renderString(file->d->tag->title()); const ByteVector v1 = renderString(file->d->tag->title());
ByteVector v2 = file->renderString(file->d->tag->artist()); const ByteVector v2 = renderString(file->d->tag->artist());
ByteVector v3 = file->renderString(file->d->tag->copyright()); const ByteVector v3 = renderString(file->d->tag->copyright());
ByteVector v4 = file->renderString(file->d->tag->comment()); const ByteVector v4 = renderString(file->d->tag->comment());
ByteVector v5 = file->renderString(file->d->tag->rating()); const ByteVector v5 = renderString(file->d->tag->rating());
data.clear(); data.clear();
data.append(ByteVector::fromShort(v1.size(), false)); data.append(ByteVector::fromShort(v1.size(), false));
data.append(ByteVector::fromShort(v2.size(), false)); data.append(ByteVector::fromShort(v2.size(), false));
@ -241,15 +292,15 @@ ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file)
return BaseObject::render(file); return BaseObject::render(file);
} }
ByteVector ASF::File::ExtendedContentDescriptionObject::guid() ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() const
{ {
return extendedContentDescriptionGuid; return extendedContentDescriptionGuid;
} }
void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/)
{ {
file->d->extendedContentDescriptionObject = this; file->d->extendedContentDescriptionObject = this;
int count = file->readWORD(); int count = readWORD(file);
while(count--) { while(count--) {
ASF::Attribute attribute; ASF::Attribute attribute;
String name = attribute.parse(*file); String name = attribute.parse(*file);
@ -257,7 +308,7 @@ void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*
} }
} }
ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file) ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF::File *file)
{ {
data.clear(); data.clear();
data.append(ByteVector::fromShort(attributeData.size(), false)); data.append(ByteVector::fromShort(attributeData.size(), false));
@ -265,15 +316,15 @@ ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file)
return BaseObject::render(file); return BaseObject::render(file);
} }
ByteVector ASF::File::MetadataObject::guid() ByteVector ASF::File::FilePrivate::MetadataObject::guid() const
{ {
return metadataGuid; return metadataGuid;
} }
void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, uint /*size*/)
{ {
file->d->metadataObject = this; file->d->metadataObject = this;
int count = file->readWORD(); int count = readWORD(file);
while(count--) { while(count--) {
ASF::Attribute attribute; ASF::Attribute attribute;
String name = attribute.parse(*file, 1); String name = attribute.parse(*file, 1);
@ -281,7 +332,7 @@ void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/)
} }
} }
ByteVector ASF::File::MetadataObject::render(ASF::File *file) ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file)
{ {
data.clear(); data.clear();
data.append(ByteVector::fromShort(attributeData.size(), false)); data.append(ByteVector::fromShort(attributeData.size(), false));
@ -289,15 +340,15 @@ ByteVector ASF::File::MetadataObject::render(ASF::File *file)
return BaseObject::render(file); return BaseObject::render(file);
} }
ByteVector ASF::File::MetadataLibraryObject::guid() ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const
{ {
return metadataLibraryGuid; return metadataLibraryGuid;
} }
void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/)
{ {
file->d->metadataLibraryObject = this; file->d->metadataLibraryObject = this;
int count = file->readWORD(); int count = readWORD(file);
while(count--) { while(count--) {
ASF::Attribute attribute; ASF::Attribute attribute;
String name = attribute.parse(*file, 2); String name = attribute.parse(*file, 2);
@ -305,7 +356,7 @@ void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/)
} }
} }
ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file) ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file)
{ {
data.clear(); data.clear();
data.append(ByteVector::fromShort(attributeData.size(), false)); data.append(ByteVector::fromShort(attributeData.size(), false));
@ -313,16 +364,21 @@ ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file)
return BaseObject::render(file); return BaseObject::render(file);
} }
ByteVector ASF::File::HeaderExtensionObject::guid() ASF::File::FilePrivate::HeaderExtensionObject::HeaderExtensionObject()
{
objects.setAutoDelete(true);
}
ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const
{ {
return headerExtensionGuid; return headerExtensionGuid;
} }
void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
{ {
file->d->headerExtensionObject = this; file->d->headerExtensionObject = this;
file->seek(18, File::Current); file->seek(18, File::Current);
long long dataSize = file->readDWORD(); long long dataSize = readDWORD(file);
long long dataPos = 0; long long dataPos = 0;
while(dataPos < dataSize) { while(dataPos < dataSize) {
ByteVector guid = file->readBlock(16); ByteVector guid = file->readBlock(16);
@ -331,7 +387,7 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
break; break;
} }
bool ok; bool ok;
long long size = file->readQWORD(&ok); long long size = readQWORD(file, &ok);
if(!ok) { if(!ok) {
file->setValid(false); file->setValid(false);
break; break;
@ -352,47 +408,93 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
} }
} }
ByteVector ASF::File::HeaderExtensionObject::render(ASF::File *file) ByteVector ASF::File::FilePrivate::HeaderExtensionObject::render(ASF::File *file)
{ {
data.clear(); data.clear();
for(unsigned int i = 0; i < objects.size(); i++) { for(List<BaseObject *>::ConstIterator it = objects.begin(); it != objects.end(); ++it) {
data.append(objects[i]->render(file)); data.append((*it)->render(file));
} }
data = ByteVector("\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65\x06\x00", 18) + ByteVector::fromUInt(data.size(), false) + data; data = ByteVector("\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65\x06\x00", 18) + ByteVector::fromUInt(data.size(), false) + data;
return BaseObject::render(file); return BaseObject::render(file);
} }
ByteVector ASF::File::FilePrivate::CodecListObject::guid() const
{
return codecListGuid;
}
void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size)
{
BaseObject::parse(file, size);
if(data.size() <= 20) {
debug("ASF::File::FilePrivate::CodecListObject::parse() -- data is too short.");
return;
}
uint pos = 16;
const int count = data.toUInt(pos, false);
pos += 4;
for(int i = 0; i < count; ++i) {
if(pos >= data.size())
break;
const CodecType type = static_cast<CodecType>(data.toUShort(pos, false));
pos += 2;
int nameLength = data.toUShort(pos, false);
pos += 2;
const uint namePos = pos;
pos += nameLength * 2;
const int descLength = data.toUShort(pos, false);
pos += 2;
const uint descPos = pos;
pos += descLength * 2;
const int infoLength = data.toUShort(pos, false);
pos += 2 + infoLength * 2;
if(type == CodecListObject::Audio) {
// First audio codec found.
const String name(data.mid(namePos, nameLength * 2), String::UTF16LE);
file->d->properties->setCodecName(name.stripWhiteSpace());
const String desc(data.mid(descPos, descLength * 2), String::UTF16LE);
file->d->properties->setCodecDescription(desc.stripWhiteSpace());
break;
}
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) ASF::File::File(FileName file, bool, Properties::ReadStyle) :
: TagLib::File(file) TagLib::File(file),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read();
} }
ASF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) ASF::File::File(IOStream *stream, bool, Properties::ReadStyle) :
: TagLib::File(stream) TagLib::File(stream),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read();
} }
ASF::File::~File() ASF::File::~File()
{ {
for(unsigned int i = 0; i < d->objects.size(); i++) {
delete d->objects[i];
}
if(d->tag) {
delete d->tag;
}
if(d->properties) {
delete d->properties;
}
delete d; delete d;
} }
@ -421,7 +523,85 @@ ASF::Properties *ASF::File::audioProperties() const
return d->properties; return d->properties;
} }
void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*propertiesStyle*/) bool ASF::File::save()
{
if(readOnly()) {
debug("ASF::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("ASF::File::save() -- Trying to save invalid file.");
return false;
}
if(!d->contentDescriptionObject) {
d->contentDescriptionObject = new FilePrivate::ContentDescriptionObject();
d->objects.append(d->contentDescriptionObject);
}
if(!d->extendedContentDescriptionObject) {
d->extendedContentDescriptionObject = new FilePrivate::ExtendedContentDescriptionObject();
d->objects.append(d->extendedContentDescriptionObject);
}
if(!d->headerExtensionObject) {
d->headerExtensionObject = new FilePrivate::HeaderExtensionObject();
d->objects.append(d->headerExtensionObject);
}
if(!d->metadataObject) {
d->metadataObject = new FilePrivate::MetadataObject();
d->headerExtensionObject->objects.append(d->metadataObject);
}
if(!d->metadataLibraryObject) {
d->metadataLibraryObject = new FilePrivate::MetadataLibraryObject();
d->headerExtensionObject->objects.append(d->metadataLibraryObject);
}
const AttributeListMap allAttributes = d->tag->attributeListMap();
for(AttributeListMap::ConstIterator it = allAttributes.begin(); it != allAttributes.end(); ++it) {
const String &name = it->first;
const AttributeList &attributes = it->second;
bool inExtendedContentDescriptionObject = false;
bool inMetadataObject = false;
for(AttributeList::ConstIterator jt = attributes.begin(); jt != attributes.end(); ++jt) {
const Attribute &attribute = *jt;
const bool largeValue = (attribute.dataSize() > 65535);
const bool guid = (attribute.type() == Attribute::GuidType);
if(!inExtendedContentDescriptionObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() == 0) {
d->extendedContentDescriptionObject->attributeData.append(attribute.render(name));
inExtendedContentDescriptionObject = true;
}
else if(!inMetadataObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() != 0) {
d->metadataObject->attributeData.append(attribute.render(name, 1));
inMetadataObject = true;
}
else {
d->metadataLibraryObject->attributeData.append(attribute.render(name, 2));
}
}
}
ByteVector data;
for(List<FilePrivate::BaseObject *>::ConstIterator it = d->objects.begin(); it != d->objects.end(); ++it) {
data.append((*it)->render(this));
}
data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data;
insert(data, 0, (TagLib::ulong)d->size);
return true;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void ASF::File::read()
{ {
if(!isValid()) if(!isValid())
return; return;
@ -437,12 +617,12 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
d->properties = new ASF::Properties(); d->properties = new ASF::Properties();
bool ok; bool ok;
d->size = readQWORD(&ok); d->size = readQWORD(this, &ok);
if(!ok) { if(!ok) {
setValid(false); setValid(false);
return; return;
} }
int numObjects = readDWORD(&ok); int numObjects = readDWORD(this, &ok);
if(!ok) { if(!ok) {
setValid(false); setValid(false);
return; return;
@ -450,31 +630,34 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
seek(2, Current); seek(2, Current);
for(int i = 0; i < numObjects; i++) { for(int i = 0; i < numObjects; i++) {
ByteVector guid = readBlock(16); guid = readBlock(16);
if(guid.size() != 16) { if(guid.size() != 16) {
setValid(false); setValid(false);
break; break;
} }
long size = (long)readQWORD(&ok); long size = (long)readQWORD(this, &ok);
if(!ok) { if(!ok) {
setValid(false); setValid(false);
break; break;
} }
BaseObject *obj; FilePrivate::BaseObject *obj;
if(guid == filePropertiesGuid) { if(guid == filePropertiesGuid) {
obj = new FilePropertiesObject(); obj = new FilePrivate::FilePropertiesObject();
} }
else if(guid == streamPropertiesGuid) { else if(guid == streamPropertiesGuid) {
obj = new StreamPropertiesObject(); obj = new FilePrivate::StreamPropertiesObject();
} }
else if(guid == contentDescriptionGuid) { else if(guid == contentDescriptionGuid) {
obj = new ContentDescriptionObject(); obj = new FilePrivate::ContentDescriptionObject();
} }
else if(guid == extendedContentDescriptionGuid) { else if(guid == extendedContentDescriptionGuid) {
obj = new ExtendedContentDescriptionObject(); obj = new FilePrivate::ExtendedContentDescriptionObject();
} }
else if(guid == headerExtensionGuid) { else if(guid == headerExtensionGuid) {
obj = new HeaderExtensionObject(); obj = new FilePrivate::HeaderExtensionObject();
}
else if(guid == codecListGuid) {
obj = new FilePrivate::CodecListObject();
} }
else { else {
if(guid == contentEncryptionGuid || if(guid == contentEncryptionGuid ||
@ -482,149 +665,9 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
guid == advancedContentEncryptionGuid) { guid == advancedContentEncryptionGuid) {
d->properties->setEncrypted(true); d->properties->setEncrypted(true);
} }
obj = new UnknownObject(guid); obj = new FilePrivate::UnknownObject(guid);
} }
obj->parse(this, size); obj->parse(this, size);
d->objects.append(obj); d->objects.append(obj);
} }
} }
bool ASF::File::save()
{
if(readOnly()) {
debug("ASF::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("ASF::File::save() -- Trying to save invalid file.");
return false;
}
if(!d->contentDescriptionObject) {
d->contentDescriptionObject = new ContentDescriptionObject();
d->objects.append(d->contentDescriptionObject);
}
if(!d->extendedContentDescriptionObject) {
d->extendedContentDescriptionObject = new ExtendedContentDescriptionObject();
d->objects.append(d->extendedContentDescriptionObject);
}
if(!d->headerExtensionObject) {
d->headerExtensionObject = new HeaderExtensionObject();
d->objects.append(d->headerExtensionObject);
}
if(!d->metadataObject) {
d->metadataObject = new MetadataObject();
d->headerExtensionObject->objects.append(d->metadataObject);
}
if(!d->metadataLibraryObject) {
d->metadataLibraryObject = new MetadataLibraryObject();
d->headerExtensionObject->objects.append(d->metadataLibraryObject);
}
ASF::AttributeListMap::ConstIterator it = d->tag->attributeListMap().begin();
for(; it != d->tag->attributeListMap().end(); it++) {
const String &name = it->first;
const AttributeList &attributes = it->second;
bool inExtendedContentDescriptionObject = false;
bool inMetadataObject = false;
for(unsigned int j = 0; j < attributes.size(); j++) {
const Attribute &attribute = attributes[j];
bool largeValue = attribute.dataSize() > 65535;
if(!inExtendedContentDescriptionObject && !largeValue && attribute.language() == 0 && attribute.stream() == 0) {
d->extendedContentDescriptionObject->attributeData.append(attribute.render(name));
inExtendedContentDescriptionObject = true;
}
else if(!inMetadataObject && !largeValue && attribute.language() == 0 && attribute.stream() != 0) {
d->metadataObject->attributeData.append(attribute.render(name, 1));
inMetadataObject = true;
}
else {
d->metadataLibraryObject->attributeData.append(attribute.render(name, 2));
}
}
}
ByteVector data;
for(unsigned int i = 0; i < d->objects.size(); i++) {
data.append(d->objects[i]->render(this));
}
data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data;
insert(data, 0, (TagLib::ulong)d->size);
return true;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
int ASF::File::readBYTE(bool *ok)
{
ByteVector v = readBlock(1);
if(v.size() != 1) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v[0];
}
int ASF::File::readWORD(bool *ok)
{
ByteVector v = readBlock(2);
if(v.size() != 2) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUShort(false);
}
unsigned int ASF::File::readDWORD(bool *ok)
{
ByteVector v = readBlock(4);
if(v.size() != 4) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUInt(false);
}
long long ASF::File::readQWORD(bool *ok)
{
ByteVector v = readBlock(8);
if(v.size() != 8) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toLongLong(false);
}
String ASF::File::readString(int length)
{
ByteVector data = readBlock(length);
unsigned int size = data.size();
while (size >= 2) {
if(data[size - 1] != '\0' || data[size - 2] != '\0') {
break;
}
size -= 2;
}
if(size != data.size()) {
data.resize(size);
}
return String(data, String::UTF16LE);
}
ByteVector ASF::File::renderString(const String &str, bool includeLength)
{
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false);
if(includeLength) {
data = ByteVector::fromShort(data.size(), false) + data;
}
return data;
}

View File

@ -54,7 +54,7 @@ namespace TagLib {
* \a propertiesStyle are ignored. The audio properties are always * \a propertiesStyle are ignored. The audio properties are always
* read. * read.
*/ */
File(FileName file, bool readProperties = true, File(FileName file, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average); Properties::ReadStyle propertiesStyle = Properties::Average);
/*! /*!
@ -67,7 +67,7 @@ namespace TagLib {
* \note TagLib will *not* take ownership of the stream, the caller is * \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object. * responsible for deleting it after the File object.
*/ */
File(IOStream *stream, bool readProperties = true, File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average); Properties::ReadStyle propertiesStyle = Properties::Average);
/*! /*!
@ -112,30 +112,14 @@ namespace TagLib {
* Save the file. * Save the file.
* *
* This returns true if the save was successful. * This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
virtual bool save(); virtual bool save();
private: private:
int readBYTE(bool *ok = 0); void read();
int readWORD(bool *ok = 0);
unsigned int readDWORD(bool *ok = 0);
long long readQWORD(bool *ok = 0);
static ByteVector renderString(const String &str, bool includeLength = false);
String readString(int len);
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
friend class Attribute;
friend class Picture;
class BaseObject;
class UnknownObject;
class FilePropertiesObject;
class StreamPropertiesObject;
class ContentDescriptionObject;
class ExtendedContentDescriptionObject;
class HeaderExtensionObject;
class MetadataObject;
class MetadataLibraryObject;
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;

View File

@ -25,10 +25,12 @@
#include <taglib.h> #include <taglib.h>
#include <tdebug.h> #include <tdebug.h>
#include "trefcounter.h" #include <trefcounter.h>
#include "asfattribute.h" #include "asfattribute.h"
#include "asffile.h" #include "asffile.h"
#include "asfpicture.h" #include "asfpicture.h"
#include "asfutils.h"
using namespace TagLib; using namespace TagLib;
@ -134,8 +136,8 @@ ByteVector ASF::Picture::render() const
return return
ByteVector((char)d->type) + ByteVector((char)d->type) +
ByteVector::fromUInt(d->picture.size(), false) + ByteVector::fromUInt(d->picture.size(), false) +
ASF::File::renderString(d->mimeType) + renderString(d->mimeType) +
ASF::File::renderString(d->description) + renderString(d->description) +
d->picture; d->picture;
} }

View File

@ -205,8 +205,8 @@ namespace TagLib
/* THIS IS PRIVATE, DON'T TOUCH IT! */ /* THIS IS PRIVATE, DON'T TOUCH IT! */
void parse(const ByteVector& ); void parse(const ByteVector& );
static Picture fromInvalid(); static Picture fromInvalid();
friend class Attribute;
#endif #endif
private: private:
class PicturePrivate; class PicturePrivate;
PicturePrivate *d; PicturePrivate *d;

View File

@ -32,11 +32,23 @@ using namespace TagLib;
class ASF::Properties::PropertiesPrivate class ASF::Properties::PropertiesPrivate
{ {
public: public:
PropertiesPrivate(): length(0), bitrate(0), sampleRate(0), channels(0), encrypted(false) {} PropertiesPrivate() :
length(0),
bitrate(0),
sampleRate(0),
channels(0),
bitsPerSample(0),
codec(ASF::Properties::Unknown),
encrypted(false) {}
int length; int length;
int bitrate; int bitrate;
int sampleRate; int sampleRate;
int channels; int channels;
int bitsPerSample;
ASF::Properties::Codec codec;
String codecName;
String codecDescription;
bool encrypted; bool encrypted;
}; };
@ -44,18 +56,28 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
ASF::Properties::Properties() : AudioProperties(AudioProperties::Average) ASF::Properties::Properties() :
AudioProperties(AudioProperties::Average),
d(new PropertiesPrivate())
{ {
d = new PropertiesPrivate;
} }
ASF::Properties::~Properties() ASF::Properties::~Properties()
{ {
if(d) delete d;
delete d;
} }
int ASF::Properties::length() const int ASF::Properties::length() const
{
return lengthInSeconds();
}
int ASF::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int ASF::Properties::lengthInMilliseconds() const
{ {
return d->length; return d->length;
} }
@ -75,6 +97,26 @@ int ASF::Properties::channels() const
return d->channels; return d->channels;
} }
int ASF::Properties::bitsPerSample() const
{
return d->bitsPerSample;
}
ASF::Properties::Codec ASF::Properties::codec() const
{
return d->codec;
}
String ASF::Properties::codecName() const
{
return d->codecName;
}
String ASF::Properties::codecDescription() const
{
return d->codecDescription;
}
bool ASF::Properties::isEncrypted() const bool ASF::Properties::isEncrypted() const
{ {
return d->encrypted; return d->encrypted;
@ -84,28 +126,69 @@ bool ASF::Properties::isEncrypted() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void ASF::Properties::setLength(int length) void ASF::Properties::setLength(int /*length*/)
{ {
d->length = length; debug("ASF::Properties::setLength() -- This method is deprecated. Do not use.");
} }
void ASF::Properties::setBitrate(int length) void ASF::Properties::setLengthInMilliseconds(int value)
{ {
d->bitrate = length; d->length = value;
} }
void ASF::Properties::setSampleRate(int length) void ASF::Properties::setBitrate(int value)
{ {
d->sampleRate = length; d->bitrate = value;
} }
void ASF::Properties::setChannels(int length) void ASF::Properties::setSampleRate(int value)
{ {
d->channels = length; d->sampleRate = value;
} }
void ASF::Properties::setEncrypted(bool encrypted) void ASF::Properties::setChannels(int value)
{ {
d->encrypted = encrypted; d->channels = value;
} }
void ASF::Properties::setBitsPerSample(int value)
{
d->bitsPerSample = value;
}
void ASF::Properties::setCodec(int value)
{
switch(value)
{
case 0x0160:
d->codec = WMA1;
break;
case 0x0161:
d->codec = WMA2;
break;
case 0x0162:
d->codec = WMA9Pro;
break;
case 0x0163:
d->codec = WMA9Lossless;
break;
default:
d->codec = Unknown;
break;
}
}
void ASF::Properties::setCodecName(const String &value)
{
d->codecName = value;
}
void ASF::Properties::setCodecDescription(const String &value)
{
d->codecDescription = value;
}
void ASF::Properties::setEncrypted(bool value)
{
d->encrypted = value;
}

View File

@ -40,7 +40,38 @@ namespace TagLib {
public: public:
/*! /*!
* Create an instance of ASF::Properties. * Audio codec types can be used in ASF file.
*/
enum Codec
{
/*!
* Couldn't detect the codec.
*/
Unknown = 0,
/*!
* Windows Media Audio 1
*/
WMA1,
/*!
* Windows Media Audio 2 or above
*/
WMA2,
/*!
* Windows Media Audio 9 Professional
*/
WMA9Pro,
/*!
* Windows Media Audio 9 Lossless
*/
WMA9Lossless,
};
/*!
* Creates an instance of ASF::Properties.
*/ */
Properties(); Properties();
@ -49,18 +80,97 @@ namespace TagLib {
*/ */
virtual ~Properties(); virtual ~Properties();
// Reimplementations. /*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const; virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const; virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const; virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const; virtual int channels() const;
/*!
* Returns the number of bits per audio sample.
*/
int bitsPerSample() const;
/*!
* Returns the codec used in the file.
*
* \see codecName()
* \see codecDescription()
*/
Codec codec() const;
/*!
* Returns the concrete codec name, for example "Windows Media Audio 9.1"
* used in the file if available, otherwise an empty string.
*
* \see codec()
* \see codecDescription()
*/
String codecName() const;
/*!
* Returns the codec description, typically contains the encoder settings,
* for example "VBR Quality 50, 44kHz, stereo 1-pass VBR" if available,
* otherwise an empty string.
*
* \see codec()
* \see codecName()
*/
String codecDescription() const;
/*!
* Returns whether or not the file is encrypted.
*/
bool isEncrypted() const; bool isEncrypted() const;
#ifndef DO_NOT_DOCUMENT #ifndef DO_NOT_DOCUMENT
// deprecated
void setLength(int value); void setLength(int value);
void setLengthInMilliseconds(int value);
void setBitrate(int value); void setBitrate(int value);
void setSampleRate(int value); void setSampleRate(int value);
void setChannels(int value); void setChannels(int value);
void setBitsPerSample(int value);
void setCodec(int value);
void setCodecName(const String &value);
void setCodecDescription(const String &value);
void setEncrypted(bool value); void setEncrypted(bool value);
#endif #endif

View File

@ -47,8 +47,7 @@ ASF::Tag::Tag()
ASF::Tag::~Tag() ASF::Tag::~Tag()
{ {
if(d) delete d;
delete d;
} }
String ASF::Tag::title() const String ASF::Tag::title() const
@ -161,11 +160,24 @@ ASF::AttributeListMap& ASF::Tag::attributeListMap()
return d->attributeListMap; return d->attributeListMap;
} }
const ASF::AttributeListMap &ASF::Tag::attributeListMap() const
{
return d->attributeListMap;
}
bool ASF::Tag::contains(const String &key) const
{
return d->attributeListMap.contains(key);
}
void ASF::Tag::removeItem(const String &key) void ASF::Tag::removeItem(const String &key)
{ {
AttributeListMap::Iterator it = d->attributeListMap.find(key); d->attributeListMap.erase(key);
if(it != d->attributeListMap.end()) }
d->attributeListMap.erase(it);
ASF::AttributeList ASF::Tag::attribute(const String &name) const
{
return d->attributeListMap[name];
} }
void ASF::Tag::setAttribute(const String &name, const Attribute &attribute) void ASF::Tag::setAttribute(const String &name, const Attribute &attribute)
@ -175,6 +187,11 @@ void ASF::Tag::setAttribute(const String &name, const Attribute &attribute)
d->attributeListMap.insert(name, value); d->attributeListMap.insert(name, value);
} }
void ASF::Tag::setAttribute(const String &name, const AttributeList &values)
{
d->attributeListMap.insert(name, values);
}
void ASF::Tag::addAttribute(const String &name, const Attribute &attribute) void ASF::Tag::addAttribute(const String &name, const Attribute &attribute)
{ {
if(d->attributeListMap.contains(name)) { if(d->attributeListMap.contains(name)) {
@ -195,6 +212,7 @@ bool ASF::Tag::isEmpty() const
static const char *keyTranslation[][2] = { static const char *keyTranslation[][2] = {
{ "WM/AlbumTitle", "ALBUM" }, { "WM/AlbumTitle", "ALBUM" },
{ "WM/AlbumArtist", "ALBUMARTIST" },
{ "WM/Composer", "COMPOSER" }, { "WM/Composer", "COMPOSER" },
{ "WM/Writer", "WRITER" }, { "WM/Writer", "WRITER" },
{ "WM/Conductor", "CONDUCTOR" }, { "WM/Conductor", "CONDUCTOR" },

View File

@ -152,24 +152,43 @@ namespace TagLib {
virtual bool isEmpty() const; virtual bool isEmpty() const;
/*! /*!
* Returns a reference to the item list map. This is an AttributeListMap of * \deprecated
* all of the items in the tag.
*
* This is the most powerfull structure for accessing the items of the tag.
*/ */
AttributeListMap &attributeListMap(); AttributeListMap &attributeListMap();
/*!
* Returns a reference to the item list map. This is an AttributeListMap of
* all of the items in the tag.
*/
const AttributeListMap &attributeListMap() const;
/*!
* \return True if a value for \a attribute is currently set.
*/
bool contains(const String &name) const;
/*! /*!
* Removes the \a key attribute from the tag * Removes the \a key attribute from the tag
*/ */
void removeItem(const String &name); void removeItem(const String &name);
/*!
* \return The list of values for the key \a name, or an empty list if no
* values have been set.
*/
AttributeList attribute(const String &name) const;
/*! /*!
* Sets the \a key attribute to the value of \a attribute. If an attribute * Sets the \a key attribute to the value of \a attribute. If an attribute
* with the \a key is already present, it will be replaced. * with the \a key is already present, it will be replaced.
*/ */
void setAttribute(const String &name, const Attribute &attribute); void setAttribute(const String &name, const Attribute &attribute);
/*!
* Sets multiple \a values to the key \a name.
*/
void setAttribute(const String &name, const AttributeList &values);
/*! /*!
* Sets the \a key attribute to the value of \a attribute. If an attribute * Sets the \a key attribute to the value of \a attribute. If an attribute
* with the \a key is already present, it will be added to the list. * with the \a key is already present, it will be added to the list.

101
3rdparty/taglib/asf/asfutils.h vendored Normal file
View File

@ -0,0 +1,101 @@
/***************************************************************************
copyright : (C) 2015 by Tsuda Kageyu
email : tsuda.kageyu@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_ASFUTILS_H
#define TAGLIB_ASFUTILS_H
// THIS FILE IS NOT A PART OF THE TAGLIB API
#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header
namespace TagLib
{
namespace ASF
{
inline ushort readWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(2);
if(v.size() != 2) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUShort(false);
}
inline uint readDWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(4);
if(v.size() != 4) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toUInt(false);
}
inline long long readQWORD(File *file, bool *ok = 0)
{
const ByteVector v = file->readBlock(8);
if(v.size() != 8) {
if(ok) *ok = false;
return 0;
}
if(ok) *ok = true;
return v.toLongLong(false);
}
inline String readString(File *file, int length)
{
ByteVector data = file->readBlock(length);
unsigned int size = data.size();
while (size >= 2) {
if(data[size - 1] != '\0' || data[size - 2] != '\0') {
break;
}
size -= 2;
}
if(size != data.size()) {
data.resize(size);
}
return String(data, String::UTF16LE);
}
inline ByteVector renderString(const String &str, bool includeLength = false)
{
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false);
if(includeLength) {
data = ByteVector::fromShort(data.size(), false) + data;
}
return data;
}
}
}
#endif
#endif

View File

@ -23,6 +23,22 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include <tbytevector.h>
#include "aiffproperties.h"
#include "apeproperties.h"
#include "asfproperties.h"
#include "flacproperties.h"
#include "mp4properties.h"
#include "mpcproperties.h"
#include "mpegproperties.h"
#include "opusproperties.h"
#include "speexproperties.h"
#include "trueaudioproperties.h"
#include "vorbisproperties.h"
#include "wavproperties.h"
#include "wavpackproperties.h"
#include "audioproperties.h" #include "audioproperties.h"
using namespace TagLib; using namespace TagLib;
@ -41,11 +57,108 @@ AudioProperties::~AudioProperties()
} }
int TagLib::AudioProperties::lengthInSeconds() const
{
// This is an ugly workaround but we can't add a virtual function.
// Should be virtual in taglib2.
if(dynamic_cast<const APE::Properties*>(this))
return dynamic_cast<const APE::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const ASF::Properties*>(this))
return dynamic_cast<const ASF::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const FLAC::Properties*>(this))
return dynamic_cast<const FLAC::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const MP4::Properties*>(this))
return dynamic_cast<const MP4::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const MPC::Properties*>(this))
return dynamic_cast<const MPC::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const MPEG::Properties*>(this))
return dynamic_cast<const MPEG::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const Ogg::Opus::Properties*>(this))
return dynamic_cast<const Ogg::Opus::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const Ogg::Speex::Properties*>(this))
return dynamic_cast<const Ogg::Speex::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const TrueAudio::Properties*>(this))
return dynamic_cast<const TrueAudio::Properties*>(this)->lengthInSeconds();
else if (dynamic_cast<const RIFF::AIFF::Properties*>(this))
return dynamic_cast<const RIFF::AIFF::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const RIFF::WAV::Properties*>(this))
return dynamic_cast<const RIFF::WAV::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const Vorbis::Properties*>(this))
return dynamic_cast<const Vorbis::Properties*>(this)->lengthInSeconds();
else if(dynamic_cast<const WavPack::Properties*>(this))
return dynamic_cast<const WavPack::Properties*>(this)->lengthInSeconds();
else
return 0;
}
int TagLib::AudioProperties::lengthInMilliseconds() const
{
// This is an ugly workaround but we can't add a virtual function.
// Should be virtual in taglib2.
if(dynamic_cast<const APE::Properties*>(this))
return dynamic_cast<const APE::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const ASF::Properties*>(this))
return dynamic_cast<const ASF::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const FLAC::Properties*>(this))
return dynamic_cast<const FLAC::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const MP4::Properties*>(this))
return dynamic_cast<const MP4::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const MPC::Properties*>(this))
return dynamic_cast<const MPC::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const MPEG::Properties*>(this))
return dynamic_cast<const MPEG::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const Ogg::Opus::Properties*>(this))
return dynamic_cast<const Ogg::Opus::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const Ogg::Speex::Properties*>(this))
return dynamic_cast<const Ogg::Speex::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const TrueAudio::Properties*>(this))
return dynamic_cast<const TrueAudio::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const RIFF::AIFF::Properties*>(this))
return dynamic_cast<const RIFF::AIFF::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const RIFF::WAV::Properties*>(this))
return dynamic_cast<const RIFF::WAV::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const Vorbis::Properties*>(this))
return dynamic_cast<const Vorbis::Properties*>(this)->lengthInMilliseconds();
else if(dynamic_cast<const WavPack::Properties*>(this))
return dynamic_cast<const WavPack::Properties*>(this)->lengthInMilliseconds();
else
return 0;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// protected methods // protected methods
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
AudioProperties::AudioProperties(ReadStyle) AudioProperties::AudioProperties(ReadStyle) :
d(0)
{ {
} }

View File

@ -34,7 +34,7 @@ namespace TagLib {
/*! /*!
* The values here are common to most audio formats. For more specific, codec * The values here are common to most audio formats. For more specific, codec
* dependant values, please see see the subclasses APIs. This is meant to * dependent values, please see see the subclasses APIs. This is meant to
* compliment the TagLib::File and TagLib::Tag APIs in providing a simple * compliment the TagLib::File and TagLib::Tag APIs in providing a simple
* interface that is sufficient for most applications. * interface that is sufficient for most applications.
*/ */
@ -69,6 +69,23 @@ namespace TagLib {
*/ */
virtual int length() const = 0; virtual int length() const = 0;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*! /*!
* Returns the most appropriate bit rate for the file in kb/s. For constant * Returns the most appropriate bit rate for the file in kb/s. For constant
* bitrate formats this is simply the bitrate of the file. For variable * bitrate formats this is simply the bitrate of the file. For variable

View File

@ -265,7 +265,7 @@ File *FileRef::create(FileName fileName, bool readAudioProperties,
return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle); return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "WMA" || ext == "ASF") if(ext == "WMA" || ext == "ASF")
return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle); return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "AIF" || ext == "AIFF") if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC")
return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle); return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "WAV") if(ext == "WAV")
return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle); return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle);

View File

@ -72,7 +72,7 @@ namespace TagLib {
* *
* class MyFileTypeResolver : FileTypeResolver * class MyFileTypeResolver : FileTypeResolver
* { * {
* TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) * TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) const
* { * {
* if(someCheckForAnMP3File(fileName)) * if(someCheckForAnMP3File(fileName))
* return new TagLib::MPEG::File(fileName); * return new TagLib::MPEG::File(fileName);
@ -128,7 +128,7 @@ namespace TagLib {
audioPropertiesStyle = AudioProperties::Average); audioPropertiesStyle = AudioProperties::Average);
/*! /*!
* Contruct a FileRef using \a file. The FileRef now takes ownership of the * Construct a FileRef using \a file. The FileRef now takes ownership of the
* pointer and will delete the File when it passes out of scope. * pointer and will delete the File when it passes out of scope.
*/ */
explicit FileRef(File *file); explicit FileRef(File *file);
@ -191,7 +191,7 @@ namespace TagLib {
* is tried. * is tried.
* *
* Returns a pointer to the added resolver (the same one that's passed in -- * Returns a pointer to the added resolver (the same one that's passed in --
* this is mostly so that static inialializers have something to use for * this is mostly so that static initializers have something to use for
* assignment). * assignment).
* *
* \see FileTypeResolver * \see FileTypeResolver
@ -209,7 +209,7 @@ namespace TagLib {
* by TagLib for resolution is case-insensitive. * by TagLib for resolution is case-insensitive.
* *
* \note This does not account for any additional file type resolvers that * \note This does not account for any additional file type resolvers that
* are plugged in. Also note that this is not intended to replace a propper * are plugged in. Also note that this is not intended to replace a proper
* mime-type resolution system, but is just here for reference. * mime-type resolution system, but is just here for reference.
* *
* \see FileTypeResolver * \see FileTypeResolver

View File

@ -60,7 +60,6 @@ public:
properties(0), properties(0),
flacStart(0), flacStart(0),
streamStart(0), streamStart(0),
streamLength(0),
scanned(false), scanned(false),
hasXiphComment(false), hasXiphComment(false),
hasID3v2(false), hasID3v2(false),
@ -86,13 +85,11 @@ public:
TagUnion tag; TagUnion tag;
Properties *properties; Properties *properties;
ByteVector streamInfoData;
ByteVector xiphCommentData; ByteVector xiphCommentData;
List<MetadataBlock *> blocks; List<MetadataBlock *> blocks;
long flacStart; long flacStart;
long streamStart; long streamStart;
long streamLength;
bool scanned; bool scanned;
bool hasXiphComment; bool hasXiphComment;
@ -104,33 +101,32 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
FLAC::File::File(FileName file, bool readProperties, FLAC::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
Properties::ReadStyle propertiesStyle) : TagLib::File(file),
TagLib::File(file) d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory, FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
bool readProperties, Properties::ReadStyle propertiesStyle) : bool readProperties, Properties::ReadStyle) :
TagLib::File(file) TagLib::File(file),
d(new FilePrivate())
{ {
d = new FilePrivate;
d->ID3v2FrameFactory = frameFactory; d->ID3v2FrameFactory = frameFactory;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
bool readProperties, Properties::ReadStyle propertiesStyle) : bool readProperties, Properties::ReadStyle) :
TagLib::File(stream) TagLib::File(stream),
d(new FilePrivate())
{ {
d = new FilePrivate;
d->ID3v2FrameFactory = frameFactory; d->ID3v2FrameFactory = frameFactory;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
FLAC::File::~File() FLAC::File::~File()
@ -176,7 +172,6 @@ FLAC::Properties *FLAC::File::audioProperties() const
return d->properties; return d->properties;
} }
bool FLAC::File::save() bool FLAC::File::save()
{ {
if(readOnly()) { if(readOnly()) {
@ -235,7 +230,7 @@ bool FLAC::File::save()
long originalLength = d->streamStart - d->flacStart; long originalLength = d->streamStart - d->flacStart;
int paddingLength = originalLength - data.size() - 4; int paddingLength = originalLength - data.size() - 4;
if (paddingLength < 0) { if(paddingLength <= 0) {
paddingLength = MinPaddingLength; paddingLength = MinPaddingLength;
} }
ByteVector padding = ByteVector::fromUInt(paddingLength); ByteVector padding = ByteVector::fromUInt(paddingLength);
@ -263,8 +258,16 @@ bool FLAC::File::save()
} }
if(ID3v1Tag()) { if(ID3v1Tag()) {
seek(-128, End); if(d->hasID3v1) {
seek(d->ID3v1Location);
}
else {
seek(0, End);
d->ID3v1Location = tell();
}
writeBlock(ID3v1Tag()->render()); writeBlock(ID3v1Tag()->render());
d->hasID3v1 = true;
} }
return true; return true;
@ -294,219 +297,16 @@ void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
d->ID3v2FrameFactory = factory; d->ID3v2FrameFactory = factory;
} }
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
{
// Look for an ID3v2 tag
d->ID3v2Location = findID3v2();
if(d->ID3v2Location >= 0) {
d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
if(ID3v2Tag()->header()->tagSize() <= 0)
d->tag.set(FlacID3v2Index, 0);
else
d->hasID3v2 = true;
}
// Look for an ID3v1 tag
d->ID3v1Location = findID3v1();
if(d->ID3v1Location >= 0) {
d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for FLAC metadata, including vorbis comments
scan();
if(!isValid())
return;
if(d->hasXiphComment)
d->tag.set(FlacXiphIndex, new Ogg::XiphComment(xiphCommentData()));
else
d->tag.set(FlacXiphIndex, new Ogg::XiphComment);
if(readProperties)
d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle);
}
ByteVector FLAC::File::streamInfoData() ByteVector FLAC::File::streamInfoData()
{ {
return isValid() ? d->streamInfoData : ByteVector(); debug("FLAC::File::streamInfoData() -- This function is obsolete. Returning an empty ByteVector.");
} return ByteVector();
ByteVector FLAC::File::xiphCommentData() const
{
return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector();
} }
long FLAC::File::streamLength() long FLAC::File::streamLength()
{ {
return d->streamLength; debug("FLAC::File::streamLength() -- This function is obsolete. Returning zero.");
} return 0;
void FLAC::File::scan()
{
// Scan the metadata pages
if(d->scanned)
return;
if(!isValid())
return;
long nextBlockOffset;
if(d->hasID3v2)
nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
else
nextBlockOffset = find("fLaC");
if(nextBlockOffset < 0) {
debug("FLAC::File::scan() -- FLAC stream not found");
setValid(false);
return;
}
nextBlockOffset += 4;
d->flacStart = nextBlockOffset;
seek(nextBlockOffset);
ByteVector header = readBlock(4);
// Header format (from spec):
// <1> Last-metadata-block flag
// <7> BLOCK_TYPE
// 0 : STREAMINFO
// 1 : PADDING
// ..
// 4 : VORBIS_COMMENT
// ..
// <24> Length of metadata to follow
char blockType = header[0] & 0x7f;
bool isLastBlock = (header[0] & 0x80) != 0;
uint length = header.toUInt(1U, 3U);
// First block should be the stream_info metadata
if(blockType != MetadataBlock::StreamInfo) {
debug("FLAC::File::scan() -- invalid FLAC stream");
setValid(false);
return;
}
d->streamInfoData = readBlock(length);
d->blocks.append(new UnknownMetadataBlock(blockType, d->streamInfoData));
nextBlockOffset += length + 4;
// Search through the remaining metadata
while(!isLastBlock) {
header = readBlock(4);
blockType = header[0] & 0x7f;
isLastBlock = (header[0] & 0x80) != 0;
length = header.toUInt(1U, 3U);
ByteVector data = readBlock(length);
if(data.size() != length || length == 0) {
debug("FLAC::File::scan() -- FLAC stream corrupted");
setValid(false);
return;
}
MetadataBlock *block = 0;
// Found the vorbis-comment
if(blockType == MetadataBlock::VorbisComment) {
if(!d->hasXiphComment) {
d->xiphCommentData = data;
d->hasXiphComment = true;
}
else {
debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one");
}
}
else if(blockType == MetadataBlock::Picture) {
FLAC::Picture *picture = new FLAC::Picture();
if(picture->parse(data)) {
block = picture;
}
else {
debug("FLAC::File::scan() -- invalid picture found, discarting");
delete picture;
}
}
if(!block) {
block = new UnknownMetadataBlock(blockType, data);
}
if(block->code() != MetadataBlock::Padding) {
d->blocks.append(block);
}
else {
delete block;
}
nextBlockOffset += length + 4;
if(nextBlockOffset >= File::length()) {
debug("FLAC::File::scan() -- FLAC stream corrupted");
setValid(false);
return;
}
seek(nextBlockOffset);
}
// End of metadata, now comes the datastream
d->streamStart = nextBlockOffset;
d->streamLength = File::length() - d->streamStart;
if(d->hasID3v1)
d->streamLength -= 128;
d->scanned = true;
}
long FLAC::File::findID3v1()
{
if(!isValid())
return -1;
seek(-128, End);
long p = tell();
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p;
return -1;
}
long FLAC::File::findID3v2()
{
if(!isValid())
return -1;
seek(0);
if(readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
} }
List<FLAC::Picture *> FLAC::File::pictureList() List<FLAC::Picture *> FLAC::File::pictureList()
@ -566,3 +366,216 @@ bool FLAC::File::hasID3v2Tag() const
{ {
return d->hasID3v2; return d->hasID3v2;
} }
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void FLAC::File::read(bool readProperties)
{
// Look for an ID3v2 tag
d->ID3v2Location = findID3v2();
if(d->ID3v2Location >= 0) {
d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
if(ID3v2Tag()->header()->tagSize() <= 0)
d->tag.set(FlacID3v2Index, 0);
else
d->hasID3v2 = true;
}
// Look for an ID3v1 tag
d->ID3v1Location = findID3v1();
if(d->ID3v1Location >= 0) {
d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
d->hasID3v1 = true;
}
// Look for FLAC metadata, including vorbis comments
scan();
if(!isValid())
return;
if(d->hasXiphComment)
d->tag.set(FlacXiphIndex, new Ogg::XiphComment(d->xiphCommentData));
else
d->tag.set(FlacXiphIndex, new Ogg::XiphComment);
if(readProperties) {
// First block should be the stream_info metadata
const ByteVector infoData = d->blocks.front()->render();
long streamLength;
if(d->hasID3v1)
streamLength = d->ID3v1Location - d->streamStart;
else
streamLength = File::length() - d->streamStart;
d->properties = new Properties(infoData, streamLength);
}
}
void FLAC::File::scan()
{
// Scan the metadata pages
if(d->scanned)
return;
if(!isValid())
return;
long nextBlockOffset;
if(d->hasID3v2)
nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize);
else
nextBlockOffset = find("fLaC");
if(nextBlockOffset < 0) {
debug("FLAC::File::scan() -- FLAC stream not found");
setValid(false);
return;
}
nextBlockOffset += 4;
d->flacStart = nextBlockOffset;
seek(nextBlockOffset);
ByteVector header = readBlock(4);
// Header format (from spec):
// <1> Last-metadata-block flag
// <7> BLOCK_TYPE
// 0 : STREAMINFO
// 1 : PADDING
// ..
// 4 : VORBIS_COMMENT
// ..
// <24> Length of metadata to follow
char blockType = header[0] & 0x7f;
bool isLastBlock = (header[0] & 0x80) != 0;
uint length = header.toUInt(1U, 3U);
// First block should be the stream_info metadata
if(blockType != MetadataBlock::StreamInfo) {
debug("FLAC::File::scan() -- invalid FLAC stream");
setValid(false);
return;
}
d->blocks.append(new UnknownMetadataBlock(blockType, readBlock(length)));
nextBlockOffset += length + 4;
// Search through the remaining metadata
while(!isLastBlock) {
header = readBlock(4);
blockType = header[0] & 0x7f;
isLastBlock = (header[0] & 0x80) != 0;
length = header.toUInt(1U, 3U);
if(length == 0 && blockType != MetadataBlock::Padding) {
debug("FLAC::File::scan() -- Zero-sized metadata block found");
setValid(false);
return;
}
const ByteVector data = readBlock(length);
if(data.size() != length) {
debug("FLAC::File::scan() -- Failed to read a metadata block");
setValid(false);
return;
}
MetadataBlock *block = 0;
// Found the vorbis-comment
if(blockType == MetadataBlock::VorbisComment) {
if(!d->hasXiphComment) {
d->xiphCommentData = data;
d->hasXiphComment = true;
}
else {
debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one");
}
}
else if(blockType == MetadataBlock::Picture) {
FLAC::Picture *picture = new FLAC::Picture();
if(picture->parse(data)) {
block = picture;
}
else {
debug("FLAC::File::scan() -- invalid picture found, discarding");
delete picture;
}
}
if(!block) {
block = new UnknownMetadataBlock(blockType, data);
}
if(block->code() != MetadataBlock::Padding) {
d->blocks.append(block);
}
else {
delete block;
}
nextBlockOffset += length + 4;
if(nextBlockOffset >= File::length()) {
debug("FLAC::File::scan() -- FLAC stream corrupted");
setValid(false);
return;
}
seek(nextBlockOffset);
}
// End of metadata, now comes the datastream
d->streamStart = nextBlockOffset;
d->scanned = true;
}
long FLAC::File::findID3v1()
{
if(!isValid())
return -1;
seek(-128, End);
long p = tell();
if(readBlock(3) == ID3v1::Tag::fileIdentifier())
return p;
return -1;
}
long FLAC::File::findID3v2()
{
if(!isValid())
return -1;
seek(0);
if(readBlock(3) == ID3v2::Header::fileIdentifier())
return 0;
return -1;
}

View File

@ -46,7 +46,7 @@ namespace TagLib {
/*! /*!
* This is implementation of FLAC metadata for non-Ogg FLAC files. At some * This is implementation of FLAC metadata for non-Ogg FLAC files. At some
* point when Ogg / FLAC is more common there will be a similar implementation * point when Ogg / FLAC is more common there will be a similar implementation
* under the Ogg hiearchy. * under the Ogg hierarchy.
* *
* This supports ID3v1, ID3v2 and Xiph style comments as well as reading stream * This supports ID3v1, ID3v2 and Xiph style comments as well as reading stream
* properties from the file. * properties from the file.
@ -79,7 +79,7 @@ namespace TagLib {
Properties::ReadStyle propertiesStyle = Properties::Average); Properties::ReadStyle propertiesStyle = Properties::Average);
/*! /*!
* Constructs an APE file from \a file. If \a readProperties is true the * Constructs an FLAC file from \a file. If \a readProperties is true the
* file's audio properties will also be read. * file's audio properties will also be read.
* *
* If this file contains and ID3v2 tag the frames will be created using * If this file contains and ID3v2 tag the frames will be created using
@ -155,6 +155,9 @@ namespace TagLib {
* has no XiphComment, one will be constructed from the ID3-tags. * has no XiphComment, one will be constructed from the ID3-tags.
* *
* This returns true if the save was successful. * This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
virtual bool save(); virtual bool save();
@ -165,8 +168,8 @@ namespace TagLib {
* if there is no valid ID3v2 tag. If \a create is true it will create * if there is no valid ID3v2 tag. If \a create is true it will create
* an ID3v2 tag if one does not exist and returns a valid pointer. * an ID3v2 tag if one does not exist and returns a valid pointer.
* *
* \note This may return a valid pointer regardless of whether or not the * \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file
* on disk actually has an ID3v2 tag. * on disk actually has an ID3v2 tag.
* *
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be * \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -184,8 +187,8 @@ namespace TagLib {
* if there is no valid APE tag. If \a create is true it will create * if there is no valid APE tag. If \a create is true it will create
* an APE tag if one does not exist and returns a valid pointer. * an APE tag if one does not exist and returns a valid pointer.
* *
* \note This may return a valid pointer regardless of whether or not the * \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
* on disk actually has an ID3v1 tag. * on disk actually has an ID3v1 tag.
* *
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be * \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -203,10 +206,10 @@ namespace TagLib {
* if there is no valid XiphComment. If \a create is true it will create * if there is no valid XiphComment. If \a create is true it will create
* a XiphComment if one does not exist and returns a valid pointer. * a XiphComment if one does not exist and returns a valid pointer.
* *
* \note This may return a valid pointer regardless of whether or not the * \note This may return a valid pointer regardless of whether or not the
* file on disk has a XiphComment. Use hasXiphComment() to check if the * file on disk has a XiphComment. Use hasXiphComment() to check if the
* file on disk actually has a XiphComment. * file on disk actually has a XiphComment.
* *
* \note The Tag <b>is still</b> owned by the FLAC::File and should not be * \note The Tag <b>is still</b> owned by the FLAC::File and should not be
* deleted by the user. It will be deleted when the file (object) is * deleted by the user. It will be deleted when the file (object) is
* destroyed. * destroyed.
@ -221,6 +224,7 @@ namespace TagLib {
* when * when
* *
* \see ID3v2FrameFactory * \see ID3v2FrameFactory
* \deprecated This value should be passed in via the constructor
*/ */
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
@ -228,7 +232,7 @@ namespace TagLib {
* Returns the block of data used by FLAC::Properties for parsing the * Returns the block of data used by FLAC::Properties for parsing the
* stream properties. * stream properties.
* *
* \deprecated This method will not be public in a future release. * \deprecated Always returns an empty vector.
*/ */
ByteVector streamInfoData(); // BIC: remove ByteVector streamInfoData(); // BIC: remove
@ -236,7 +240,7 @@ namespace TagLib {
* Returns the length of the audio-stream, used by FLAC::Properties for * Returns the length of the audio-stream, used by FLAC::Properties for
* calculating the bitrate. * calculating the bitrate.
* *
* \deprecated This method will not be public in a future release. * \deprecated Always returns zero.
*/ */
long streamLength(); // BIC: remove long streamLength(); // BIC: remove
@ -289,12 +293,10 @@ namespace TagLib {
File(const File &); File(const File &);
File &operator=(const File &); File &operator=(const File &);
void read(bool readProperties, Properties::ReadStyle propertiesStyle); void read(bool readProperties);
void scan(); void scan();
long findID3v2(); long findID3v2();
long findID3v1(); long findID3v1();
ByteVector xiphCommentData() const;
long findPaddingBreak(long nextPageOffset, long targetOffset, bool *isLast);
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;

View File

@ -34,24 +34,18 @@ using namespace TagLib;
class FLAC::Properties::PropertiesPrivate class FLAC::Properties::PropertiesPrivate
{ {
public: public:
PropertiesPrivate(ByteVector d, long st, ReadStyle s) : PropertiesPrivate() :
data(d),
streamLength(st),
style(s),
length(0), length(0),
bitrate(0), bitrate(0),
sampleRate(0), sampleRate(0),
sampleWidth(0), bitsPerSample(0),
channels(0), channels(0),
sampleFrames(0) {} sampleFrames(0) {}
ByteVector data;
long streamLength;
ReadStyle style;
int length; int length;
int bitrate; int bitrate;
int sampleRate; int sampleRate;
int sampleWidth; int bitsPerSample;
int channels; int channels;
unsigned long long sampleFrames; unsigned long long sampleFrames;
ByteVector signature; ByteVector signature;
@ -61,16 +55,18 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
FLAC::Properties::Properties(ByteVector data, long streamLength, ReadStyle style) : AudioProperties(style) FLAC::Properties::Properties(ByteVector data, long streamLength, ReadStyle style) :
AudioProperties(style),
d(new PropertiesPrivate())
{ {
d = new PropertiesPrivate(data, streamLength, style); read(data, streamLength);
read();
} }
FLAC::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) FLAC::Properties::Properties(File *, ReadStyle style) :
AudioProperties(style),
d(new PropertiesPrivate())
{ {
d = new PropertiesPrivate(file->streamInfoData(), file->streamLength(), style); debug("FLAC::Properties::Properties() - This constructor is no longer used.");
read();
} }
FLAC::Properties::~Properties() FLAC::Properties::~Properties()
@ -79,6 +75,16 @@ FLAC::Properties::~Properties()
} }
int FLAC::Properties::length() const int FLAC::Properties::length() const
{
return lengthInSeconds();
}
int FLAC::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int FLAC::Properties::lengthInMilliseconds() const
{ {
return d->length; return d->length;
} }
@ -93,9 +99,14 @@ int FLAC::Properties::sampleRate() const
return d->sampleRate; return d->sampleRate;
} }
int FLAC::Properties::bitsPerSample() const
{
return d->bitsPerSample;
}
int FLAC::Properties::sampleWidth() const int FLAC::Properties::sampleWidth() const
{ {
return d->sampleWidth; return bitsPerSample();
} }
int FLAC::Properties::channels() const int FLAC::Properties::channels() const
@ -117,9 +128,9 @@ ByteVector FLAC::Properties::signature() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void FLAC::Properties::read() void FLAC::Properties::read(const ByteVector &data, long streamLength)
{ {
if(d->data.size() < 18) { if(data.size() < 18) {
debug("FLAC::Properties::read() - FLAC properties must contain at least 18 bytes."); debug("FLAC::Properties::read() - FLAC properties must contain at least 18 bytes.");
return; return;
} }
@ -138,32 +149,28 @@ void FLAC::Properties::read()
// Maximum frame size (in bytes) // Maximum frame size (in bytes)
pos += 3; pos += 3;
uint flags = d->data.toUInt(pos, true); const uint flags = data.toUInt(pos, true);
pos += 4; pos += 4;
d->sampleRate = flags >> 12; d->sampleRate = flags >> 12;
d->channels = ((flags >> 9) & 7) + 1; d->channels = ((flags >> 9) & 7) + 1;
d->sampleWidth = ((flags >> 4) & 31) + 1; d->bitsPerSample = ((flags >> 4) & 31) + 1;
// The last 4 bits are the most significant 4 bits for the 36 bit // The last 4 bits are the most significant 4 bits for the 36 bit
// stream length in samples. (Audio files measured in days) // stream length in samples. (Audio files measured in days)
unsigned long long hi = flags & 0xf; const ulonglong hi = flags & 0xf;
unsigned long long lo = d->data.toUInt(pos, true); const ulonglong lo = data.toUInt(pos, true);
pos += 4; pos += 4;
d->sampleFrames = (hi << 32) | lo; d->sampleFrames = (hi << 32) | lo;
if(d->sampleRate > 0) if(d->sampleFrames > 0 && d->sampleRate > 0) {
d->length = int(d->sampleFrames / d->sampleRate); const double length = d->sampleFrames * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
}
// Uncompressed bitrate: if(data.size() >= pos + 16)
d->signature = data.mid(pos, 16);
//d->bitrate = ((d->sampleRate * d->channels) / 1000) * d->sampleWidth;
// Real bitrate:
d->bitrate = d->length > 0 ? ((d->streamLength * 8UL) / d->length) / 1000 : 0;
d->signature = d->data.mid(pos, 32);
} }

View File

@ -64,27 +64,72 @@ namespace TagLib {
*/ */
virtual ~Properties(); virtual ~Properties();
// Reimplementations. /*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const; virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const; virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const; virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const; virtual int channels() const;
/*!
* Returns the number of bits per audio sample as read from the FLAC
* identification header.
*/
int bitsPerSample() const;
/*! /*!
* Returns the sample width as read from the FLAC identification * Returns the sample width as read from the FLAC identification
* header. * header.
*
* \note This method is just an alias of bitsPerSample().
*
* \deprecated
*/ */
int sampleWidth() const; int sampleWidth() const;
/*! /*!
* Return the number of sample frames * Return the number of sample frames.
*/ */
unsigned long long sampleFrames() const; unsigned long long sampleFrames() const;
/*! /*!
* Returns the MD5 signature of the uncompressed audio stream as read * Returns the MD5 signature of the uncompressed audio stream as read
* from the stream info header header. * from the stream info header.
*/ */
ByteVector signature() const; ByteVector signature() const;
@ -92,7 +137,7 @@ namespace TagLib {
Properties(const Properties &); Properties(const Properties &);
Properties &operator=(const Properties &); Properties &operator=(const Properties &);
void read(); void read(const ByteVector &data, long streamLength);
class PropertiesPrivate; class PropertiesPrivate;
PropertiesPrivate *d; PropertiesPrivate *d;

View File

@ -79,6 +79,16 @@ int IT::Properties::length() const
return 0; return 0;
} }
int IT::Properties::lengthInSeconds() const
{
return 0;
}
int IT::Properties::lengthInMilliseconds() const
{
return 0;
}
int IT::Properties::bitrate() const int IT::Properties::bitrate() const
{ {
return 0; return 0;

View File

@ -51,10 +51,12 @@ namespace TagLib {
Properties(AudioProperties::ReadStyle propertiesStyle); Properties(AudioProperties::ReadStyle propertiesStyle);
virtual ~Properties(); virtual ~Properties();
int length() const; int length() const;
int bitrate() const; int lengthInSeconds() const;
int sampleRate() const; int lengthInMilliseconds() const;
int channels() const; int bitrate() const;
int sampleRate() const;
int channels() const;
ushort lengthInPatterns() const; ushort lengthInPatterns() const;
bool stereo() const; bool stereo() const;

View File

@ -55,6 +55,16 @@ int Mod::Properties::length() const
return 0; return 0;
} }
int Mod::Properties::lengthInSeconds() const
{
return 0;
}
int Mod::Properties::lengthInMilliseconds() const
{
return 0;
}
int Mod::Properties::bitrate() const int Mod::Properties::bitrate() const
{ {
return 0; return 0;

View File

@ -35,12 +35,14 @@ namespace TagLib {
Properties(AudioProperties::ReadStyle propertiesStyle); Properties(AudioProperties::ReadStyle propertiesStyle);
virtual ~Properties(); virtual ~Properties();
int length() const; int length() const;
int bitrate() const; int lengthInSeconds() const;
int sampleRate() const; int lengthInMilliseconds() const;
int channels() const; int bitrate() const;
int sampleRate() const;
int channels() const;
uint instrumentCount() const; uint instrumentCount() const;
uchar lengthInPatterns() const; uchar lengthInPatterns() const;
void setChannels(int channels); void setChannels(int channels);

View File

@ -158,7 +158,7 @@ PropertyMap Mod::Tag::setProperties(const PropertyMap &origProps)
// for each tag that has been set above, remove the first entry in the corresponding // for each tag that has been set above, remove the first entry in the corresponding
// value list. The others will be returned as unsupported by this format. // value list. The others will be returned as unsupported by this format.
for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) { for(StringList::ConstIterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) {
if(properties[*it].size() == 1) if(properties[*it].size() == 1)
properties.erase(*it); properties.erase(*it);
else else

View File

@ -97,7 +97,7 @@ namespace TagLib {
* Sets the title to \a title. If \a title is String::null then this * Sets the title to \a title. If \a title is String::null then this
* value will be cleared. * value will be cleared.
* *
* The length limits per file type are (1 characetr = 1 byte): * The length limits per file type are (1 character = 1 byte):
* Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20 * Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20
* characters. * characters.
*/ */
@ -126,7 +126,7 @@ namespace TagLib {
* an thus the line length in comments are limited. Too big comments * an thus the line length in comments are limited. Too big comments
* will be truncated. * will be truncated.
* *
* The line length limits per file type are (1 characetr = 1 byte): * The line length limits per file type are (1 character = 1 byte):
* Mod 22 characters, S3M 27 characters, IT 25 characters and XM 22 * Mod 22 characters, S3M 27 characters, IT 25 characters and XM 22
* characters. * characters.
*/ */
@ -169,7 +169,7 @@ namespace TagLib {
* Implements the unified property interface -- import function. * Implements the unified property interface -- import function.
* Because of the limitations of the module file tag, any tags besides * Because of the limitations of the module file tag, any tags besides
* COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be * COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be
* returened. Additionally, if the map contains tags with multiple values, * returned. Additionally, if the map contains tags with multiple values,
* all but the first will be contained in the returned map of unsupported * all but the first will be contained in the returned map of unsupported
* properties. * properties.
*/ */

View File

@ -23,6 +23,12 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <climits>
#include <tdebug.h> #include <tdebug.h>
#include <tstring.h> #include <tstring.h>
#include "mp4atom.h" #include "mp4atom.h"
@ -50,20 +56,25 @@ MP4::Atom::Atom(File *file)
length = header.toUInt(); length = header.toUInt();
if (length == 1) { if(length == 1) {
const long long longLength = file->readBlock(8).toLongLong(); const long long longLength = file->readBlock(8).toLongLong();
if (longLength >= 8 && longLength <= 0xFFFFFFFF) { if(sizeof(long) == sizeof(long long)) {
// The atom has a 64-bit length, but it's actually a 32-bit value length = static_cast<long>(longLength);
length = (long)longLength;
} }
else { else {
if(longLength <= LONG_MAX) {
// The atom has a 64-bit length, but it's actually a 31-bit value
length = static_cast<long>(longLength);
}
else {
debug("MP4: 64-bit atoms are not supported"); debug("MP4: 64-bit atoms are not supported");
length = 0; length = 0;
file->seek(0, File::End); file->seek(0, File::End);
return; return;
}
} }
} }
if (length < 8) { if(length < 8) {
debug("MP4: Invalid atom size"); debug("MP4: Invalid atom size");
length = 0; length = 0;
file->seek(0, File::End); file->seek(0, File::End);

View File

@ -59,7 +59,7 @@ namespace TagLib {
TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits
TypeGenred = 18, // a list of enumerated values TypeGenred = 18, // a list of enumerated values
TypeInteger = 21, // a signed big-endian integer with length one of { 1,2,3,4,8 } bytes 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 TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit integer
TypeUPC = 25, // Universal Product Code, in text UTF-8 format (valid as an ID) TypeUPC = 25, // Universal Product Code, in text UTF-8 format (valid as an ID)
TypeBMP = 27, // Windows bitmap image TypeBMP = 27, // Windows bitmap image
TypeUndefined = 255 // undefined TypeUndefined = 255 // undefined

View File

@ -54,11 +54,12 @@ MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d)
MP4::CoverArt & MP4::CoverArt &
MP4::CoverArt::operator=(const CoverArt &item) MP4::CoverArt::operator=(const CoverArt &item)
{ {
if(d->deref()) { if(&item != this) {
delete d; if(d->deref())
delete d;
d = item.d;
d->ref();
} }
d = item.d;
d->ref();
return *this; return *this;
} }

View File

@ -32,48 +32,57 @@
using namespace TagLib; using namespace TagLib;
namespace
{
bool checkValid(const MP4::AtomList &list)
{
for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) {
if((*it)->length == 0)
return false;
if(!checkValid((*it)->children))
return false;
}
return true;
}
}
class MP4::File::FilePrivate class MP4::File::FilePrivate
{ {
public: public:
FilePrivate() : tag(0), atoms(0), properties(0) FilePrivate() :
{ tag(0),
} atoms(0),
properties(0) {}
~FilePrivate() ~FilePrivate()
{ {
if(atoms) { delete atoms;
delete atoms; delete tag;
atoms = 0; delete properties;
}
if(tag) {
delete tag;
tag = 0;
}
if(properties) {
delete properties;
properties = 0;
}
} }
MP4::Tag *tag; MP4::Tag *tag;
MP4::Atoms *atoms; MP4::Atoms *atoms;
MP4::Properties *properties; MP4::Properties *properties;
}; };
MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
: TagLib::File(file) TagLib::File(file),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, audioPropertiesStyle); read(readProperties);
} }
MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
: TagLib::File(stream) TagLib::File(stream),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, audioPropertiesStyle); read(readProperties);
} }
MP4::File::~File() MP4::File::~File()
@ -108,26 +117,14 @@ MP4::File::audioProperties() const
return d->properties; 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 void
MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle) MP4::File::read(bool readProperties)
{ {
if(!isValid()) if(!isValid())
return; return;
d->atoms = new Atoms(this); d->atoms = new Atoms(this);
if (!checkValid(d->atoms->atoms)) { if(!checkValid(d->atoms->atoms)) {
setValid(false); setValid(false);
return; return;
} }
@ -141,7 +138,7 @@ MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle)
d->tag = new Tag(this, d->atoms); d->tag = new Tag(this, d->atoms);
if(readProperties) { if(readProperties) {
d->properties = new Properties(this, d->atoms, audioPropertiesStyle); d->properties = new Properties(this, d->atoms);
} }
} }

View File

@ -54,7 +54,7 @@ namespace TagLib {
* *
* \note In the current implementation, \a propertiesStyle is ignored. * \note In the current implementation, \a propertiesStyle is ignored.
*/ */
File(FileName file, bool readProperties = true, File(FileName file, bool readProperties = true,
Properties::ReadStyle audioPropertiesStyle = Properties::Average); Properties::ReadStyle audioPropertiesStyle = Properties::Average);
/*! /*!
@ -66,7 +66,7 @@ namespace TagLib {
* *
* \note In the current implementation, \a propertiesStyle is ignored. * \note In the current implementation, \a propertiesStyle is ignored.
*/ */
File(IOStream *stream, bool readProperties = true, File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle audioPropertiesStyle = Properties::Average); Properties::ReadStyle audioPropertiesStyle = Properties::Average);
/*! /*!
@ -111,13 +111,14 @@ namespace TagLib {
* Save the file. * Save the file.
* *
* This returns true if the save was successful. * This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
bool save(); bool save();
private: private:
void read(bool readProperties);
void read(bool readProperties, Properties::ReadStyle audioPropertiesStyle);
bool checkValid(const MP4::AtomList &list);
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;

View File

@ -64,11 +64,13 @@ MP4::Item::Item(const Item &item) : d(item.d)
MP4::Item & MP4::Item &
MP4::Item::operator=(const Item &item) MP4::Item::operator=(const Item &item)
{ {
if(d->deref()) { if(&item != this) {
delete d; if(d->deref()) {
delete d;
}
d = item.d;
d->ref();
} }
d = item.d;
d->ref();
return *this; return *this;
} }

View File

@ -34,7 +34,14 @@ using namespace TagLib;
class MP4::Properties::PropertiesPrivate class MP4::Properties::PropertiesPrivate
{ {
public: public:
PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), bitsPerSample(0), encrypted(false), codec(MP4::Properties::Unknown) {} PropertiesPrivate() :
length(0),
bitrate(0),
sampleRate(0),
channels(0),
bitsPerSample(0),
encrypted(false),
codec(MP4::Properties::Unknown) {}
int length; int length;
int bitrate; int bitrate;
@ -45,110 +52,15 @@ public:
Codec codec; Codec codec;
}; };
MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) ////////////////////////////////////////////////////////////////////////////////
: AudioProperties(style) // public members
////////////////////////////////////////////////////////////////////////////////
MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) :
AudioProperties(style),
d(new PropertiesPrivate())
{ {
d = new PropertiesPrivate; read(file, atoms);
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() MP4::Properties::~Properties()
@ -170,6 +82,18 @@ MP4::Properties::sampleRate() const
int int
MP4::Properties::length() const MP4::Properties::length() const
{
return lengthInSeconds();
}
int
MP4::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int
MP4::Properties::lengthInMilliseconds() const
{ {
return d->length; return d->length;
} }
@ -192,8 +116,119 @@ MP4::Properties::isEncrypted() const
return d->encrypted; return d->encrypted;
} }
MP4::Properties::Codec MP4::Properties::codec() const MP4::Properties::Codec
MP4::Properties::codec() const
{ {
return d->codec; return d->codec;
} }
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void
MP4::Properties::read(File *file, Atoms *atoms)
{
MP4::Atom *moov = atoms->find("moov");
if(!moov) {
debug("MP4: Atom 'moov' not found");
return;
}
MP4::Atom *trak = 0;
ByteVector data;
const MP4::AtomList trakList = moov->findall("trak");
for(MP4::AtomList::ConstIterator it = trakList.begin(); it != trakList.end(); ++it) {
trak = *it;
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.containsAt("soun", 16)) {
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);
const uint version = data[8];
long long unit;
long long length;
if(version == 1) {
if(data.size() < 36 + 8) {
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
return;
}
unit = data.toLongLong(28U);
length = data.toLongLong(36U);
}
else {
if(data.size() < 24 + 4) {
debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected");
return;
}
unit = data.toUInt(20U);
length = data.toUInt(24U);
}
if(unit > 0 && length > 0)
d->length = static_cast<int>(length * 1000.0 / unit);
MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd");
if(!atom) {
return;
}
file->seek(atom->offset);
data = file->readBlock(atom->length);
if(data.containsAt("mp4a", 20)) {
d->codec = AAC;
d->channels = data.toShort(40U);
d->bitsPerSample = data.toShort(42U);
d->sampleRate = data.toUInt(46U);
if(data.containsAt("esds", 56) && data[64] == 0x03) {
uint pos = 65;
if(data.containsAt("\x80\x80\x80", pos)) {
pos += 3;
}
pos += 4;
if(data[pos] == 0x04) {
pos += 1;
if(data.containsAt("\x80\x80\x80", pos)) {
pos += 3;
}
pos += 10;
d->bitrate = static_cast<int>((data.toUInt(pos) + 500) / 1000.0 + 0.5);
}
}
}
else if(data.containsAt("alac", 20)) {
if(atom->length == 88 && data.containsAt("alac", 56)) {
d->codec = ALAC;
d->bitsPerSample = data.at(69);
d->channels = data.at(73);
d->bitrate = static_cast<int>(data.toUInt(80U) / 1000.0 + 0.5);
d->sampleRate = data.toUInt(84U);
}
}
MP4::Atom *drms = atom->find("drms");
if(drms) {
d->encrypted = true;
}
}

View File

@ -49,17 +49,66 @@ namespace TagLib {
Properties(File *file, Atoms *atoms, ReadStyle style = Average); Properties(File *file, Atoms *atoms, ReadStyle style = Average);
virtual ~Properties(); virtual ~Properties();
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const; virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const; virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const; virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const; virtual int channels() const;
/*!
* Returns the number of bits per audio sample.
*/
virtual int bitsPerSample() const; virtual int bitsPerSample() const;
/*!
* Returns whether or not the file is encrypted.
*/
bool isEncrypted() const; bool isEncrypted() const;
//! Audio codec used in the MP4 file /*!
* Returns the codec used in the file.
*/
Codec codec() const; Codec codec() const;
private: private:
void read(File *file, Atoms *atoms);
class PropertiesPrivate; class PropertiesPrivate;
PropertiesPrivate *d; PropertiesPrivate *d;
}; };

View File

@ -39,7 +39,7 @@ public:
~TagPrivate() {} ~TagPrivate() {}
TagLib::File *file; TagLib::File *file;
Atoms *atoms; Atoms *atoms;
ItemListMap items; ItemMap items;
}; };
MP4::Tag::Tag() MP4::Tag::Tag()
@ -63,36 +63,36 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms)
MP4::Atom *atom = ilst->children[i]; MP4::Atom *atom = ilst->children[i];
file->seek(atom->offset + 8); file->seek(atom->offset + 8);
if(atom->name == "----") { if(atom->name == "----") {
parseFreeForm(atom, file); parseFreeForm(atom);
} }
else if(atom->name == "trkn" || atom->name == "disk") { else if(atom->name == "trkn" || atom->name == "disk") {
parseIntPair(atom, file); parseIntPair(atom);
} }
else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst" || else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst" ||
atom->name == "hdvd") { atom->name == "hdvd") {
parseBool(atom, file); parseBool(atom);
} }
else if(atom->name == "tmpo") { else if(atom->name == "tmpo") {
parseInt(atom, file); parseInt(atom);
} }
else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" || else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" ||
atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") { atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") {
parseUInt(atom, file); parseUInt(atom);
} }
else if(atom->name == "plID") { else if(atom->name == "plID") {
parseLongLong(atom, file); parseLongLong(atom);
} }
else if(atom->name == "stik" || atom->name == "rtng" || atom->name == "akID") { else if(atom->name == "stik" || atom->name == "rtng" || atom->name == "akID") {
parseByte(atom, file); parseByte(atom);
} }
else if(atom->name == "gnre") { else if(atom->name == "gnre") {
parseGnre(atom, file); parseGnre(atom);
} }
else if(atom->name == "covr") { else if(atom->name == "covr") {
parseCovr(atom, file); parseCovr(atom);
} }
else { else {
parseText(atom, file); parseText(atom);
} }
} }
} }
@ -103,15 +103,20 @@ MP4::Tag::~Tag()
} }
MP4::AtomDataList MP4::AtomDataList
MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) MP4::Tag::parseData2(const MP4::Atom *atom, int expectedFlags, bool freeForm)
{ {
AtomDataList result; AtomDataList result;
ByteVector data = file->readBlock(atom->length - 8); ByteVector data = d->file->readBlock(atom->length - 8);
int i = 0; int i = 0;
unsigned int pos = 0; unsigned int pos = 0;
while(pos < data.size()) { while(pos < data.size()) {
const int length = static_cast<int>(data.toUInt(pos)); const int length = static_cast<int>(data.toUInt(pos));
ByteVector name = data.mid(pos + 4, 4); if(length < 12) {
debug("MP4: Too short atom");
return result;
}
const ByteVector name = data.mid(pos + 4, 4);
const int flags = static_cast<int>(data.toUInt(pos + 8)); const int flags = static_cast<int>(data.toUInt(pos + 8));
if(freeForm && i < 2) { if(freeForm && i < 2) {
if(i == 0 && name != "mean") { if(i == 0 && name != "mean") {
@ -140,9 +145,9 @@ MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, boo
} }
ByteVectorList ByteVectorList
MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) MP4::Tag::parseData(const MP4::Atom *atom, int expectedFlags, bool freeForm)
{ {
AtomDataList data = parseData2(atom, file, expectedFlags, freeForm); AtomDataList data = parseData2(atom, expectedFlags, freeForm);
ByteVectorList result; ByteVectorList result;
for(uint i = 0; i < data.size(); i++) { for(uint i = 0; i < data.size(); i++) {
result.append(data[i].data); result.append(data[i].data);
@ -151,45 +156,45 @@ MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool
} }
void void
MP4::Tag::parseInt(MP4::Atom *atom, TagLib::File *file) MP4::Tag::parseInt(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom, file); ByteVectorList data = parseData(atom);
if(data.size()) { if(data.size()) {
addItem(atom->name, (int)data[0].toShort()); addItem(atom->name, (int)data[0].toShort());
} }
} }
void void
MP4::Tag::parseUInt(MP4::Atom *atom, TagLib::File *file) MP4::Tag::parseUInt(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom, file); ByteVectorList data = parseData(atom);
if(data.size()) { if(data.size()) {
addItem(atom->name, data[0].toUInt()); addItem(atom->name, data[0].toUInt());
} }
} }
void void
MP4::Tag::parseLongLong(MP4::Atom *atom, TagLib::File *file) MP4::Tag::parseLongLong(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom, file); ByteVectorList data = parseData(atom);
if(data.size()) { if(data.size()) {
addItem(atom->name, data[0].toLongLong()); addItem(atom->name, data[0].toLongLong());
} }
} }
void void
MP4::Tag::parseByte(MP4::Atom *atom, TagLib::File *file) MP4::Tag::parseByte(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom, file); ByteVectorList data = parseData(atom);
if(data.size()) { if(data.size()) {
addItem(atom->name, (uchar)data[0].at(0)); addItem(atom->name, (uchar)data[0].at(0));
} }
} }
void void
MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file) MP4::Tag::parseGnre(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom, file); ByteVectorList data = parseData(atom);
if(data.size()) { if(data.size()) {
int idx = (int)data[0].toShort(); int idx = (int)data[0].toShort();
if(idx > 0) { if(idx > 0) {
@ -199,9 +204,9 @@ MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file)
} }
void void
MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file) MP4::Tag::parseIntPair(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom, file); ByteVectorList data = parseData(atom);
if(data.size()) { if(data.size()) {
const int a = data[0].toShort(2U); const int a = data[0].toShort(2U);
const int b = data[0].toShort(4U); const int b = data[0].toShort(4U);
@ -210,9 +215,9 @@ MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file)
} }
void void
MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file) MP4::Tag::parseBool(const MP4::Atom *atom)
{ {
ByteVectorList data = parseData(atom, file); ByteVectorList data = parseData(atom);
if(data.size()) { if(data.size()) {
bool value = data[0].size() ? data[0][0] != '\0' : false; bool value = data[0].size() ? data[0][0] != '\0' : false;
addItem(atom->name, value); addItem(atom->name, value);
@ -220,9 +225,9 @@ MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file)
} }
void void
MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags) MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags)
{ {
ByteVectorList data = parseData(atom, file, expectedFlags); ByteVectorList data = parseData(atom, expectedFlags);
if(data.size()) { if(data.size()) {
StringList value; StringList value;
for(unsigned int i = 0; i < data.size(); i++) { for(unsigned int i = 0; i < data.size(); i++) {
@ -233,9 +238,9 @@ MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags)
} }
void void
MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file) MP4::Tag::parseFreeForm(const MP4::Atom *atom)
{ {
AtomDataList data = parseData2(atom, file, -1, true); AtomDataList data = parseData2(atom, -1, true);
if(data.size() > 2) { if(data.size() > 2) {
String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8); String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8);
AtomDataType type = data[2].type; AtomDataType type = data[2].type;
@ -267,20 +272,26 @@ MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file)
} }
void void
MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) MP4::Tag::parseCovr(const MP4::Atom *atom)
{ {
MP4::CoverArtList value; MP4::CoverArtList value;
ByteVector data = file->readBlock(atom->length - 8); ByteVector data = d->file->readBlock(atom->length - 8);
unsigned int pos = 0; unsigned int pos = 0;
while(pos < data.size()) { while(pos < data.size()) {
const int length = static_cast<int>(data.toUInt(pos)); const int length = static_cast<int>(data.toUInt(pos));
ByteVector name = data.mid(pos + 4, 4); if(length < 12) {
debug("MP4: Too short atom");
break;;
}
const ByteVector name = data.mid(pos + 4, 4);
const int flags = static_cast<int>(data.toUInt(pos + 8)); const int flags = static_cast<int>(data.toUInt(pos + 8));
if(name != "data") { if(name != "data") {
debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\"");
break; break;
} }
if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF || flags == TypeImplicit) { if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP ||
flags == TypeGIF || flags == TypeImplicit) {
value.append(MP4::CoverArt(MP4::CoverArt::Format(flags), value.append(MP4::CoverArt(MP4::CoverArt::Format(flags),
data.mid(pos + 16, length - 16))); data.mid(pos + 16, length - 16)));
} }
@ -294,22 +305,22 @@ MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file)
} }
ByteVector ByteVector
MP4::Tag::padIlst(const ByteVector &data, int length) MP4::Tag::padIlst(const ByteVector &data, int length) const
{ {
if (length == -1) { if(length == -1) {
length = ((data.size() + 1023) & ~1023) - data.size(); length = ((data.size() + 1023) & ~1023) - data.size();
} }
return renderAtom("free", ByteVector(length, '\1')); return renderAtom("free", ByteVector(length, '\1'));
} }
ByteVector ByteVector
MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) const
{ {
return ByteVector::fromUInt(data.size() + 8) + name + data; return ByteVector::fromUInt(data.size() + 8) + name + data;
} }
ByteVector ByteVector
MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const
{ {
ByteVector result; ByteVector result;
for(unsigned int i = 0; i < data.size(); i++) { for(unsigned int i = 0; i < data.size(); i++) {
@ -319,7 +330,7 @@ MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &da
} }
ByteVector ByteVector
MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item) MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) const
{ {
ByteVectorList data; ByteVectorList data;
data.append(ByteVector(1, item.toBool() ? '\1' : '\0')); data.append(ByteVector(1, item.toBool() ? '\1' : '\0'));
@ -327,7 +338,7 @@ MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item)
} }
ByteVector ByteVector
MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item) MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) const
{ {
ByteVectorList data; ByteVectorList data;
data.append(ByteVector::fromShort(item.toInt())); data.append(ByteVector::fromShort(item.toInt()));
@ -335,7 +346,7 @@ MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item)
} }
ByteVector ByteVector
MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item) MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) const
{ {
ByteVectorList data; ByteVectorList data;
data.append(ByteVector::fromUInt(item.toUInt())); data.append(ByteVector::fromUInt(item.toUInt()));
@ -343,7 +354,7 @@ MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item)
} }
ByteVector ByteVector
MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item) MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) const
{ {
ByteVectorList data; ByteVectorList data;
data.append(ByteVector::fromLongLong(item.toLongLong())); data.append(ByteVector::fromLongLong(item.toLongLong()));
@ -351,7 +362,7 @@ MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item)
} }
ByteVector ByteVector
MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item) MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) const
{ {
ByteVectorList data; ByteVectorList data;
data.append(ByteVector(1, item.toByte())); data.append(ByteVector(1, item.toByte()));
@ -359,7 +370,7 @@ MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item)
} }
ByteVector ByteVector
MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item) MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) const
{ {
ByteVectorList data; ByteVectorList data;
data.append(ByteVector(2, '\0') + data.append(ByteVector(2, '\0') +
@ -370,7 +381,7 @@ MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item)
} }
ByteVector ByteVector
MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item) MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) const
{ {
ByteVectorList data; ByteVectorList data;
data.append(ByteVector(2, '\0') + data.append(ByteVector(2, '\0') +
@ -380,7 +391,7 @@ MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item)
} }
ByteVector ByteVector
MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags) MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) const
{ {
ByteVectorList data; ByteVectorList data;
StringList value = item.toStringList(); StringList value = item.toStringList();
@ -391,7 +402,7 @@ MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags)
} }
ByteVector ByteVector
MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item) MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const
{ {
ByteVector data; ByteVector data;
MP4::CoverArtList value = item.toCoverArtList(); MP4::CoverArtList value = item.toCoverArtList();
@ -403,7 +414,7 @@ MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item)
} }
ByteVector ByteVector
MP4::Tag::renderFreeForm(const String &name, MP4::Item &item) MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const
{ {
StringList header = StringList::split(name, ":"); StringList header = StringList::split(name, ":");
if (header.size() != 3) { if (header.size() != 3) {
@ -441,38 +452,38 @@ bool
MP4::Tag::save() MP4::Tag::save()
{ {
ByteVector data; ByteVector data;
for(MP4::ItemListMap::Iterator i = d->items.begin(); i != d->items.end(); i++) { for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) {
const String name = i->first; const String name = it->first;
if(name.startsWith("----")) { if(name.startsWith("----")) {
data.append(renderFreeForm(name, i->second)); data.append(renderFreeForm(name, it->second));
} }
else if(name == "trkn") { else if(name == "trkn") {
data.append(renderIntPair(name.data(String::Latin1), i->second)); data.append(renderIntPair(name.data(String::Latin1), it->second));
} }
else if(name == "disk") { else if(name == "disk") {
data.append(renderIntPairNoTrailing(name.data(String::Latin1), i->second)); data.append(renderIntPairNoTrailing(name.data(String::Latin1), it->second));
} }
else if(name == "cpil" || name == "pgap" || name == "pcst" || name == "hdvd") { else if(name == "cpil" || name == "pgap" || name == "pcst" || name == "hdvd") {
data.append(renderBool(name.data(String::Latin1), i->second)); data.append(renderBool(name.data(String::Latin1), it->second));
} }
else if(name == "tmpo") { else if(name == "tmpo") {
data.append(renderInt(name.data(String::Latin1), i->second)); data.append(renderInt(name.data(String::Latin1), it->second));
} }
else if(name == "tvsn" || name == "tves" || name == "cnID" || else if(name == "tvsn" || name == "tves" || name == "cnID" ||
name == "sfID" || name == "atID" || name == "geID") { name == "sfID" || name == "atID" || name == "geID") {
data.append(renderUInt(name.data(String::Latin1), i->second)); data.append(renderUInt(name.data(String::Latin1), it->second));
} }
else if(name == "plID") { else if(name == "plID") {
data.append(renderLongLong(name.data(String::Latin1), i->second)); data.append(renderLongLong(name.data(String::Latin1), it->second));
} }
else if(name == "stik" || name == "rtng" || name == "akID") { else if(name == "stik" || name == "rtng" || name == "akID") {
data.append(renderByte(name.data(String::Latin1), i->second)); data.append(renderByte(name.data(String::Latin1), it->second));
} }
else if(name == "covr") { else if(name == "covr") {
data.append(renderCovr(name.data(String::Latin1), i->second)); data.append(renderCovr(name.data(String::Latin1), it->second));
} }
else if(name.size() == 4){ else if(name.size() == 4){
data.append(renderText(name.data(String::Latin1), i->second)); data.append(renderText(name.data(String::Latin1), it->second));
} }
else { else {
debug("MP4: Unknown item name \"" + name + "\""); debug("MP4: Unknown item name \"" + name + "\"");
@ -492,7 +503,7 @@ MP4::Tag::save()
} }
void void
MP4::Tag::updateParents(AtomList &path, long delta, int ignore) MP4::Tag::updateParents(const AtomList &path, long delta, int ignore)
{ {
for(unsigned int i = 0; i < path.size() - ignore; i++) { for(unsigned int i = 0; i < path.size() - ignore; i++) {
d->file->seek(path[i]->offset); d->file->seek(path[i]->offset);
@ -585,10 +596,11 @@ MP4::Tag::updateOffsets(long delta, long offset)
} }
void void
MP4::Tag::saveNew(ByteVector &data) MP4::Tag::saveNew(ByteVector data)
{ {
data = renderAtom("meta", TagLib::ByteVector(4, '\0') + data = renderAtom("meta", ByteVector(4, '\0') +
renderAtom("hdlr", TagLib::ByteVector(8, '\0') + TagLib::ByteVector("mdirappl") + TagLib::ByteVector(9, '\0')) + renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") +
ByteVector(9, '\0')) +
data + padIlst(data)); data + padIlst(data));
AtomList path = d->atoms->path("moov", "udta"); AtomList path = d->atoms->path("moov", "udta");
@ -605,18 +617,18 @@ MP4::Tag::saveNew(ByteVector &data)
} }
void void
MP4::Tag::saveExisting(ByteVector &data, AtomList &path) MP4::Tag::saveExisting(ByteVector data, const AtomList &path)
{ {
MP4::Atom *ilst = path[path.size() - 1]; MP4::Atom *ilst = path[path.size() - 1];
long offset = ilst->offset; long offset = ilst->offset;
long length = ilst->length; long length = ilst->length;
MP4::Atom *meta = path[path.size() - 2]; MP4::Atom *meta = path[path.size() - 2];
AtomList::Iterator index = meta->children.find(ilst); AtomList::ConstIterator index = meta->children.find(ilst);
// check if there is an atom before 'ilst', and possibly use it as padding // check if there is an atom before 'ilst', and possibly use it as padding
if(index != meta->children.begin()) { if(index != meta->children.begin()) {
AtomList::Iterator prevIndex = index; AtomList::ConstIterator prevIndex = index;
prevIndex--; prevIndex--;
MP4::Atom *prev = *prevIndex; MP4::Atom *prev = *prevIndex;
if(prev->name == "free") { if(prev->name == "free") {
@ -625,7 +637,7 @@ MP4::Tag::saveExisting(ByteVector &data, AtomList &path)
} }
} }
// check if there is an atom after 'ilst', and possibly use it as padding // check if there is an atom after 'ilst', and possibly use it as padding
AtomList::Iterator nextIndex = index; AtomList::ConstIterator nextIndex = index;
nextIndex++; nextIndex++;
if(nextIndex != meta->children.end()) { if(nextIndex != meta->children.end()) {
MP4::Atom *next = *nextIndex; MP4::Atom *next = *nextIndex;
@ -750,12 +762,41 @@ MP4::Tag::setTrack(uint value)
d->items["trkn"] = MP4::Item(value, 0); d->items["trkn"] = MP4::Item(value, 0);
} }
MP4::ItemListMap & bool MP4::Tag::isEmpty() const
MP4::Tag::itemListMap() {
return d->items.isEmpty();
}
MP4::ItemMap &MP4::Tag::itemListMap()
{ {
return d->items; return d->items;
} }
const MP4::ItemMap &MP4::Tag::itemMap() const
{
return d->items;
}
MP4::Item MP4::Tag::item(const String &key) const
{
return d->items[key];
}
void MP4::Tag::setItem(const String &key, const Item &value)
{
d->items[key] = value;
}
void MP4::Tag::removeItem(const String &key)
{
d->items.erase(key);
}
bool MP4::Tag::contains(const String &key) const
{
return d->items.contains(key);
}
static const char *keyTranslation[][2] = { static const char *keyTranslation[][2] = {
{ "\251nam", "TITLE" }, { "\251nam", "TITLE" },
{ "\251ART", "ARTIST" }, { "\251ART", "ARTIST" },
@ -765,6 +806,7 @@ static const char *keyTranslation[][2] = {
{ "\251day", "DATE" }, { "\251day", "DATE" },
{ "\251wrt", "COMPOSER" }, { "\251wrt", "COMPOSER" },
{ "\251grp", "GROUPING" }, { "\251grp", "GROUPING" },
{ "aART", "ALBUMARTIST" },
{ "trkn", "TRACKNUMBER" }, { "trkn", "TRACKNUMBER" },
{ "disk", "DISCNUMBER" }, { "disk", "DISCNUMBER" },
{ "cpil", "COMPILATION" }, { "cpil", "COMPILATION" },
@ -816,7 +858,7 @@ PropertyMap MP4::Tag::properties() const
} }
PropertyMap props; PropertyMap props;
MP4::ItemListMap::ConstIterator it = d->items.begin(); MP4::ItemMap::ConstIterator it = d->items.begin();
for(; it != d->items.end(); ++it) { for(; it != d->items.end(); ++it) {
if(keyMap.contains(it->first)) { if(keyMap.contains(it->first)) {
String key = keyMap[it->first]; String key = keyMap[it->first];
@ -875,7 +917,7 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
for(; it != props.end(); ++it) { for(; it != props.end(); ++it) {
if(reverseKeyMap.contains(it->first)) { if(reverseKeyMap.contains(it->first)) {
String name = reverseKeyMap[it->first]; String name = reverseKeyMap[it->first];
if(it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") { if((it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") && !it->second.isEmpty()) {
int first = 0, second = 0; int first = 0, second = 0;
StringList parts = StringList::split(it->second.front(), "/"); StringList parts = StringList::split(it->second.front(), "/");
if(parts.size() > 0) { if(parts.size() > 0) {
@ -886,11 +928,11 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
d->items[name] = MP4::Item(first, second); d->items[name] = MP4::Item(first, second);
} }
} }
else if(it->first == "BPM") { else if(it->first == "BPM" && !it->second.isEmpty()) {
int value = it->second.front().toInt(); int value = it->second.front().toInt();
d->items[name] = MP4::Item(value); d->items[name] = MP4::Item(value);
} }
else if(it->first == "COMPILATION") { else if(it->first == "COMPILATION" && !it->second.isEmpty()) {
bool value = (it->second.front().toInt() != 0); bool value = (it->second.front().toInt() != 0);
d->items[name] = MP4::Item(value); d->items[name] = MP4::Item(value);
} }

View File

@ -39,7 +39,11 @@ namespace TagLib {
namespace MP4 { namespace MP4 {
/*!
* \deprecated
*/
typedef TagLib::Map<String, Item> ItemListMap; typedef TagLib::Map<String, Item> ItemListMap;
typedef TagLib::Map<String, Item> ItemMap;
class TAGLIB_EXPORT Tag: public TagLib::Tag class TAGLIB_EXPORT Tag: public TagLib::Tag
{ {
@ -65,45 +69,80 @@ namespace TagLib {
void setYear(uint value); void setYear(uint value);
void setTrack(uint value); void setTrack(uint value);
ItemListMap &itemListMap(); virtual bool isEmpty() const;
/*!
* \deprecated Use the item() and setItem() API instead
*/
ItemMap &itemListMap();
/*!
* Returns a string-keyed map of the MP4::Items for this tag.
*/
const ItemMap &itemMap() const;
/*!
* \return The item, if any, corresponding to \a key.
*/
Item item(const String &key) const;
/*!
* Sets the value of \a key to \a value, overwriting any previous value.
*/
void setItem(const String &key, const Item &value);
/*!
* Removes the entry with \a key from the tag, or does nothing if it does
* not exist.
*/
void removeItem(const String &key);
/*!
* \return True if the tag contains an entry for \a key.
*/
bool contains(const String &key) const;
PropertyMap properties() const; PropertyMap properties() const;
void removeUnsupportedProperties(const StringList& properties); void removeUnsupportedProperties(const StringList& properties);
PropertyMap setProperties(const PropertyMap &properties); PropertyMap setProperties(const PropertyMap &properties);
private: private:
AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); AtomDataList parseData2(const Atom *atom, int expectedFlags = -1,
TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); bool freeForm = false);
void parseText(Atom *atom, TagLib::File *file, int expectedFlags = 1); ByteVectorList parseData(const Atom *atom, int expectedFlags = -1,
void parseFreeForm(Atom *atom, TagLib::File *file); bool freeForm = false);
void parseInt(Atom *atom, TagLib::File *file); void parseText(const Atom *atom, int expectedFlags = 1);
void parseByte(Atom *atom, TagLib::File *file); void parseFreeForm(const Atom *atom);
void parseUInt(Atom *atom, TagLib::File *file); void parseInt(const Atom *atom);
void parseLongLong(Atom *atom, TagLib::File *file); void parseByte(const Atom *atom);
void parseGnre(Atom *atom, TagLib::File *file); void parseUInt(const Atom *atom);
void parseIntPair(Atom *atom, TagLib::File *file); void parseLongLong(const Atom *atom);
void parseBool(Atom *atom, TagLib::File *file); void parseGnre(const Atom *atom);
void parseCovr(Atom *atom, TagLib::File *file); void parseIntPair(const Atom *atom);
void parseBool(const Atom *atom);
void parseCovr(const Atom *atom);
TagLib::ByteVector padIlst(const ByteVector &data, int length = -1); ByteVector padIlst(const ByteVector &data, int length = -1) const;
TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data); ByteVector renderAtom(const ByteVector &name, const ByteVector &data) const;
TagLib::ByteVector renderData(const ByteVector &name, int flags, const TagLib::ByteVectorList &data); ByteVector renderData(const ByteVector &name, int flags,
TagLib::ByteVector renderText(const ByteVector &name, Item &item, int flags = TypeUTF8); const ByteVectorList &data) const;
TagLib::ByteVector renderFreeForm(const String &name, Item &item); ByteVector renderText(const ByteVector &name, const Item &item,
TagLib::ByteVector renderBool(const ByteVector &name, Item &item); int flags = TypeUTF8) const;
TagLib::ByteVector renderInt(const ByteVector &name, Item &item); ByteVector renderFreeForm(const String &name, const Item &item) const;
TagLib::ByteVector renderByte(const ByteVector &name, Item &item); ByteVector renderBool(const ByteVector &name, const Item &item) const;
TagLib::ByteVector renderUInt(const ByteVector &name, Item &item); ByteVector renderInt(const ByteVector &name, const Item &item) const;
TagLib::ByteVector renderLongLong(const ByteVector &name, Item &item); ByteVector renderByte(const ByteVector &name, const Item &item) const;
TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item); ByteVector renderUInt(const ByteVector &name, const Item &item) const;
TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item); ByteVector renderLongLong(const ByteVector &name, const Item &item) const;
TagLib::ByteVector renderCovr(const ByteVector &name, Item &item); ByteVector renderIntPair(const ByteVector &name, const Item &item) const;
ByteVector renderIntPairNoTrailing(const ByteVector &name, const Item &item) const;
ByteVector renderCovr(const ByteVector &name, const Item &item) const;
void updateParents(AtomList &path, long delta, int ignore = 0); void updateParents(const AtomList &path, long delta, int ignore = 0);
void updateOffsets(long delta, long offset); void updateOffsets(long delta, long offset);
void saveNew(TagLib::ByteVector &data); void saveNew(ByteVector data);
void saveExisting(TagLib::ByteVector &data, AtomList &path); void saveExisting(ByteVector data, const AtomList &path);
void addItem(const String &name, const Item &value); void addItem(const String &name, const Item &value);

View File

@ -53,7 +53,6 @@ public:
ID3v2Location(-1), ID3v2Location(-1),
ID3v2Size(0), ID3v2Size(0),
properties(0), properties(0),
scanned(false),
hasAPE(false), hasAPE(false),
hasID3v1(false), hasID3v1(false),
hasID3v2(false) {} hasID3v2(false) {}
@ -76,7 +75,6 @@ public:
TagUnion tag; TagUnion tag;
Properties *properties; Properties *properties;
bool scanned;
// These indicate whether the file *on disk* has these tags, not if // These indicate whether the file *on disk* has these tags, not if
// this data structure does. This is used in computing offsets. // this data structure does. This is used in computing offsets.
@ -90,20 +88,20 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
MPC::File::File(FileName file, bool readProperties, MPC::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
Properties::ReadStyle propertiesStyle) : TagLib::File(file) TagLib::File(file),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
MPC::File::File(IOStream *stream, bool readProperties, MPC::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
Properties::ReadStyle propertiesStyle) : TagLib::File(stream) TagLib::File(stream),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
MPC::File::~File() MPC::File::~File()
@ -270,7 +268,7 @@ bool MPC::File::hasAPETag() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) void MPC::File::read(bool readProperties)
{ {
// Look for an ID3v1 tag // Look for an ID3v1 tag
@ -283,8 +281,6 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
// Look for an APE tag // Look for an APE tag
findAPE();
d->APELocation = findAPE(); d->APELocation = findAPE();
if(d->APELocation >= 0) { if(d->APELocation >= 0) {
@ -298,7 +294,7 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
if(!d->hasID3v1) if(!d->hasID3v1)
APETag(true); APETag(true);
// Look for and skip an ID3v2 tag // Look for an ID3v2 tag
d->ID3v2Location = findID3v2(); d->ID3v2Location = findID3v2();
@ -309,15 +305,28 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
d->hasID3v2 = true; d->hasID3v2 = true;
} }
if(d->hasID3v2)
seek(d->ID3v2Location + d->ID3v2Size);
else
seek(0);
// Look for MPC metadata // Look for MPC metadata
if(readProperties) { if(readProperties) {
d->properties = new Properties(this, length() - d->ID3v2Size - d->APESize);
long streamLength;
if(d->hasAPE)
streamLength = d->APELocation;
else if(d->hasID3v1)
streamLength = d->ID3v1Location;
else
streamLength = length();
if(d->hasID3v2) {
seek(d->ID3v2Location + d->ID3v2Size);
streamLength -= (d->ID3v2Location + d->ID3v2Size);
}
else {
seek(0);
}
d->properties = new Properties(this, streamLength);
} }
} }

View File

@ -139,6 +139,11 @@ namespace TagLib {
/*! /*!
* Saves the file. * Saves the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
virtual bool save(); virtual bool save();
@ -149,8 +154,8 @@ namespace TagLib {
* if there is no valid APE tag. If \a create is true it will create * if there is no valid APE tag. If \a create is true it will create
* an APE tag if one does not exist and returns a valid pointer. * an APE tag if one does not exist and returns a valid pointer.
* *
* \note This may return a valid pointer regardless of whether or not the * \note This may return a valid pointer regardless of whether or not the
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
* on disk actually has an ID3v1 tag. * on disk actually has an ID3v1 tag.
* *
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be * \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -166,11 +171,11 @@ namespace TagLib {
* *
* If \a create is false (the default) this may return a null pointer * If \a create is false (the default) this may return a null pointer
* if there is no valid APE tag. If \a create is true it will create * if there is no valid APE tag. If \a create is true it will create
* an APE tag if one does not exist and returns a valid pointer. If * an APE tag if one does not exist and returns a valid pointer. If
* there already be an ID3v1 tag, the new APE tag will be placed before it. * there already be an ID3v1 tag, the new APE tag will be placed before it.
* *
* \note This may return a valid pointer regardless of whether or not the * \note This may return a valid pointer regardless of whether or not the
* file on disk has an APE tag. Use hasAPETag() to check if the file * file on disk has an APE tag. Use hasAPETag() to check if the file
* on disk actually has an APE tag. * on disk actually has an APE tag.
* *
* \note The Tag <b>is still</b> owned by the MPEG::File and should not be * \note The Tag <b>is still</b> owned by the MPEG::File and should not be
@ -216,8 +221,7 @@ namespace TagLib {
File(const File &); File(const File &);
File &operator=(const File &); File &operator=(const File &);
void read(bool readProperties, Properties::ReadStyle propertiesStyle); void read(bool readProperties);
void scan();
long findAPE(); long findAPE();
long findID3v1(); long findID3v1();
long findID3v2(); long findID3v2();

View File

@ -36,9 +36,7 @@ using namespace TagLib;
class MPC::Properties::PropertiesPrivate class MPC::Properties::PropertiesPrivate
{ {
public: public:
PropertiesPrivate(long length, ReadStyle s) : PropertiesPrivate() :
streamLength(length),
style(s),
version(0), version(0),
length(0), length(0),
bitrate(0), bitrate(0),
@ -51,8 +49,6 @@ public:
albumGain(0), albumGain(0),
albumPeak(0) {} albumPeak(0) {}
long streamLength;
ReadStyle style;
int version; int version;
int length; int length;
int bitrate; int bitrate;
@ -71,23 +67,25 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
MPC::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) MPC::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) :
AudioProperties(style),
d(new PropertiesPrivate())
{ {
d = new PropertiesPrivate(streamLength, style); readSV7(data, streamLength);
readSV7(data);
} }
MPC::Properties::Properties(File *file, long streamLength, ReadStyle style) : AudioProperties(style) MPC::Properties::Properties(File *file, long streamLength, ReadStyle style) :
AudioProperties(style),
d(new PropertiesPrivate())
{ {
d = new PropertiesPrivate(streamLength, style);
ByteVector magic = file->readBlock(4); ByteVector magic = file->readBlock(4);
if(magic == "MPCK") { if(magic == "MPCK") {
// Musepack version 8 // Musepack version 8
readSV8(file); readSV8(file, streamLength);
} }
else { else {
// Musepack version 7 or older, fixed size header // Musepack version 7 or older, fixed size header
readSV7(magic + file->readBlock(MPC::HeaderSize - 4)); readSV7(magic + file->readBlock(MPC::HeaderSize - 4), streamLength);
} }
} }
@ -97,6 +95,16 @@ MPC::Properties::~Properties()
} }
int MPC::Properties::length() const int MPC::Properties::length() const
{
return lengthInSeconds();
}
int MPC::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int MPC::Properties::lengthInMilliseconds() const
{ {
return d->length; return d->length;
} }
@ -155,78 +163,121 @@ int MPC::Properties::albumPeak() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
unsigned long readSize(File *file, TagLib::uint &sizelength) unsigned long readSize(File *file, TagLib::uint &sizeLength, bool &eof)
{ {
sizeLength = 0;
eof = false;
unsigned char tmp; unsigned char tmp;
unsigned long size = 0; unsigned long size = 0;
do { do {
ByteVector b = file->readBlock(1); const ByteVector b = file->readBlock(1);
if(b.isEmpty()) {
eof = true;
break;
}
tmp = b[0]; tmp = b[0];
size = (size << 7) | (tmp & 0x7F); size = (size << 7) | (tmp & 0x7F);
sizelength++; sizeLength++;
} while((tmp & 0x80)); } while((tmp & 0x80));
return size; return size;
} }
unsigned long readSize(const ByteVector &data, TagLib::uint &sizelength) unsigned long readSize(const ByteVector &data, TagLib::uint &pos)
{ {
unsigned char tmp; unsigned char tmp;
unsigned long size = 0; unsigned long size = 0;
unsigned long pos = 0;
do { do {
tmp = data[pos++]; tmp = data[pos++];
size = (size << 7) | (tmp & 0x7F); size = (size << 7) | (tmp & 0x7F);
sizelength++;
} while((tmp & 0x80) && (pos < data.size())); } while((tmp & 0x80) && (pos < data.size()));
return size; return size;
} }
static const unsigned short sftable [4] = { 44100, 48000, 37800, 32000 }; namespace
{
// This array looks weird, but the same as original MusePack code found at:
// https://www.musepack.net/index.php?pg=src
const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 };
}
void MPC::Properties::readSV8(File *file) void MPC::Properties::readSV8(File *file, long streamLength)
{ {
bool readSH = false, readRG = false; bool readSH = false, readRG = false;
while(!readSH && !readRG) { while(!readSH && !readRG) {
ByteVector packetType = file->readBlock(2); const ByteVector packetType = file->readBlock(2);
uint packetSizeLength = 0;
unsigned long packetSize = readSize(file, packetSizeLength); uint packetSizeLength;
unsigned long dataSize = packetSize - 2 - packetSizeLength; bool eof;
const unsigned long packetSize = readSize(file, packetSizeLength, eof);
if(eof) {
debug("MPC::Properties::readSV8() - Reached to EOF.");
break;
}
const unsigned long dataSize = packetSize - 2 - packetSizeLength;
const ByteVector data = file->readBlock(dataSize);
if(data.size() != dataSize) {
debug("MPC::Properties::readSV8() - dataSize doesn't match the actual data size.");
break;
}
if(packetType == "SH") { if(packetType == "SH") {
// Stream Header // Stream Header
// http://trac.musepack.net/wiki/SV8Specification#StreamHeaderPacket // http://trac.musepack.net/wiki/SV8Specification#StreamHeaderPacket
ByteVector data = file->readBlock(dataSize);
if(dataSize <= 5) {
debug("MPC::Properties::readSV8() - \"SH\" packet is too short to parse.");
break;
}
readSH = true; readSH = true;
TagLib::uint pos = 4; TagLib::uint pos = 4;
d->version = data[pos]; d->version = data[pos];
pos += 1; pos += 1;
d->sampleFrames = readSize(data.mid(pos), pos); d->sampleFrames = readSize(data, pos);
ulong begSilence = readSize(data.mid(pos), pos); if(pos > dataSize - 3) {
debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt.");
break;
}
std::bitset<16> flags(TAGLIB_CONSTRUCT_BITSET(data.toUShort(pos, true))); const ulong begSilence = readSize(data, pos);
if(pos > dataSize - 2) {
debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt.");
break;
}
const ushort flags = data.toUShort(pos, true);
pos += 2; pos += 2;
d->sampleRate = sftable[flags[15] * 4 + flags[14] * 2 + flags[13]]; d->sampleRate = sftable[(flags >> 13) & 0x07];
d->channels = flags[7] * 8 + flags[6] * 4 + flags[5] * 2 + flags[4] + 1; d->channels = ((flags >> 4) & 0x0F) + 1;
if((d->sampleFrames - begSilence) != 0) const uint frameCount = d->sampleFrames - begSilence;
d->bitrate = (int)(d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence)); if(frameCount > 0 && d->sampleRate > 0) {
d->bitrate = d->bitrate / 1000; const double length = frameCount * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->length = (d->sampleFrames - begSilence) / d->sampleRate; d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
}
} }
else if (packetType == "RG") { else if (packetType == "RG") {
// Replay Gain // Replay Gain
// http://trac.musepack.net/wiki/SV8Specification#ReplaygainPacket // http://trac.musepack.net/wiki/SV8Specification#ReplaygainPacket
ByteVector data = file->readBlock(dataSize);
if(dataSize <= 9) {
debug("MPC::Properties::readSV8() - \"RG\" packet is too short to parse.");
break;
}
readRG = true; readRG = true;
int replayGainVersion = data[0]; const int replayGainVersion = data[0];
if(replayGainVersion == 1) { if(replayGainVersion == 1) {
d->trackGain = data.toShort(1, true); d->trackGain = data.toShort(1, true);
d->trackPeak = data.toShort(3, true); d->trackPeak = data.toShort(3, true);
@ -245,7 +296,7 @@ void MPC::Properties::readSV8(File *file)
} }
} }
void MPC::Properties::readSV7(const ByteVector &data) void MPC::Properties::readSV7(const ByteVector &data, long streamLength)
{ {
if(data.startsWith("MP+")) { if(data.startsWith("MP+")) {
d->version = data[3] & 15; d->version = data[3] & 15;
@ -254,11 +305,11 @@ void MPC::Properties::readSV7(const ByteVector &data)
d->totalFrames = data.toUInt(4, false); d->totalFrames = data.toUInt(4, false);
std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt(8, false))); const uint flags = data.toUInt(8, false);
d->sampleRate = sftable[flags[17] * 2 + flags[16]]; d->sampleRate = sftable[(flags >> 16) & 0x03];
d->channels = 2; d->channels = 2;
uint gapless = data.toUInt(5, false); const uint gapless = data.toUInt(5, false);
d->trackGain = data.toShort(14, false); d->trackGain = data.toShort(14, false);
d->trackPeak = data.toShort(12, false); d->trackPeak = data.toShort(12, false);
@ -293,12 +344,12 @@ void MPC::Properties::readSV7(const ByteVector &data)
d->sampleFrames = d->totalFrames * 1152 - 576; d->sampleFrames = d->totalFrames * 1152 - 576;
} }
else { else {
uint headerData = data.toUInt(0, false); const uint headerData = data.toUInt(0, false);
d->bitrate = (headerData >> 23) & 0x01ff; d->bitrate = (headerData >> 23) & 0x01ff;
d->version = (headerData >> 11) & 0x03ff; d->version = (headerData >> 11) & 0x03ff;
d->sampleRate = 44100; d->sampleRate = 44100;
d->channels = 2; d->channels = 2;
if(d->version >= 5) if(d->version >= 5)
d->totalFrames = data.toUInt(4, false); d->totalFrames = data.toUInt(4, false);
@ -308,9 +359,11 @@ void MPC::Properties::readSV7(const ByteVector &data)
d->sampleFrames = d->totalFrames * 1152 - 576; d->sampleFrames = d->totalFrames * 1152 - 576;
} }
d->length = d->sampleRate > 0 ? (d->sampleFrames + (d->sampleRate / 2)) / d->sampleRate : 0; if(d->sampleFrames > 0 && d->sampleRate > 0) {
const double length = d->sampleFrames * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
if(!d->bitrate) if(d->bitrate == 0)
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
}
} }

View File

@ -66,17 +66,53 @@ namespace TagLib {
*/ */
virtual ~Properties(); virtual ~Properties();
// Reimplementations. /*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const; virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const; virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const; virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const; virtual int channels() const;
/*! /*!
* Returns the version of the bitstream (SV4-SV8) * Returns the version of the bitstream (SV4-SV8)
*/ */
int mpcVersion() const; int mpcVersion() const;
uint totalFrames() const; uint totalFrames() const;
uint sampleFrames() const; uint sampleFrames() const;
@ -110,8 +146,8 @@ namespace TagLib {
Properties(const Properties &); Properties(const Properties &);
Properties &operator=(const Properties &); Properties &operator=(const Properties &);
void readSV7(const ByteVector &data); void readSV7(const ByteVector &data, long streamLength);
void readSV8(File *file); void readSV8(File *file, long streamLength);
class PropertiesPrivate; class PropertiesPrivate;
PropertiesPrivate *d; PropertiesPrivate *d;

View File

@ -30,7 +30,7 @@ using namespace TagLib;
namespace TagLib { namespace TagLib {
namespace ID3v1 { namespace ID3v1 {
static const int genresSize = 148; static const int genresSize = 192;
static const String genres[] = { static const String genres[] = {
"Blues", "Blues",
"Classic Rock", "Classic Rock",
@ -179,7 +179,51 @@ namespace TagLib {
"Thrash Metal", "Thrash Metal",
"Anime", "Anime",
"Jpop", "Jpop",
"Synthpop" "Synthpop",
"Abstract",
"Art Rock",
"Baroque",
"Bhangra",
"Big Beat",
"Breakbeat",
"Chillout",
"Downtempo",
"Dub",
"EBM",
"Eclectic",
"Electro",
"Electroclash",
"Emo",
"Experimental",
"Garage",
"Global",
"IDM",
"Illbient",
"Industro-Goth",
"Jam Band",
"Krautrock",
"Leftfield",
"Lounge",
"Math Rock",
"New Romantic",
"Nu-Breakz",
"Post-Punk",
"Post-Rock",
"Psytrance",
"Shoegaze",
"Space Rock",
"Trop Rock",
"World Music",
"Neoclassical",
"Audiobook",
"Audio Theatre",
"Neue Deutsche Welle",
"Podcast",
"Indie Rock",
"G-Funk",
"Dubstep",
"Garage Rock",
"Psybient"
}; };
} }
} }

View File

@ -49,7 +49,7 @@ namespace TagLib {
/*! /*!
* Returns the name of the genre at \a index in the ID3v1 genre list. If * Returns the name of the genre at \a index in the ID3v1 genre list. If
* \a index is out of range -- less than zero or greater than 146 -- a null * \a index is out of range -- less than zero or greater than 191 -- a null
* string will be returned. * string will be returned.
*/ */
String TAGLIB_EXPORT genre(int index); String TAGLIB_EXPORT genre(int index);

View File

@ -85,7 +85,7 @@ namespace TagLib {
//! The main class in the ID3v1 implementation //! The main class in the ID3v1 implementation
/*! /*!
* This is an implementation of the ID3v1 format. ID3v1 is both the simplist * This is an implementation of the ID3v1 format. ID3v1 is both the simplest
* and most common of tag formats but is rather limited. Because of its * and most common of tag formats but is rather limited. Because of its
* pervasiveness and the way that applications have been written around the * pervasiveness and the way that applications have been written around the
* fields that it provides, the generic TagLib::Tag API is a mirror of what is * fields that it provides, the generic TagLib::Tag API is a mirror of what is
@ -154,14 +154,14 @@ namespace TagLib {
/*! /*!
* Returns the genre in number. * Returns the genre in number.
* *
* /note Normally 255 indicates that this tag contains no genre. * \note Normally 255 indicates that this tag contains no genre.
*/ */
TagLib::uint genreNumber() const; TagLib::uint genreNumber() const;
/*! /*!
* Sets the genre in number to \a i. * Sets the genre in number to \a i.
* *
* /note Valid value is from 0 up to 255. Normally 255 indicates that * \note Valid value is from 0 up to 255. Normally 255 indicates that
* this tag contains no genre. * this tag contains no genre.
*/ */
void setGenreNumber(TagLib::uint i); void setGenreNumber(TagLib::uint i);

View File

@ -0,0 +1,306 @@
/***************************************************************************
copyright : (C) 2013 by Lukas Krejci
email : krejclu6@fel.cvut.cz
***************************************************************************/
/***************************************************************************
* 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 <tbytevectorlist.h>
#include <tpropertymap.h>
#include <tdebug.h>
#include <stdio.h>
#include "chapterframe.h"
using namespace TagLib;
using namespace ID3v2;
class ChapterFrame::ChapterFramePrivate
{
public:
ChapterFramePrivate() :
tagHeader(0)
{
embeddedFrameList.setAutoDelete(true);
}
const ID3v2::Header *tagHeader;
ByteVector elementID;
TagLib::uint startTime;
TagLib::uint endTime;
TagLib::uint startOffset;
TagLib::uint endOffset;
FrameListMap embeddedFrameListMap;
FrameList embeddedFrameList;
};
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////
ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data) :
ID3v2::Frame(data)
{
d = new ChapterFramePrivate;
d->tagHeader = tagHeader;
setData(data);
}
ChapterFrame::ChapterFrame(const ByteVector &elementID,
TagLib::uint startTime, TagLib::uint endTime,
TagLib::uint startOffset, TagLib::uint endOffset,
const FrameList &embeddedFrames) :
ID3v2::Frame("CHAP")
{
d = new ChapterFramePrivate;
// setElementID has a workaround for a previously silly API where you had to
// specifically include the null byte.
setElementID(elementID);
d->startTime = startTime;
d->endTime = endTime;
d->startOffset = startOffset;
d->endOffset = endOffset;
for(FrameList::ConstIterator it = embeddedFrames.begin();
it != embeddedFrames.end(); ++it)
addEmbeddedFrame(*it);
}
ChapterFrame::~ChapterFrame()
{
delete d;
}
ByteVector ChapterFrame::elementID() const
{
return d->elementID;
}
TagLib::uint ChapterFrame::startTime() const
{
return d->startTime;
}
TagLib::uint ChapterFrame::endTime() const
{
return d->endTime;
}
TagLib::uint ChapterFrame::startOffset() const
{
return d->startOffset;
}
TagLib::uint ChapterFrame::endOffset() const
{
return d->endOffset;
}
void ChapterFrame::setElementID(const ByteVector &eID)
{
d->elementID = eID;
if(d->elementID.endsWith(char(0)))
d->elementID = d->elementID.mid(0, d->elementID.size() - 1);
}
void ChapterFrame::setStartTime(const TagLib::uint &sT)
{
d->startTime = sT;
}
void ChapterFrame::setEndTime(const TagLib::uint &eT)
{
d->endTime = eT;
}
void ChapterFrame::setStartOffset(const TagLib::uint &sO)
{
d->startOffset = sO;
}
void ChapterFrame::setEndOffset(const TagLib::uint &eO)
{
d->endOffset = eO;
}
const FrameListMap &ChapterFrame::embeddedFrameListMap() const
{
return d->embeddedFrameListMap;
}
const FrameList &ChapterFrame::embeddedFrameList() const
{
return d->embeddedFrameList;
}
const FrameList &ChapterFrame::embeddedFrameList(const ByteVector &frameID) const
{
return d->embeddedFrameListMap[frameID];
}
void ChapterFrame::addEmbeddedFrame(Frame *frame)
{
d->embeddedFrameList.append(frame);
d->embeddedFrameListMap[frame->frameID()].append(frame);
}
void ChapterFrame::removeEmbeddedFrame(Frame *frame, bool del)
{
// remove the frame from the frame list
FrameList::Iterator it = d->embeddedFrameList.find(frame);
d->embeddedFrameList.erase(it);
// ...and from the frame list map
it = d->embeddedFrameListMap[frame->frameID()].find(frame);
d->embeddedFrameListMap[frame->frameID()].erase(it);
// ...and delete as desired
if(del)
delete frame;
}
void ChapterFrame::removeEmbeddedFrames(const ByteVector &id)
{
FrameList l = d->embeddedFrameListMap[id];
for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
removeEmbeddedFrame(*it, true);
}
String ChapterFrame::toString() const
{
String s = String(d->elementID) +
": start time: " + String::number(d->startTime) +
", end time: " + String::number(d->endTime);
if(d->startOffset != 0xFFFFFFFF)
s += ", start offset: " + String::number(d->startOffset);
if(d->endOffset != 0xFFFFFFFF)
s += ", start offset: " + String::number(d->endOffset);
if(!d->embeddedFrameList.isEmpty()) {
StringList frameIDs;
for(FrameList::ConstIterator it = d->embeddedFrameList.begin();
it != d->embeddedFrameList.end(); ++it)
frameIDs.append((*it)->frameID());
s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]";
}
return s;
}
PropertyMap ChapterFrame::asProperties() const
{
PropertyMap map;
map.unsupportedData().append(frameID() + String("/") + d->elementID);
return map;
}
ChapterFrame *ChapterFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static
{
ID3v2::FrameList comments = tag->frameList("CHAP");
for(ID3v2::FrameList::ConstIterator it = comments.begin();
it != comments.end();
++it)
{
ChapterFrame *frame = dynamic_cast<ChapterFrame *>(*it);
if(frame && frame->elementID() == eID)
return frame;
}
return 0;
}
void ChapterFrame::parseFields(const ByteVector &data)
{
TagLib::uint size = data.size();
if(size < 18) {
debug("A CHAP frame must contain at least 18 bytes (1 byte element ID "
"terminated by null and 4x4 bytes for start and end time and offset).");
return;
}
int pos = 0;
TagLib::uint embPos = 0;
d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
d->startTime = data.toUInt(pos, true);
pos += 4;
d->endTime = data.toUInt(pos, true);
pos += 4;
d->startOffset = data.toUInt(pos, true);
pos += 4;
d->endOffset = data.toUInt(pos, true);
pos += 4;
size -= pos;
// Embedded frames are optional
if(size < header()->size())
return;
while(embPos < size - header()->size()) {
Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), (d->tagHeader != 0));
if(!frame)
return;
// Checks to make sure that frame parsed correctly.
if(frame->size() <= 0) {
delete frame;
return;
}
embPos += frame->size() + header()->size();
addEmbeddedFrame(frame);
}
}
ByteVector ChapterFrame::renderFields() const
{
ByteVector data;
data.append(d->elementID);
data.append('\0');
data.append(ByteVector::fromUInt(d->startTime, true));
data.append(ByteVector::fromUInt(d->endTime, true));
data.append(ByteVector::fromUInt(d->startOffset, true));
data.append(ByteVector::fromUInt(d->endOffset, true));
FrameList l = d->embeddedFrameList;
for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
data.append((*it)->render());
return data;
}
ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h) :
Frame(h)
{
d = new ChapterFramePrivate;
d->tagHeader = tagHeader;
parseFields(fieldData(data));
}

View File

@ -0,0 +1,249 @@
/***************************************************************************
copyright : (C) 2013 by Lukas Krejci
email : krejclu6@fel.cvut.cz
***************************************************************************/
/***************************************************************************
* 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_CHAPTERFRAME
#define TAGLIB_CHAPTERFRAME
#include "id3v2tag.h"
#include "id3v2frame.h"
#include "taglib_export.h"
namespace TagLib {
namespace ID3v2 {
/*!
* This is an implementation of ID3v2 chapter frames. The purpose of this
* frame is to describe a single chapter within an audio file.
*/
//! An implementation of ID3v2 chapter frames
class TAGLIB_EXPORT ChapterFrame : public ID3v2::Frame
{
friend class FrameFactory;
public:
/*!
* Creates a chapter frame based on \a data. \a tagHeader is required as
* the internal frames are parsed based on the tag version.
*/
ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data);
/*!
* Creates a chapter frame with the element ID \a elementID, start time
* \a startTime, end time \a endTime, start offset \a startOffset,
* end offset \a endOffset and optionally a list of embedded frames,
* whose ownership will then be taken over by this Frame, in
* \a embeededFrames;
*
* All times are in milliseconds.
*/
ChapterFrame(const ByteVector &elementID,
uint startTime, uint endTime,
uint startOffset, uint endOffset,
const FrameList &embeddedFrames = FrameList());
/*!
* Destroys the frame.
*/
virtual ~ChapterFrame();
/*!
* Returns the element ID of the frame. Element ID
* is a null terminated string, however it's not human-readable.
*
* \see setElementID()
*/
ByteVector elementID() const;
/*!
* Returns time of chapter's start (in milliseconds).
*
* \see setStartTime()
*/
uint startTime() const;
/*!
* Returns time of chapter's end (in milliseconds).
*
* \see setEndTime()
*/
uint endTime() const;
/*!
* Returns zero based byte offset (count of bytes from the beginning
* of the audio file) of chapter's start.
*
* \note If returned value is 0xFFFFFFFF, start time should be used instead.
* \see setStartOffset()
*/
uint startOffset() const;
/*!
* Returns zero based byte offset (count of bytes from the beginning
* of the audio file) of chapter's end.
*
* \note If returned value is 0xFFFFFFFF, end time should be used instead.
* \see setEndOffset()
*/
uint endOffset() const;
/*!
* Sets the element ID of the frame to \a eID. If \a eID isn't
* null terminated, a null char is appended automatically.
*
* \see elementID()
*/
void setElementID(const ByteVector &eID);
/*!
* Sets time of chapter's start (in milliseconds) to \a sT.
*
* \see startTime()
*/
void setStartTime(const uint &sT);
/*!
* Sets time of chapter's end (in milliseconds) to \a eT.
*
* \see endTime()
*/
void setEndTime(const uint &eT);
/*!
* Sets zero based byte offset (count of bytes from the beginning
* of the audio file) of chapter's start to \a sO.
*
* \see startOffset()
*/
void setStartOffset(const uint &sO);
/*!
* Sets zero based byte offset (count of bytes from the beginning
* of the audio file) of chapter's end to \a eO.
*
* \see endOffset()
*/
void setEndOffset(const uint &eO);
/*!
* Returns a reference to the frame list map. This is an FrameListMap of
* all of the frames embedded in the CHAP frame.
*
* This is the most convenient structure for accessing the CHAP frame's
* embedded frames. Many frame types allow multiple instances of the same
* frame type so this is a map of lists. In most cases however there will
* only be a single frame of a certain type.
*
* \warning You should not modify this data structure directly, instead
* use addEmbeddedFrame() and removeEmbeddedFrame().
*
* \see embeddedFrameList()
*/
const FrameListMap &embeddedFrameListMap() const;
/*!
* Returns a reference to the embedded frame list. This is an FrameList
* of all of the frames embedded in the CHAP frame in the order that they
* were parsed.
*
* This can be useful if for example you want iterate over the CHAP frame's
* embedded frames in the order that they occur in the CHAP frame.
*
* \warning You should not modify this data structure directly, instead
* use addEmbeddedFrame() and removeEmbeddedFrame().
*/
const FrameList &embeddedFrameList() const;
/*!
* Returns the embedded frame list for frames with the id \a frameID
* or an empty list if there are no embedded frames of that type. This
* is just a convenience and is equivalent to:
*
* \code
* embeddedFrameListMap()[frameID];
* \endcode
*
* \see embeddedFrameListMap()
*/
const FrameList &embeddedFrameList(const ByteVector &frameID) const;
/*!
* Add an embedded frame to the CHAP frame. At this point the CHAP frame
* takes ownership of the embedded frame and will handle freeing its memory.
*
* \note Using this method will invalidate any pointers on the list
* returned by embeddedFrameList()
*/
void addEmbeddedFrame(Frame *frame);
/*!
* Remove an embedded frame from the CHAP frame. If \a del is true the frame's
* memory will be freed; if it is false, it must be deleted by the user.
*
* \note Using this method will invalidate any pointers on the list
* returned by embeddedFrameList()
*/
void removeEmbeddedFrame(Frame *frame, bool del = true);
/*!
* Remove all embedded frames of type \a id from the CHAP frame and free their
* memory.
*
* \note Using this method will invalidate any pointers on the list
* returned by embeddedFrameList()
*/
void removeEmbeddedFrames(const ByteVector &id);
virtual String toString() const;
PropertyMap asProperties() const;
/*!
* CHAP frames each have a unique element ID. This searches for a CHAP
* frame with the element ID \a eID and returns a pointer to it. This
* can be used to link CTOC and CHAP frames together.
*
* \see elementID()
*/
static ChapterFrame *findByElementID(const Tag *tag, const ByteVector &eID);
protected:
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;
private:
ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h);
ChapterFrame(const ChapterFrame &);
ChapterFrame &operator=(const ChapterFrame &);
class ChapterFramePrivate;
ChapterFramePrivate *d;
};
}
}
#endif

View File

@ -36,7 +36,7 @@ namespace TagLib {
//! An implementation of ID3v2 comments //! An implementation of ID3v2 comments
/*! /*!
* This implements the ID3v2 comment format. An ID3v2 comment concists of * This implements the ID3v2 comment format. An ID3v2 comment consists of
* a language encoding, a description and a single text field. * a language encoding, a description and a single text field.
*/ */
@ -106,7 +106,7 @@ namespace TagLib {
/*! /*!
* Sets the description of the comment to \a s. * Sets the description of the comment to \a s.
* *
* \see decription() * \see description()
*/ */
void setDescription(const String &s); void setDescription(const String &s);
@ -149,7 +149,7 @@ namespace TagLib {
/*! /*!
* Comments each have a unique description. This searches for a comment * Comments each have a unique description. This searches for a comment
* frame with the decription \a d and returns a pointer to it. If no * frame with the description \a d and returns a pointer to it. If no
* frame is found that matches the given description null is returned. * frame is found that matches the given description null is returned.
* *
* \see description() * \see description()

View File

@ -0,0 +1,144 @@
/***************************************************************************
copyright : (C) 2014 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* 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 "eventtimingcodesframe.h"
#include <tbytevectorlist.h>
#include <id3v2tag.h>
#include <tdebug.h>
#include <tpropertymap.h>
using namespace TagLib;
using namespace ID3v2;
class EventTimingCodesFrame::EventTimingCodesFramePrivate
{
public:
EventTimingCodesFramePrivate() :
timestampFormat(EventTimingCodesFrame::AbsoluteMilliseconds) {}
EventTimingCodesFrame::TimestampFormat timestampFormat;
EventTimingCodesFrame::SynchedEventList synchedEvents;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
EventTimingCodesFrame::EventTimingCodesFrame() :
Frame("ETCO")
{
d = new EventTimingCodesFramePrivate;
}
EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data) :
Frame(data)
{
d = new EventTimingCodesFramePrivate;
setData(data);
}
EventTimingCodesFrame::~EventTimingCodesFrame()
{
delete d;
}
String EventTimingCodesFrame::toString() const
{
return String();
}
EventTimingCodesFrame::TimestampFormat
EventTimingCodesFrame::timestampFormat() const
{
return d->timestampFormat;
}
EventTimingCodesFrame::SynchedEventList
EventTimingCodesFrame::synchedEvents() const
{
return d->synchedEvents;
}
void EventTimingCodesFrame::setTimestampFormat(
EventTimingCodesFrame::TimestampFormat f)
{
d->timestampFormat = f;
}
void EventTimingCodesFrame::setSynchedEvents(
const EventTimingCodesFrame::SynchedEventList &e)
{
d->synchedEvents = e;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
void EventTimingCodesFrame::parseFields(const ByteVector &data)
{
const int end = data.size();
if(end < 1) {
debug("An event timing codes frame must contain at least 1 byte.");
return;
}
d->timestampFormat = TimestampFormat(data[0]);
int pos = 1;
d->synchedEvents.clear();
while(pos + 4 < end) {
EventType type = EventType(uchar(data[pos++]));
uint time = data.toUInt(pos, true);
pos += 4;
d->synchedEvents.append(SynchedEvent(time, type));
}
}
ByteVector EventTimingCodesFrame::renderFields() const
{
ByteVector v;
v.append(char(d->timestampFormat));
for(SynchedEventList::ConstIterator it = d->synchedEvents.begin();
it != d->synchedEvents.end();
++it) {
const SynchedEvent &entry = *it;
v.append(char(entry.type));
v.append(ByteVector::fromUInt(entry.time));
}
return v;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data, Header *h)
: Frame(h)
{
d = new EventTimingCodesFramePrivate();
parseFields(fieldData(data));
}

View File

@ -0,0 +1,185 @@
/***************************************************************************
copyright : (C) 2014 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* 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_EVENTTIMINGCODESFRAME_H
#define TAGLIB_EVENTTIMINGCODESFRAME_H
#include "id3v2frame.h"
#include "tlist.h"
namespace TagLib {
namespace ID3v2 {
//! ID3v2 event timing codes frame
/*!
* An implementation of ID3v2 event timing codes.
*/
class TAGLIB_EXPORT EventTimingCodesFrame : public Frame
{
friend class FrameFactory;
public:
/*!
* Specifies the timestamp format used.
*/
enum TimestampFormat {
//! The timestamp is of unknown format.
Unknown = 0x00,
//! The timestamp represents the number of MPEG frames since
//! the beginning of the audio stream.
AbsoluteMpegFrames = 0x01,
//! The timestamp represents the number of milliseconds since
//! the beginning of the audio stream.
AbsoluteMilliseconds = 0x02
};
/*!
* Event types defined in id3v2.4.0-frames.txt 4.5. Event timing codes.
*/
enum EventType {
Padding = 0x00,
EndOfInitialSilence = 0x01,
IntroStart = 0x02,
MainPartStart = 0x03,
OutroStart = 0x04,
OutroEnd = 0x05,
VerseStart = 0x06,
RefrainStart = 0x07,
InterludeStart = 0x08,
ThemeStart = 0x09,
VariationStart = 0x0a,
KeyChange = 0x0b,
TimeChange = 0x0c,
MomentaryUnwantedNoise = 0x0d,
SustainedNoise = 0x0e,
SustainedNoiseEnd = 0x0f,
IntroEnd = 0x10,
MainPartEnd = 0x11,
VerseEnd = 0x12,
RefrainEnd = 0x13,
ThemeEnd = 0x14,
Profanity = 0x15,
ProfanityEnd = 0x16,
NotPredefinedSynch0 = 0xe0,
NotPredefinedSynch1 = 0xe1,
NotPredefinedSynch2 = 0xe2,
NotPredefinedSynch3 = 0xe3,
NotPredefinedSynch4 = 0xe4,
NotPredefinedSynch5 = 0xe5,
NotPredefinedSynch6 = 0xe6,
NotPredefinedSynch7 = 0xe7,
NotPredefinedSynch8 = 0xe8,
NotPredefinedSynch9 = 0xe9,
NotPredefinedSynchA = 0xea,
NotPredefinedSynchB = 0xeb,
NotPredefinedSynchC = 0xec,
NotPredefinedSynchD = 0xed,
NotPredefinedSynchE = 0xee,
NotPredefinedSynchF = 0xef,
AudioEnd = 0xfd,
AudioFileEnds = 0xfe
};
/*!
* Single entry of time stamp and event.
*/
struct SynchedEvent {
SynchedEvent(uint ms, EventType t) : time(ms), type(t) {}
uint time;
EventType type;
};
/*!
* List of synchronized events.
*/
typedef TagLib::List<SynchedEvent> SynchedEventList;
/*!
* Construct an empty event timing codes frame.
*/
explicit EventTimingCodesFrame();
/*!
* Construct a event timing codes frame based on the data in \a data.
*/
explicit EventTimingCodesFrame(const ByteVector &data);
/*!
* Destroys this EventTimingCodesFrame instance.
*/
virtual ~EventTimingCodesFrame();
/*!
* Returns a null string.
*/
virtual String toString() const;
/*!
* Returns the timestamp format.
*/
TimestampFormat timestampFormat() const;
/*!
* Returns the events with the time stamps.
*/
SynchedEventList synchedEvents() const;
/*!
* Set the timestamp format.
*
* \see timestampFormat()
*/
void setTimestampFormat(TimestampFormat f);
/*!
* Sets the text with the time stamps.
*
* \see text()
*/
void setSynchedEvents(const SynchedEventList &e);
protected:
// Reimplementations.
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;
private:
/*!
* The constructor used by the FrameFactory.
*/
EventTimingCodesFrame(const ByteVector &data, Header *h);
EventTimingCodesFrame(const EventTimingCodesFrame &);
EventTimingCodesFrame &operator=(const EventTimingCodesFrame &);
class EventTimingCodesFramePrivate;
EventTimingCodesFramePrivate *d;
};
}
}
#endif

View File

@ -94,21 +94,21 @@ namespace TagLib {
* \see pricePaid() * \see pricePaid()
*/ */
void setPricePaid(const String &pricePaid); void setPricePaid(const String &pricePaid);
/*! /*!
* Returns the seller. * Returns the seller.
* *
* \see setSeller() * \see setSeller()
*/ */
String seller() const; String seller() const;
/*! /*!
* Set the seller. * Set the seller.
* *
* \see seller() * \see seller()
*/ */
void setSeller(const String &seller); void setSeller(const String &seller);
/*! /*!
* Returns the text encoding that will be used in rendering this frame. * Returns the text encoding that will be used in rendering this frame.
* This defaults to the type that was either specified in the constructor * This defaults to the type that was either specified in the constructor
@ -118,7 +118,7 @@ namespace TagLib {
* \see render() * \see render()
*/ */
String::Type textEncoding() const; String::Type textEncoding() const;
/*! /*!
* Sets the text encoding to be used when rendering this frame to * Sets the text encoding to be used when rendering this frame to
* \a encoding. * \a encoding.

View File

@ -36,7 +36,7 @@ namespace TagLib {
//! An implementation of ID3v2 "popularimeter" //! An implementation of ID3v2 "popularimeter"
/*! /*!
* This implements the ID3v2 popularimeter (POPM frame). It concists of * This implements the ID3v2 popularimeter (POPM frame). It consists of
* an email, a rating and an optional counter. * an email, a rating and an optional counter.
*/ */

View File

@ -140,7 +140,7 @@ namespace TagLib {
/* /*
* There was a terrible API goof here, and while this can't be changed to * There was a terrible API goof here, and while this can't be changed to
* the way it appears below for binary compaibility reasons, let's at * the way it appears below for binary compatibility reasons, let's at
* least pretend that it looks clean. * least pretend that it looks clean.
*/ */
@ -149,7 +149,7 @@ namespace TagLib {
/*! /*!
* Returns the relative volume adjustment "index". As indicated by the * Returns the relative volume adjustment "index". As indicated by the
* ID3v2 standard this is a 16-bit signed integer that reflects the * ID3v2 standard this is a 16-bit signed integer that reflects the
* decibils of adjustment when divided by 512. * decibels of adjustment when divided by 512.
* *
* This defaults to returning the value for the master volume channel if * This defaults to returning the value for the master volume channel if
* available and returns 0 if the specified channel does not exist. * available and returns 0 if the specified channel does not exist.
@ -161,7 +161,7 @@ namespace TagLib {
/*! /*!
* Set the volume adjustment to \a index. As indicated by the ID3v2 * Set the volume adjustment to \a index. As indicated by the ID3v2
* standard this is a 16-bit signed integer that reflects the decibils of * standard this is a 16-bit signed integer that reflects the decibels of
* adjustment when divided by 512. * adjustment when divided by 512.
* *
* By default this sets the value for the master volume. * By default this sets the value for the master volume.

View File

@ -0,0 +1,243 @@
/***************************************************************************
copyright : (C) 2014 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* 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 "synchronizedlyricsframe.h"
#include <tbytevectorlist.h>
#include <id3v2tag.h>
#include <tdebug.h>
#include <tpropertymap.h>
using namespace TagLib;
using namespace ID3v2;
class SynchronizedLyricsFrame::SynchronizedLyricsFramePrivate
{
public:
SynchronizedLyricsFramePrivate() :
textEncoding(String::Latin1),
timestampFormat(SynchronizedLyricsFrame::AbsoluteMilliseconds),
type(SynchronizedLyricsFrame::Lyrics) {}
String::Type textEncoding;
ByteVector language;
SynchronizedLyricsFrame::TimestampFormat timestampFormat;
SynchronizedLyricsFrame::Type type;
String description;
SynchronizedLyricsFrame::SynchedTextList synchedText;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
SynchronizedLyricsFrame::SynchronizedLyricsFrame(String::Type encoding) :
Frame("SYLT")
{
d = new SynchronizedLyricsFramePrivate;
d->textEncoding = encoding;
}
SynchronizedLyricsFrame::SynchronizedLyricsFrame(const ByteVector &data) :
Frame(data)
{
d = new SynchronizedLyricsFramePrivate;
setData(data);
}
SynchronizedLyricsFrame::~SynchronizedLyricsFrame()
{
delete d;
}
String SynchronizedLyricsFrame::toString() const
{
return d->description;
}
String::Type SynchronizedLyricsFrame::textEncoding() const
{
return d->textEncoding;
}
ByteVector SynchronizedLyricsFrame::language() const
{
return d->language;
}
SynchronizedLyricsFrame::TimestampFormat
SynchronizedLyricsFrame::timestampFormat() const
{
return d->timestampFormat;
}
SynchronizedLyricsFrame::Type SynchronizedLyricsFrame::type() const
{
return d->type;
}
String SynchronizedLyricsFrame::description() const
{
return d->description;
}
SynchronizedLyricsFrame::SynchedTextList
SynchronizedLyricsFrame::synchedText() const
{
return d->synchedText;
}
void SynchronizedLyricsFrame::setTextEncoding(String::Type encoding)
{
d->textEncoding = encoding;
}
void SynchronizedLyricsFrame::setLanguage(const ByteVector &languageEncoding)
{
d->language = languageEncoding.mid(0, 3);
}
void SynchronizedLyricsFrame::setTimestampFormat(
SynchronizedLyricsFrame::TimestampFormat f)
{
d->timestampFormat = f;
}
void SynchronizedLyricsFrame::setType(SynchronizedLyricsFrame::Type t)
{
d->type = t;
}
void SynchronizedLyricsFrame::setDescription(const String &s)
{
d->description = s;
}
void SynchronizedLyricsFrame::setSynchedText(
const SynchronizedLyricsFrame::SynchedTextList &t)
{
d->synchedText = t;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
void SynchronizedLyricsFrame::parseFields(const ByteVector &data)
{
const int end = data.size();
if(end < 7) {
debug("A synchronized lyrics frame must contain at least 7 bytes.");
return;
}
d->textEncoding = String::Type(data[0]);
d->language = data.mid(1, 3);
d->timestampFormat = TimestampFormat(data[4]);
d->type = Type(data[5]);
int pos = 6;
d->description = readStringField(data, d->textEncoding, &pos);
if(d->description.isNull())
return;
/*
* If UTF16 strings are found in SYLT frames, a BOM may only be
* present in the first string (content descriptor), and the strings of
* the synchronized text have no BOM. Here the BOM is read from
* the first string to have a specific encoding with endianness for the
* case of strings without BOM so that readStringField() will work.
*/
String::Type encWithEndianness = d->textEncoding;
if(d->textEncoding == String::UTF16) {
ushort bom = data.toUShort(6, true);
if(bom == 0xfffe) {
encWithEndianness = String::UTF16LE;
} else if(bom == 0xfeff) {
encWithEndianness = String::UTF16BE;
}
}
d->synchedText.clear();
while(pos < end) {
String::Type enc = d->textEncoding;
// If a UTF16 string has no BOM, use the encoding found above.
if(enc == String::UTF16 && pos + 1 < end) {
ushort bom = data.toUShort(pos, true);
if(bom != 0xfffe && bom != 0xfeff) {
enc = encWithEndianness;
}
}
String text = readStringField(data, enc, &pos);
if(text.isNull() || pos + 4 > end)
return;
uint time = data.toUInt(pos, true);
pos += 4;
d->synchedText.append(SynchedText(time, text));
}
}
ByteVector SynchronizedLyricsFrame::renderFields() const
{
ByteVector v;
String::Type encoding = d->textEncoding;
encoding = checkTextEncoding(d->description, encoding);
for(SynchedTextList::ConstIterator it = d->synchedText.begin();
it != d->synchedText.end();
++it) {
encoding = checkTextEncoding(it->text, encoding);
}
v.append(char(encoding));
v.append(d->language.size() == 3 ? d->language : "XXX");
v.append(char(d->timestampFormat));
v.append(char(d->type));
v.append(d->description.data(encoding));
v.append(textDelimiter(encoding));
for(SynchedTextList::ConstIterator it = d->synchedText.begin();
it != d->synchedText.end();
++it) {
const SynchedText &entry = *it;
v.append(entry.text.data(encoding));
v.append(textDelimiter(encoding));
v.append(ByteVector::fromUInt(entry.time));
}
return v;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
SynchronizedLyricsFrame::SynchronizedLyricsFrame(const ByteVector &data, Header *h)
: Frame(h)
{
d = new SynchronizedLyricsFramePrivate();
parseFields(fieldData(data));
}

View File

@ -0,0 +1,231 @@
/***************************************************************************
copyright : (C) 2014 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* 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_SYNCHRONIZEDLYRICSFRAME_H
#define TAGLIB_SYNCHRONIZEDLYRICSFRAME_H
#include "id3v2frame.h"
#include "tlist.h"
namespace TagLib {
namespace ID3v2 {
//! ID3v2 synchronized lyrics frame
/*!
* An implementation of ID3v2 synchronized lyrics.
*/
class TAGLIB_EXPORT SynchronizedLyricsFrame : public Frame
{
friend class FrameFactory;
public:
/*!
* Specifies the timestamp format used.
*/
enum TimestampFormat {
//! The timestamp is of unknown format.
Unknown = 0x00,
//! The timestamp represents the number of MPEG frames since
//! the beginning of the audio stream.
AbsoluteMpegFrames = 0x01,
//! The timestamp represents the number of milliseconds since
//! the beginning of the audio stream.
AbsoluteMilliseconds = 0x02
};
/*!
* Specifies the type of text contained.
*/
enum Type {
//! The text is some other type of text.
Other = 0x00,
//! The text contains lyrical data.
Lyrics = 0x01,
//! The text contains a transcription.
TextTranscription = 0x02,
//! The text lists the movements in the piece.
Movement = 0x03,
//! The text describes events that occur.
Events = 0x04,
//! The text contains chord changes that occur in the music.
Chord = 0x05,
//! The text contains trivia or "pop up" information about the media.
Trivia = 0x06,
//! The text contains URLs for relevant webpages.
WebpageUrls = 0x07,
//! The text contains URLs for relevant images.
ImageUrls = 0x08
};
/*!
* Single entry of time stamp and lyrics text.
*/
struct SynchedText {
SynchedText(uint ms, String str) : time(ms), text(str) {}
uint time;
String text;
};
/*!
* List of synchronized lyrics.
*/
typedef TagLib::List<SynchedText> SynchedTextList;
/*!
* Construct an empty synchronized lyrics frame that will use the text
* encoding \a encoding.
*/
explicit SynchronizedLyricsFrame(String::Type encoding = String::Latin1);
/*!
* Construct a synchronized lyrics frame based on the data in \a data.
*/
explicit SynchronizedLyricsFrame(const ByteVector &data);
/*!
* Destroys this SynchronizedLyricsFrame instance.
*/
virtual ~SynchronizedLyricsFrame();
/*!
* Returns the description of this synchronized lyrics frame.
*
* \see description()
*/
virtual String toString() const;
/*!
* Returns the text encoding that will be used in rendering this frame.
* This defaults to the type that was either specified in the constructor
* or read from the frame when parsed.
*
* \see setTextEncoding()
* \see render()
*/
String::Type textEncoding() const;
/*!
* Returns the language encoding as a 3 byte encoding as specified by
* <a href="http://en.wikipedia.org/wiki/ISO_639">ISO-639-2</a>.
*
* \note Most taggers simply ignore this value.
*
* \see setLanguage()
*/
ByteVector language() const;
/*!
* Returns the timestamp format.
*/
TimestampFormat timestampFormat() const;
/*!
* Returns the type of text contained.
*/
Type type() const;
/*!
* Returns the description of this synchronized lyrics frame.
*
* \note Most taggers simply ignore this value.
*
* \see setDescription()
*/
String description() const;
/*!
* Returns the text with the time stamps.
*/
SynchedTextList synchedText() const;
/*!
* Sets the text encoding to be used when rendering this frame to
* \a encoding.
*
* \see textEncoding()
* \see render()
*/
void setTextEncoding(String::Type encoding);
/*!
* Set the language using the 3 byte language code from
* <a href="http://en.wikipedia.org/wiki/ISO_639">ISO-639-2</a> to
* \a languageCode.
*
* \see language()
*/
void setLanguage(const ByteVector &languageCode);
/*!
* Set the timestamp format.
*
* \see timestampFormat()
*/
void setTimestampFormat(TimestampFormat f);
/*!
* Set the type of text contained.
*
* \see type()
*/
void setType(Type t);
/*!
* Sets the description of the synchronized lyrics frame to \a s.
*
* \see description()
*/
void setDescription(const String &s);
/*!
* Sets the text with the time stamps.
*
* \see text()
*/
void setSynchedText(const SynchedTextList &t);
protected:
// Reimplementations.
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;
private:
/*!
* The constructor used by the FrameFactory.
*/
SynchronizedLyricsFrame(const ByteVector &data, Header *h);
SynchronizedLyricsFrame(const SynchronizedLyricsFrame &);
SynchronizedLyricsFrame &operator=(const SynchronizedLyricsFrame &);
class SynchronizedLyricsFramePrivate;
SynchronizedLyricsFramePrivate *d;
};
}
}
#endif

View File

@ -0,0 +1,338 @@
/***************************************************************************
copyright : (C) 2013 by Lukas Krejci
email : krejclu6@fel.cvut.cz
***************************************************************************/
/***************************************************************************
* 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 <tbytevectorlist.h>
#include <tpropertymap.h>
#include <tdebug.h>
#include "tableofcontentsframe.h"
using namespace TagLib;
using namespace ID3v2;
class TableOfContentsFrame::TableOfContentsFramePrivate
{
public:
TableOfContentsFramePrivate() :
tagHeader(0)
{
embeddedFrameList.setAutoDelete(true);
}
const ID3v2::Header *tagHeader;
ByteVector elementID;
bool isTopLevel;
bool isOrdered;
ByteVectorList childElements;
FrameListMap embeddedFrameListMap;
FrameList embeddedFrameList;
};
namespace {
// These functions are needed to try to aim for backward compatibility with
// an API that previously (unreasonably) required null bytes to be appeneded
// at the end of identifiers explicitly by the API user.
// BIC: remove these
ByteVector &strip(ByteVector &b)
{
if(b.endsWith('\0'))
b.resize(b.size() - 1);
return b;
}
ByteVectorList &strip(ByteVectorList &l)
{
for(ByteVectorList::Iterator it = l.begin(); it != l.end(); ++it)
{
strip(*it);
}
return l;
}
}
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////
TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data) :
ID3v2::Frame(data)
{
d = new TableOfContentsFramePrivate;
d->tagHeader = tagHeader;
setData(data);
}
TableOfContentsFrame::TableOfContentsFrame(const ByteVector &elementID,
const ByteVectorList &children,
const FrameList &embeddedFrames) :
ID3v2::Frame("CTOC")
{
d = new TableOfContentsFramePrivate;
d->elementID = elementID;
strip(d->elementID);
d->childElements = children;
for(FrameList::ConstIterator it = embeddedFrames.begin(); it != embeddedFrames.end(); ++it)
addEmbeddedFrame(*it);
}
TableOfContentsFrame::~TableOfContentsFrame()
{
delete d;
}
ByteVector TableOfContentsFrame::elementID() const
{
return d->elementID;
}
bool TableOfContentsFrame::isTopLevel() const
{
return d->isTopLevel;
}
bool TableOfContentsFrame::isOrdered() const
{
return d->isOrdered;
}
TagLib::uint TableOfContentsFrame::entryCount() const
{
return d->childElements.size();
}
ByteVectorList TableOfContentsFrame::childElements() const
{
return d->childElements;
}
void TableOfContentsFrame::setElementID(const ByteVector &eID)
{
d->elementID = eID;
strip(d->elementID);
}
void TableOfContentsFrame::setIsTopLevel(const bool &t)
{
d->isTopLevel = t;
}
void TableOfContentsFrame::setIsOrdered(const bool &o)
{
d->isOrdered = o;
}
void TableOfContentsFrame::setChildElements(const ByteVectorList &l)
{
d->childElements = l;
strip(d->childElements);
}
void TableOfContentsFrame::addChildElement(const ByteVector &cE)
{
d->childElements.append(cE);
strip(d->childElements);
}
void TableOfContentsFrame::removeChildElement(const ByteVector &cE)
{
ByteVectorList::Iterator it = d->childElements.find(cE);
if(it == d->childElements.end())
it = d->childElements.find(cE + ByteVector("\0"));
d->childElements.erase(it);
}
const FrameListMap &TableOfContentsFrame::embeddedFrameListMap() const
{
return d->embeddedFrameListMap;
}
const FrameList &TableOfContentsFrame::embeddedFrameList() const
{
return d->embeddedFrameList;
}
const FrameList &TableOfContentsFrame::embeddedFrameList(const ByteVector &frameID) const
{
return d->embeddedFrameListMap[frameID];
}
void TableOfContentsFrame::addEmbeddedFrame(Frame *frame)
{
d->embeddedFrameList.append(frame);
d->embeddedFrameListMap[frame->frameID()].append(frame);
}
void TableOfContentsFrame::removeEmbeddedFrame(Frame *frame, bool del)
{
// remove the frame from the frame list
FrameList::Iterator it = d->embeddedFrameList.find(frame);
d->embeddedFrameList.erase(it);
// ...and from the frame list map
it = d->embeddedFrameListMap[frame->frameID()].find(frame);
d->embeddedFrameListMap[frame->frameID()].erase(it);
// ...and delete as desired
if(del)
delete frame;
}
void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id)
{
FrameList l = d->embeddedFrameListMap[id];
for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
removeEmbeddedFrame(*it, true);
}
String TableOfContentsFrame::toString() const
{
return String::null;
}
PropertyMap TableOfContentsFrame::asProperties() const
{
PropertyMap map;
map.unsupportedData().append(frameID() + String("/") + d->elementID);
return map;
}
TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag,
const ByteVector &eID) // static
{
ID3v2::FrameList tablesOfContents = tag->frameList("CTOC");
for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin();
it != tablesOfContents.end();
++it)
{
TableOfContentsFrame *frame = dynamic_cast<TableOfContentsFrame *>(*it);
if(frame && frame->elementID() == eID)
return frame;
}
return 0;
}
TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const ID3v2::Tag *tag) // static
{
ID3v2::FrameList tablesOfContents = tag->frameList("CTOC");
for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin();
it != tablesOfContents.end();
++it)
{
TableOfContentsFrame *frame = dynamic_cast<TableOfContentsFrame *>(*it);
if(frame && frame->isTopLevel() == true)
return frame;
}
return 0;
}
void TableOfContentsFrame::parseFields(const ByteVector &data)
{
TagLib::uint size = data.size();
if(size < 6) {
debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by "
"null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated "
"by null.");
return;
}
int pos = 0;
TagLib::uint embPos = 0;
d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
d->isTopLevel = (data.at(pos) & 2) > 0;
d->isOrdered = (data.at(pos++) & 1) > 0;
TagLib::uint entryCount = data.at(pos++);
for(TagLib::uint i = 0; i < entryCount; i++) {
ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
d->childElements.append(childElementID);
}
size -= pos;
if(size < header()->size())
return;
while(embPos < size - header()->size()) {
Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), (d->tagHeader != 0));
if(!frame)
return;
// Checks to make sure that frame parsed correctly.
if(frame->size() <= 0) {
delete frame;
return;
}
embPos += frame->size() + header()->size();
addEmbeddedFrame(frame);
}
}
ByteVector TableOfContentsFrame::renderFields() const
{
ByteVector data;
data.append(d->elementID);
data.append('\0');
char flags = 0;
if(d->isTopLevel)
flags += 2;
if(d->isOrdered)
flags += 1;
data.append(flags);
data.append((char)(entryCount()));
ByteVectorList::ConstIterator it = d->childElements.begin();
while(it != d->childElements.end()) {
data.append(*it);
data.append('\0');
it++;
}
FrameList l = d->embeddedFrameList;
for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
data.append((*it)->render());
return data;
}
TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader,
const ByteVector &data, Header *h) :
Frame(h)
{
d = new TableOfContentsFramePrivate;
d->tagHeader = tagHeader;
parseFields(fieldData(data));
}

View File

@ -0,0 +1,258 @@
/***************************************************************************
copyright : (C) 2013 by Lukas Krejci
email : krejclu6@fel.cvut.cz
***************************************************************************/
/***************************************************************************
* 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_TABLEOFCONTENTSFRAME
#define TAGLIB_TABLEOFCONTENTSFRAME
#include "id3v2tag.h"
#include "id3v2frame.h"
namespace TagLib {
namespace ID3v2 {
/*!
* This is an implementation of ID3v2 table of contents frames. Purpose
* of this frame is to allow a table of contents to be defined.
*/
//! An implementation of ID3v2 table of contents frames
class TAGLIB_EXPORT TableOfContentsFrame : public ID3v2::Frame
{
friend class FrameFactory;
public:
/*!
* Creates a table of contents frame based on \a data. \a tagHeader is
* required as the internal frames are parsed based on the tag version.
*/
TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data);
/*!
* Creates a table of contents frame with the element ID \a elementID,
* the child elements \a children and embedded frames, which become owned
* by this frame, in \a embeddedFrames.
*/
TableOfContentsFrame(const ByteVector &elementID,
const ByteVectorList &children = ByteVectorList(),
const FrameList &embeddedFrames = FrameList());
/*!
* Destroys the frame.
*/
~TableOfContentsFrame();
/*!
* Returns the elementID of the frame. Element ID
* is a null terminated string, however it's not human-readable.
*
* \see setElementID()
*/
ByteVector elementID() const;
/*!
* Returns true, if the frame is top-level (doesn't have
* any parent CTOC frame).
*
* \see setIsTopLevel()
*/
bool isTopLevel() const;
/*!
* Returns true, if the child elements list entries
* are ordered.
*
* \see setIsOrdered()
*/
bool isOrdered() const;
/*!
* Returns count of child elements of the frame. It always
* corresponds to size of child elements list.
*
* \see childElements()
*/
uint entryCount() const;
/*!
* Returns list of child elements of the frame.
*
* \see setChildElements()
*/
ByteVectorList childElements() const;
/*!
* Sets the elementID of the frame to \a eID. If \a eID isn't
* null terminated, a null char is appended automatically.
*
* \see elementID()
*/
void setElementID(const ByteVector &eID);
/*!
* Sets, if the frame is top-level (doesn't have
* any parent CTOC frame).
*
* \see isTopLevel()
*/
void setIsTopLevel(const bool &t);
/*!
* Sets, if the child elements list entries
* are ordered.
*
* \see isOrdered()
*/
void setIsOrdered(const bool &o);
/*!
* Sets list of child elements of the frame to \a l.
*
* \see childElements()
*/
void setChildElements(const ByteVectorList &l);
/*!
* Adds \a cE to list of child elements of the frame.
*
* \see childElements()
*/
void addChildElement(const ByteVector &cE);
/*!
* Removes \a cE to list of child elements of the frame.
*
* \see childElements()
*/
void removeChildElement(const ByteVector &cE);
/*!
* Returns a reference to the frame list map. This is an FrameListMap of
* all of the frames embedded in the CTOC frame.
*
* This is the most convenient structure for accessing the CTOC frame's
* embedded frames. Many frame types allow multiple instances of the same
* frame type so this is a map of lists. In most cases however there will
* only be a single frame of a certain type.
*
* \warning You should not modify this data structure directly, instead
* use addEmbeddedFrame() and removeEmbeddedFrame().
*
* \see embeddedFrameList()
*/
const FrameListMap &embeddedFrameListMap() const;
/*!
* Returns a reference to the embedded frame list. This is an FrameList
* of all of the frames embedded in the CTOC frame in the order that they
* were parsed.
*
* This can be useful if for example you want iterate over the CTOC frame's
* embedded frames in the order that they occur in the CTOC frame.
*
* \warning You should not modify this data structure directly, instead
* use addEmbeddedFrame() and removeEmbeddedFrame().
*/
const FrameList &embeddedFrameList() const;
/*!
* Returns the embedded frame list for frames with the id \a frameID
* or an empty list if there are no embedded frames of that type. This
* is just a convenience and is equivalent to:
*
* \code
* embeddedFrameListMap()[frameID];
* \endcode
*
* \see embeddedFrameListMap()
*/
const FrameList &embeddedFrameList(const ByteVector &frameID) const;
/*!
* Add an embedded frame to the CTOC frame. At this point the CTOC frame
* takes ownership of the embedded frame and will handle freeing its memory.
*
* \note Using this method will invalidate any pointers on the list
* returned by embeddedFrameList()
*/
void addEmbeddedFrame(Frame *frame);
/*!
* Remove an embedded frame from the CTOC frame. If \a del is true the frame's
* memory will be freed; if it is false, it must be deleted by the user.
*
* \note Using this method will invalidate any pointers on the list
* returned by embeddedFrameList()
*/
void removeEmbeddedFrame(Frame *frame, bool del = true);
/*!
* Remove all embedded frames of type \a id from the CTOC frame and free their
* memory.
*
* \note Using this method will invalidate any pointers on the list
* returned by embeddedFrameList()
*/
void removeEmbeddedFrames(const ByteVector &id);
virtual String toString() const;
PropertyMap asProperties() const;
/*!
* CTOC frames each have a unique element ID. This searches for a CTOC
* frame with the element ID \a eID and returns a pointer to it. This
* can be used to link together parent and child CTOC frames.
*
* \see elementID()
*/
static TableOfContentsFrame *findByElementID(const Tag *tag, const ByteVector &eID);
/*!
* CTOC frames each contain a flag that indicates, if CTOC frame is top-level (there isn't
* any frame, which contains this frame in its child elements list). Only a single frame
* within tag can be top-level. This searches for a top-level CTOC frame.
*
* \see isTopLevel()
*/
static TableOfContentsFrame *findTopLevel(const Tag *tag);
protected:
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;
private:
TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h);
TableOfContentsFrame(const TableOfContentsFrame &);
TableOfContentsFrame &operator=(const TableOfContentsFrame &);
class TableOfContentsFramePrivate;
TableOfContentsFramePrivate *d;
};
}
}
#endif

View File

@ -211,12 +211,12 @@ void TextIdentificationFrame::parseFields(const ByteVector &data)
// append those split values to the list and make sure that the new string's // append those split values to the list and make sure that the new string's
// type is the same specified for this frame // type is the same specified for this frame
for(ByteVectorList::Iterator it = l.begin(); it != l.end(); it++) { for(ByteVectorList::ConstIterator it = l.begin(); it != l.end(); it++) {
if(!(*it).isEmpty()) { if(!(*it).isEmpty()) {
if(d->textEncoding == String::Latin1) if(d->textEncoding == String::Latin1)
d->fieldList.append(Tag::latin1StringHandler()->parse(*it)); d->fieldList.append(Tag::latin1StringHandler()->parse(*it));
else else
d->fieldList.append(String(*it, d->textEncoding)); d->fieldList.append(String(*it, d->textEncoding));
} }
} }
} }
@ -394,7 +394,7 @@ UserTextIdentificationFrame *UserTextIdentificationFrame::find(
ID3v2::Tag *tag, const String &description) // static ID3v2::Tag *tag, const String &description) // static
{ {
FrameList l = tag->frameList("TXXX"); FrameList l = tag->frameList("TXXX");
for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) { for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) {
UserTextIdentificationFrame *f = dynamic_cast<UserTextIdentificationFrame *>(*it); UserTextIdentificationFrame *f = dynamic_cast<UserTextIdentificationFrame *>(*it);
if(f && f->description() == description) if(f && f->description() == description)
return f; return f;

View File

@ -234,7 +234,7 @@ namespace TagLib {
* This description identifies the frame and must be unique. * This description identifies the frame and must be unique.
*/ */
//! An ID3v2 custom text identification frame implementationx //! An ID3v2 custom text identification frame implementation
class TAGLIB_EXPORT UserTextIdentificationFrame : public TextIdentificationFrame class TAGLIB_EXPORT UserTextIdentificationFrame : public TextIdentificationFrame
{ {

View File

@ -46,7 +46,7 @@ namespace TagLib {
public: public:
/*! /*!
* Creates a uniqe file identifier frame based on \a data. * Creates a unique file identifier frame based on \a data.
*/ */
UniqueFileIdentifierFrame(const ByteVector &data); UniqueFileIdentifierFrame(const ByteVector &data);

View File

@ -104,7 +104,7 @@ namespace TagLib {
/*! /*!
* Sets the description of the unsynchronized lyrics frame to \a s. * Sets the description of the unsynchronized lyrics frame to \a s.
* *
* \see decription() * \see description()
*/ */
void setDescription(const String &s); void setDescription(const String &s);
@ -149,7 +149,7 @@ namespace TagLib {
/*! /*!
* LyricsFrames each have a unique description. This searches for a lyrics * LyricsFrames each have a unique description. This searches for a lyrics
* frame with the decription \a d and returns a pointer to it. If no * frame with the description \a d and returns a pointer to it. If no
* frame is found that matches the given description null is returned. * frame is found that matches the given description null is returned.
* *
* \see description() * \see description()

View File

@ -169,7 +169,7 @@ PropertyMap UserUrlLinkFrame::asProperties() const
UserUrlLinkFrame *UserUrlLinkFrame::find(ID3v2::Tag *tag, const String &description) // static UserUrlLinkFrame *UserUrlLinkFrame::find(ID3v2::Tag *tag, const String &description) // static
{ {
FrameList l = tag->frameList("WXXX"); FrameList l = tag->frameList("WXXX");
for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) { for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) {
UserUrlLinkFrame *f = dynamic_cast<UserUrlLinkFrame *>(*it); UserUrlLinkFrame *f = dynamic_cast<UserUrlLinkFrame *>(*it);
if(f && f->description() == description) if(f && f->description() == description)
return f; return f;

View File

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

View File

@ -254,12 +254,49 @@ ByteVector Frame::fieldData(const ByteVector &frameData) const
if(d->header->compression() && if(d->header->compression() &&
!d->header->encryption()) !d->header->encryption())
{ {
ByteVector data(frameDataLength); if(frameData.size() <= frameDataOffset) {
uLongf uLongTmp = frameDataLength; debug("Compressed frame doesn't have enough data to decode");
::uncompress((Bytef *) data.data(), return ByteVector();
(uLongf *) &uLongTmp, }
(Bytef *) frameData.data() + frameDataOffset,
size()); z_stream stream = {};
if(inflateInit(&stream) != Z_OK)
return ByteVector();
stream.avail_in = (uLongf) frameData.size() - frameDataOffset;
stream.next_in = (Bytef *) frameData.data() + frameDataOffset;
static const uint chunkSize = 1024;
ByteVector data;
ByteVector chunk(chunkSize);
do {
stream.avail_out = (uLongf) chunk.size();
stream.next_out = (Bytef *) chunk.data();
int result = inflate(&stream, Z_NO_FLUSH);
if(result == Z_STREAM_ERROR ||
result == Z_NEED_DICT ||
result == Z_DATA_ERROR ||
result == Z_MEM_ERROR)
{
if(result != Z_STREAM_ERROR)
inflateEnd(&stream);
debug("Error reading compressed stream");
return ByteVector();
}
data.append(stream.avail_out == 0 ? chunk : chunk.mid(0, chunk.size() - stream.avail_out));
} while(stream.avail_out == 0);
inflateEnd(&stream);
if(frameDataLength != data.size())
debug("frameDataLength does not match the data length returned by zlib");
return data; return data;
} }
else else

View File

@ -165,7 +165,7 @@ namespace TagLib {
* All other processing of \a data should be handled in a subclass. * All other processing of \a data should be handled in a subclass.
* *
* \note This need not contain anything more than a frame ID, but * \note This need not contain anything more than a frame ID, but
* \e must constain at least that. * \e must contain at least that.
*/ */
explicit Frame(const ByteVector &data); explicit Frame(const ByteVector &data);
@ -218,9 +218,9 @@ namespace TagLib {
ByteVector fieldData(const ByteVector &frameData) const; ByteVector fieldData(const ByteVector &frameData) const;
/*! /*!
* Reads a String of type \a encodiong from the ByteVector \a data. If \a * Reads a String of type \a encoding from the ByteVector \a data. If \a
* position is passed in it is used both as the starting point and is * position is passed in it is used both as the starting point and is
* updated to replect the position just after the string that has been read. * updated to return the position just after the string that has been read.
* This is useful for reading strings sequentially. * This is useful for reading strings sequentially.
*/ */
String readStringField(const ByteVector &data, String::Type encoding, String readStringField(const ByteVector &data, String::Type encoding,
@ -460,7 +460,7 @@ namespace TagLib {
bool readOnly() const; bool readOnly() const;
/*! /*!
* Returns true if the flag for the grouping identifity is set. * Returns true if the flag for the grouping identity is set.
* *
* \note This flag is currently ignored internally in TagLib. * \note This flag is currently ignored internally in TagLib.
*/ */

View File

@ -45,6 +45,10 @@
#include "frames/popularimeterframe.h" #include "frames/popularimeterframe.h"
#include "frames/privateframe.h" #include "frames/privateframe.h"
#include "frames/ownershipframe.h" #include "frames/ownershipframe.h"
#include "frames/synchronizedlyricsframe.h"
#include "frames/eventtimingcodesframe.h"
#include "frames/chapterframe.h"
#include "frames/tableofcontentsframe.h"
using namespace TagLib; using namespace TagLib;
using namespace ID3v2; using namespace ID3v2;
@ -241,6 +245,20 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
return f; return f;
} }
// Synchronised lyrics/text (frames 4.9)
if(frameID == "SYLT") {
SynchronizedLyricsFrame *f = new SynchronizedLyricsFrame(data, header);
if(d->useDefaultEncoding)
f->setTextEncoding(d->defaultEncoding);
return f;
}
// Event timing codes (frames 4.5)
if(frameID == "ETCO")
return new EventTimingCodesFrame(data, header);
// Popularimeter (frames 4.17) // Popularimeter (frames 4.17)
if(frameID == "POPM") if(frameID == "POPM")
@ -250,18 +268,50 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
if(frameID == "PRIV") if(frameID == "PRIV")
return new PrivateFrame(data, header); return new PrivateFrame(data, header);
// Ownership (frames 4.22) // Ownership (frames 4.22)
if(frameID == "OWNE") { if(frameID == "OWNE") {
OwnershipFrame *f = new OwnershipFrame(data, header); OwnershipFrame *f = new OwnershipFrame(data, header);
d->setTextEncoding(f); d->setTextEncoding(f);
return f; return f;
} }
// Chapter (ID3v2 chapters 1.0)
if(frameID == "CHAP")
return new ChapterFrame(tagHeader, data, header);
// Table of contents (ID3v2 chapters 1.0)
if(frameID == "CTOC")
return new TableOfContentsFrame(tagHeader, data, header);
return new UnknownFrame(data, header); return new UnknownFrame(data, header);
} }
void FrameFactory::rebuildAggregateFrames(ID3v2::Tag *tag) const
{
if(tag->header()->majorVersion() < 4 &&
tag->frameList("TDRC").size() == 1 &&
tag->frameList("TDAT").size() == 1)
{
TextIdentificationFrame *trdc =
static_cast<TextIdentificationFrame *>(tag->frameList("TDRC").front());
UnknownFrame *tdat =
static_cast<UnknownFrame *>(tag->frameList("TDAT").front());
if(trdc->fieldList().size() == 1 &&
trdc->fieldList().front().size() == 4 &&
tdat->data().size() >= 5)
{
String date(tdat->data().mid(1), String::Type(tdat->data()[0]));
if(date.length() == 4)
trdc->setText(trdc->toString() + '-' + date.substr(2, 2) + '-' + date.substr(0, 2));
}
}
}
String::Type FrameFactory::defaultTextEncoding() const String::Type FrameFactory::defaultTextEncoding() const
{ {
return d->defaultEncoding; return d->defaultEncoding;
@ -430,7 +480,7 @@ void FrameFactory::updateGenre(TextIdentificationFrame *frame) const
StringList fields = frame->fieldList(); StringList fields = frame->fieldList();
StringList newfields; StringList newfields;
for(StringList::Iterator it = fields.begin(); it != fields.end(); ++it) { for(StringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) {
String s = *it; String s = *it;
int end = s.find(")"); int end = s.find(")");

View File

@ -93,11 +93,19 @@ namespace TagLib {
// BIC: make virtual // BIC: make virtual
Frame *createFrame(const ByteVector &data, Header *tagHeader) const; Frame *createFrame(const ByteVector &data, Header *tagHeader) const;
/*!
* After a tag has been read, this tries to rebuild some of them
* information, most notably the recording date, from frames that
* have been deprecated and can't be upgraded directly.
*/
// BIC: Make virtual
void rebuildAggregateFrames(ID3v2::Tag *tag) const;
/*! /*!
* Returns the default text encoding for text frames. If setTextEncoding() * Returns the default text encoding for text frames. If setTextEncoding()
* has not been explicitly called this will only be used for new text * has not been explicitly called this will only be used for new text
* frames. However, if this value has been set explicitly all frames will be * frames. However, if this value has been set explicitly all frames will be
* converted to this type (unless it's explitly set differently for the * converted to this type (unless it's explicitly set differently for the
* individual frame) when being rendered. * individual frame) when being rendered.
* *
* \see setDefaultTextEncoding() * \see setDefaultTextEncoding()

View File

@ -215,7 +215,7 @@ void Header::parse(const ByteVector &data)
return; return;
} }
for(ByteVector::Iterator it = sizeData.begin(); it != sizeData.end(); it++) { for(ByteVector::ConstIterator it = sizeData.begin(); it != sizeData.end(); it++) {
if(uchar(*it) >= 128) { if(uchar(*it) >= 128) {
d->tagSize = 0; d->tagSize = 0;
debug("TagLib::ID3v2::Header::parse() - One of the size bytes in the id3v2 header was greater than the allowed 128."); debug("TagLib::ID3v2::Header::parse() - One of the size bytes in the id3v2 header was greater than the allowed 128.");

View File

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

View File

@ -24,10 +24,10 @@
***************************************************************************/ ***************************************************************************/
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
#include <config.h> #include "config.h"
#endif #endif
#include <tfile.h> #include "tfile.h"
#include "id3v2tag.h" #include "id3v2tag.h"
#include "id3v2header.h" #include "id3v2header.h"
@ -37,7 +37,7 @@
#include "tbytevector.h" #include "tbytevector.h"
#include "id3v1genres.h" #include "id3v1genres.h"
#include "tpropertymap.h" #include "tpropertymap.h"
#include <tdebug.h> #include "tdebug.h"
#include "frames/textidentificationframe.h" #include "frames/textidentificationframe.h"
#include "frames/commentsframe.h" #include "frames/commentsframe.h"
@ -81,6 +81,11 @@ public:
static const Latin1StringHandler defaultStringHandler; static const Latin1StringHandler defaultStringHandler;
const ID3v2::Latin1StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStringHandler; const ID3v2::Latin1StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStringHandler;
namespace
{
const TagLib::uint DefaultPaddingSize = 1024;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// StringHandler implementation // StringHandler implementation
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -356,7 +361,7 @@ void ID3v2::Tag::removeFrame(Frame *frame, bool del)
void ID3v2::Tag::removeFrames(const ByteVector &id) void ID3v2::Tag::removeFrames(const ByteVector &id)
{ {
FrameList l = d->frameListMap[id]; FrameList l = d->frameListMap[id];
for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
removeFrame(*it, true); removeFrame(*it, true);
} }
@ -469,7 +474,7 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
ID3v2::TextIdentificationFrame *frameTDRC = 0; ID3v2::TextIdentificationFrame *frameTDRC = 0;
ID3v2::TextIdentificationFrame *frameTIPL = 0; ID3v2::TextIdentificationFrame *frameTIPL = 0;
ID3v2::TextIdentificationFrame *frameTMCL = 0; ID3v2::TextIdentificationFrame *frameTMCL = 0;
for(FrameList::Iterator it = d->frameList.begin(); it != d->frameList.end(); it++) { for(FrameList::ConstIterator it = d->frameList.begin(); it != d->frameList.end(); it++) {
ID3v2::Frame *frame = *it; ID3v2::Frame *frame = *it;
ByteVector frameID = frame->header()->frameID(); ByteVector frameID = frame->header()->frameID();
for(int i = 0; unsupportedFrames[i]; i++) { for(int i = 0; unsupportedFrames[i]; i++) {
@ -583,28 +588,41 @@ ByteVector ID3v2::Tag::render(int version) const
downgradeFrames(&frameList, &newFrames); downgradeFrames(&frameList, &newFrames);
} }
for(FrameList::Iterator it = frameList.begin(); it != frameList.end(); it++) { for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) {
(*it)->header()->setVersion(version); (*it)->header()->setVersion(version);
if((*it)->header()->frameID().size() != 4) { if((*it)->header()->frameID().size() != 4) {
debug("A frame of unsupported or unknown type \'" debug("An ID3v2 frame of unsupported or unknown type \'"
+ String((*it)->header()->frameID()) + "\' has been discarded"); + String((*it)->header()->frameID()) + "\' has been discarded");
continue; continue;
} }
if(!(*it)->header()->tagAlterPreservation()) if(!(*it)->header()->tagAlterPreservation()) {
tagData.append((*it)->render()); const ByteVector frameData = (*it)->render();
if(frameData.size() == Frame::headerSize((*it)->header()->version())) {
debug("An empty ID3v2 frame \'"
+ String((*it)->header()->frameID()) + "\' has been discarded");
continue;
}
tagData.append(frameData);
}
} }
// Compute the amount of padding, and append that to tagData. // Compute the amount of padding, and append that to tagData.
uint paddingSize = 0; uint paddingSize = DefaultPaddingSize;
uint originalSize = d->header.tagSize();
if(tagData.size() < originalSize) if(d->file && tagData.size() < d->header.tagSize()) {
paddingSize = originalSize - tagData.size(); paddingSize = d->header.tagSize() - tagData.size();
else
paddingSize = 1024;
tagData.append(ByteVector(paddingSize, char(0))); // Padding won't increase beyond 1% of the file size.
if(paddingSize > DefaultPaddingSize) {
const uint threshold = d->file->length() / 100; // should be ulonglong in taglib2.
if(paddingSize > threshold)
paddingSize = DefaultPaddingSize;
}
}
tagData.append(ByteVector(paddingSize, '\0'));
// Set the version and data size. // Set the version and data size.
d->header.setMajorVersion(version); d->header.setMajorVersion(version);
@ -693,7 +711,7 @@ void ID3v2::Tag::parse(const ByteVector &origData)
} }
d->paddingSize = frameDataLength - frameDataPosition; d->paddingSize = frameDataLength - frameDataPosition;
return; break;
} }
Frame *frame = d->factory->createFrame(data.mid(frameDataPosition), Frame *frame = d->factory->createFrame(data.mid(frameDataPosition),
@ -712,6 +730,8 @@ void ID3v2::Tag::parse(const ByteVector &origData)
frameDataPosition += frame->size() + Frame::headerSize(d->header.majorVersion()); frameDataPosition += frame->size() + Frame::headerSize(d->header.majorVersion());
addFrame(frame); addFrame(frame);
} }
d->factory->rebuildAggregateFrames(this);
} }
void ID3v2::Tag::setTextFrame(const ByteVector &id, const String &value) void ID3v2::Tag::setTextFrame(const ByteVector &id, const String &value)

View File

@ -60,13 +60,13 @@ namespace TagLib {
//! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags. //! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags.
/*! /*!
* ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only * ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only
* supports genuine ISO-8859-1 by default. However, in practice, non * supports genuine ISO-8859-1 by default. However, in practice, non
* ISO-8859-1 encodings are often used instead of ISO-8859-1, such as * ISO-8859-1 encodings are often used instead of ISO-8859-1, such as
* Windows-1252 for western languages, Shift_JIS for Japanese and so on. * Windows-1252 for western languages, Shift_JIS for Japanese and so on.
* *
* Here is an option to read such tags by subclassing this class, * Here is an option to read such tags by subclassing this class,
* reimplementing parse() and setting your reimplementation as the default * reimplementing parse() and setting your reimplementation as the default
* with ID3v2::Tag::setStringHandler(). * with ID3v2::Tag::setStringHandler().
* *
* \note Writing non-ISO-8859-1 tags is not implemented intentionally. * \note Writing non-ISO-8859-1 tags is not implemented intentionally.
@ -98,7 +98,7 @@ namespace TagLib {
* split into data components. * split into data components.
* *
* ID3v2 tags have several parts, TagLib attempts to provide an interface * ID3v2 tags have several parts, TagLib attempts to provide an interface
* for them all. header(), footer() and extendedHeader() corespond to those * for them all. header(), footer() and extendedHeader() correspond to those
* data structures in the ID3v2 standard and the APIs for the classes that * data structures in the ID3v2 standard and the APIs for the classes that
* they return attempt to reflect this. * they return attempt to reflect this.
* *
@ -115,7 +115,7 @@ namespace TagLib {
* class. * class.
* *
* read() and parse() pass binary data to the other ID3v2 class structures, * read() and parse() pass binary data to the other ID3v2 class structures,
* they do not handle parsing of flags or fields, for instace. Those are * they do not handle parsing of flags or fields, for instance. Those are
* handled by similar functions within those classes. * handled by similar functions within those classes.
* *
* \note All pointers to data structures within the tag will become invalid * \note All pointers to data structures within the tag will become invalid
@ -126,7 +126,7 @@ namespace TagLib {
* rather long, but if you're planning on messing with this class and others * rather long, but if you're planning on messing with this class and others
* that deal with the details of ID3v2 (rather than the nice, safe, abstract * that deal with the details of ID3v2 (rather than the nice, safe, abstract
* TagLib::Tag and friends), it's worth your time to familiarize yourself * TagLib::Tag and friends), it's worth your time to familiarize yourself
* with said spec (which is distrubuted with the TagLib sources). TagLib * with said spec (which is distributed with the TagLib sources). TagLib
* tries to do most of the work, but with a little luck, you can still * tries to do most of the work, but with a little luck, you can still
* convince it to generate invalid ID3v2 tags. The APIs for ID3v2 assume a * convince it to generate invalid ID3v2 tags. The APIs for ID3v2 assume a
* working knowledge of ID3v2 structure. You're been warned. * working knowledge of ID3v2 structure. You're been warned.
@ -150,7 +150,7 @@ namespace TagLib {
* \note You should be able to ignore the \a factory parameter in almost * \note You should be able to ignore the \a factory parameter in almost
* all situations. You would want to specify your own FrameFactory * all situations. You would want to specify your own FrameFactory
* subclass in the case that you are extending TagLib to support additional * subclass in the case that you are extending TagLib to support additional
* frame types, which would be incorperated into your factory. * frame types, which would be incorporated into your factory.
* *
* \see FrameFactory * \see FrameFactory
*/ */
@ -353,9 +353,9 @@ namespace TagLib {
*/ */
// BIC: combine with the above method // BIC: combine with the above method
ByteVector render(int version) const; ByteVector render(int version) const;
/*! /*!
* Gets the current string handler that decides how the "Latin-1" data * Gets the current string handler that decides how the "Latin-1" data
* will be converted to and from binary data. * will be converted to and from binary data.
* *
* \see Latin1StringHandler * \see Latin1StringHandler
@ -369,7 +369,7 @@ namespace TagLib {
* released and default ISO-8859-1 handler is restored. * released and default ISO-8859-1 handler is restored.
* *
* \note The caller is responsible for deleting the previous handler * \note The caller is responsible for deleting the previous handler
* as needed after it is released. * as needed after it is released.
* *
* \see Latin1StringHandler * \see Latin1StringHandler
*/ */

View File

@ -31,14 +31,31 @@
#include <apetag.h> #include <apetag.h>
#include <tdebug.h> #include <tdebug.h>
#include <bitset>
#include "mpegfile.h" #include "mpegfile.h"
#include "mpegheader.h" #include "mpegheader.h"
#include "tpropertymap.h" #include "tpropertymap.h"
using namespace TagLib; using namespace TagLib;
namespace
{
/*!
* MPEG frames can be recognized by the bit pattern 11111111 111, so the
* first byte is easy to check for, however checking to see if the second byte
* starts with \e 111 is a bit more tricky, hence these functions.
*/
inline bool firstSyncByte(uchar byte)
{
return (byte == 0xFF);
}
inline bool secondSynchByte(uchar byte)
{
return ((byte & 0xE0) == 0xE0);
}
}
namespace namespace
{ {
enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 }; enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 };
@ -60,7 +77,6 @@ public:
hasAPE(false), hasAPE(false),
properties(0) properties(0)
{ {
} }
~FilePrivate() ~FilePrivate()
@ -95,33 +111,30 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
MPEG::File::File(FileName file, bool readProperties, MPEG::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
Properties::ReadStyle propertiesStyle) : TagLib::File(file) TagLib::File(file),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory, MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
bool readProperties, Properties::ReadStyle propertiesStyle) : bool readProperties, Properties::ReadStyle) :
TagLib::File(file) TagLib::File(file),
d(new FilePrivate(frameFactory))
{ {
d = new FilePrivate(frameFactory);
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
bool readProperties, Properties::ReadStyle propertiesStyle) : bool readProperties, Properties::ReadStyle) :
TagLib::File(stream) TagLib::File(stream),
d(new FilePrivate(frameFactory))
{ {
d = new FilePrivate(frameFactory);
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
MPEG::File::~File() MPEG::File::~File()
@ -392,11 +405,11 @@ long MPEG::File::nextFrameOffset(long position)
return position - 1; return position - 1;
for(uint i = 0; i < buffer.size() - 1; i++) { for(uint i = 0; i < buffer.size() - 1; i++) {
if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1]))
return position + i; return position + i;
} }
foundLastSyncPattern = uchar(buffer[buffer.size() - 1]) == 0xff; foundLastSyncPattern = firstSyncByte(buffer[buffer.size() - 1]);
position += buffer.size(); position += buffer.size();
} }
} }
@ -416,11 +429,11 @@ long MPEG::File::previousFrameOffset(long position)
if(buffer.size() <= 0) if(buffer.size() <= 0)
break; break;
if(foundFirstSyncPattern && uchar(buffer[buffer.size() - 1]) == 0xff) if(foundFirstSyncPattern && firstSyncByte(buffer[buffer.size() - 1]))
return position + buffer.size() - 1; return position + buffer.size() - 1;
for(int i = buffer.size() - 2; i >= 0; i--) { for(int i = buffer.size() - 2; i >= 0; i--) {
if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1]))
return position + i; return position + i;
} }
@ -433,7 +446,7 @@ long MPEG::File::firstFrameOffset()
{ {
long position = 0; long position = 0;
if(ID3v2Tag()) { if(hasID3v2Tag()) {
position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize(); position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize();
// Skip duplicate ID3v2 tags. // Skip duplicate ID3v2 tags.
@ -456,7 +469,7 @@ long MPEG::File::firstFrameOffset()
long MPEG::File::lastFrameOffset() long MPEG::File::lastFrameOffset()
{ {
return previousFrameOffset(ID3v1Tag() ? d->ID3v1Location - 1 : length()); return previousFrameOffset(hasID3v1Tag() ? d->ID3v1Location - 1 : length());
} }
bool MPEG::File::hasID3v1Tag() const bool MPEG::File::hasID3v1Tag() const
@ -478,7 +491,7 @@ bool MPEG::File::hasAPETag() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) void MPEG::File::read(bool readProperties)
{ {
// Look for an ID3v2 tag // Look for an ID3v2 tag
@ -517,7 +530,7 @@ void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle
} }
if(readProperties) if(readProperties)
d->properties = new Properties(this, propertiesStyle); d->properties = new Properties(this);
// Make sure that we have our default tag types available. // Make sure that we have our default tag types available.
@ -528,7 +541,7 @@ void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle
long MPEG::File::findID3v2(long offset) long MPEG::File::findID3v2(long offset)
{ {
// This method is based on the contents of TagLib::File::find(), but because // This method is based on the contents of TagLib::File::find(), but because
// of some subtlteies -- specifically the need to look for the bit pattern of // of some subtleties -- specifically the need to look for the bit pattern of
// an MPEG sync, it has been modified for use here. // an MPEG sync, it has been modified for use here.
if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) { if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) {
@ -668,11 +681,3 @@ void MPEG::File::findAPE()
d->APELocation = -1; d->APELocation = -1;
d->APEFooterLocation = -1; d->APEFooterLocation = -1;
} }
bool MPEG::File::secondSynchByte(char byte)
{
std::bitset<8> b(byte);
// check to see if the byte matches 111xxxxx
return b.test(7) && b.test(6) && b.test(5);
}

View File

@ -173,7 +173,10 @@ namespace TagLib {
* This is the same as calling save(AllTags); * This is the same as calling save(AllTags);
* *
* If you would like more granular control over the content of the tags, * If you would like more granular control over the content of the tags,
* with the concession of generality, use paramaterized save call below. * with the concession of generality, use parameterized save call below.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
* *
* \see save(int tags) * \see save(int tags)
*/ */
@ -187,6 +190,9 @@ namespace TagLib {
* This strips all tags not included in the mask, but does not modify them * This strips all tags not included in the mask, but does not modify them
* in memory, so later calls to save() which make use of these tags will * in memory, so later calls to save() which make use of these tags will
* remain valid. This also strips empty tags. * remain valid. This also strips empty tags.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
bool save(int tags); bool save(int tags);
@ -198,6 +204,9 @@ namespace TagLib {
* If \a stripOthers is true this strips all tags not included in the mask, * If \a stripOthers is true this strips all tags not included in the mask,
* but does not modify them in memory, so later calls to save() which make * but does not modify them in memory, so later calls to save() which make
* use of these tags will remain valid. This also strips empty tags. * use of these tags will remain valid. This also strips empty tags.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
// BIC: combine with the above method // BIC: combine with the above method
bool save(int tags, bool stripOthers); bool save(int tags, bool stripOthers);
@ -213,6 +222,9 @@ namespace TagLib {
* *
* The \a id3v2Version parameter specifies the version of the saved * The \a id3v2Version parameter specifies the version of the saved
* ID3v2 tag. It can be either 4 or 3. * ID3v2 tag. It can be either 4 or 3.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
// BIC: combine with the above method // BIC: combine with the above method
bool save(int tags, bool stripOthers, int id3v2Version); bool save(int tags, bool stripOthers, int id3v2Version);
@ -231,6 +243,9 @@ namespace TagLib {
* *
* If \a duplicateTags is true and at least one tag -- ID3v1 or ID3v2 -- * If \a duplicateTags is true and at least one tag -- ID3v1 or ID3v2 --
* exists this will duplicate its content into the other tag. * exists this will duplicate its content into the other tag.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. At worst it will corrupt the file.
*/ */
// BIC: combine with the above method // BIC: combine with the above method
bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags); bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags);
@ -301,6 +316,8 @@ namespace TagLib {
* *
* \note This will also invalidate pointers to the ID3 and APE tags * \note This will also invalidate pointers to the ID3 and APE tags
* as their memory will be freed. * as their memory will be freed.
*
* \note This will update the file immediately.
*/ */
bool strip(int tags = AllTags); bool strip(int tags = AllTags);
@ -311,6 +328,8 @@ namespace TagLib {
* *
* If \a freeMemory is true the ID3 and APE tags will be deleted and * If \a freeMemory is true the ID3 and APE tags will be deleted and
* pointers to them will be invalidated. * pointers to them will be invalidated.
*
* \note This will update the file immediately.
*/ */
// BIC: merge with the method above // BIC: merge with the method above
bool strip(int tags, bool freeMemory); bool strip(int tags, bool freeMemory);
@ -319,6 +338,7 @@ namespace TagLib {
* Set the ID3v2::FrameFactory to something other than the default. * Set the ID3v2::FrameFactory to something other than the default.
* *
* \see ID3v2FrameFactory * \see ID3v2FrameFactory
* \deprecated This value should be passed in via the constructor
*/ */
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
@ -369,18 +389,11 @@ namespace TagLib {
File(const File &); File(const File &);
File &operator=(const File &); File &operator=(const File &);
void read(bool readProperties, Properties::ReadStyle propertiesStyle); void read(bool readProperties);
long findID3v2(long offset); long findID3v2(long offset);
long findID3v1(); long findID3v1();
void findAPE(); void findAPE();
/*!
* MPEG frames can be recognized by the bit pattern 11111111 111, so the
* first byte is easy to check for, however checking to see if the second byte
* starts with \e 111 is a bit more tricky, hence this member function.
*/
static bool secondSynchByte(char byte);
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;
}; };

View File

@ -213,8 +213,8 @@ void MPEG::Header::parse(const ByteVector &data)
} }
}; };
const int versionIndex = d->version == Version1 ? 0 : 1; const int versionIndex = (d->version == Version1) ? 0 : 1;
const int layerIndex = d->layer > 0 ? d->layer - 1 : 0; const int layerIndex = (d->layer > 0) ? d->layer - 1 : 0;
// The bitrate index is encoded as the first 4 bits of the 3rd byte, // The bitrate index is encoded as the first 4 bits of the 3rd byte,
// i.e. 1111xxxx // i.e. 1111xxxx
@ -253,13 +253,6 @@ void MPEG::Header::parse(const ByteVector &data)
d->isCopyrighted = flags[3]; d->isCopyrighted = flags[3];
d->isPadded = flags[9]; d->isPadded = flags[9];
// Calculate the frame length
if(d->layer == 1)
d->frameLength = 24000 * 2 * d->bitrate / d->sampleRate + int(d->isPadded);
else
d->frameLength = 72000 * d->bitrate / d->sampleRate + int(d->isPadded);
// Samples per frame // Samples per frame
static const int samplesPerFrame[3][2] = { static const int samplesPerFrame[3][2] = {
@ -271,6 +264,15 @@ void MPEG::Header::parse(const ByteVector &data)
d->samplesPerFrame = samplesPerFrame[layerIndex][versionIndex]; d->samplesPerFrame = samplesPerFrame[layerIndex][versionIndex];
// Calculate the frame length
static const int paddingSize[3] = { 4, 1, 1 };
d->frameLength = d->samplesPerFrame * d->bitrate * 125 / d->sampleRate;
if(d->isPadded)
d->frameLength += paddingSize[layerIndex];
// Now that we're done parsing, set this to be a valid frame. // Now that we're done parsing, set this to be a valid frame.
d->isValid = true; d->isValid = true;

View File

@ -140,7 +140,7 @@ namespace TagLib {
bool isOriginal() const; bool isOriginal() const;
/*! /*!
* Returns the frame length. * Returns the frame length in bytes.
*/ */
int frameLength() const; int frameLength() const;

View File

@ -29,16 +29,16 @@
#include "mpegproperties.h" #include "mpegproperties.h"
#include "mpegfile.h" #include "mpegfile.h"
#include "xingheader.h" #include "xingheader.h"
#include "apetag.h"
#include "apefooter.h"
using namespace TagLib; using namespace TagLib;
class MPEG::Properties::PropertiesPrivate class MPEG::Properties::PropertiesPrivate
{ {
public: public:
PropertiesPrivate(File *f, ReadStyle s) : PropertiesPrivate() :
file(f),
xingHeader(0), xingHeader(0),
style(s),
length(0), length(0),
bitrate(0), bitrate(0),
sampleRate(0), sampleRate(0),
@ -55,9 +55,7 @@ public:
delete xingHeader; delete xingHeader;
} }
File *file;
XingHeader *xingHeader; XingHeader *xingHeader;
ReadStyle style;
int length; int length;
int bitrate; int bitrate;
int sampleRate; int sampleRate;
@ -74,12 +72,11 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
MPEG::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) MPEG::Properties::Properties(File *file, ReadStyle style) :
AudioProperties(style),
d(new PropertiesPrivate())
{ {
d = new PropertiesPrivate(file, style); read(file);
if(file && file->isOpen())
read();
} }
MPEG::Properties::~Properties() MPEG::Properties::~Properties()
@ -88,6 +85,16 @@ MPEG::Properties::~Properties()
} }
int MPEG::Properties::length() const int MPEG::Properties::length() const
{
return lengthInSeconds();
}
int MPEG::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int MPEG::Properties::lengthInMilliseconds() const
{ {
return d->length; return d->length;
} }
@ -146,109 +153,72 @@ bool MPEG::Properties::isOriginal() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void MPEG::Properties::read() void MPEG::Properties::read(File *file)
{ {
// Since we've likely just looked for the ID3v1 tag, start at the end of the // Only the first frame is required if we have a VBR header.
// file where we're least likely to have to have to move the disk head.
long last = d->file->lastFrameOffset();
if(last < 0) {
debug("MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream.");
return;
}
d->file->seek(last);
Header lastHeader(d->file->readBlock(4));
long first = d->file->firstFrameOffset();
const long first = file->firstFrameOffset();
if(first < 0) { if(first < 0) {
debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream."); debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream.");
return; return;
} }
if(!lastHeader.isValid()) { file->seek(first);
const Header firstHeader(file->readBlock(4));
long pos = last; if(!firstHeader.isValid()) {
debug("MPEG::Properties::read() -- The first page header is invalid.");
while(pos > first) {
pos = d->file->previousFrameOffset(pos);
if(pos < 0)
break;
d->file->seek(pos);
Header header(d->file->readBlock(4));
if(header.isValid()) {
lastHeader = header;
last = pos;
break;
}
}
}
// Now jump back to the front of the file and read what we need from there.
d->file->seek(first);
Header firstHeader(d->file->readBlock(4));
if(!firstHeader.isValid() || !lastHeader.isValid()) {
debug("MPEG::Properties::read() -- Page headers were invalid.");
return; return;
} }
// Check for a Xing header that will help us in gathering information about a // Check for a VBR header that will help us in gathering information about a
// VBR stream. // VBR stream.
int xingHeaderOffset = MPEG::XingHeader::xingHeaderOffset(firstHeader.version(), file->seek(first + 4);
firstHeader.channelMode()); d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength() - 4));
if(!d->xingHeader->isValid()) {
d->file->seek(first + xingHeaderOffset);
d->xingHeader = new XingHeader(d->file->readBlock(16));
// Read the length and the bitrate from the Xing header.
if(d->xingHeader->isValid() &&
firstHeader.sampleRate() > 0 &&
d->xingHeader->totalFrames() > 0)
{
double timePerFrame =
double(firstHeader.samplesPerFrame()) / firstHeader.sampleRate();
double length = timePerFrame * d->xingHeader->totalFrames();
d->length = int(length);
d->bitrate = d->length > 0 ? (int)(d->xingHeader->totalSize() * 8 / length / 1000) : 0;
}
else {
// Since there was no valid Xing header found, we hope that we're in a constant
// bitrate file.
delete d->xingHeader; delete d->xingHeader;
d->xingHeader = 0; d->xingHeader = 0;
}
if(d->xingHeader && firstHeader.samplesPerFrame() > 0 && firstHeader.sampleRate() > 0) {
// Read the length and the bitrate from the VBR header.
const double timePerFrame = firstHeader.samplesPerFrame() * 1000.0 / firstHeader.sampleRate();
const double length = timePerFrame * d->xingHeader->totalFrames();
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(d->xingHeader->totalSize() * 8.0 / length + 0.5);
}
else if(firstHeader.bitrate() > 0) {
// Since there was no valid VBR header found, we hope that we're in a constant
// bitrate file.
// TODO: Make this more robust with audio property detection for VBR without a // TODO: Make this more robust with audio property detection for VBR without a
// Xing header. // Xing header.
if(firstHeader.frameLength() > 0 && firstHeader.bitrate() > 0) { d->bitrate = firstHeader.bitrate();
int frames = (last - first) / firstHeader.frameLength() + 1;
d->length = int(float(firstHeader.frameLength() * frames) / long streamLength = file->length() - first;
float(firstHeader.bitrate() * 125) + 0.5);
d->bitrate = firstHeader.bitrate(); if(file->hasID3v1Tag())
} streamLength -= 128;
if(file->hasAPETag())
streamLength -= file->APETag()->footer()->completeTagSize();
if(streamLength > 0)
d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5);
} }
d->sampleRate = firstHeader.sampleRate();
d->sampleRate = firstHeader.sampleRate(); d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2;
d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2; d->version = firstHeader.version();
d->version = firstHeader.version(); d->layer = firstHeader.layer();
d->layer = firstHeader.layer();
d->protectionEnabled = firstHeader.protectionEnabled(); d->protectionEnabled = firstHeader.protectionEnabled();
d->channelMode = firstHeader.channelMode(); d->channelMode = firstHeader.channelMode();
d->isCopyrighted = firstHeader.isCopyrighted(); d->isCopyrighted = firstHeader.isCopyrighted();
d->isOriginal = firstHeader.isOriginal(); d->isOriginal = firstHeader.isOriginal();
} }

View File

@ -59,18 +59,52 @@ namespace TagLib {
*/ */
virtual ~Properties(); virtual ~Properties();
// Reimplementations. /*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const; virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const; virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*/
virtual int sampleRate() const; virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const; virtual int channels() const;
/*! /*!
* Returns a pointer to the XingHeader if one exists or null if no * Returns a pointer to the Xing/VBRI header if one exists or null if no
* XingHeader was found. * Xing/VBRI header was found.
*/ */
const XingHeader *xingHeader() const; const XingHeader *xingHeader() const;
/*! /*!
@ -107,7 +141,7 @@ namespace TagLib {
Properties(const Properties &); Properties(const Properties &);
Properties &operator=(const Properties &); Properties &operator=(const Properties &);
void read(); void read(File *file);
class PropertiesPrivate; class PropertiesPrivate;
PropertiesPrivate *d; PropertiesPrivate *d;

View File

@ -28,6 +28,7 @@
#include <tdebug.h> #include <tdebug.h>
#include "xingheader.h" #include "xingheader.h"
#include "mpegfile.h"
using namespace TagLib; using namespace TagLib;
@ -37,17 +38,21 @@ public:
XingHeaderPrivate() : XingHeaderPrivate() :
frames(0), frames(0),
size(0), size(0),
valid(false) type(MPEG::XingHeader::Invalid) {}
{}
uint frames; uint frames;
uint size; uint size;
bool valid;
MPEG::XingHeader::HeaderType type;
}; };
MPEG::XingHeader::XingHeader(const ByteVector &data) ////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
MPEG::XingHeader::XingHeader(const ByteVector &data) :
d(new XingHeaderPrivate())
{ {
d = new XingHeaderPrivate;
parse(data); parse(data);
} }
@ -58,7 +63,7 @@ MPEG::XingHeader::~XingHeader()
bool MPEG::XingHeader::isValid() const bool MPEG::XingHeader::isValid() const
{ {
return d->valid; return (d->type != Invalid && d->frames > 0 && d->size > 0);
} }
TagLib::uint MPEG::XingHeader::totalFrames() const TagLib::uint MPEG::XingHeader::totalFrames() const
@ -71,45 +76,65 @@ TagLib::uint MPEG::XingHeader::totalSize() const
return d->size; return d->size;
} }
int MPEG::XingHeader::xingHeaderOffset(TagLib::MPEG::Header::Version v, MPEG::XingHeader::HeaderType MPEG::XingHeader::type() const
TagLib::MPEG::Header::ChannelMode c)
{ {
if(v == MPEG::Header::Version1) { return d->type;
if(c == MPEG::Header::SingleChannel)
return 0x15;
else
return 0x24;
}
else {
if(c == MPEG::Header::SingleChannel)
return 0x0D;
else
return 0x15;
}
} }
int MPEG::XingHeader::xingHeaderOffset(TagLib::MPEG::Header::Version /*v*/,
TagLib::MPEG::Header::ChannelMode /*c*/)
{
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void MPEG::XingHeader::parse(const ByteVector &data) void MPEG::XingHeader::parse(const ByteVector &data)
{ {
// Check to see if a valid Xing header is available. // Look for a Xing header.
if(!data.startsWith("Xing") && !data.startsWith("Info")) long offset = data.find("Xing");
return; if(offset < 0)
offset = data.find("Info");
// If the XingHeader doesn't contain the number of frames and the total stream if(offset >= 0) {
// info it's invalid.
if(!(data[7] & 0x01)) { // Xing header found.
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total number of frames.");
return; if(data.size() < static_cast<ulong>(offset + 16)) {
debug("MPEG::XingHeader::parse() -- Xing header found but too short.");
return;
}
if((data[offset + 7] & 0x03) != 0x03) {
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the required information.");
return;
}
d->frames = data.toUInt(offset + 8, true);
d->size = data.toUInt(offset + 12, true);
d->type = Xing;
} }
else {
if(!(data[7] & 0x02)) { // Xing header not found. Then look for a VBRI header.
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total stream size.");
return; offset = data.find("VBRI");
if(offset >= 0) {
// VBRI header found.
if(data.size() < static_cast<ulong>(offset + 32)) {
debug("MPEG::XingHeader::parse() -- VBRI header found but too short.");
return;
}
d->frames = data.toUInt(offset + 14, true);
d->size = data.toUInt(offset + 10, true);
d->type = VBRI;
}
} }
d->frames = data.toUInt(8U);
d->size = data.toUInt(12U);
d->valid = true;
} }

View File

@ -35,24 +35,47 @@ namespace TagLib {
namespace MPEG { namespace MPEG {
//! An implementation of the Xing VBR headers class File;
//! An implementation of the Xing/VBRI headers
/*! /*!
* This is a minimalistic implementation of the Xing VBR headers. Xing * This is a minimalistic implementation of the Xing/VBRI VBR headers.
* headers are often added to VBR (variable bit rate) MP3 streams to make it * Xing/VBRI headers are often added to VBR (variable bit rate) MP3 streams
* easy to compute the length and quality of a VBR stream. Our implementation * to make it easy to compute the length and quality of a VBR stream. Our
* is only concerned with the total size of the stream (so that we can * implementation is only concerned with the total size of the stream (so
* calculate the total playing time and the average bitrate). It uses * that we can calculate the total playing time and the average bitrate).
* <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt">this text</a> * It uses <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt">
* and the XMMS sources as references. * this text</a> and the XMMS sources as references.
*/ */
class TAGLIB_EXPORT XingHeader class TAGLIB_EXPORT XingHeader
{ {
public: public:
/*! /*!
* Parses a Xing header based on \a data. The data must be at least 16 * The type of the VBR header.
* bytes long (anything longer than this is discarded). */
enum HeaderType
{
/*!
* Invalid header or no VBR header found.
*/
Invalid = 0,
/*!
* Xing header.
*/
Xing = 1,
/*!
* VBRI header.
*/
VBRI = 2,
};
/*!
* Parses an Xing/VBRI header based on \a data which contains the entire
* first MPEG frame.
*/ */
XingHeader(const ByteVector &data); XingHeader(const ByteVector &data);
@ -63,7 +86,7 @@ namespace TagLib {
/*! /*!
* Returns true if the data was parsed properly and if there is a valid * Returns true if the data was parsed properly and if there is a valid
* Xing header present. * Xing/VBRI header present.
*/ */
bool isValid() const; bool isValid() const;
@ -77,11 +100,17 @@ namespace TagLib {
*/ */
uint totalSize() const; uint totalSize() const;
/*!
* Returns the type of the VBR header.
*/
HeaderType type() const;
/*! /*!
* Returns the offset for the start of this Xing header, given the * Returns the offset for the start of this Xing header, given the
* version and channels of the frame * version and channels of the frame
*
* \deprecated Always returns 0.
*/ */
// BIC: rename to offset()
static int xingHeaderOffset(TagLib::MPEG::Header::Version v, static int xingHeaderOffset(TagLib::MPEG::Header::Version v,
TagLib::MPEG::Header::ChannelMode c); TagLib::MPEG::Header::ChannelMode c);

View File

@ -103,7 +103,7 @@ PropertyMap Ogg::FLAC::File::properties() const
PropertyMap Ogg::FLAC::File::setProperties(const PropertyMap &properties) PropertyMap Ogg::FLAC::File::setProperties(const PropertyMap &properties)
{ {
return d->comment->setProperties(properties); return d->comment->setProperties(properties);
} }
Properties *Ogg::FLAC::File::audioProperties() const Properties *Ogg::FLAC::File::audioProperties() const
{ {
@ -211,29 +211,30 @@ void Ogg::FLAC::File::scan()
long overhead = 0; long overhead = 0;
ByteVector metadataHeader = packet(ipacket); ByteVector metadataHeader = packet(ipacket);
if(metadataHeader.isNull()) if(metadataHeader.isEmpty())
return; return;
ByteVector header; if(!metadataHeader.startsWith("fLaC")) {
if (!metadataHeader.startsWith("fLaC")) {
// FLAC 1.1.2+ // FLAC 1.1.2+
if (metadataHeader.mid(1,4) != "FLAC") return; if(metadataHeader.mid(1, 4) != "FLAC")
return;
if (metadataHeader[5] != 1) return; // not version 1 if(metadataHeader[5] != 1)
return; // not version 1
metadataHeader = metadataHeader.mid(13); metadataHeader = metadataHeader.mid(13);
} }
else { else {
// FLAC 1.1.0 & 1.1.1 // FLAC 1.1.0 & 1.1.1
metadataHeader = packet(++ipacket); metadataHeader = packet(++ipacket);
if(metadataHeader.isNull())
return;
} }
header = metadataHeader.mid(0,4); ByteVector header = metadataHeader.mid(0, 4);
if(header.size() != 4) {
debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header");
return;
}
// Header format (from spec): // Header format (from spec):
// <1> Last-metadata-block flag // <1> Last-metadata-block flag
// <7> BLOCK_TYPE // <7> BLOCK_TYPE
@ -262,11 +263,12 @@ void Ogg::FLAC::File::scan()
while(!lastBlock) { while(!lastBlock) {
metadataHeader = packet(++ipacket); metadataHeader = packet(++ipacket);
if(metadataHeader.isNull())
return;
header = metadataHeader.mid(0, 4); header = metadataHeader.mid(0, 4);
if(header.size() != 4) {
debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header");
return;
}
blockType = header[0] & 0x7f; blockType = header[0] & 0x7f;
lastBlock = (header[0] & 0x80) != 0; lastBlock = (header[0] & 0x80) != 0;
length = header.toUInt(1, 3, true); length = header.toUInt(1, 3, true);

View File

@ -42,7 +42,7 @@ namespace TagLib {
/*! /*!
* This is implementation of FLAC metadata for Ogg FLAC files. For "pure" * This is implementation of FLAC metadata for Ogg FLAC files. For "pure"
* FLAC files look under the FLAC hiearchy. * FLAC files look under the FLAC hierarchy.
* *
* Unlike "pure" FLAC-files, Ogg FLAC only supports Xiph-comments, * Unlike "pure" FLAC-files, Ogg FLAC only supports Xiph-comments,
* while the audio-properties are the same. * while the audio-properties are the same.
@ -64,7 +64,7 @@ namespace TagLib {
{ {
public: public:
/*! /*!
* Constructs an Ogg/FLAC file from \a file. If \a readProperties is true * Constructs an Ogg/FLAC file from \a file. If \a readProperties is true
* the file's audio properties will also be read. * the file's audio properties will also be read.
* *
* \note In the current implementation, \a propertiesStyle is ignored. * \note In the current implementation, \a propertiesStyle is ignored.
@ -73,7 +73,7 @@ namespace TagLib {
Properties::ReadStyle propertiesStyle = Properties::Average); Properties::ReadStyle propertiesStyle = Properties::Average);
/*! /*!
* Constructs an Ogg/FLAC file from \a stream. If \a readProperties is true * Constructs an Ogg/FLAC file from \a stream. If \a readProperties is true
* the file's audio properties will also be read. * the file's audio properties will also be read.
* *
* \note TagLib will *not* take ownership of the stream, the caller is * \note TagLib will *not* take ownership of the stream, the caller is
@ -92,10 +92,10 @@ namespace TagLib {
/*! /*!
* Returns the Tag for this file. This will always be a XiphComment. * Returns the Tag for this file. This will always be a XiphComment.
* *
* \note This always returns a valid pointer regardless of whether or not * \note This always returns a valid pointer regardless of whether or not
* the file on disk has a XiphComment. Use hasXiphComment() to check if * the file on disk has a XiphComment. Use hasXiphComment() to check if
* the file on disk actually has a XiphComment. * the file on disk actually has a XiphComment.
* *
* \note The Tag <b>is still</b> owned by the FLAC::File and should not be * \note The Tag <b>is still</b> owned by the FLAC::File and should not be
* deleted by the user. It will be deleted when the file (object) is * deleted by the user. It will be deleted when the file (object) is
* destroyed. * destroyed.
@ -111,22 +111,25 @@ namespace TagLib {
virtual Properties *audioProperties() const; virtual Properties *audioProperties() const;
/*! /*!
* Implements the unified property interface -- export function. * Implements the unified property interface -- export function.
* This forwards directly to XiphComment::properties(). * This forwards directly to XiphComment::properties().
*/ */
PropertyMap properties() const; PropertyMap properties() const;
/*! /*!
* Implements the unified tag dictionary interface -- import function. * Implements the unified tag dictionary interface -- import function.
* Like properties(), this is a forwarder to the file's XiphComment. * Like properties(), this is a forwarder to the file's XiphComment.
*/ */
PropertyMap setProperties(const PropertyMap &); PropertyMap setProperties(const PropertyMap &);
/*! /*!
* Save the file. This will primarily save and update the XiphComment. * Save the file. This will primarily save and update the XiphComment.
* Returns true if the save is successful. * Returns true if the save is successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. It leads to a segfault.
*/ */
virtual bool save(); virtual bool save();

View File

@ -354,7 +354,7 @@ void Ogg::File::writePageGroup(const List<int> &thePageGroup)
// create a gap for the new pages // create a gap for the new pages
int numberOfNewPages = pages.back()->header()->pageSequenceNumber() - pageGroup.back(); int numberOfNewPages = pages.back()->header()->pageSequenceNumber() - pageGroup.back();
List<Page *>::Iterator pageIter = d->pages.begin(); List<Page *>::ConstIterator pageIter = d->pages.begin();
for(int i = 0; i < pageGroup.back(); i++) { for(int i = 0; i < pageGroup.back(); i++) {
if(pageIter != d->pages.end()) { if(pageIter != d->pages.end()) {
++pageIter; ++pageIter;

View File

@ -41,7 +41,7 @@ namespace TagLib {
/*! /*!
* This is an implementation of the pages that make up an Ogg stream. * This is an implementation of the pages that make up an Ogg stream.
* This handles parsing pages and breaking them down into packets and handles * This handles parsing pages and breaking them down into packets and handles
* the details of packets spanning multiple pages and pages that contiain * the details of packets spanning multiple pages and pages that contain
* multiple packets. * multiple packets.
* *
* In most Xiph.org formats the comments are found in the first few packets, * In most Xiph.org formats the comments are found in the first few packets,
@ -162,7 +162,7 @@ namespace TagLib {
/*! /*!
* Pack \a packets into Ogg pages using the \a strategy for pagination. * Pack \a packets into Ogg pages using the \a strategy for pagination.
* The page number indicater inside of the rendered packets will start * The page number indicator inside of the rendered packets will start
* with \a firstPage and be incremented for each page rendered. * with \a firstPage and be incremented for each page rendered.
* \a containsLastPacket should be set to true if \a packets contains the * \a containsLastPacket should be set to true if \a packets contains the
* last page in the stream and will set the appropriate flag in the last * last page in the stream and will set the appropriate flag in the last

View File

@ -27,8 +27,6 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include <bitset>
#include <tstring.h> #include <tstring.h>
#include <tdebug.h> #include <tdebug.h>
#include <tpropertymap.h> #include <tpropertymap.h>
@ -59,20 +57,20 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
Opus::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : Opus::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
Ogg::File(file), Ogg::File(file),
d(new FilePrivate()) d(new FilePrivate())
{ {
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
Opus::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : Opus::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
Ogg::File(stream), Ogg::File(stream),
d(new FilePrivate()) d(new FilePrivate())
{ {
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
Opus::File::~File() Opus::File::~File()
@ -114,7 +112,7 @@ bool Opus::File::save()
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void Opus::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) void Opus::File::read(bool readProperties)
{ {
ByteVector opusHeaderData = packet(0); ByteVector opusHeaderData = packet(0);
@ -135,5 +133,5 @@ void Opus::File::read(bool readProperties, Properties::ReadStyle propertiesStyle
d->comment = new Ogg::XiphComment(commentHeaderData.mid(8)); d->comment = new Ogg::XiphComment(commentHeaderData.mid(8));
if(readProperties) if(readProperties)
d->properties = new Properties(this, propertiesStyle); d->properties = new Properties(this);
} }

View File

@ -106,13 +106,21 @@ namespace TagLib {
*/ */
virtual Properties *audioProperties() const; virtual Properties *audioProperties() const;
/*!
* Save the file.
*
* This returns true if the save was successful.
*
* \warning In the current implementation, it's dangerous to call save()
* repeatedly. It leads to a segfault.
*/
virtual bool save(); virtual bool save();
private: private:
File(const File &); File(const File &);
File &operator=(const File &); File &operator=(const File &);
void read(bool readProperties, Properties::ReadStyle propertiesStyle); void read(bool readProperties);
class FilePrivate; class FilePrivate;
FilePrivate *d; FilePrivate *d;

View File

@ -41,17 +41,15 @@ using namespace TagLib::Ogg;
class Opus::Properties::PropertiesPrivate class Opus::Properties::PropertiesPrivate
{ {
public: public:
PropertiesPrivate(File *f, ReadStyle s) : PropertiesPrivate() :
file(f),
style(s),
length(0), length(0),
bitrate(0),
inputSampleRate(0), inputSampleRate(0),
channels(0), channels(0),
opusVersion(0) {} opusVersion(0) {}
File *file;
ReadStyle style;
int length; int length;
int bitrate;
int inputSampleRate; int inputSampleRate;
int channels; int channels;
int opusVersion; int opusVersion;
@ -61,10 +59,11 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
Opus::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) Opus::Properties::Properties(File *file, ReadStyle style) :
AudioProperties(style),
d(new PropertiesPrivate())
{ {
d = new PropertiesPrivate(file, style); read(file);
read();
} }
Opus::Properties::~Properties() Opus::Properties::~Properties()
@ -73,13 +72,23 @@ Opus::Properties::~Properties()
} }
int Opus::Properties::length() const int Opus::Properties::length() const
{
return lengthInSeconds();
}
int Ogg::Opus::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int Ogg::Opus::Properties::lengthInMilliseconds() const
{ {
return d->length; return d->length;
} }
int Opus::Properties::bitrate() const int Opus::Properties::bitrate() const
{ {
return 0; return d->bitrate;
} }
int Opus::Properties::sampleRate() const int Opus::Properties::sampleRate() const
@ -109,13 +118,13 @@ int Opus::Properties::opusVersion() const
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void Opus::Properties::read() void Opus::Properties::read(File *file)
{ {
// Get the identification header from the Ogg implementation. // Get the identification header from the Ogg implementation.
// http://tools.ietf.org/html/draft-terriberry-oggopus-01#section-5.1 // http://tools.ietf.org/html/draft-terriberry-oggopus-01#section-5.1
ByteVector data = d->file->packet(0); const ByteVector data = file->packet(0);
// *Magic Signature* // *Magic Signature*
uint pos = 8; uint pos = 8;
@ -142,15 +151,22 @@ void Opus::Properties::read()
// *Channel Mapping Family* (8 bits, unsigned) // *Channel Mapping Family* (8 bits, unsigned)
pos += 1; pos += 1;
const Ogg::PageHeader *first = d->file->firstPageHeader(); const Ogg::PageHeader *first = file->firstPageHeader();
const Ogg::PageHeader *last = d->file->lastPageHeader(); const Ogg::PageHeader *last = file->lastPageHeader();
if(first && last) { if(first && last) {
long long start = first->absoluteGranularPosition(); const long long start = first->absoluteGranularPosition();
long long end = last->absoluteGranularPosition(); const long long end = last->absoluteGranularPosition();
if(start >= 0 && end >= 0) if(start >= 0 && end >= 0) {
d->length = (int) ((end - start - preSkip) / 48000); const long long frameCount = (end - start - preSkip);
if(frameCount > 0) {
const double length = frameCount * 1000.0 / 48000.0;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(file->length() * 8.0 / length + 0.5);
}
}
else { else {
debug("Opus::Properties::read() -- The PCM values for the start or " debug("Opus::Properties::read() -- The PCM values for the start or "
"end of this file was incorrect."); "end of this file was incorrect.");

View File

@ -61,11 +61,49 @@ namespace TagLib {
*/ */
virtual ~Properties(); virtual ~Properties();
// Reimplementations. /*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \note This method is just an alias of lengthInSeconds().
*
* \deprecated
*/
virtual int length() const; virtual int length() const;
/*!
* Returns the length of the file in seconds. The length is rounded down to
* the nearest whole second.
*
* \see lengthInMilliseconds()
*/
// BIC: make virtual
int lengthInSeconds() const;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
// BIC: make virtual
int lengthInMilliseconds() const;
/*!
* Returns the average bit rate of the file in kb/s.
*/
virtual int bitrate() const; virtual int bitrate() const;
/*!
* Returns the sample rate in Hz.
*
* \note Always returns 48000, because Opus can decode any stream at a
* sample rate of 8, 12, 16, 24, or 48 kHz,
*/
virtual int sampleRate() const; virtual int sampleRate() const;
/*!
* Returns the number of audio channels.
*/
virtual int channels() const; virtual int channels() const;
/*! /*!
@ -76,7 +114,7 @@ namespace TagLib {
int inputSampleRate() const; int inputSampleRate() const;
/*! /*!
* Returns the Opus version, currently "0" (as specified by the spec). * Returns the Opus version, in the range 0...255.
*/ */
int opusVersion() const; int opusVersion() const;
@ -84,7 +122,7 @@ namespace TagLib {
Properties(const Properties &); Properties(const Properties &);
Properties &operator=(const Properties &); Properties &operator=(const Properties &);
void read(); void read(File *file);
class PropertiesPrivate; class PropertiesPrivate;
PropertiesPrivate *d; PropertiesPrivate *d;

View File

@ -27,8 +27,6 @@
* http://www.mozilla.org/MPL/ * * http://www.mozilla.org/MPL/ *
***************************************************************************/ ***************************************************************************/
#include <bitset>
#include <tstring.h> #include <tstring.h>
#include <tdebug.h> #include <tdebug.h>
#include <tpropertymap.h> #include <tpropertymap.h>
@ -59,20 +57,20 @@ public:
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
Speex::File::File(FileName file, bool readProperties, Speex::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
Properties::ReadStyle propertiesStyle) : Ogg::File(file) Ogg::File(file),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
Speex::File::File(IOStream *stream, bool readProperties, Speex::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
Properties::ReadStyle propertiesStyle) : Ogg::File(stream) Ogg::File(stream),
d(new FilePrivate())
{ {
d = new FilePrivate;
if(isOpen()) if(isOpen())
read(readProperties, propertiesStyle); read(readProperties);
} }
Speex::File::~File() Speex::File::~File()
@ -114,7 +112,7 @@ bool Speex::File::save()
// private members // private members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void Speex::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) void Speex::File::read(bool readProperties)
{ {
ByteVector speexHeaderData = packet(0); ByteVector speexHeaderData = packet(0);
@ -128,5 +126,5 @@ void Speex::File::read(bool readProperties, Properties::ReadStyle propertiesStyl
d->comment = new Ogg::XiphComment(commentHeaderData); d->comment = new Ogg::XiphComment(commentHeaderData);
if(readProperties) if(readProperties)
d->properties = new Properties(this, propertiesStyle); d->properties = new Properties(this);
} }

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