Merge remote-tracking branch 'upstream/master' into qt5
This commit is contained in:
commit
ff7026c9fe
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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} )
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ using namespace mygpo;
|
|||
|
||||
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() )
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -59,10 +59,18 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void escapePressed()
|
||||
void keyDownPressed()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +95,22 @@ public:
|
|||
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 {
|
||||
// No Q_ASSERT here as it is called on destruction.
|
||||
if (!pimpl) return;
|
||||
|
@ -95,8 +119,6 @@ public:
|
|||
|
||||
if ([[[notification userInfo] objectForKey:@"NSTextMovement"] intValue] == NSReturnTextMovement)
|
||||
pimpl->returnPressed();
|
||||
else if ([[[notification userInfo] objectForKey:@"NSTextMovement"] intValue] == NSOtherTextMovement)
|
||||
pimpl->escapePressed();
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -124,21 +146,17 @@ public:
|
|||
}
|
||||
else if (keyString == "c") // Cmd+c
|
||||
{
|
||||
QClipboard* clipboard = QApplication::clipboard();
|
||||
clipboard->setText(toQString([self stringValue]));
|
||||
[[self currentEditor] copy: nil];
|
||||
return YES;
|
||||
}
|
||||
else if (keyString == "v") // Cmd+v
|
||||
{
|
||||
QClipboard* clipboard = QApplication::clipboard();
|
||||
[self setStringValue:fromQString(clipboard->text())];
|
||||
[[self currentEditor] paste: nil];
|
||||
return YES;
|
||||
}
|
||||
else if (keyString == "x") // Cmd+x
|
||||
{
|
||||
QClipboard* clipboard = QApplication::clipboard();
|
||||
clipboard->setText(toQString([self stringValue]));
|
||||
[self setStringValue:@""];
|
||||
[[self currentEditor] cut: nil];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
@ -176,6 +194,10 @@ void QSearchField::setText(const QString &text)
|
|||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
[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];
|
||||
}
|
||||
|
||||
|
@ -198,12 +220,25 @@ QString QSearchField::placeholderText() const {
|
|||
|
||||
void QSearchField::setFocus(Qt::FocusReason reason)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
/* Do nothing: we were previously using makeFirstResponder on search field, but
|
||||
* that resulted in having the text being selected (and I didn't find any way to
|
||||
* 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])
|
||||
[[pimpl->nsSearchField window] makeFirstResponder: pimpl->nsSearchField];
|
||||
// Q_ASSERT(pimpl);
|
||||
// if (!pimpl)
|
||||
// return;
|
||||
|
||||
// if ([pimpl->nsSearchField acceptsFirstResponder]) {
|
||||
// [[pimpl->nsSearchField window] makeFirstResponder: pimpl->nsSearchField];
|
||||
// }
|
||||
}
|
||||
|
||||
void QSearchField::setFocus()
|
||||
|
|
|
@ -63,7 +63,7 @@ QSearchField::QSearchField(QWidget *parent) : QWidget(parent)
|
|||
connect(lineEdit, SIGNAL(textChanged(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);
|
||||
clearButton->setIcon(clearIcon);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-delete-non-virtual-dtor")
|
||||
|
||||
set(TAGLIB_SOVERSION_CURRENT 13)
|
||||
set(TAGLIB_SOVERSION_REVISION 0)
|
||||
set(TAGLIB_SOVERSION_AGE 12)
|
||||
set(TAGLIB_SOVERSION_CURRENT 16)
|
||||
set(TAGLIB_SOVERSION_REVISION 1)
|
||||
set(TAGLIB_SOVERSION_AGE 15)
|
||||
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
|
||||
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")
|
||||
math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}")
|
||||
|
@ -48,6 +48,8 @@ include_directories(
|
|||
|
||||
if(ZLIB_FOUND)
|
||||
include_directories(${ZLIB_INCLUDE_DIR})
|
||||
elseif(HAVE_ZLIB_SOURCE)
|
||||
include_directories(${ZLIB_SOURCE})
|
||||
endif()
|
||||
|
||||
set(tag_HDRS
|
||||
|
@ -87,16 +89,20 @@ set(tag_HDRS
|
|||
mpeg/id3v2/id3v2tag.h
|
||||
mpeg/id3v2/frames/attachedpictureframe.h
|
||||
mpeg/id3v2/frames/commentsframe.h
|
||||
mpeg/id3v2/frames/eventtimingcodesframe.h
|
||||
mpeg/id3v2/frames/generalencapsulatedobjectframe.h
|
||||
mpeg/id3v2/frames/ownershipframe.h
|
||||
mpeg/id3v2/frames/popularimeterframe.h
|
||||
mpeg/id3v2/frames/privateframe.h
|
||||
mpeg/id3v2/frames/relativevolumeframe.h
|
||||
mpeg/id3v2/frames/synchronizedlyricsframe.h
|
||||
mpeg/id3v2/frames/textidentificationframe.h
|
||||
mpeg/id3v2/frames/uniquefileidentifierframe.h
|
||||
mpeg/id3v2/frames/unknownframe.h
|
||||
mpeg/id3v2/frames/unsynchronizedlyricsframe.h
|
||||
mpeg/id3v2/frames/urllinkframe.h
|
||||
mpeg/id3v2/frames/chapterframe.h
|
||||
mpeg/id3v2/frames/tableofcontentsframe.h
|
||||
ogg/oggfile.h
|
||||
ogg/oggpage.h
|
||||
ogg/oggpageheader.h
|
||||
|
@ -177,16 +183,20 @@ set(id3v2_SRCS
|
|||
set(frames_SRCS
|
||||
mpeg/id3v2/frames/attachedpictureframe.cpp
|
||||
mpeg/id3v2/frames/commentsframe.cpp
|
||||
mpeg/id3v2/frames/eventtimingcodesframe.cpp
|
||||
mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp
|
||||
mpeg/id3v2/frames/ownershipframe.cpp
|
||||
mpeg/id3v2/frames/popularimeterframe.cpp
|
||||
mpeg/id3v2/frames/privateframe.cpp
|
||||
mpeg/id3v2/frames/relativevolumeframe.cpp
|
||||
mpeg/id3v2/frames/synchronizedlyricsframe.cpp
|
||||
mpeg/id3v2/frames/textidentificationframe.cpp
|
||||
mpeg/id3v2/frames/uniquefileidentifierframe.cpp
|
||||
mpeg/id3v2/frames/unknownframe.cpp
|
||||
mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp
|
||||
mpeg/id3v2/frames/urllinkframe.cpp
|
||||
mpeg/id3v2/frames/chapterframe.cpp
|
||||
mpeg/id3v2/frames/tableofcontentsframe.cpp
|
||||
)
|
||||
|
||||
set(ogg_SRCS
|
||||
|
@ -313,14 +323,31 @@ set(toolkit_SRCS
|
|||
toolkit/tpropertymap.cpp
|
||||
toolkit/trefcounter.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
|
||||
${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS}
|
||||
${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS}
|
||||
${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS}
|
||||
${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS}
|
||||
${unicode_SRCS} ${zlib_SRCS}
|
||||
tag.cpp
|
||||
tagunion.cpp
|
||||
fileref.cpp
|
||||
|
@ -345,6 +372,7 @@ foreach(header ${tag_HDRS})
|
|||
configure_file(
|
||||
"${header}"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/headers/taglib/${header_name}"
|
||||
COPY_ONLY
|
||||
COPYONLY
|
||||
)
|
||||
endforeach()
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <tdebug.h>
|
||||
#include <tagunion.h>
|
||||
#include <id3v1tag.h>
|
||||
#include <id3v2header.h>
|
||||
#include <tpropertymap.h>
|
||||
|
||||
#include "apefile.h"
|
||||
|
@ -57,12 +58,17 @@ public:
|
|||
APELocation(-1),
|
||||
APESize(0),
|
||||
ID3v1Location(-1),
|
||||
ID3v2Header(0),
|
||||
ID3v2Location(-1),
|
||||
ID3v2Size(0),
|
||||
properties(0),
|
||||
hasAPE(false),
|
||||
hasID3v1(false) {}
|
||||
hasID3v1(false),
|
||||
hasID3v2(false) {}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
delete ID3v2Header;
|
||||
delete properties;
|
||||
}
|
||||
|
||||
|
@ -71,6 +77,10 @@ public:
|
|||
|
||||
long ID3v1Location;
|
||||
|
||||
ID3v2::Header *ID3v2Header;
|
||||
long ID3v2Location;
|
||||
uint ID3v2Size;
|
||||
|
||||
TagUnion tag;
|
||||
|
||||
Properties *properties;
|
||||
|
@ -80,26 +90,27 @@ public:
|
|||
|
||||
bool hasAPE;
|
||||
bool hasID3v1;
|
||||
bool hasID3v2;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
APE::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
|
||||
APE::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
APE::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
|
||||
APE::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
APE::File::~File()
|
||||
|
@ -249,8 +260,19 @@ bool APE::File::hasID3v1Tag() const
|
|||
// 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
|
||||
|
||||
d->ID3v1Location = findID3v1();
|
||||
|
@ -277,7 +299,25 @@ void APE::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
|
|||
// Look for APE audio properties
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
long APE::File::findID3v2()
|
||||
{
|
||||
if(!isValid())
|
||||
return -1;
|
||||
|
||||
seek(0);
|
||||
|
||||
if(readBlock(3) == ID3v2::Header::fileIdentifier())
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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.
|
||||
*/
|
||||
PropertyMap setProperties(const PropertyMap &);
|
||||
|
@ -146,6 +146,9 @@ namespace TagLib {
|
|||
*
|
||||
* \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.
|
||||
*
|
||||
* \warning In the current implementation, it's dangerous to call save()
|
||||
* repeatedly. At worst it will corrupt the file.
|
||||
*/
|
||||
virtual bool save();
|
||||
|
||||
|
@ -156,8 +159,8 @@ namespace TagLib {
|
|||
* 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.
|
||||
*
|
||||
* \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
|
||||
* \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
|
||||
* on disk actually has an ID3v1 tag.
|
||||
*
|
||||
* \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
|
||||
* 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
|
||||
* file on disk has an APE tag. Use hasAPETag() to check if the file
|
||||
* \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
|
||||
* on disk actually has an APE tag.
|
||||
*
|
||||
* \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 &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
void scan();
|
||||
long findID3v1();
|
||||
void read(bool readProperties);
|
||||
long findAPE();
|
||||
long findID3v1();
|
||||
long findID3v2();
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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
|
||||
* headers specified there.
|
||||
*/
|
||||
|
|
|
@ -86,8 +86,10 @@ APE::Item::~Item()
|
|||
|
||||
Item &APE::Item::operator=(const Item &item)
|
||||
{
|
||||
delete d;
|
||||
d = new ItemPrivate(*item.d);
|
||||
if(&item != this) {
|
||||
delete d;
|
||||
d = new ItemPrivate(*item.d);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
|
@ -153,7 +153,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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.
|
||||
*/
|
||||
String toString() const;
|
||||
|
@ -164,7 +164,7 @@ namespace TagLib {
|
|||
#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.
|
||||
*/
|
||||
StringList values() const;
|
||||
|
|
|
@ -33,22 +33,22 @@
|
|||
#include "id3v2tag.h"
|
||||
#include "apeproperties.h"
|
||||
#include "apefile.h"
|
||||
#include "apetag.h"
|
||||
#include "apefooter.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
class APE::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(File *file, long streamLength) :
|
||||
PropertiesPrivate() :
|
||||
length(0),
|
||||
bitrate(0),
|
||||
sampleRate(0),
|
||||
channels(0),
|
||||
version(0),
|
||||
bitsPerSample(0),
|
||||
sampleFrames(0),
|
||||
file(file),
|
||||
streamLength(streamLength) {}
|
||||
sampleFrames(0) {}
|
||||
|
||||
int length;
|
||||
int bitrate;
|
||||
|
@ -57,18 +57,24 @@ public:
|
|||
int version;
|
||||
int bitsPerSample;
|
||||
uint sampleFrames;
|
||||
File *file;
|
||||
long streamLength;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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());
|
||||
read();
|
||||
debug("APE::Properties::Properties() -- This constructor is no longer used.");
|
||||
}
|
||||
|
||||
APE::Properties::Properties(File *file, long streamLength, ReadStyle style) :
|
||||
AudioProperties(style),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
read(file, streamLength);
|
||||
}
|
||||
|
||||
APE::Properties::~Properties()
|
||||
|
@ -77,6 +83,16 @@ APE::Properties::~Properties()
|
|||
}
|
||||
|
||||
int APE::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int APE::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int APE::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
|
@ -115,98 +131,93 @@ TagLib::uint APE::Properties::sampleFrames() const
|
|||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
void APE::Properties::read()
|
||||
namespace
|
||||
{
|
||||
// First we are searching the descriptor
|
||||
long offset = findDescriptor();
|
||||
if(offset < 0)
|
||||
return;
|
||||
inline int headerVersion(const ByteVector &header)
|
||||
{
|
||||
if(header.size() < 6 || !header.startsWith("MAC "))
|
||||
return -1;
|
||||
|
||||
// Then we read the header common for all versions of APE
|
||||
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();
|
||||
return header.toUShort(4, false);
|
||||
}
|
||||
}
|
||||
|
||||
long APE::Properties::findDescriptor()
|
||||
void APE::Properties::read(File *file, long streamLength)
|
||||
{
|
||||
long ID3v2Location = findID3v2();
|
||||
long ID3v2OriginalSize = 0;
|
||||
bool hasID3v2 = false;
|
||||
if(ID3v2Location >= 0) {
|
||||
ID3v2::Tag tag(d->file, ID3v2Location);
|
||||
ID3v2OriginalSize = tag.header()->completeTagSize();
|
||||
if(tag.header()->tagSize() > 0)
|
||||
hasID3v2 = true;
|
||||
// First, we assume that the file pointer is set at the first descriptor.
|
||||
long offset = file->tell();
|
||||
int version = headerVersion(file->readBlock(6));
|
||||
|
||||
// Next, we look for the descriptor.
|
||||
if(version < 0) {
|
||||
offset = file->find("MAC ", offset);
|
||||
file->seek(offset);
|
||||
version = headerVersion(file->readBlock(6));
|
||||
}
|
||||
|
||||
long offset = 0;
|
||||
if(hasID3v2)
|
||||
offset = d->file->find("MAC ", ID3v2Location + ID3v2OriginalSize);
|
||||
if(version < 0) {
|
||||
debug("APE::Properties::read() -- APE descriptor not found");
|
||||
return;
|
||||
}
|
||||
|
||||
d->version = version;
|
||||
|
||||
if(d->version >= 3980)
|
||||
analyzeCurrent(file);
|
||||
else
|
||||
offset = d->file->find("MAC ");
|
||||
analyzeOld(file);
|
||||
|
||||
if(offset < 0) {
|
||||
debug("APE::Properties::findDescriptor() -- APE descriptor not found");
|
||||
return -1;
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
long APE::Properties::findID3v2()
|
||||
{
|
||||
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()
|
||||
void APE::Properties::analyzeCurrent(File *file)
|
||||
{
|
||||
// Read the descriptor
|
||||
d->file->seek(2, File::Current);
|
||||
ByteVector descriptor = d->file->readBlock(44);
|
||||
file->seek(2, File::Current);
|
||||
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);
|
||||
|
||||
if ((descriptorBytes - 52) > 0)
|
||||
d->file->seek(descriptorBytes - 52, File::Current);
|
||||
if((descriptorBytes - 52) > 0)
|
||||
file->seek(descriptorBytes - 52, File::Current);
|
||||
|
||||
// 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
|
||||
d->channels = header.toShort(18, false);
|
||||
d->sampleRate = header.toUInt(20, 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 finalFrameBlocks = header.toUInt(8, false);
|
||||
d->sampleFrames = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0;
|
||||
d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0;
|
||||
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
|
||||
d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Fail on 0 length APE files (catches non-finalized APE files)
|
||||
|
@ -221,12 +232,21 @@ void APE::Properties::analyzeOld()
|
|||
blocksPerFrame = 73728;
|
||||
else
|
||||
blocksPerFrame = 9216;
|
||||
|
||||
// Get the APE info
|
||||
d->channels = header.toShort(4, 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);
|
||||
}
|
||||
|
|
|
@ -51,26 +51,73 @@ namespace TagLib {
|
|||
public:
|
||||
/*!
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns number of bits per sample.
|
||||
* Returns the number of bits per audio sample.
|
||||
*/
|
||||
int bitsPerSample() const;
|
||||
|
||||
/*!
|
||||
* Returns the total number of audio samples in file.
|
||||
*/
|
||||
uint sampleFrames() const;
|
||||
|
||||
/*!
|
||||
|
@ -82,13 +129,10 @@ namespace TagLib {
|
|||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void read();
|
||||
void read(File *file, long streamLength);
|
||||
|
||||
long findDescriptor();
|
||||
long findID3v2();
|
||||
|
||||
void analyzeCurrent();
|
||||
void analyzeOld();
|
||||
void analyzeCurrent(File *file);
|
||||
void analyzeOld(File *file);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
|
|
|
@ -46,11 +46,12 @@ using namespace APE;
|
|||
class APE::Tag::TagPrivate
|
||||
{
|
||||
public:
|
||||
TagPrivate() : file(0), footerLocation(-1), tagLength(0) {}
|
||||
TagPrivate() :
|
||||
file(0),
|
||||
footerLocation(-1) {}
|
||||
|
||||
TagLib::File *file;
|
||||
long footerLocation;
|
||||
long tagLength;
|
||||
|
||||
Footer footer;
|
||||
|
||||
|
@ -61,14 +62,16 @@ public:
|
|||
// 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->footerLocation = footerLocation;
|
||||
|
||||
|
@ -89,35 +92,35 @@ String APE::Tag::title() const
|
|||
{
|
||||
if(d->itemListMap["TITLE"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["TITLE"].toString();
|
||||
return d->itemListMap["TITLE"].values().toString();
|
||||
}
|
||||
|
||||
String APE::Tag::artist() const
|
||||
{
|
||||
if(d->itemListMap["ARTIST"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["ARTIST"].toString();
|
||||
return d->itemListMap["ARTIST"].values().toString();
|
||||
}
|
||||
|
||||
String APE::Tag::album() const
|
||||
{
|
||||
if(d->itemListMap["ALBUM"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["ALBUM"].toString();
|
||||
return d->itemListMap["ALBUM"].values().toString();
|
||||
}
|
||||
|
||||
String APE::Tag::comment() const
|
||||
{
|
||||
if(d->itemListMap["COMMENT"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["COMMENT"].toString();
|
||||
return d->itemListMap["COMMENT"].values().toString();
|
||||
}
|
||||
|
||||
String APE::Tag::genre() const
|
||||
{
|
||||
if(d->itemListMap["GENRE"].isEmpty())
|
||||
return String::null;
|
||||
return d->itemListMap["GENRE"].toString();
|
||||
return d->itemListMap["GENRE"].values().toString();
|
||||
}
|
||||
|
||||
TagLib::uint APE::Tag::year() const
|
||||
|
@ -233,7 +236,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
|
|||
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);
|
||||
|
||||
// now sync in the "forward direction"
|
||||
|
@ -284,9 +287,7 @@ const APE::ItemListMap& APE::Tag::itemListMap() const
|
|||
|
||||
void APE::Tag::removeItem(const String &key)
|
||||
{
|
||||
Map<const String, Item>::Iterator it = d->itemListMap.find(key.upper());
|
||||
if(it != d->itemListMap.end())
|
||||
d->itemListMap.erase(it);
|
||||
d->itemListMap.erase(key.upper());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
uint pos = 0;
|
||||
|
||||
// 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++) {
|
||||
APE::Item item;
|
||||
item.parse(data.mid(pos));
|
||||
|
|
|
@ -143,7 +143,7 @@ namespace TagLib {
|
|||
* Returns a reference to the item list map. This is an ItemListMap of
|
||||
* 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
|
||||
* to upper case.
|
||||
|
|
|
@ -25,9 +25,11 @@
|
|||
|
||||
#include <taglib.h>
|
||||
#include <tdebug.h>
|
||||
#include "trefcounter.h"
|
||||
#include <trefcounter.h>
|
||||
|
||||
#include "asfattribute.h"
|
||||
#include "asffile.h"
|
||||
#include "asfutils.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
|
@ -70,10 +72,12 @@ ASF::Attribute::Attribute(const ASF::Attribute &other)
|
|||
|
||||
ASF::Attribute &ASF::Attribute::operator=(const ASF::Attribute &other)
|
||||
{
|
||||
if(d->deref())
|
||||
delete d;
|
||||
d = other.d;
|
||||
d->ref();
|
||||
if(&other != this) {
|
||||
if(d->deref())
|
||||
delete d;
|
||||
d = other.d;
|
||||
d->ref();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -181,23 +185,23 @@ String ASF::Attribute::parse(ASF::File &f, int kind)
|
|||
d->pictureValue = Picture::fromInvalid();
|
||||
// extended content descriptor
|
||||
if(kind == 0) {
|
||||
nameLength = f.readWORD();
|
||||
name = f.readString(nameLength);
|
||||
d->type = ASF::Attribute::AttributeTypes(f.readWORD());
|
||||
size = f.readWORD();
|
||||
nameLength = readWORD(&f);
|
||||
name = readString(&f, nameLength);
|
||||
d->type = ASF::Attribute::AttributeTypes(readWORD(&f));
|
||||
size = readWORD(&f);
|
||||
}
|
||||
// metadata & metadata library
|
||||
else {
|
||||
int temp = f.readWORD();
|
||||
int temp = readWORD(&f);
|
||||
// metadata library
|
||||
if(kind == 2) {
|
||||
d->language = temp;
|
||||
}
|
||||
d->stream = f.readWORD();
|
||||
nameLength = f.readWORD();
|
||||
d->type = ASF::Attribute::AttributeTypes(f.readWORD());
|
||||
size = f.readDWORD();
|
||||
name = f.readString(nameLength);
|
||||
d->stream = readWORD(&f);
|
||||
nameLength = readWORD(&f);
|
||||
d->type = ASF::Attribute::AttributeTypes(readWORD(&f));
|
||||
size = readDWORD(&f);
|
||||
name = readString(&f, nameLength);
|
||||
}
|
||||
|
||||
if(kind != 2 && size > 65535) {
|
||||
|
@ -206,28 +210,28 @@ String ASF::Attribute::parse(ASF::File &f, int kind)
|
|||
|
||||
switch(d->type) {
|
||||
case WordType:
|
||||
d->shortValue = f.readWORD();
|
||||
d->shortValue = readWORD(&f);
|
||||
break;
|
||||
|
||||
case BoolType:
|
||||
if(kind == 0) {
|
||||
d->boolValue = f.readDWORD() == 1;
|
||||
d->boolValue = (readDWORD(&f) == 1);
|
||||
}
|
||||
else {
|
||||
d->boolValue = f.readWORD() == 1;
|
||||
d->boolValue = (readWORD(&f) == 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case DWordType:
|
||||
d->intValue = f.readDWORD();
|
||||
d->intValue = readDWORD(&f);
|
||||
break;
|
||||
|
||||
case QWordType:
|
||||
d->longLongValue = f.readQWORD();
|
||||
d->longLongValue = readQWORD(&f);
|
||||
break;
|
||||
|
||||
case UnicodeType:
|
||||
d->stringValue = f.readString(size);
|
||||
d->stringValue = readString(&f, size);
|
||||
break;
|
||||
|
||||
case BytesType:
|
||||
|
@ -295,7 +299,7 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
|
|||
break;
|
||||
|
||||
case UnicodeType:
|
||||
data.append(File::renderString(d->stringValue));
|
||||
data.append(renderString(d->stringValue));
|
||||
break;
|
||||
|
||||
case BytesType:
|
||||
|
@ -309,13 +313,13 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
|
|||
}
|
||||
|
||||
if(kind == 0) {
|
||||
data = File::renderString(name, true) +
|
||||
data = renderString(name, true) +
|
||||
ByteVector::fromShort((int)d->type, false) +
|
||||
ByteVector::fromShort(data.size(), false) +
|
||||
data;
|
||||
}
|
||||
else {
|
||||
ByteVector nameData = File::renderString(name);
|
||||
ByteVector nameData = renderString(name);
|
||||
data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) +
|
||||
ByteVector::fromShort(d->stream, false) +
|
||||
ByteVector::fromShort(nameData.size(), false) +
|
||||
|
|
|
@ -27,15 +27,28 @@
|
|||
#include <tbytevectorlist.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <tstring.h>
|
||||
|
||||
#include "asffile.h"
|
||||
#include "asftag.h"
|
||||
#include "asfproperties.h"
|
||||
#include "asfutils.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
class ASF::File::FilePrivate
|
||||
{
|
||||
public:
|
||||
class BaseObject;
|
||||
class UnknownObject;
|
||||
class FilePropertiesObject;
|
||||
class StreamPropertiesObject;
|
||||
class ContentDescriptionObject;
|
||||
class ExtendedContentDescriptionObject;
|
||||
class HeaderExtensionObject;
|
||||
class CodecListObject;
|
||||
class MetadataObject;
|
||||
class MetadataLibraryObject;
|
||||
|
||||
FilePrivate():
|
||||
size(0),
|
||||
tag(0),
|
||||
|
@ -44,189 +57,227 @@ public:
|
|||
extendedContentDescriptionObject(0),
|
||||
headerExtensionObject(0),
|
||||
metadataObject(0),
|
||||
metadataLibraryObject(0) {}
|
||||
metadataLibraryObject(0)
|
||||
{
|
||||
objects.setAutoDelete(true);
|
||||
}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
delete tag;
|
||||
delete properties;
|
||||
}
|
||||
|
||||
unsigned long long size;
|
||||
|
||||
ASF::Tag *tag;
|
||||
ASF::Properties *properties;
|
||||
List<ASF::File::BaseObject *> objects;
|
||||
ASF::File::ContentDescriptionObject *contentDescriptionObject;
|
||||
ASF::File::ExtendedContentDescriptionObject *extendedContentDescriptionObject;
|
||||
ASF::File::HeaderExtensionObject *headerExtensionObject;
|
||||
ASF::File::MetadataObject *metadataObject;
|
||||
ASF::File::MetadataLibraryObject *metadataLibraryObject;
|
||||
|
||||
List<BaseObject *> objects;
|
||||
|
||||
ContentDescriptionObject *contentDescriptionObject;
|
||||
ExtendedContentDescriptionObject *extendedContentDescriptionObject;
|
||||
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);
|
||||
static ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16);
|
||||
static ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16);
|
||||
static ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16);
|
||||
static ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16);
|
||||
static ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16);
|
||||
static ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16);
|
||||
static ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16);
|
||||
static ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16);
|
||||
static ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16);
|
||||
static ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16);
|
||||
namespace
|
||||
{
|
||||
const ByteVector headerGuid("\x30\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);
|
||||
const ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16);
|
||||
const ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16);
|
||||
const ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16);
|
||||
const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16);
|
||||
const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16);
|
||||
const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 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:
|
||||
ByteVector data;
|
||||
virtual ~BaseObject() {}
|
||||
virtual ByteVector guid() = 0;
|
||||
virtual ByteVector guid() const = 0;
|
||||
virtual void parse(ASF::File *file, unsigned int size);
|
||||
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;
|
||||
public:
|
||||
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:
|
||||
ByteVector guid();
|
||||
ByteVector guid() const;
|
||||
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:
|
||||
ByteVector guid();
|
||||
ByteVector guid() const;
|
||||
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:
|
||||
ByteVector guid();
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
class ASF::File::ExtendedContentDescriptionObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::ExtendedContentDescriptionObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVectorList attributeData;
|
||||
ByteVector guid();
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
class ASF::File::MetadataObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::MetadataObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVectorList attributeData;
|
||||
ByteVector guid();
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
class ASF::File::MetadataLibraryObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::MetadataLibraryObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
ByteVectorList attributeData;
|
||||
ByteVector guid();
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, uint size);
|
||||
ByteVector render(ASF::File *file);
|
||||
};
|
||||
|
||||
class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject
|
||||
class ASF::File::FilePrivate::HeaderExtensionObject : public ASF::File::FilePrivate::BaseObject
|
||||
{
|
||||
public:
|
||||
List<ASF::File::BaseObject *> objects;
|
||||
~HeaderExtensionObject();
|
||||
ByteVector guid();
|
||||
List<ASF::File::FilePrivate::BaseObject *> objects;
|
||||
HeaderExtensionObject();
|
||||
ByteVector guid() const;
|
||||
void parse(ASF::File *file, uint size);
|
||||
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++) {
|
||||
delete objects[i];
|
||||
}
|
||||
}
|
||||
public:
|
||||
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();
|
||||
if (size > 24 && size <= (unsigned int)(file->length()))
|
||||
if(size > 24 && size <= (unsigned int)(file->length()))
|
||||
data = file->readBlock(size - 24);
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ByteVector ASF::File::FilePropertiesObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::FilePropertiesObject::guid() const
|
||||
{
|
||||
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);
|
||||
file->d->properties->setLength(
|
||||
(int)(data.toLongLong(40, false) / 10000000L - data.toLongLong(56, false) / 1000L));
|
||||
if(data.size() < 64) {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
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->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;
|
||||
}
|
||||
|
||||
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;
|
||||
int titleLength = file->readWORD();
|
||||
int artistLength = file->readWORD();
|
||||
int copyrightLength = file->readWORD();
|
||||
int commentLength = file->readWORD();
|
||||
int ratingLength = file->readWORD();
|
||||
file->d->tag->setTitle(file->readString(titleLength));
|
||||
file->d->tag->setArtist(file->readString(artistLength));
|
||||
file->d->tag->setCopyright(file->readString(copyrightLength));
|
||||
file->d->tag->setComment(file->readString(commentLength));
|
||||
file->d->tag->setRating(file->readString(ratingLength));
|
||||
const int titleLength = readWORD(file);
|
||||
const int artistLength = readWORD(file);
|
||||
const int copyrightLength = readWORD(file);
|
||||
const int commentLength = readWORD(file);
|
||||
const int ratingLength = readWORD(file);
|
||||
file->d->tag->setTitle(readString(file,titleLength));
|
||||
file->d->tag->setArtist(readString(file,artistLength));
|
||||
file->d->tag->setCopyright(readString(file,copyrightLength));
|
||||
file->d->tag->setComment(readString(file,commentLength));
|
||||
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());
|
||||
ByteVector v2 = file->renderString(file->d->tag->artist());
|
||||
ByteVector v3 = file->renderString(file->d->tag->copyright());
|
||||
ByteVector v4 = file->renderString(file->d->tag->comment());
|
||||
ByteVector v5 = file->renderString(file->d->tag->rating());
|
||||
const ByteVector v1 = renderString(file->d->tag->title());
|
||||
const ByteVector v2 = renderString(file->d->tag->artist());
|
||||
const ByteVector v3 = renderString(file->d->tag->copyright());
|
||||
const ByteVector v4 = renderString(file->d->tag->comment());
|
||||
const ByteVector v5 = renderString(file->d->tag->rating());
|
||||
data.clear();
|
||||
data.append(ByteVector::fromShort(v1.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);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::ExtendedContentDescriptionObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() const
|
||||
{
|
||||
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;
|
||||
int count = file->readWORD();
|
||||
int count = readWORD(file);
|
||||
while(count--) {
|
||||
ASF::Attribute attribute;
|
||||
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.append(ByteVector::fromShort(attributeData.size(), false));
|
||||
|
@ -265,15 +316,15 @@ ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file)
|
|||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::MetadataObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::MetadataObject::guid() const
|
||||
{
|
||||
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;
|
||||
int count = file->readWORD();
|
||||
int count = readWORD(file);
|
||||
while(count--) {
|
||||
ASF::Attribute attribute;
|
||||
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.append(ByteVector::fromShort(attributeData.size(), false));
|
||||
|
@ -289,15 +340,15 @@ ByteVector ASF::File::MetadataObject::render(ASF::File *file)
|
|||
return BaseObject::render(file);
|
||||
}
|
||||
|
||||
ByteVector ASF::File::MetadataLibraryObject::guid()
|
||||
ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const
|
||||
{
|
||||
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;
|
||||
int count = file->readWORD();
|
||||
int count = readWORD(file);
|
||||
while(count--) {
|
||||
ASF::Attribute attribute;
|
||||
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.append(ByteVector::fromShort(attributeData.size(), false));
|
||||
|
@ -313,16 +364,21 @@ ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *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;
|
||||
}
|
||||
|
||||
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->seek(18, File::Current);
|
||||
long long dataSize = file->readDWORD();
|
||||
long long dataSize = readDWORD(file);
|
||||
long long dataPos = 0;
|
||||
while(dataPos < dataSize) {
|
||||
ByteVector guid = file->readBlock(16);
|
||||
|
@ -331,7 +387,7 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/)
|
|||
break;
|
||||
}
|
||||
bool ok;
|
||||
long long size = file->readQWORD(&ok);
|
||||
long long size = readQWORD(file, &ok);
|
||||
if(!ok) {
|
||||
file->setValid(false);
|
||||
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();
|
||||
for(unsigned int i = 0; i < objects.size(); i++) {
|
||||
data.append(objects[i]->render(file));
|
||||
for(List<BaseObject *>::ConstIterator it = objects.begin(); it != objects.end(); ++it) {
|
||||
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;
|
||||
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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
: TagLib::File(file)
|
||||
ASF::File::File(FileName file, bool, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read();
|
||||
}
|
||||
|
||||
ASF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
: TagLib::File(stream)
|
||||
ASF::File::File(IOStream *stream, bool, Properties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -421,7 +523,85 @@ ASF::Properties *ASF::File::audioProperties() const
|
|||
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())
|
||||
return;
|
||||
|
@ -437,12 +617,12 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
|
|||
d->properties = new ASF::Properties();
|
||||
|
||||
bool ok;
|
||||
d->size = readQWORD(&ok);
|
||||
d->size = readQWORD(this, &ok);
|
||||
if(!ok) {
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
int numObjects = readDWORD(&ok);
|
||||
int numObjects = readDWORD(this, &ok);
|
||||
if(!ok) {
|
||||
setValid(false);
|
||||
return;
|
||||
|
@ -450,31 +630,34 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
|
|||
seek(2, Current);
|
||||
|
||||
for(int i = 0; i < numObjects; i++) {
|
||||
ByteVector guid = readBlock(16);
|
||||
guid = readBlock(16);
|
||||
if(guid.size() != 16) {
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
long size = (long)readQWORD(&ok);
|
||||
long size = (long)readQWORD(this, &ok);
|
||||
if(!ok) {
|
||||
setValid(false);
|
||||
break;
|
||||
}
|
||||
BaseObject *obj;
|
||||
FilePrivate::BaseObject *obj;
|
||||
if(guid == filePropertiesGuid) {
|
||||
obj = new FilePropertiesObject();
|
||||
obj = new FilePrivate::FilePropertiesObject();
|
||||
}
|
||||
else if(guid == streamPropertiesGuid) {
|
||||
obj = new StreamPropertiesObject();
|
||||
obj = new FilePrivate::StreamPropertiesObject();
|
||||
}
|
||||
else if(guid == contentDescriptionGuid) {
|
||||
obj = new ContentDescriptionObject();
|
||||
obj = new FilePrivate::ContentDescriptionObject();
|
||||
}
|
||||
else if(guid == extendedContentDescriptionGuid) {
|
||||
obj = new ExtendedContentDescriptionObject();
|
||||
obj = new FilePrivate::ExtendedContentDescriptionObject();
|
||||
}
|
||||
else if(guid == headerExtensionGuid) {
|
||||
obj = new HeaderExtensionObject();
|
||||
obj = new FilePrivate::HeaderExtensionObject();
|
||||
}
|
||||
else if(guid == codecListGuid) {
|
||||
obj = new FilePrivate::CodecListObject();
|
||||
}
|
||||
else {
|
||||
if(guid == contentEncryptionGuid ||
|
||||
|
@ -482,149 +665,9 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties
|
|||
guid == advancedContentEncryptionGuid) {
|
||||
d->properties->setEncrypted(true);
|
||||
}
|
||||
obj = new UnknownObject(guid);
|
||||
obj = new FilePrivate::UnknownObject(guid);
|
||||
}
|
||||
obj->parse(this, size);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace TagLib {
|
|||
* \a propertiesStyle are ignored. The audio properties are always
|
||||
* read.
|
||||
*/
|
||||
File(FileName file, bool readProperties = true,
|
||||
File(FileName file, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
/*!
|
||||
|
@ -67,7 +67,7 @@ namespace TagLib {
|
|||
* \note TagLib will *not* take ownership of the stream, the caller is
|
||||
* responsible for deleting it after the File object.
|
||||
*/
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
File(IOStream *stream, bool readProperties = true,
|
||||
Properties::ReadStyle propertiesStyle = Properties::Average);
|
||||
|
||||
/*!
|
||||
|
@ -112,30 +112,14 @@ namespace TagLib {
|
|||
* Save 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();
|
||||
|
||||
private:
|
||||
int readBYTE(bool *ok = 0);
|
||||
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;
|
||||
void read();
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
|
|
@ -25,10 +25,12 @@
|
|||
|
||||
#include <taglib.h>
|
||||
#include <tdebug.h>
|
||||
#include "trefcounter.h"
|
||||
#include <trefcounter.h>
|
||||
|
||||
#include "asfattribute.h"
|
||||
#include "asffile.h"
|
||||
#include "asfpicture.h"
|
||||
#include "asfutils.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
|
@ -134,8 +136,8 @@ ByteVector ASF::Picture::render() const
|
|||
return
|
||||
ByteVector((char)d->type) +
|
||||
ByteVector::fromUInt(d->picture.size(), false) +
|
||||
ASF::File::renderString(d->mimeType) +
|
||||
ASF::File::renderString(d->description) +
|
||||
renderString(d->mimeType) +
|
||||
renderString(d->description) +
|
||||
d->picture;
|
||||
}
|
||||
|
||||
|
|
|
@ -205,8 +205,8 @@ namespace TagLib
|
|||
/* THIS IS PRIVATE, DON'T TOUCH IT! */
|
||||
void parse(const ByteVector& );
|
||||
static Picture fromInvalid();
|
||||
friend class Attribute;
|
||||
#endif
|
||||
|
||||
private:
|
||||
class PicturePrivate;
|
||||
PicturePrivate *d;
|
||||
|
|
|
@ -32,11 +32,23 @@ using namespace TagLib;
|
|||
class ASF::Properties::PropertiesPrivate
|
||||
{
|
||||
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 bitrate;
|
||||
int sampleRate;
|
||||
int channels;
|
||||
int bitsPerSample;
|
||||
ASF::Properties::Codec codec;
|
||||
String codecName;
|
||||
String codecDescription;
|
||||
bool encrypted;
|
||||
};
|
||||
|
||||
|
@ -44,18 +56,28 @@ public:
|
|||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ASF::Properties::Properties() : AudioProperties(AudioProperties::Average)
|
||||
ASF::Properties::Properties() :
|
||||
AudioProperties(AudioProperties::Average),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
d = new PropertiesPrivate;
|
||||
}
|
||||
|
||||
ASF::Properties::~Properties()
|
||||
{
|
||||
if(d)
|
||||
delete d;
|
||||
delete d;
|
||||
}
|
||||
|
||||
int ASF::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int ASF::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int ASF::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
|
@ -75,6 +97,26 @@ int ASF::Properties::channels() const
|
|||
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
|
||||
{
|
||||
return d->encrypted;
|
||||
|
@ -84,28 +126,69 @@ bool ASF::Properties::isEncrypted() const
|
|||
// 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;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,38 @@ namespace TagLib {
|
|||
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();
|
||||
|
||||
|
@ -49,18 +80,97 @@ namespace TagLib {
|
|||
*/
|
||||
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;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
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;
|
||||
|
||||
#ifndef DO_NOT_DOCUMENT
|
||||
// deprecated
|
||||
void setLength(int value);
|
||||
|
||||
void setLengthInMilliseconds(int value);
|
||||
void setBitrate(int value);
|
||||
void setSampleRate(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);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -47,8 +47,7 @@ ASF::Tag::Tag()
|
|||
|
||||
ASF::Tag::~Tag()
|
||||
{
|
||||
if(d)
|
||||
delete d;
|
||||
delete d;
|
||||
}
|
||||
|
||||
String ASF::Tag::title() const
|
||||
|
@ -161,11 +160,24 @@ ASF::AttributeListMap& ASF::Tag::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)
|
||||
{
|
||||
AttributeListMap::Iterator it = d->attributeListMap.find(key);
|
||||
if(it != d->attributeListMap.end())
|
||||
d->attributeListMap.erase(it);
|
||||
d->attributeListMap.erase(key);
|
||||
}
|
||||
|
||||
ASF::AttributeList ASF::Tag::attribute(const String &name) const
|
||||
{
|
||||
return d->attributeListMap[name];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if(d->attributeListMap.contains(name)) {
|
||||
|
@ -195,6 +212,7 @@ bool ASF::Tag::isEmpty() const
|
|||
|
||||
static const char *keyTranslation[][2] = {
|
||||
{ "WM/AlbumTitle", "ALBUM" },
|
||||
{ "WM/AlbumArtist", "ALBUMARTIST" },
|
||||
{ "WM/Composer", "COMPOSER" },
|
||||
{ "WM/Writer", "WRITER" },
|
||||
{ "WM/Conductor", "CONDUCTOR" },
|
||||
|
|
|
@ -152,24 +152,43 @@ namespace TagLib {
|
|||
virtual bool isEmpty() const;
|
||||
|
||||
/*!
|
||||
* Returns a reference to the item list map. This is an AttributeListMap of
|
||||
* all of the items in the tag.
|
||||
*
|
||||
* This is the most powerfull structure for accessing the items of the tag.
|
||||
* \deprecated
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
* with the \a key is already present, it will be replaced.
|
||||
*/
|
||||
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
|
||||
* with the \a key is already present, it will be added to the list.
|
||||
|
|
|
@ -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
|
|
@ -23,6 +23,22 @@
|
|||
* 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"
|
||||
|
||||
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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AudioProperties::AudioProperties(ReadStyle)
|
||||
AudioProperties::AudioProperties(ReadStyle) :
|
||||
d(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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
|
||||
* interface that is sufficient for most applications.
|
||||
*/
|
||||
|
@ -69,6 +69,23 @@ namespace TagLib {
|
|||
*/
|
||||
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
|
||||
* bitrate formats this is simply the bitrate of the file. For variable
|
||||
|
|
|
@ -265,7 +265,7 @@ File *FileRef::create(FileName fileName, bool readAudioProperties,
|
|||
return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
if(ext == "WMA" || ext == "ASF")
|
||||
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);
|
||||
if(ext == "WAV")
|
||||
return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle);
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace TagLib {
|
|||
*
|
||||
* 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))
|
||||
* return new TagLib::MPEG::File(fileName);
|
||||
|
@ -128,7 +128,7 @@ namespace TagLib {
|
|||
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.
|
||||
*/
|
||||
explicit FileRef(File *file);
|
||||
|
@ -191,7 +191,7 @@ namespace TagLib {
|
|||
* is tried.
|
||||
*
|
||||
* 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).
|
||||
*
|
||||
* \see FileTypeResolver
|
||||
|
@ -209,7 +209,7 @@ namespace TagLib {
|
|||
* by TagLib for resolution is case-insensitive.
|
||||
*
|
||||
* \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.
|
||||
*
|
||||
* \see FileTypeResolver
|
||||
|
|
|
@ -60,7 +60,6 @@ public:
|
|||
properties(0),
|
||||
flacStart(0),
|
||||
streamStart(0),
|
||||
streamLength(0),
|
||||
scanned(false),
|
||||
hasXiphComment(false),
|
||||
hasID3v2(false),
|
||||
|
@ -86,13 +85,11 @@ public:
|
|||
TagUnion tag;
|
||||
|
||||
Properties *properties;
|
||||
ByteVector streamInfoData;
|
||||
ByteVector xiphCommentData;
|
||||
List<MetadataBlock *> blocks;
|
||||
|
||||
long flacStart;
|
||||
long streamStart;
|
||||
long streamLength;
|
||||
bool scanned;
|
||||
|
||||
bool hasXiphComment;
|
||||
|
@ -104,33 +101,32 @@ public:
|
|||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FLAC::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(file)
|
||||
FLAC::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
|
||||
bool readProperties, Properties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(file)
|
||||
bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
d->ID3v2FrameFactory = frameFactory;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
|
||||
bool readProperties, Properties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(stream)
|
||||
bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
d->ID3v2FrameFactory = frameFactory;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
FLAC::File::~File()
|
||||
|
@ -176,7 +172,6 @@ FLAC::Properties *FLAC::File::audioProperties() const
|
|||
return d->properties;
|
||||
}
|
||||
|
||||
|
||||
bool FLAC::File::save()
|
||||
{
|
||||
if(readOnly()) {
|
||||
|
@ -235,7 +230,7 @@ bool FLAC::File::save()
|
|||
|
||||
long originalLength = d->streamStart - d->flacStart;
|
||||
int paddingLength = originalLength - data.size() - 4;
|
||||
if (paddingLength < 0) {
|
||||
if(paddingLength <= 0) {
|
||||
paddingLength = MinPaddingLength;
|
||||
}
|
||||
ByteVector padding = ByteVector::fromUInt(paddingLength);
|
||||
|
@ -263,8 +258,16 @@ bool FLAC::File::save()
|
|||
}
|
||||
|
||||
if(ID3v1Tag()) {
|
||||
seek(-128, End);
|
||||
if(d->hasID3v1) {
|
||||
seek(d->ID3v1Location);
|
||||
}
|
||||
else {
|
||||
seek(0, End);
|
||||
d->ID3v1Location = tell();
|
||||
}
|
||||
|
||||
writeBlock(ID3v1Tag()->render());
|
||||
d->hasID3v1 = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -294,219 +297,16 @@ void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *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()
|
||||
{
|
||||
return isValid() ? d->streamInfoData : ByteVector();
|
||||
}
|
||||
|
||||
ByteVector FLAC::File::xiphCommentData() const
|
||||
{
|
||||
return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector();
|
||||
debug("FLAC::File::streamInfoData() -- This function is obsolete. Returning an empty ByteVector.");
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
long FLAC::File::streamLength()
|
||||
{
|
||||
return d->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->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;
|
||||
debug("FLAC::File::streamLength() -- This function is obsolete. Returning zero.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<FLAC::Picture *> FLAC::File::pictureList()
|
||||
|
@ -566,3 +366,216 @@ bool FLAC::File::hasID3v2Tag() const
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace TagLib {
|
|||
/*!
|
||||
* 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
|
||||
* under the Ogg hiearchy.
|
||||
* under the Ogg hierarchy.
|
||||
*
|
||||
* This supports ID3v1, ID3v2 and Xiph style comments as well as reading stream
|
||||
* properties from the file.
|
||||
|
@ -79,7 +79,7 @@ namespace TagLib {
|
|||
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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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();
|
||||
|
||||
|
@ -165,8 +168,8 @@ namespace TagLib {
|
|||
* 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.
|
||||
*
|
||||
* \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
|
||||
* \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
|
||||
* on disk actually has an ID3v2 tag.
|
||||
*
|
||||
* \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
|
||||
* 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
|
||||
* file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file
|
||||
* \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
|
||||
* on disk actually has an ID3v1 tag.
|
||||
*
|
||||
* \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
|
||||
* 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
|
||||
* file on disk has a XiphComment. Use hasXiphComment() to check if 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 actually has a XiphComment.
|
||||
*
|
||||
*
|
||||
* \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
|
||||
* destroyed.
|
||||
|
@ -221,6 +224,7 @@ namespace TagLib {
|
|||
* when
|
||||
*
|
||||
* \see ID3v2FrameFactory
|
||||
* \deprecated This value should be passed in via the constructor
|
||||
*/
|
||||
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
|
||||
|
||||
|
@ -228,7 +232,7 @@ namespace TagLib {
|
|||
* Returns the block of data used by FLAC::Properties for parsing the
|
||||
* stream properties.
|
||||
*
|
||||
* \deprecated This method will not be public in a future release.
|
||||
* \deprecated Always returns an empty vector.
|
||||
*/
|
||||
ByteVector streamInfoData(); // BIC: remove
|
||||
|
||||
|
@ -236,7 +240,7 @@ namespace TagLib {
|
|||
* Returns the length of the audio-stream, used by FLAC::Properties for
|
||||
* calculating the bitrate.
|
||||
*
|
||||
* \deprecated This method will not be public in a future release.
|
||||
* \deprecated Always returns zero.
|
||||
*/
|
||||
long streamLength(); // BIC: remove
|
||||
|
||||
|
@ -289,12 +293,10 @@ namespace TagLib {
|
|||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
void read(bool readProperties);
|
||||
void scan();
|
||||
long findID3v2();
|
||||
long findID3v1();
|
||||
ByteVector xiphCommentData() const;
|
||||
long findPaddingBreak(long nextPageOffset, long targetOffset, bool *isLast);
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
|
|
@ -34,24 +34,18 @@ using namespace TagLib;
|
|||
class FLAC::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(ByteVector d, long st, ReadStyle s) :
|
||||
data(d),
|
||||
streamLength(st),
|
||||
style(s),
|
||||
PropertiesPrivate() :
|
||||
length(0),
|
||||
bitrate(0),
|
||||
sampleRate(0),
|
||||
sampleWidth(0),
|
||||
bitsPerSample(0),
|
||||
channels(0),
|
||||
sampleFrames(0) {}
|
||||
|
||||
ByteVector data;
|
||||
long streamLength;
|
||||
ReadStyle style;
|
||||
int length;
|
||||
int bitrate;
|
||||
int sampleRate;
|
||||
int sampleWidth;
|
||||
int bitsPerSample;
|
||||
int channels;
|
||||
unsigned long long sampleFrames;
|
||||
ByteVector signature;
|
||||
|
@ -61,16 +55,18 @@ public:
|
|||
// 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();
|
||||
read(data, streamLength);
|
||||
}
|
||||
|
||||
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);
|
||||
read();
|
||||
debug("FLAC::Properties::Properties() - This constructor is no longer used.");
|
||||
}
|
||||
|
||||
FLAC::Properties::~Properties()
|
||||
|
@ -79,6 +75,16 @@ FLAC::Properties::~Properties()
|
|||
}
|
||||
|
||||
int FLAC::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int FLAC::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int FLAC::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
|
@ -93,9 +99,14 @@ int FLAC::Properties::sampleRate() const
|
|||
return d->sampleRate;
|
||||
}
|
||||
|
||||
int FLAC::Properties::bitsPerSample() const
|
||||
{
|
||||
return d->bitsPerSample;
|
||||
}
|
||||
|
||||
int FLAC::Properties::sampleWidth() const
|
||||
{
|
||||
return d->sampleWidth;
|
||||
return bitsPerSample();
|
||||
}
|
||||
|
||||
int FLAC::Properties::channels() const
|
||||
|
@ -117,9 +128,9 @@ ByteVector FLAC::Properties::signature() const
|
|||
// 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.");
|
||||
return;
|
||||
}
|
||||
|
@ -138,32 +149,28 @@ void FLAC::Properties::read()
|
|||
// Maximum frame size (in bytes)
|
||||
pos += 3;
|
||||
|
||||
uint flags = d->data.toUInt(pos, true);
|
||||
const uint flags = data.toUInt(pos, true);
|
||||
pos += 4;
|
||||
|
||||
d->sampleRate = flags >> 12;
|
||||
d->channels = ((flags >> 9) & 7) + 1;
|
||||
d->sampleWidth = ((flags >> 4) & 31) + 1;
|
||||
d->sampleRate = flags >> 12;
|
||||
d->channels = ((flags >> 9) & 7) + 1;
|
||||
d->bitsPerSample = ((flags >> 4) & 31) + 1;
|
||||
|
||||
// The last 4 bits are the most significant 4 bits for the 36 bit
|
||||
// stream length in samples. (Audio files measured in days)
|
||||
|
||||
unsigned long long hi = flags & 0xf;
|
||||
unsigned long long lo = d->data.toUInt(pos, true);
|
||||
const ulonglong hi = flags & 0xf;
|
||||
const ulonglong lo = data.toUInt(pos, true);
|
||||
pos += 4;
|
||||
|
||||
d->sampleFrames = (hi << 32) | lo;
|
||||
|
||||
if(d->sampleRate > 0)
|
||||
d->length = int(d->sampleFrames / d->sampleRate);
|
||||
if(d->sampleFrames > 0 && d->sampleRate > 0) {
|
||||
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:
|
||||
|
||||
//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);
|
||||
if(data.size() >= pos + 16)
|
||||
d->signature = data.mid(pos, 16);
|
||||
}
|
||||
|
|
|
@ -64,27 +64,72 @@ namespace TagLib {
|
|||
*/
|
||||
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;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
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
|
||||
* header.
|
||||
*
|
||||
* \note This method is just an alias of bitsPerSample().
|
||||
*
|
||||
* \deprecated
|
||||
*/
|
||||
int sampleWidth() const;
|
||||
|
||||
/*!
|
||||
* Return the number of sample frames
|
||||
* Return the number of sample frames.
|
||||
*/
|
||||
unsigned long long sampleFrames() const;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
|
@ -92,7 +137,7 @@ namespace TagLib {
|
|||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void read();
|
||||
void read(const ByteVector &data, long streamLength);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
|
|
|
@ -79,6 +79,16 @@ int IT::Properties::length() const
|
|||
return 0;
|
||||
}
|
||||
|
||||
int IT::Properties::lengthInSeconds() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IT::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IT::Properties::bitrate() const
|
||||
{
|
||||
return 0;
|
||||
|
|
|
@ -51,10 +51,12 @@ namespace TagLib {
|
|||
Properties(AudioProperties::ReadStyle propertiesStyle);
|
||||
virtual ~Properties();
|
||||
|
||||
int length() const;
|
||||
int bitrate() const;
|
||||
int sampleRate() const;
|
||||
int channels() const;
|
||||
int length() const;
|
||||
int lengthInSeconds() const;
|
||||
int lengthInMilliseconds() const;
|
||||
int bitrate() const;
|
||||
int sampleRate() const;
|
||||
int channels() const;
|
||||
|
||||
ushort lengthInPatterns() const;
|
||||
bool stereo() const;
|
||||
|
|
|
@ -55,6 +55,16 @@ int Mod::Properties::length() const
|
|||
return 0;
|
||||
}
|
||||
|
||||
int Mod::Properties::lengthInSeconds() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Mod::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Mod::Properties::bitrate() const
|
||||
{
|
||||
return 0;
|
||||
|
|
|
@ -35,12 +35,14 @@ namespace TagLib {
|
|||
Properties(AudioProperties::ReadStyle propertiesStyle);
|
||||
virtual ~Properties();
|
||||
|
||||
int length() const;
|
||||
int bitrate() const;
|
||||
int sampleRate() const;
|
||||
int channels() const;
|
||||
int length() const;
|
||||
int lengthInSeconds() const;
|
||||
int lengthInMilliseconds() const;
|
||||
int bitrate() const;
|
||||
int sampleRate() const;
|
||||
int channels() const;
|
||||
|
||||
uint instrumentCount() const;
|
||||
uint instrumentCount() const;
|
||||
uchar lengthInPatterns() const;
|
||||
|
||||
void setChannels(int channels);
|
||||
|
|
|
@ -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
|
||||
// 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)
|
||||
properties.erase(*it);
|
||||
else
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace TagLib {
|
|||
* Sets the title to \a title. If \a title is String::null then this
|
||||
* 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
|
||||
* characters.
|
||||
*/
|
||||
|
@ -126,7 +126,7 @@ namespace TagLib {
|
|||
* an thus the line length in comments are limited. Too big comments
|
||||
* 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
|
||||
* characters.
|
||||
*/
|
||||
|
@ -169,7 +169,7 @@ namespace TagLib {
|
|||
* Implements the unified property interface -- import function.
|
||||
* Because of the limitations of the module file tag, any tags besides
|
||||
* 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
|
||||
* properties.
|
||||
*/
|
||||
|
|
|
@ -23,6 +23,12 @@
|
|||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <climits>
|
||||
|
||||
#include <tdebug.h>
|
||||
#include <tstring.h>
|
||||
#include "mp4atom.h"
|
||||
|
@ -50,20 +56,25 @@ MP4::Atom::Atom(File *file)
|
|||
|
||||
length = header.toUInt();
|
||||
|
||||
if (length == 1) {
|
||||
if(length == 1) {
|
||||
const long long longLength = file->readBlock(8).toLongLong();
|
||||
if (longLength >= 8 && longLength <= 0xFFFFFFFF) {
|
||||
// The atom has a 64-bit length, but it's actually a 32-bit value
|
||||
length = (long)longLength;
|
||||
if(sizeof(long) == sizeof(long long)) {
|
||||
length = static_cast<long>(longLength);
|
||||
}
|
||||
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");
|
||||
length = 0;
|
||||
file->seek(0, File::End);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (length < 8) {
|
||||
if(length < 8) {
|
||||
debug("MP4: Invalid atom size");
|
||||
length = 0;
|
||||
file->seek(0, File::End);
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace TagLib {
|
|||
TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits
|
||||
TypeGenred = 18, // a list of enumerated values
|
||||
TypeInteger = 21, // a signed big-endian integer with length one of { 1,2,3,4,8 } bytes
|
||||
TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit ingteger
|
||||
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)
|
||||
TypeBMP = 27, // Windows bitmap image
|
||||
TypeUndefined = 255 // undefined
|
||||
|
|
|
@ -54,11 +54,12 @@ MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d)
|
|||
MP4::CoverArt &
|
||||
MP4::CoverArt::operator=(const CoverArt &item)
|
||||
{
|
||||
if(d->deref()) {
|
||||
delete d;
|
||||
if(&item != this) {
|
||||
if(d->deref())
|
||||
delete d;
|
||||
d = item.d;
|
||||
d->ref();
|
||||
}
|
||||
d = item.d;
|
||||
d->ref();
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,48 +32,57 @@
|
|||
|
||||
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
|
||||
{
|
||||
public:
|
||||
FilePrivate() : tag(0), atoms(0), properties(0)
|
||||
{
|
||||
}
|
||||
FilePrivate() :
|
||||
tag(0),
|
||||
atoms(0),
|
||||
properties(0) {}
|
||||
|
||||
~FilePrivate()
|
||||
{
|
||||
if(atoms) {
|
||||
delete atoms;
|
||||
atoms = 0;
|
||||
}
|
||||
if(tag) {
|
||||
delete tag;
|
||||
tag = 0;
|
||||
}
|
||||
if(properties) {
|
||||
delete properties;
|
||||
properties = 0;
|
||||
}
|
||||
delete atoms;
|
||||
delete tag;
|
||||
delete properties;
|
||||
}
|
||||
|
||||
MP4::Tag *tag;
|
||||
MP4::Atoms *atoms;
|
||||
MP4::Tag *tag;
|
||||
MP4::Atoms *atoms;
|
||||
MP4::Properties *properties;
|
||||
};
|
||||
|
||||
MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle)
|
||||
: TagLib::File(file)
|
||||
MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, audioPropertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle)
|
||||
: TagLib::File(stream)
|
||||
MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, audioPropertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MP4::File::~File()
|
||||
|
@ -108,26 +117,14 @@ MP4::File::audioProperties() const
|
|||
return d->properties;
|
||||
}
|
||||
|
||||
bool
|
||||
MP4::File::checkValid(const MP4::AtomList &list)
|
||||
{
|
||||
for(uint i = 0; i < list.size(); i++) {
|
||||
if(list[i]->length == 0)
|
||||
return false;
|
||||
if(!checkValid(list[i]->children))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle)
|
||||
MP4::File::read(bool readProperties)
|
||||
{
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
d->atoms = new Atoms(this);
|
||||
if (!checkValid(d->atoms->atoms)) {
|
||||
if(!checkValid(d->atoms->atoms)) {
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
@ -141,7 +138,7 @@ MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle)
|
|||
|
||||
d->tag = new Tag(this, d->atoms);
|
||||
if(readProperties) {
|
||||
d->properties = new Properties(this, d->atoms, audioPropertiesStyle);
|
||||
d->properties = new Properties(this, d->atoms);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace TagLib {
|
|||
*
|
||||
* \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);
|
||||
|
||||
/*!
|
||||
|
@ -66,7 +66,7 @@ namespace TagLib {
|
|||
*
|
||||
* \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);
|
||||
|
||||
/*!
|
||||
|
@ -111,13 +111,14 @@ namespace TagLib {
|
|||
* Save 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.
|
||||
*/
|
||||
bool save();
|
||||
|
||||
private:
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle audioPropertiesStyle);
|
||||
bool checkValid(const MP4::AtomList &list);
|
||||
void read(bool readProperties);
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
|
|
@ -64,11 +64,13 @@ MP4::Item::Item(const Item &item) : d(item.d)
|
|||
MP4::Item &
|
||||
MP4::Item::operator=(const Item &item)
|
||||
{
|
||||
if(d->deref()) {
|
||||
delete d;
|
||||
if(&item != this) {
|
||||
if(d->deref()) {
|
||||
delete d;
|
||||
}
|
||||
d = item.d;
|
||||
d->ref();
|
||||
}
|
||||
d = item.d;
|
||||
d->ref();
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,14 @@ using namespace TagLib;
|
|||
class MP4::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), bitsPerSample(0), encrypted(false), codec(MP4::Properties::Unknown) {}
|
||||
PropertiesPrivate() :
|
||||
length(0),
|
||||
bitrate(0),
|
||||
sampleRate(0),
|
||||
channels(0),
|
||||
bitsPerSample(0),
|
||||
encrypted(false),
|
||||
codec(MP4::Properties::Unknown) {}
|
||||
|
||||
int length;
|
||||
int bitrate;
|
||||
|
@ -45,110 +52,15 @@ public:
|
|||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
read(file, atoms);
|
||||
}
|
||||
|
||||
MP4::Properties::~Properties()
|
||||
|
@ -170,6 +82,18 @@ MP4::Properties::sampleRate() const
|
|||
|
||||
int
|
||||
MP4::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int
|
||||
MP4::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int
|
||||
MP4::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
|
@ -192,8 +116,119 @@ MP4::Properties::isEncrypted() const
|
|||
return d->encrypted;
|
||||
}
|
||||
|
||||
MP4::Properties::Codec MP4::Properties::codec() const
|
||||
MP4::Properties::Codec
|
||||
MP4::Properties::codec() const
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,17 +49,66 @@ namespace TagLib {
|
|||
Properties(File *file, Atoms *atoms, ReadStyle style = Average);
|
||||
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;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of bits per audio sample.
|
||||
*/
|
||||
virtual int bitsPerSample() const;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file is encrypted.
|
||||
*/
|
||||
bool isEncrypted() const;
|
||||
|
||||
//! Audio codec used in the MP4 file
|
||||
/*!
|
||||
* Returns the codec used in the file.
|
||||
*/
|
||||
Codec codec() const;
|
||||
|
||||
private:
|
||||
void read(File *file, Atoms *atoms);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
};
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
~TagPrivate() {}
|
||||
TagLib::File *file;
|
||||
Atoms *atoms;
|
||||
ItemListMap items;
|
||||
ItemMap items;
|
||||
};
|
||||
|
||||
MP4::Tag::Tag()
|
||||
|
@ -63,36 +63,36 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms)
|
|||
MP4::Atom *atom = ilst->children[i];
|
||||
file->seek(atom->offset + 8);
|
||||
if(atom->name == "----") {
|
||||
parseFreeForm(atom, file);
|
||||
parseFreeForm(atom);
|
||||
}
|
||||
else if(atom->name == "trkn" || atom->name == "disk") {
|
||||
parseIntPair(atom, file);
|
||||
parseIntPair(atom);
|
||||
}
|
||||
else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst" ||
|
||||
atom->name == "hdvd") {
|
||||
parseBool(atom, file);
|
||||
parseBool(atom);
|
||||
}
|
||||
else if(atom->name == "tmpo") {
|
||||
parseInt(atom, file);
|
||||
parseInt(atom);
|
||||
}
|
||||
else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" ||
|
||||
atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") {
|
||||
parseUInt(atom, file);
|
||||
parseUInt(atom);
|
||||
}
|
||||
else if(atom->name == "plID") {
|
||||
parseLongLong(atom, file);
|
||||
parseLongLong(atom);
|
||||
}
|
||||
else if(atom->name == "stik" || atom->name == "rtng" || atom->name == "akID") {
|
||||
parseByte(atom, file);
|
||||
parseByte(atom);
|
||||
}
|
||||
else if(atom->name == "gnre") {
|
||||
parseGnre(atom, file);
|
||||
parseGnre(atom);
|
||||
}
|
||||
else if(atom->name == "covr") {
|
||||
parseCovr(atom, file);
|
||||
parseCovr(atom);
|
||||
}
|
||||
else {
|
||||
parseText(atom, file);
|
||||
parseText(atom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,15 +103,20 @@ MP4::Tag::~Tag()
|
|||
}
|
||||
|
||||
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;
|
||||
ByteVector data = file->readBlock(atom->length - 8);
|
||||
ByteVector data = d->file->readBlock(atom->length - 8);
|
||||
int i = 0;
|
||||
unsigned int pos = 0;
|
||||
while(pos < data.size()) {
|
||||
const int length = static_cast<int>(data.toUInt(pos));
|
||||
ByteVector name = data.mid(pos + 4, 4);
|
||||
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));
|
||||
if(freeForm && i < 2) {
|
||||
if(i == 0 && name != "mean") {
|
||||
|
@ -140,9 +145,9 @@ MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, boo
|
|||
}
|
||||
|
||||
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;
|
||||
for(uint i = 0; i < data.size(); i++) {
|
||||
result.append(data[i].data);
|
||||
|
@ -151,45 +156,45 @@ MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool
|
|||
}
|
||||
|
||||
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()) {
|
||||
addItem(atom->name, (int)data[0].toShort());
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
addItem(atom->name, data[0].toUInt());
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
addItem(atom->name, data[0].toLongLong());
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
addItem(atom->name, (uchar)data[0].at(0));
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
int idx = (int)data[0].toShort();
|
||||
if(idx > 0) {
|
||||
|
@ -199,9 +204,9 @@ MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file)
|
|||
}
|
||||
|
||||
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()) {
|
||||
const int a = data[0].toShort(2U);
|
||||
const int b = data[0].toShort(4U);
|
||||
|
@ -210,9 +215,9 @@ MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file)
|
|||
}
|
||||
|
||||
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()) {
|
||||
bool value = data[0].size() ? data[0][0] != '\0' : false;
|
||||
addItem(atom->name, value);
|
||||
|
@ -220,9 +225,9 @@ MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file)
|
|||
}
|
||||
|
||||
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()) {
|
||||
StringList value;
|
||||
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
|
||||
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) {
|
||||
String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8);
|
||||
AtomDataType type = data[2].type;
|
||||
|
@ -267,20 +272,26 @@ MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file)
|
|||
}
|
||||
|
||||
void
|
||||
MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file)
|
||||
MP4::Tag::parseCovr(const MP4::Atom *atom)
|
||||
{
|
||||
MP4::CoverArtList value;
|
||||
ByteVector data = file->readBlock(atom->length - 8);
|
||||
ByteVector data = d->file->readBlock(atom->length - 8);
|
||||
unsigned int pos = 0;
|
||||
while(pos < data.size()) {
|
||||
const int length = static_cast<int>(data.toUInt(pos));
|
||||
ByteVector name = data.mid(pos + 4, 4);
|
||||
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));
|
||||
if(name != "data") {
|
||||
debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\"");
|
||||
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),
|
||||
data.mid(pos + 16, length - 16)));
|
||||
}
|
||||
|
@ -294,22 +305,22 @@ MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file)
|
|||
}
|
||||
|
||||
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();
|
||||
}
|
||||
return renderAtom("free", ByteVector(length, '\1'));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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
|
||||
MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(1, item.toBool() ? '\1' : '\0'));
|
||||
|
@ -327,7 +338,7 @@ MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item)
|
|||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector::fromShort(item.toInt()));
|
||||
|
@ -335,7 +346,7 @@ MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item)
|
|||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector::fromUInt(item.toUInt()));
|
||||
|
@ -343,7 +354,7 @@ MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item)
|
|||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector::fromLongLong(item.toLongLong()));
|
||||
|
@ -351,7 +362,7 @@ MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item)
|
|||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(1, item.toByte()));
|
||||
|
@ -359,7 +370,7 @@ MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item)
|
|||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(2, '\0') +
|
||||
|
@ -370,7 +381,7 @@ MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item)
|
|||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVectorList data;
|
||||
data.append(ByteVector(2, '\0') +
|
||||
|
@ -380,7 +391,7 @@ MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item)
|
|||
}
|
||||
|
||||
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;
|
||||
StringList value = item.toStringList();
|
||||
|
@ -391,7 +402,7 @@ MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags)
|
|||
}
|
||||
|
||||
ByteVector
|
||||
MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item)
|
||||
MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const
|
||||
{
|
||||
ByteVector data;
|
||||
MP4::CoverArtList value = item.toCoverArtList();
|
||||
|
@ -403,7 +414,7 @@ MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item)
|
|||
}
|
||||
|
||||
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, ":");
|
||||
if (header.size() != 3) {
|
||||
|
@ -441,38 +452,38 @@ bool
|
|||
MP4::Tag::save()
|
||||
{
|
||||
ByteVector data;
|
||||
for(MP4::ItemListMap::Iterator i = d->items.begin(); i != d->items.end(); i++) {
|
||||
const String name = i->first;
|
||||
for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) {
|
||||
const String name = it->first;
|
||||
if(name.startsWith("----")) {
|
||||
data.append(renderFreeForm(name, i->second));
|
||||
data.append(renderFreeForm(name, it->second));
|
||||
}
|
||||
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") {
|
||||
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") {
|
||||
data.append(renderBool(name.data(String::Latin1), i->second));
|
||||
data.append(renderBool(name.data(String::Latin1), it->second));
|
||||
}
|
||||
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" ||
|
||||
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") {
|
||||
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") {
|
||||
data.append(renderByte(name.data(String::Latin1), i->second));
|
||||
data.append(renderByte(name.data(String::Latin1), it->second));
|
||||
}
|
||||
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){
|
||||
data.append(renderText(name.data(String::Latin1), i->second));
|
||||
data.append(renderText(name.data(String::Latin1), it->second));
|
||||
}
|
||||
else {
|
||||
debug("MP4: Unknown item name \"" + name + "\"");
|
||||
|
@ -492,7 +503,7 @@ MP4::Tag::save()
|
|||
}
|
||||
|
||||
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++) {
|
||||
d->file->seek(path[i]->offset);
|
||||
|
@ -585,10 +596,11 @@ MP4::Tag::updateOffsets(long delta, long offset)
|
|||
}
|
||||
|
||||
void
|
||||
MP4::Tag::saveNew(ByteVector &data)
|
||||
MP4::Tag::saveNew(ByteVector data)
|
||||
{
|
||||
data = renderAtom("meta", TagLib::ByteVector(4, '\0') +
|
||||
renderAtom("hdlr", TagLib::ByteVector(8, '\0') + TagLib::ByteVector("mdirappl") + TagLib::ByteVector(9, '\0')) +
|
||||
data = renderAtom("meta", ByteVector(4, '\0') +
|
||||
renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") +
|
||||
ByteVector(9, '\0')) +
|
||||
data + padIlst(data));
|
||||
|
||||
AtomList path = d->atoms->path("moov", "udta");
|
||||
|
@ -605,18 +617,18 @@ MP4::Tag::saveNew(ByteVector &data)
|
|||
}
|
||||
|
||||
void
|
||||
MP4::Tag::saveExisting(ByteVector &data, AtomList &path)
|
||||
MP4::Tag::saveExisting(ByteVector data, const AtomList &path)
|
||||
{
|
||||
MP4::Atom *ilst = path[path.size() - 1];
|
||||
long offset = ilst->offset;
|
||||
long length = ilst->length;
|
||||
|
||||
MP4::Atom *meta = path[path.size() - 2];
|
||||
AtomList::Iterator index = meta->children.find(ilst);
|
||||
AtomList::ConstIterator index = meta->children.find(ilst);
|
||||
|
||||
// check if there is an atom before 'ilst', and possibly use it as padding
|
||||
if(index != meta->children.begin()) {
|
||||
AtomList::Iterator prevIndex = index;
|
||||
AtomList::ConstIterator prevIndex = index;
|
||||
prevIndex--;
|
||||
MP4::Atom *prev = *prevIndex;
|
||||
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
|
||||
AtomList::Iterator nextIndex = index;
|
||||
AtomList::ConstIterator nextIndex = index;
|
||||
nextIndex++;
|
||||
if(nextIndex != meta->children.end()) {
|
||||
MP4::Atom *next = *nextIndex;
|
||||
|
@ -750,12 +762,41 @@ MP4::Tag::setTrack(uint value)
|
|||
d->items["trkn"] = MP4::Item(value, 0);
|
||||
}
|
||||
|
||||
MP4::ItemListMap &
|
||||
MP4::Tag::itemListMap()
|
||||
bool MP4::Tag::isEmpty() const
|
||||
{
|
||||
return d->items.isEmpty();
|
||||
}
|
||||
|
||||
MP4::ItemMap &MP4::Tag::itemListMap()
|
||||
{
|
||||
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] = {
|
||||
{ "\251nam", "TITLE" },
|
||||
{ "\251ART", "ARTIST" },
|
||||
|
@ -765,6 +806,7 @@ static const char *keyTranslation[][2] = {
|
|||
{ "\251day", "DATE" },
|
||||
{ "\251wrt", "COMPOSER" },
|
||||
{ "\251grp", "GROUPING" },
|
||||
{ "aART", "ALBUMARTIST" },
|
||||
{ "trkn", "TRACKNUMBER" },
|
||||
{ "disk", "DISCNUMBER" },
|
||||
{ "cpil", "COMPILATION" },
|
||||
|
@ -816,7 +858,7 @@ PropertyMap MP4::Tag::properties() const
|
|||
}
|
||||
|
||||
PropertyMap props;
|
||||
MP4::ItemListMap::ConstIterator it = d->items.begin();
|
||||
MP4::ItemMap::ConstIterator it = d->items.begin();
|
||||
for(; it != d->items.end(); ++it) {
|
||||
if(keyMap.contains(it->first)) {
|
||||
String key = keyMap[it->first];
|
||||
|
@ -875,7 +917,7 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
|
|||
for(; it != props.end(); ++it) {
|
||||
if(reverseKeyMap.contains(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;
|
||||
StringList parts = StringList::split(it->second.front(), "/");
|
||||
if(parts.size() > 0) {
|
||||
|
@ -886,11 +928,11 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
|
|||
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();
|
||||
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);
|
||||
d->items[name] = MP4::Item(value);
|
||||
}
|
||||
|
|
|
@ -39,7 +39,11 @@ namespace TagLib {
|
|||
|
||||
namespace MP4 {
|
||||
|
||||
/*!
|
||||
* \deprecated
|
||||
*/
|
||||
typedef TagLib::Map<String, Item> ItemListMap;
|
||||
typedef TagLib::Map<String, Item> ItemMap;
|
||||
|
||||
class TAGLIB_EXPORT Tag: public TagLib::Tag
|
||||
{
|
||||
|
@ -65,45 +69,80 @@ namespace TagLib {
|
|||
void setYear(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;
|
||||
void removeUnsupportedProperties(const StringList& properties);
|
||||
PropertyMap setProperties(const PropertyMap &properties);
|
||||
|
||||
private:
|
||||
AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
|
||||
TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
|
||||
void parseText(Atom *atom, TagLib::File *file, int expectedFlags = 1);
|
||||
void parseFreeForm(Atom *atom, TagLib::File *file);
|
||||
void parseInt(Atom *atom, TagLib::File *file);
|
||||
void parseByte(Atom *atom, TagLib::File *file);
|
||||
void parseUInt(Atom *atom, TagLib::File *file);
|
||||
void parseLongLong(Atom *atom, TagLib::File *file);
|
||||
void parseGnre(Atom *atom, TagLib::File *file);
|
||||
void parseIntPair(Atom *atom, TagLib::File *file);
|
||||
void parseBool(Atom *atom, TagLib::File *file);
|
||||
void parseCovr(Atom *atom, TagLib::File *file);
|
||||
AtomDataList parseData2(const Atom *atom, int expectedFlags = -1,
|
||||
bool freeForm = false);
|
||||
ByteVectorList parseData(const Atom *atom, int expectedFlags = -1,
|
||||
bool freeForm = false);
|
||||
void parseText(const Atom *atom, int expectedFlags = 1);
|
||||
void parseFreeForm(const Atom *atom);
|
||||
void parseInt(const Atom *atom);
|
||||
void parseByte(const Atom *atom);
|
||||
void parseUInt(const Atom *atom);
|
||||
void parseLongLong(const Atom *atom);
|
||||
void parseGnre(const Atom *atom);
|
||||
void parseIntPair(const Atom *atom);
|
||||
void parseBool(const Atom *atom);
|
||||
void parseCovr(const Atom *atom);
|
||||
|
||||
TagLib::ByteVector padIlst(const ByteVector &data, int length = -1);
|
||||
TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data);
|
||||
TagLib::ByteVector renderData(const ByteVector &name, int flags, const TagLib::ByteVectorList &data);
|
||||
TagLib::ByteVector renderText(const ByteVector &name, Item &item, int flags = TypeUTF8);
|
||||
TagLib::ByteVector renderFreeForm(const String &name, Item &item);
|
||||
TagLib::ByteVector renderBool(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderInt(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderByte(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderUInt(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderLongLong(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item);
|
||||
TagLib::ByteVector renderCovr(const ByteVector &name, Item &item);
|
||||
ByteVector padIlst(const ByteVector &data, int length = -1) const;
|
||||
ByteVector renderAtom(const ByteVector &name, const ByteVector &data) const;
|
||||
ByteVector renderData(const ByteVector &name, int flags,
|
||||
const ByteVectorList &data) const;
|
||||
ByteVector renderText(const ByteVector &name, const Item &item,
|
||||
int flags = TypeUTF8) const;
|
||||
ByteVector renderFreeForm(const String &name, const Item &item) const;
|
||||
ByteVector renderBool(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderInt(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderByte(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderUInt(const ByteVector &name, const Item &item) const;
|
||||
ByteVector renderLongLong(const ByteVector &name, const Item &item) const;
|
||||
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 saveNew(TagLib::ByteVector &data);
|
||||
void saveExisting(TagLib::ByteVector &data, AtomList &path);
|
||||
void saveNew(ByteVector data);
|
||||
void saveExisting(ByteVector data, const AtomList &path);
|
||||
|
||||
void addItem(const String &name, const Item &value);
|
||||
|
||||
|
|
|
@ -53,7 +53,6 @@ public:
|
|||
ID3v2Location(-1),
|
||||
ID3v2Size(0),
|
||||
properties(0),
|
||||
scanned(false),
|
||||
hasAPE(false),
|
||||
hasID3v1(false),
|
||||
hasID3v2(false) {}
|
||||
|
@ -76,7 +75,6 @@ public:
|
|||
TagUnion tag;
|
||||
|
||||
Properties *properties;
|
||||
bool scanned;
|
||||
|
||||
// These indicate whether the file *on disk* has these tags, not if
|
||||
// this data structure does. This is used in computing offsets.
|
||||
|
@ -90,20 +88,20 @@ public:
|
|||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MPC::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
|
||||
MPC::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MPC::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
|
||||
MPC::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MPC::File::~File()
|
||||
|
@ -270,7 +268,7 @@ bool MPC::File::hasAPETag() const
|
|||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */)
|
||||
void MPC::File::read(bool readProperties)
|
||||
{
|
||||
// Look for an ID3v1 tag
|
||||
|
||||
|
@ -283,8 +281,6 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
|
|||
|
||||
// Look for an APE tag
|
||||
|
||||
findAPE();
|
||||
|
||||
d->APELocation = findAPE();
|
||||
|
||||
if(d->APELocation >= 0) {
|
||||
|
@ -298,7 +294,7 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
|
|||
if(!d->hasID3v1)
|
||||
APETag(true);
|
||||
|
||||
// Look for and skip an ID3v2 tag
|
||||
// Look for an ID3v2 tag
|
||||
|
||||
d->ID3v2Location = findID3v2();
|
||||
|
||||
|
@ -309,15 +305,28 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty
|
|||
d->hasID3v2 = true;
|
||||
}
|
||||
|
||||
if(d->hasID3v2)
|
||||
seek(d->ID3v2Location + d->ID3v2Size);
|
||||
else
|
||||
seek(0);
|
||||
|
||||
// Look for MPC metadata
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -139,6 +139,11 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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();
|
||||
|
||||
|
@ -149,8 +154,8 @@ namespace TagLib {
|
|||
* 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.
|
||||
*
|
||||
* \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
|
||||
* \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
|
||||
* on disk actually has an ID3v1 tag.
|
||||
*
|
||||
* \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 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.
|
||||
*
|
||||
* \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
|
||||
* \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
|
||||
* on disk actually has an APE tag.
|
||||
*
|
||||
* \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 &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
void scan();
|
||||
void read(bool readProperties);
|
||||
long findAPE();
|
||||
long findID3v1();
|
||||
long findID3v2();
|
||||
|
|
|
@ -36,9 +36,7 @@ using namespace TagLib;
|
|||
class MPC::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(long length, ReadStyle s) :
|
||||
streamLength(length),
|
||||
style(s),
|
||||
PropertiesPrivate() :
|
||||
version(0),
|
||||
length(0),
|
||||
bitrate(0),
|
||||
|
@ -51,8 +49,6 @@ public:
|
|||
albumGain(0),
|
||||
albumPeak(0) {}
|
||||
|
||||
long streamLength;
|
||||
ReadStyle style;
|
||||
int version;
|
||||
int length;
|
||||
int bitrate;
|
||||
|
@ -71,23 +67,25 @@ public:
|
|||
// 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);
|
||||
readSV7(data, streamLength);
|
||||
}
|
||||
|
||||
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);
|
||||
if(magic == "MPCK") {
|
||||
// Musepack version 8
|
||||
readSV8(file);
|
||||
readSV8(file, streamLength);
|
||||
}
|
||||
else {
|
||||
// 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
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int MPC::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int MPC::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
|
@ -155,78 +163,121 @@ int MPC::Properties::albumPeak() const
|
|||
// 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 long size = 0;
|
||||
|
||||
do {
|
||||
ByteVector b = file->readBlock(1);
|
||||
const ByteVector b = file->readBlock(1);
|
||||
if(b.isEmpty()) {
|
||||
eof = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tmp = b[0];
|
||||
size = (size << 7) | (tmp & 0x7F);
|
||||
sizelength++;
|
||||
sizeLength++;
|
||||
} while((tmp & 0x80));
|
||||
return size;
|
||||
}
|
||||
|
||||
unsigned long readSize(const ByteVector &data, TagLib::uint &sizelength)
|
||||
unsigned long readSize(const ByteVector &data, TagLib::uint &pos)
|
||||
{
|
||||
unsigned char tmp;
|
||||
unsigned long size = 0;
|
||||
unsigned long pos = 0;
|
||||
|
||||
do {
|
||||
tmp = data[pos++];
|
||||
size = (size << 7) | (tmp & 0x7F);
|
||||
sizelength++;
|
||||
} while((tmp & 0x80) && (pos < data.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;
|
||||
|
||||
while(!readSH && !readRG) {
|
||||
ByteVector packetType = file->readBlock(2);
|
||||
uint packetSizeLength = 0;
|
||||
unsigned long packetSize = readSize(file, packetSizeLength);
|
||||
unsigned long dataSize = packetSize - 2 - packetSizeLength;
|
||||
const ByteVector packetType = file->readBlock(2);
|
||||
|
||||
uint 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") {
|
||||
// Stream Header
|
||||
// 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;
|
||||
|
||||
TagLib::uint pos = 4;
|
||||
d->version = data[pos];
|
||||
pos += 1;
|
||||
d->sampleFrames = readSize(data.mid(pos), pos);
|
||||
ulong begSilence = readSize(data.mid(pos), pos);
|
||||
d->sampleFrames = readSize(data, 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;
|
||||
|
||||
d->sampleRate = sftable[flags[15] * 4 + flags[14] * 2 + flags[13]];
|
||||
d->channels = flags[7] * 8 + flags[6] * 4 + flags[5] * 2 + flags[4] + 1;
|
||||
d->sampleRate = sftable[(flags >> 13) & 0x07];
|
||||
d->channels = ((flags >> 4) & 0x0F) + 1;
|
||||
|
||||
if((d->sampleFrames - begSilence) != 0)
|
||||
d->bitrate = (int)(d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence));
|
||||
d->bitrate = d->bitrate / 1000;
|
||||
|
||||
d->length = (d->sampleFrames - begSilence) / d->sampleRate;
|
||||
const uint frameCount = d->sampleFrames - begSilence;
|
||||
if(frameCount > 0 && d->sampleRate > 0) {
|
||||
const double length = frameCount * 1000.0 / d->sampleRate;
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
else if (packetType == "RG") {
|
||||
// Replay Gain
|
||||
// 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;
|
||||
|
||||
int replayGainVersion = data[0];
|
||||
const int replayGainVersion = data[0];
|
||||
if(replayGainVersion == 1) {
|
||||
d->trackGain = data.toShort(1, 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+")) {
|
||||
d->version = data[3] & 15;
|
||||
|
@ -254,11 +305,11 @@ void MPC::Properties::readSV7(const ByteVector &data)
|
|||
|
||||
d->totalFrames = data.toUInt(4, false);
|
||||
|
||||
std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt(8, false)));
|
||||
d->sampleRate = sftable[flags[17] * 2 + flags[16]];
|
||||
d->channels = 2;
|
||||
const uint flags = data.toUInt(8, false);
|
||||
d->sampleRate = sftable[(flags >> 16) & 0x03];
|
||||
d->channels = 2;
|
||||
|
||||
uint gapless = data.toUInt(5, false);
|
||||
const uint gapless = data.toUInt(5, false);
|
||||
|
||||
d->trackGain = data.toShort(14, false);
|
||||
d->trackPeak = data.toShort(12, false);
|
||||
|
@ -293,12 +344,12 @@ void MPC::Properties::readSV7(const ByteVector &data)
|
|||
d->sampleFrames = d->totalFrames * 1152 - 576;
|
||||
}
|
||||
else {
|
||||
uint headerData = data.toUInt(0, false);
|
||||
const uint headerData = data.toUInt(0, false);
|
||||
|
||||
d->bitrate = (headerData >> 23) & 0x01ff;
|
||||
d->version = (headerData >> 11) & 0x03ff;
|
||||
d->bitrate = (headerData >> 23) & 0x01ff;
|
||||
d->version = (headerData >> 11) & 0x03ff;
|
||||
d->sampleRate = 44100;
|
||||
d->channels = 2;
|
||||
d->channels = 2;
|
||||
|
||||
if(d->version >= 5)
|
||||
d->totalFrames = data.toUInt(4, false);
|
||||
|
@ -308,9 +359,11 @@ void MPC::Properties::readSV7(const ByteVector &data)
|
|||
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)
|
||||
d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0;
|
||||
if(d->bitrate == 0)
|
||||
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,17 +66,53 @@ namespace TagLib {
|
|||
*/
|
||||
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;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns the version of the bitstream (SV4-SV8)
|
||||
*/
|
||||
int mpcVersion() const;
|
||||
|
||||
uint totalFrames() const;
|
||||
uint sampleFrames() const;
|
||||
|
||||
|
@ -110,8 +146,8 @@ namespace TagLib {
|
|||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void readSV7(const ByteVector &data);
|
||||
void readSV8(File *file);
|
||||
void readSV7(const ByteVector &data, long streamLength);
|
||||
void readSV8(File *file, long streamLength);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
|
|
|
@ -30,7 +30,7 @@ using namespace TagLib;
|
|||
namespace TagLib {
|
||||
namespace ID3v1 {
|
||||
|
||||
static const int genresSize = 148;
|
||||
static const int genresSize = 192;
|
||||
static const String genres[] = {
|
||||
"Blues",
|
||||
"Classic Rock",
|
||||
|
@ -179,7 +179,51 @@ namespace TagLib {
|
|||
"Thrash Metal",
|
||||
"Anime",
|
||||
"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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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 TAGLIB_EXPORT genre(int index);
|
||||
|
|
|
@ -85,7 +85,7 @@ namespace TagLib {
|
|||
//! 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
|
||||
* 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
|
||||
|
@ -154,14 +154,14 @@ namespace TagLib {
|
|||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
*/
|
||||
void setGenreNumber(TagLib::uint i);
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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
|
|
@ -36,7 +36,7 @@ namespace TagLib {
|
|||
//! 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.
|
||||
*/
|
||||
|
||||
|
@ -106,7 +106,7 @@ namespace TagLib {
|
|||
/*!
|
||||
* Sets the description of the comment to \a s.
|
||||
*
|
||||
* \see decription()
|
||||
* \see description()
|
||||
*/
|
||||
void setDescription(const String &s);
|
||||
|
||||
|
@ -149,7 +149,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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.
|
||||
*
|
||||
* \see description()
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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
|
|
@ -94,21 +94,21 @@ namespace TagLib {
|
|||
* \see pricePaid()
|
||||
*/
|
||||
void setPricePaid(const String &pricePaid);
|
||||
|
||||
|
||||
/*!
|
||||
* Returns the seller.
|
||||
*
|
||||
* \see setSeller()
|
||||
*/
|
||||
String seller() const;
|
||||
|
||||
|
||||
/*!
|
||||
* Set the seller.
|
||||
*
|
||||
* \see seller()
|
||||
*/
|
||||
void setSeller(const String &seller);
|
||||
|
||||
|
||||
/*!
|
||||
* Returns the text encoding that will be used in rendering this frame.
|
||||
* This defaults to the type that was either specified in the constructor
|
||||
|
@ -118,7 +118,7 @@ namespace TagLib {
|
|||
* \see render()
|
||||
*/
|
||||
String::Type textEncoding() const;
|
||||
|
||||
|
||||
/*!
|
||||
* Sets the text encoding to be used when rendering this frame to
|
||||
* \a encoding.
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace TagLib {
|
|||
//! 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.
|
||||
*/
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ namespace TagLib {
|
|||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
@ -149,7 +149,7 @@ namespace TagLib {
|
|||
/*!
|
||||
* Returns the relative volume adjustment "index". As indicated by 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
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* By default this sets the value for the master volume.
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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
|
|
@ -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));
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
// 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(d->textEncoding == String::Latin1)
|
||||
d->fieldList.append(Tag::latin1StringHandler()->parse(*it));
|
||||
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
|
||||
{
|
||||
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);
|
||||
if(f && f->description() == description)
|
||||
return f;
|
||||
|
|
|
@ -234,7 +234,7 @@ namespace TagLib {
|
|||
* 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
|
||||
{
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace TagLib {
|
|||
|
||||
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);
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ namespace TagLib {
|
|||
/*!
|
||||
* Sets the description of the unsynchronized lyrics frame to \a s.
|
||||
*
|
||||
* \see decription()
|
||||
* \see description()
|
||||
*/
|
||||
void setDescription(const String &s);
|
||||
|
||||
|
@ -149,7 +149,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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.
|
||||
*
|
||||
* \see description()
|
||||
|
|
|
@ -169,7 +169,7 @@ PropertyMap UserUrlLinkFrame::asProperties() const
|
|||
UserUrlLinkFrame *UserUrlLinkFrame::find(ID3v2::Tag *tag, const String &description) // static
|
||||
{
|
||||
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);
|
||||
if(f && f->description() == description)
|
||||
return f;
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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
|
||||
* headers specified there. If any of the terms used in this documentation
|
||||
* are unclear please check the specification in the linked section.
|
||||
|
|
|
@ -254,12 +254,49 @@ ByteVector Frame::fieldData(const ByteVector &frameData) const
|
|||
if(d->header->compression() &&
|
||||
!d->header->encryption())
|
||||
{
|
||||
ByteVector data(frameDataLength);
|
||||
uLongf uLongTmp = frameDataLength;
|
||||
::uncompress((Bytef *) data.data(),
|
||||
(uLongf *) &uLongTmp,
|
||||
(Bytef *) frameData.data() + frameDataOffset,
|
||||
size());
|
||||
if(frameData.size() <= frameDataOffset) {
|
||||
debug("Compressed frame doesn't have enough data to decode");
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -165,7 +165,7 @@ namespace TagLib {
|
|||
* All other processing of \a data should be handled in a subclass.
|
||||
*
|
||||
* \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);
|
||||
|
||||
|
@ -218,9 +218,9 @@ namespace TagLib {
|
|||
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
|
||||
* 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.
|
||||
*/
|
||||
String readStringField(const ByteVector &data, String::Type encoding,
|
||||
|
@ -460,7 +460,7 @@ namespace TagLib {
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -45,6 +45,10 @@
|
|||
#include "frames/popularimeterframe.h"
|
||||
#include "frames/privateframe.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 ID3v2;
|
||||
|
@ -241,6 +245,20 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
|
|||
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)
|
||||
|
||||
if(frameID == "POPM")
|
||||
|
@ -250,18 +268,50 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader)
|
|||
|
||||
if(frameID == "PRIV")
|
||||
return new PrivateFrame(data, header);
|
||||
|
||||
|
||||
// Ownership (frames 4.22)
|
||||
|
||||
|
||||
if(frameID == "OWNE") {
|
||||
OwnershipFrame *f = new OwnershipFrame(data, header);
|
||||
d->setTextEncoding(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return d->defaultEncoding;
|
||||
|
@ -430,7 +480,7 @@ void FrameFactory::updateGenre(TextIdentificationFrame *frame) const
|
|||
StringList fields = frame->fieldList();
|
||||
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;
|
||||
int end = s.find(")");
|
||||
|
||||
|
|
|
@ -93,11 +93,19 @@ namespace TagLib {
|
|||
// BIC: make virtual
|
||||
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()
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* \see setDefaultTextEncoding()
|
||||
|
|
|
@ -215,7 +215,7 @@ void Header::parse(const ByteVector &data)
|
|||
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) {
|
||||
d->tagSize = 0;
|
||||
debug("TagLib::ID3v2::Header::parse() - One of the size bytes in the id3v2 header was greater than the allowed 128.");
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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
|
||||
* specified there. If any of the terms used in this documentation are
|
||||
* unclear please check the specification in the linked section.
|
||||
|
|
|
@ -24,10 +24,10 @@
|
|||
***************************************************************************/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <tfile.h>
|
||||
#include "tfile.h"
|
||||
|
||||
#include "id3v2tag.h"
|
||||
#include "id3v2header.h"
|
||||
|
@ -37,7 +37,7 @@
|
|||
#include "tbytevector.h"
|
||||
#include "id3v1genres.h"
|
||||
#include "tpropertymap.h"
|
||||
#include <tdebug.h>
|
||||
#include "tdebug.h"
|
||||
|
||||
#include "frames/textidentificationframe.h"
|
||||
#include "frames/commentsframe.h"
|
||||
|
@ -81,6 +81,11 @@ public:
|
|||
static const Latin1StringHandler defaultStringHandler;
|
||||
const ID3v2::Latin1StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStringHandler;
|
||||
|
||||
namespace
|
||||
{
|
||||
const TagLib::uint DefaultPaddingSize = 1024;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// StringHandler implementation
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -356,7 +361,7 @@ void ID3v2::Tag::removeFrame(Frame *frame, bool del)
|
|||
void ID3v2::Tag::removeFrames(const ByteVector &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);
|
||||
}
|
||||
|
||||
|
@ -469,7 +474,7 @@ void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const
|
|||
ID3v2::TextIdentificationFrame *frameTDRC = 0;
|
||||
ID3v2::TextIdentificationFrame *frameTIPL = 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;
|
||||
ByteVector frameID = frame->header()->frameID();
|
||||
for(int i = 0; unsupportedFrames[i]; i++) {
|
||||
|
@ -583,28 +588,41 @@ ByteVector ID3v2::Tag::render(int version) const
|
|||
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);
|
||||
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");
|
||||
continue;
|
||||
}
|
||||
if(!(*it)->header()->tagAlterPreservation())
|
||||
tagData.append((*it)->render());
|
||||
if(!(*it)->header()->tagAlterPreservation()) {
|
||||
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.
|
||||
|
||||
uint paddingSize = 0;
|
||||
uint originalSize = d->header.tagSize();
|
||||
uint paddingSize = DefaultPaddingSize;
|
||||
|
||||
if(tagData.size() < originalSize)
|
||||
paddingSize = originalSize - tagData.size();
|
||||
else
|
||||
paddingSize = 1024;
|
||||
if(d->file && tagData.size() < d->header.tagSize()) {
|
||||
paddingSize = d->header.tagSize() - tagData.size();
|
||||
|
||||
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.
|
||||
d->header.setMajorVersion(version);
|
||||
|
@ -693,7 +711,7 @@ void ID3v2::Tag::parse(const ByteVector &origData)
|
|||
}
|
||||
|
||||
d->paddingSize = frameDataLength - frameDataPosition;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
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());
|
||||
addFrame(frame);
|
||||
}
|
||||
|
||||
d->factory->rebuildAggregateFrames(this);
|
||||
}
|
||||
|
||||
void ID3v2::Tag::setTextFrame(const ByteVector &id, const String &value)
|
||||
|
|
|
@ -60,13 +60,13 @@ namespace TagLib {
|
|||
//! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags.
|
||||
|
||||
/*!
|
||||
* ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only
|
||||
* supports genuine ISO-8859-1 by default. However, in practice, non
|
||||
* ISO-8859-1 encodings are often used instead of ISO-8859-1, such as
|
||||
* ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only
|
||||
* supports genuine ISO-8859-1 by default. However, in practice, non
|
||||
* ISO-8859-1 encodings are often used instead of ISO-8859-1, such as
|
||||
* Windows-1252 for western languages, Shift_JIS for Japanese and so on.
|
||||
*
|
||||
* Here is an option to read such tags by subclassing this class,
|
||||
* reimplementing parse() and setting your reimplementation as the default
|
||||
* reimplementing parse() and setting your reimplementation as the default
|
||||
* with ID3v2::Tag::setStringHandler().
|
||||
*
|
||||
* \note Writing non-ISO-8859-1 tags is not implemented intentionally.
|
||||
|
@ -98,7 +98,7 @@ namespace TagLib {
|
|||
* split into data components.
|
||||
*
|
||||
* 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
|
||||
* they return attempt to reflect this.
|
||||
*
|
||||
|
@ -115,7 +115,7 @@ namespace TagLib {
|
|||
* class.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* \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
|
||||
* 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
|
||||
* 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
|
||||
* convince it to generate invalid ID3v2 tags. The APIs for ID3v2 assume a
|
||||
* 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
|
||||
* all situations. You would want to specify your own FrameFactory
|
||||
* 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
|
||||
*/
|
||||
|
@ -353,9 +353,9 @@ namespace TagLib {
|
|||
*/
|
||||
// BIC: combine with the above method
|
||||
ByteVector render(int version) const;
|
||||
|
||||
|
||||
/*!
|
||||
* Gets the current string handler that decides how the "Latin-1" data
|
||||
* Gets the current string handler that decides how the "Latin-1" data
|
||||
* will be converted to and from binary data.
|
||||
*
|
||||
* \see Latin1StringHandler
|
||||
|
@ -369,7 +369,7 @@ namespace TagLib {
|
|||
* released and default ISO-8859-1 handler is restored.
|
||||
*
|
||||
* \note The caller is responsible for deleting the previous handler
|
||||
* as needed after it is released.
|
||||
* as needed after it is released.
|
||||
*
|
||||
* \see Latin1StringHandler
|
||||
*/
|
||||
|
|
|
@ -31,14 +31,31 @@
|
|||
#include <apetag.h>
|
||||
#include <tdebug.h>
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include "mpegfile.h"
|
||||
#include "mpegheader.h"
|
||||
#include "tpropertymap.h"
|
||||
|
||||
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
|
||||
{
|
||||
enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 };
|
||||
|
@ -60,7 +77,6 @@ public:
|
|||
hasAPE(false),
|
||||
properties(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~FilePrivate()
|
||||
|
@ -95,33 +111,30 @@ public:
|
|||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MPEG::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
|
||||
MPEG::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
|
||||
bool readProperties, Properties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(file)
|
||||
bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(file),
|
||||
d(new FilePrivate(frameFactory))
|
||||
{
|
||||
d = new FilePrivate(frameFactory);
|
||||
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
|
||||
bool readProperties, Properties::ReadStyle propertiesStyle) :
|
||||
TagLib::File(stream)
|
||||
bool readProperties, Properties::ReadStyle) :
|
||||
TagLib::File(stream),
|
||||
d(new FilePrivate(frameFactory))
|
||||
{
|
||||
d = new FilePrivate(frameFactory);
|
||||
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
MPEG::File::~File()
|
||||
|
@ -392,11 +405,11 @@ long MPEG::File::nextFrameOffset(long position)
|
|||
return position - 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
foundLastSyncPattern = uchar(buffer[buffer.size() - 1]) == 0xff;
|
||||
foundLastSyncPattern = firstSyncByte(buffer[buffer.size() - 1]);
|
||||
position += buffer.size();
|
||||
}
|
||||
}
|
||||
|
@ -416,11 +429,11 @@ long MPEG::File::previousFrameOffset(long position)
|
|||
if(buffer.size() <= 0)
|
||||
break;
|
||||
|
||||
if(foundFirstSyncPattern && uchar(buffer[buffer.size() - 1]) == 0xff)
|
||||
if(foundFirstSyncPattern && firstSyncByte(buffer[buffer.size() - 1]))
|
||||
return position + buffer.size() - 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -433,7 +446,7 @@ long MPEG::File::firstFrameOffset()
|
|||
{
|
||||
long position = 0;
|
||||
|
||||
if(ID3v2Tag()) {
|
||||
if(hasID3v2Tag()) {
|
||||
position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize();
|
||||
|
||||
// Skip duplicate ID3v2 tags.
|
||||
|
@ -456,7 +469,7 @@ long MPEG::File::firstFrameOffset()
|
|||
|
||||
long MPEG::File::lastFrameOffset()
|
||||
{
|
||||
return previousFrameOffset(ID3v1Tag() ? d->ID3v1Location - 1 : length());
|
||||
return previousFrameOffset(hasID3v1Tag() ? d->ID3v1Location - 1 : length());
|
||||
}
|
||||
|
||||
bool MPEG::File::hasID3v1Tag() const
|
||||
|
@ -478,7 +491,7 @@ bool MPEG::File::hasAPETag() const
|
|||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
void MPEG::File::read(bool readProperties)
|
||||
{
|
||||
// Look for an ID3v2 tag
|
||||
|
||||
|
@ -517,7 +530,7 @@ void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle
|
|||
}
|
||||
|
||||
if(readProperties)
|
||||
d->properties = new Properties(this, propertiesStyle);
|
||||
d->properties = new Properties(this);
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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.
|
||||
|
||||
if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) {
|
||||
|
@ -668,11 +681,3 @@ void MPEG::File::findAPE()
|
|||
d->APELocation = -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);
|
||||
}
|
||||
|
|
|
@ -173,7 +173,10 @@ namespace TagLib {
|
|||
* This is the same as calling save(AllTags);
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
|
@ -187,6 +190,9 @@ namespace TagLib {
|
|||
* 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
|
||||
* 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);
|
||||
|
||||
|
@ -198,6 +204,9 @@ namespace TagLib {
|
|||
* 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
|
||||
* 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
|
||||
bool save(int tags, bool stripOthers);
|
||||
|
@ -213,6 +222,9 @@ namespace TagLib {
|
|||
*
|
||||
* The \a id3v2Version parameter specifies the version of the saved
|
||||
* 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
|
||||
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 --
|
||||
* 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
|
||||
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
|
||||
* as their memory will be freed.
|
||||
*
|
||||
* \note This will update the file immediately.
|
||||
*/
|
||||
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
|
||||
* pointers to them will be invalidated.
|
||||
*
|
||||
* \note This will update the file immediately.
|
||||
*/
|
||||
// BIC: merge with the method above
|
||||
bool strip(int tags, bool freeMemory);
|
||||
|
@ -319,6 +338,7 @@ namespace TagLib {
|
|||
* Set the ID3v2::FrameFactory to something other than the default.
|
||||
*
|
||||
* \see ID3v2FrameFactory
|
||||
* \deprecated This value should be passed in via the constructor
|
||||
*/
|
||||
void setID3v2FrameFactory(const ID3v2::FrameFactory *factory);
|
||||
|
||||
|
@ -369,18 +389,11 @@ namespace TagLib {
|
|||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
void read(bool readProperties);
|
||||
long findID3v2(long offset);
|
||||
long findID3v1();
|
||||
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;
|
||||
FilePrivate *d;
|
||||
};
|
||||
|
|
|
@ -213,8 +213,8 @@ void MPEG::Header::parse(const ByteVector &data)
|
|||
}
|
||||
};
|
||||
|
||||
const int versionIndex = d->version == Version1 ? 0 : 1;
|
||||
const int layerIndex = d->layer > 0 ? d->layer - 1 : 0;
|
||||
const int versionIndex = (d->version == Version1) ? 0 : 1;
|
||||
const int layerIndex = (d->layer > 0) ? d->layer - 1 : 0;
|
||||
|
||||
// The bitrate index is encoded as the first 4 bits of the 3rd byte,
|
||||
// i.e. 1111xxxx
|
||||
|
@ -253,13 +253,6 @@ void MPEG::Header::parse(const ByteVector &data)
|
|||
d->isCopyrighted = flags[3];
|
||||
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
|
||||
|
||||
static const int samplesPerFrame[3][2] = {
|
||||
|
@ -271,6 +264,15 @@ void MPEG::Header::parse(const ByteVector &data)
|
|||
|
||||
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.
|
||||
|
||||
d->isValid = true;
|
||||
|
|
|
@ -140,7 +140,7 @@ namespace TagLib {
|
|||
bool isOriginal() const;
|
||||
|
||||
/*!
|
||||
* Returns the frame length.
|
||||
* Returns the frame length in bytes.
|
||||
*/
|
||||
int frameLength() const;
|
||||
|
||||
|
|
|
@ -29,16 +29,16 @@
|
|||
#include "mpegproperties.h"
|
||||
#include "mpegfile.h"
|
||||
#include "xingheader.h"
|
||||
#include "apetag.h"
|
||||
#include "apefooter.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
class MPEG::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(File *f, ReadStyle s) :
|
||||
file(f),
|
||||
PropertiesPrivate() :
|
||||
xingHeader(0),
|
||||
style(s),
|
||||
length(0),
|
||||
bitrate(0),
|
||||
sampleRate(0),
|
||||
|
@ -55,9 +55,7 @@ public:
|
|||
delete xingHeader;
|
||||
}
|
||||
|
||||
File *file;
|
||||
XingHeader *xingHeader;
|
||||
ReadStyle style;
|
||||
int length;
|
||||
int bitrate;
|
||||
int sampleRate;
|
||||
|
@ -74,12 +72,11 @@ public:
|
|||
// 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);
|
||||
|
||||
if(file && file->isOpen())
|
||||
read();
|
||||
read(file);
|
||||
}
|
||||
|
||||
MPEG::Properties::~Properties()
|
||||
|
@ -88,6 +85,16 @@ MPEG::Properties::~Properties()
|
|||
}
|
||||
|
||||
int MPEG::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int MPEG::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int MPEG::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
|
@ -146,109 +153,72 @@ bool MPEG::Properties::isOriginal() const
|
|||
// 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
|
||||
// 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();
|
||||
// Only the first frame is required if we have a VBR header.
|
||||
|
||||
const long first = file->firstFrameOffset();
|
||||
if(first < 0) {
|
||||
debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!lastHeader.isValid()) {
|
||||
file->seek(first);
|
||||
const Header firstHeader(file->readBlock(4));
|
||||
|
||||
long pos = last;
|
||||
|
||||
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.");
|
||||
if(!firstHeader.isValid()) {
|
||||
debug("MPEG::Properties::read() -- The first page header is invalid.");
|
||||
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.
|
||||
|
||||
int xingHeaderOffset = MPEG::XingHeader::xingHeaderOffset(firstHeader.version(),
|
||||
firstHeader.channelMode());
|
||||
|
||||
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.
|
||||
|
||||
file->seek(first + 4);
|
||||
d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength() - 4));
|
||||
if(!d->xingHeader->isValid()) {
|
||||
delete d->xingHeader;
|
||||
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
|
||||
// Xing header.
|
||||
|
||||
if(firstHeader.frameLength() > 0 && firstHeader.bitrate() > 0) {
|
||||
int frames = (last - first) / firstHeader.frameLength() + 1;
|
||||
d->bitrate = firstHeader.bitrate();
|
||||
|
||||
d->length = int(float(firstHeader.frameLength() * frames) /
|
||||
float(firstHeader.bitrate() * 125) + 0.5);
|
||||
d->bitrate = firstHeader.bitrate();
|
||||
}
|
||||
long streamLength = file->length() - first;
|
||||
|
||||
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->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2;
|
||||
d->version = firstHeader.version();
|
||||
d->layer = firstHeader.layer();
|
||||
d->sampleRate = firstHeader.sampleRate();
|
||||
d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2;
|
||||
d->version = firstHeader.version();
|
||||
d->layer = firstHeader.layer();
|
||||
d->protectionEnabled = firstHeader.protectionEnabled();
|
||||
d->channelMode = firstHeader.channelMode();
|
||||
d->isCopyrighted = firstHeader.isCopyrighted();
|
||||
d->isOriginal = firstHeader.isOriginal();
|
||||
d->channelMode = firstHeader.channelMode();
|
||||
d->isCopyrighted = firstHeader.isCopyrighted();
|
||||
d->isOriginal = firstHeader.isOriginal();
|
||||
}
|
||||
|
|
|
@ -59,18 +59,52 @@ namespace TagLib {
|
|||
*/
|
||||
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;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the XingHeader if one exists or null if no
|
||||
* XingHeader was found.
|
||||
* Returns a pointer to the Xing/VBRI header if one exists or null if no
|
||||
* Xing/VBRI header was found.
|
||||
*/
|
||||
|
||||
const XingHeader *xingHeader() const;
|
||||
|
||||
/*!
|
||||
|
@ -107,7 +141,7 @@ namespace TagLib {
|
|||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void read();
|
||||
void read(File *file);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <tdebug.h>
|
||||
|
||||
#include "xingheader.h"
|
||||
#include "mpegfile.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
|
@ -37,17 +38,21 @@ public:
|
|||
XingHeaderPrivate() :
|
||||
frames(0),
|
||||
size(0),
|
||||
valid(false)
|
||||
{}
|
||||
type(MPEG::XingHeader::Invalid) {}
|
||||
|
||||
uint frames;
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -58,7 +63,7 @@ MPEG::XingHeader::~XingHeader()
|
|||
|
||||
bool MPEG::XingHeader::isValid() const
|
||||
{
|
||||
return d->valid;
|
||||
return (d->type != Invalid && d->frames > 0 && d->size > 0);
|
||||
}
|
||||
|
||||
TagLib::uint MPEG::XingHeader::totalFrames() const
|
||||
|
@ -71,45 +76,65 @@ TagLib::uint MPEG::XingHeader::totalSize() const
|
|||
return d->size;
|
||||
}
|
||||
|
||||
int MPEG::XingHeader::xingHeaderOffset(TagLib::MPEG::Header::Version v,
|
||||
TagLib::MPEG::Header::ChannelMode c)
|
||||
MPEG::XingHeader::HeaderType MPEG::XingHeader::type() const
|
||||
{
|
||||
if(v == MPEG::Header::Version1) {
|
||||
if(c == MPEG::Header::SingleChannel)
|
||||
return 0x15;
|
||||
else
|
||||
return 0x24;
|
||||
}
|
||||
else {
|
||||
if(c == MPEG::Header::SingleChannel)
|
||||
return 0x0D;
|
||||
else
|
||||
return 0x15;
|
||||
}
|
||||
return d->type;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Check to see if a valid Xing header is available.
|
||||
// Look for a Xing header.
|
||||
|
||||
if(!data.startsWith("Xing") && !data.startsWith("Info"))
|
||||
return;
|
||||
long offset = data.find("Xing");
|
||||
if(offset < 0)
|
||||
offset = data.find("Info");
|
||||
|
||||
// If the XingHeader doesn't contain the number of frames and the total stream
|
||||
// info it's invalid.
|
||||
if(offset >= 0) {
|
||||
|
||||
if(!(data[7] & 0x01)) {
|
||||
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total number of frames.");
|
||||
return;
|
||||
// Xing header found.
|
||||
|
||||
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)) {
|
||||
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total stream size.");
|
||||
return;
|
||||
// Xing header not found. Then look for a VBRI header.
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -35,24 +35,47 @@ namespace TagLib {
|
|||
|
||||
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
|
||||
* headers are often added to VBR (variable bit rate) MP3 streams to make it
|
||||
* easy to compute the length and quality of a VBR stream. Our implementation
|
||||
* is only concerned with the total size of the stream (so that we can
|
||||
* calculate the total playing time and the average bitrate). It uses
|
||||
* <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt">this text</a>
|
||||
* and the XMMS sources as references.
|
||||
* This is a minimalistic implementation of the Xing/VBRI VBR headers.
|
||||
* Xing/VBRI headers are often added to VBR (variable bit rate) MP3 streams
|
||||
* to make it easy to compute the length and quality of a VBR stream. Our
|
||||
* implementation is only concerned with the total size of the stream (so
|
||||
* that we can calculate the total playing time and the average bitrate).
|
||||
* It uses <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt">
|
||||
* this text</a> and the XMMS sources as references.
|
||||
*/
|
||||
|
||||
class TAGLIB_EXPORT XingHeader
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Parses a Xing header based on \a data. The data must be at least 16
|
||||
* bytes long (anything longer than this is discarded).
|
||||
* The type of the VBR header.
|
||||
*/
|
||||
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);
|
||||
|
||||
|
@ -63,7 +86,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* Returns true if the data was parsed properly and if there is a valid
|
||||
* Xing header present.
|
||||
* Xing/VBRI header present.
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
|
@ -77,11 +100,17 @@ namespace TagLib {
|
|||
*/
|
||||
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
|
||||
* version and channels of the frame
|
||||
*
|
||||
* \deprecated Always returns 0.
|
||||
*/
|
||||
// BIC: rename to offset()
|
||||
static int xingHeaderOffset(TagLib::MPEG::Header::Version v,
|
||||
TagLib::MPEG::Header::ChannelMode c);
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ PropertyMap Ogg::FLAC::File::properties() const
|
|||
PropertyMap Ogg::FLAC::File::setProperties(const PropertyMap &properties)
|
||||
{
|
||||
return d->comment->setProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
Properties *Ogg::FLAC::File::audioProperties() const
|
||||
{
|
||||
|
@ -211,29 +211,30 @@ void Ogg::FLAC::File::scan()
|
|||
long overhead = 0;
|
||||
|
||||
ByteVector metadataHeader = packet(ipacket);
|
||||
if(metadataHeader.isNull())
|
||||
if(metadataHeader.isEmpty())
|
||||
return;
|
||||
|
||||
ByteVector header;
|
||||
|
||||
if (!metadataHeader.startsWith("fLaC")) {
|
||||
if(!metadataHeader.startsWith("fLaC")) {
|
||||
// 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);
|
||||
}
|
||||
else {
|
||||
// FLAC 1.1.0 & 1.1.1
|
||||
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):
|
||||
// <1> Last-metadata-block flag
|
||||
// <7> BLOCK_TYPE
|
||||
|
@ -262,11 +263,12 @@ void Ogg::FLAC::File::scan()
|
|||
|
||||
while(!lastBlock) {
|
||||
metadataHeader = packet(++ipacket);
|
||||
|
||||
if(metadataHeader.isNull())
|
||||
return;
|
||||
|
||||
header = metadataHeader.mid(0, 4);
|
||||
if(header.size() != 4) {
|
||||
debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header");
|
||||
return;
|
||||
}
|
||||
|
||||
blockType = header[0] & 0x7f;
|
||||
lastBlock = (header[0] & 0x80) != 0;
|
||||
length = header.toUInt(1, 3, true);
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace TagLib {
|
|||
|
||||
/*!
|
||||
* 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,
|
||||
* while the audio-properties are the same.
|
||||
|
@ -64,7 +64,7 @@ namespace TagLib {
|
|||
{
|
||||
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.
|
||||
*
|
||||
* \note In the current implementation, \a propertiesStyle is ignored.
|
||||
|
@ -73,7 +73,7 @@ namespace TagLib {
|
|||
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.
|
||||
*
|
||||
* \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.
|
||||
*
|
||||
* \note This always returns a valid pointer regardless of whether or not
|
||||
* the file on disk has a XiphComment. Use hasXiphComment() to check if
|
||||
* \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 actually has a XiphComment.
|
||||
*
|
||||
*
|
||||
* \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
|
||||
* destroyed.
|
||||
|
@ -111,22 +111,25 @@ namespace TagLib {
|
|||
virtual Properties *audioProperties() const;
|
||||
|
||||
|
||||
/*!
|
||||
/*!
|
||||
* Implements the unified property interface -- export function.
|
||||
* This forwards directly to XiphComment::properties().
|
||||
*/
|
||||
PropertyMap properties() const;
|
||||
|
||||
/*!
|
||||
/*!
|
||||
* Implements the unified tag dictionary interface -- import function.
|
||||
* 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.
|
||||
* 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();
|
||||
|
||||
|
|
|
@ -354,7 +354,7 @@ void Ogg::File::writePageGroup(const List<int> &thePageGroup)
|
|||
|
||||
// create a gap for the new pages
|
||||
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++) {
|
||||
if(pageIter != d->pages.end()) {
|
||||
++pageIter;
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace TagLib {
|
|||
/*!
|
||||
* 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
|
||||
* the details of packets spanning multiple pages and pages that contiain
|
||||
* the details of packets spanning multiple pages and pages that contain
|
||||
* multiple 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.
|
||||
* 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.
|
||||
* \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
|
||||
|
|
|
@ -27,8 +27,6 @@
|
|||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include <tstring.h>
|
||||
#include <tdebug.h>
|
||||
#include <tpropertymap.h>
|
||||
|
@ -59,20 +57,20 @@ public:
|
|||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Opus::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) :
|
||||
Opus::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
|
||||
Ogg::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
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),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
Opus::File::~File()
|
||||
|
@ -114,7 +112,7 @@ bool Opus::File::save()
|
|||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Opus::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
void Opus::File::read(bool readProperties)
|
||||
{
|
||||
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));
|
||||
|
||||
if(readProperties)
|
||||
d->properties = new Properties(this, propertiesStyle);
|
||||
d->properties = new Properties(this);
|
||||
}
|
||||
|
|
|
@ -106,13 +106,21 @@ namespace TagLib {
|
|||
*/
|
||||
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();
|
||||
|
||||
private:
|
||||
File(const File &);
|
||||
File &operator=(const File &);
|
||||
|
||||
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
|
||||
void read(bool readProperties);
|
||||
|
||||
class FilePrivate;
|
||||
FilePrivate *d;
|
||||
|
|
|
@ -41,17 +41,15 @@ using namespace TagLib::Ogg;
|
|||
class Opus::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(File *f, ReadStyle s) :
|
||||
file(f),
|
||||
style(s),
|
||||
PropertiesPrivate() :
|
||||
length(0),
|
||||
bitrate(0),
|
||||
inputSampleRate(0),
|
||||
channels(0),
|
||||
opusVersion(0) {}
|
||||
|
||||
File *file;
|
||||
ReadStyle style;
|
||||
int length;
|
||||
int bitrate;
|
||||
int inputSampleRate;
|
||||
int channels;
|
||||
int opusVersion;
|
||||
|
@ -61,10 +59,11 @@ public:
|
|||
// 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();
|
||||
read(file);
|
||||
}
|
||||
|
||||
Opus::Properties::~Properties()
|
||||
|
@ -73,13 +72,23 @@ Opus::Properties::~Properties()
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int Opus::Properties::bitrate() const
|
||||
{
|
||||
return 0;
|
||||
return d->bitrate;
|
||||
}
|
||||
|
||||
int Opus::Properties::sampleRate() const
|
||||
|
@ -109,13 +118,13 @@ int Opus::Properties::opusVersion() const
|
|||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Opus::Properties::read()
|
||||
void Opus::Properties::read(File *file)
|
||||
{
|
||||
// Get the identification header from the Ogg implementation.
|
||||
|
||||
// http://tools.ietf.org/html/draft-terriberry-oggopus-01#section-5.1
|
||||
|
||||
ByteVector data = d->file->packet(0);
|
||||
const ByteVector data = file->packet(0);
|
||||
|
||||
// *Magic Signature*
|
||||
uint pos = 8;
|
||||
|
@ -142,15 +151,22 @@ void Opus::Properties::read()
|
|||
// *Channel Mapping Family* (8 bits, unsigned)
|
||||
pos += 1;
|
||||
|
||||
const Ogg::PageHeader *first = d->file->firstPageHeader();
|
||||
const Ogg::PageHeader *last = d->file->lastPageHeader();
|
||||
const Ogg::PageHeader *first = file->firstPageHeader();
|
||||
const Ogg::PageHeader *last = file->lastPageHeader();
|
||||
|
||||
if(first && last) {
|
||||
long long start = first->absoluteGranularPosition();
|
||||
long long end = last->absoluteGranularPosition();
|
||||
const long long start = first->absoluteGranularPosition();
|
||||
const long long end = last->absoluteGranularPosition();
|
||||
|
||||
if(start >= 0 && end >= 0)
|
||||
d->length = (int) ((end - start - preSkip) / 48000);
|
||||
if(start >= 0 && end >= 0) {
|
||||
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 {
|
||||
debug("Opus::Properties::read() -- The PCM values for the start or "
|
||||
"end of this file was incorrect.");
|
||||
|
|
|
@ -61,11 +61,49 @@ namespace TagLib {
|
|||
*/
|
||||
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;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
|
@ -76,7 +114,7 @@ namespace TagLib {
|
|||
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;
|
||||
|
||||
|
@ -84,7 +122,7 @@ namespace TagLib {
|
|||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void read();
|
||||
void read(File *file);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
|
|
|
@ -27,8 +27,6 @@
|
|||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include <tstring.h>
|
||||
#include <tdebug.h>
|
||||
#include <tpropertymap.h>
|
||||
|
@ -59,20 +57,20 @@ public:
|
|||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Speex::File::File(FileName file, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(file)
|
||||
Speex::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
|
||||
Ogg::File(file),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
Speex::File::File(IOStream *stream, bool readProperties,
|
||||
Properties::ReadStyle propertiesStyle) : Ogg::File(stream)
|
||||
Speex::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) :
|
||||
Ogg::File(stream),
|
||||
d(new FilePrivate())
|
||||
{
|
||||
d = new FilePrivate;
|
||||
if(isOpen())
|
||||
read(readProperties, propertiesStyle);
|
||||
read(readProperties);
|
||||
}
|
||||
|
||||
Speex::File::~File()
|
||||
|
@ -114,7 +112,7 @@ bool Speex::File::save()
|
|||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Speex::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
|
||||
void Speex::File::read(bool readProperties)
|
||||
{
|
||||
ByteVector speexHeaderData = packet(0);
|
||||
|
||||
|
@ -128,5 +126,5 @@ void Speex::File::read(bool readProperties, Properties::ReadStyle propertiesStyl
|
|||
d->comment = new Ogg::XiphComment(commentHeaderData);
|
||||
|
||||
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
Loading…
Reference in New Issue