Clementine-audio-player-Mac.../3rdparty/taglib/mpeg/mpegfile.cpp

576 lines
14 KiB
C++
Raw Normal View History

2012-10-28 02:12:18 +02:00
/***************************************************************************
copyright : (C) 2002 - 2008 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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 <tagunion.h>
2016-07-19 16:58:52 +02:00
#include <tagutils.h>
2012-10-28 02:12:18 +02:00
#include <id3v2tag.h>
#include <id3v2header.h>
#include <id3v1tag.h>
#include <apefooter.h>
#include <apetag.h>
#include <tdebug.h>
#include "mpegfile.h"
#include "mpegheader.h"
2016-07-19 16:58:52 +02:00
#include "mpegutils.h"
2012-10-28 02:12:18 +02:00
#include "tpropertymap.h"
using namespace TagLib;
namespace
{
enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 };
}
class MPEG::File::FilePrivate
{
public:
2016-07-19 16:58:52 +02:00
FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) :
2012-10-28 02:12:18 +02:00
ID3v2FrameFactory(frameFactory),
ID3v2Location(-1),
ID3v2OriginalSize(0),
APELocation(-1),
APEOriginalSize(0),
ID3v1Location(-1),
2016-07-19 16:58:52 +02:00
properties(0) {}
2012-10-28 02:12:18 +02:00
~FilePrivate()
{
delete properties;
}
const ID3v2::FrameFactory *ID3v2FrameFactory;
long ID3v2Location;
2016-07-19 16:58:52 +02:00
long ID3v2OriginalSize;
2012-10-28 02:12:18 +02:00
long APELocation;
2016-07-19 16:58:52 +02:00
long APEOriginalSize;
2012-10-28 02:12:18 +02:00
long ID3v1Location;
TagUnion tag;
Properties *properties;
};
2018-06-06 22:47:08 +02:00
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
namespace
{
// Dummy file class to make a stream work with MPEG::Header.
class AdapterFile : public TagLib::File
{
public:
AdapterFile(IOStream *stream) : File(stream) {}
Tag *tag() const { return 0; }
AudioProperties *audioProperties() const { return 0; }
bool save() { return false; }
};
}
bool MPEG::File::isSupported(IOStream *stream)
{
if(!stream || !stream->isOpen())
return false;
// An MPEG file has MPEG frame headers. An ID3v2 tag may precede.
// MPEG frame headers are really confusing with irrelevant binary data.
// So we check if a frame header is really valid.
long headerOffset;
const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true, &headerOffset);
if(buffer.isEmpty())
2019-11-14 00:33:13 +01:00
return false;
2018-06-06 22:47:08 +02:00
const long originalPosition = stream->tell();
AdapterFile file(stream);
for(unsigned int i = 0; i < buffer.size() - 1; ++i) {
if(isFrameSync(buffer, i)) {
const Header header(&file, headerOffset + i, true);
if(header.isValid()) {
stream->seek(originalPosition);
return true;
}
}
}
stream->seek(originalPosition);
return false;
}
2012-10-28 02:12:18 +02:00
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
2015-11-24 19:36:24 +01:00
MPEG::File::File(FileName file, bool readProperties, Properties::ReadStyle) :
TagLib::File(file),
d(new FilePrivate())
2012-10-28 02:12:18 +02:00
{
if(isOpen())
2015-11-24 19:36:24 +01:00
read(readProperties);
2012-10-28 02:12:18 +02:00
}
MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory,
2015-11-24 19:36:24 +01:00
bool readProperties, Properties::ReadStyle) :
TagLib::File(file),
d(new FilePrivate(frameFactory))
2012-10-28 02:12:18 +02:00
{
if(isOpen())
2015-11-24 19:36:24 +01:00
read(readProperties);
2012-10-28 02:12:18 +02:00
}
MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory,
2015-11-24 19:36:24 +01:00
bool readProperties, Properties::ReadStyle) :
TagLib::File(stream),
d(new FilePrivate(frameFactory))
2012-10-28 02:12:18 +02:00
{
if(isOpen())
2015-11-24 19:36:24 +01:00
read(readProperties);
2012-10-28 02:12:18 +02:00
}
MPEG::File::~File()
{
delete d;
}
TagLib::Tag *MPEG::File::tag() const
{
return &d->tag;
}
PropertyMap MPEG::File::properties() const
{
2016-07-19 16:58:52 +02:00
return d->tag.properties();
2012-10-28 02:12:18 +02:00
}
void MPEG::File::removeUnsupportedProperties(const StringList &properties)
{
2016-07-19 16:58:52 +02:00
d->tag.removeUnsupportedProperties(properties);
2012-10-28 02:12:18 +02:00
}
2012-10-28 02:12:18 +02:00
PropertyMap MPEG::File::setProperties(const PropertyMap &properties)
{
2016-07-19 16:58:52 +02:00
// update ID3v1 tag if it exists, but ignore the return value
if(ID3v1Tag())
ID3v1Tag()->setProperties(properties);
return ID3v2Tag(true)->setProperties(properties);
2012-10-28 02:12:18 +02:00
}
MPEG::Properties *MPEG::File::audioProperties() const
{
return d->properties;
}
bool MPEG::File::save()
{
return save(AllTags);
}
bool MPEG::File::save(int tags)
{
2019-11-14 00:33:13 +01:00
return save(tags, true);
2012-10-28 02:12:18 +02:00
}
bool MPEG::File::save(int tags, bool stripOthers)
{
2019-11-14 00:33:13 +01:00
return save(tags, stripOthers, 4);
2012-10-28 02:12:18 +02:00
}
bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version)
{
2019-11-14 00:33:13 +01:00
return save(tags, stripOthers, id3v2Version, true);
}
bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags)
2012-10-28 02:12:18 +02:00
{
if(readOnly()) {
debug("MPEG::File::save() -- File is read only.");
return false;
}
// Create the tags if we've been asked to.
2019-11-14 00:33:13 +01:00
if(duplicateTags) {
2012-10-28 02:12:18 +02:00
// Copy the values from the tag that does exist into the new tag,
// except if the existing tag is to be stripped.
2012-10-28 02:12:18 +02:00
2019-11-14 00:33:13 +01:00
if((tags & ID3v2) && ID3v1Tag() && !(stripOthers && !(tags & ID3v1)))
Tag::duplicate(ID3v1Tag(), ID3v2Tag(true), false);
2019-11-14 00:33:13 +01:00
if((tags & ID3v1) && d->tag[ID3v2Index] && !(stripOthers && !(tags & ID3v2)))
Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false);
}
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
// Remove all the tags not going to be saved.
2019-11-14 00:33:13 +01:00
if(stripOthers)
strip(~tags, false);
2012-10-28 02:12:18 +02:00
if(ID3v2 & tags) {
if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) {
2016-07-19 16:58:52 +02:00
// ID3v2 tag is not empty. Update the old one or create a new one.
if(d->ID3v2Location < 0)
2012-10-28 02:12:18 +02:00
d->ID3v2Location = 0;
2019-11-14 00:33:13 +01:00
const ByteVector data = ID3v2Tag()->render(id3v2Version);
2016-07-19 16:58:52 +02:00
insert(data, d->ID3v2Location, d->ID3v2OriginalSize);
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
if(d->APELocation >= 0)
d->APELocation += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize);
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
d->ID3v2OriginalSize = data.size();
}
else {
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
// ID3v2 tag is empty. Remove the old one.
2012-10-28 02:12:18 +02:00
2019-11-14 00:33:13 +01:00
strip(ID3v2, false);
2012-10-28 02:12:18 +02:00
}
}
if(ID3v1 & tags) {
2016-07-19 16:58:52 +02:00
2012-10-28 02:12:18 +02:00
if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) {
2016-07-19 16:58:52 +02:00
// ID3v1 tag is not empty. Update the old one or create a new one.
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
if(d->ID3v1Location >= 0) {
seek(d->ID3v1Location);
2012-10-28 02:12:18 +02:00
}
else {
seek(0, End);
2016-07-19 16:58:52 +02:00
d->ID3v1Location = tell();
2012-10-28 02:12:18 +02:00
}
2016-07-19 16:58:52 +02:00
writeBlock(ID3v1Tag()->render());
}
else {
// ID3v1 tag is empty. Remove the old one.
2019-11-14 00:33:13 +01:00
strip(ID3v1, false);
2012-10-28 02:12:18 +02:00
}
}
2016-07-19 16:58:52 +02:00
if(APE & tags) {
if(APETag() && !APETag()->isEmpty()) {
// APE tag is not empty. Update the old one or create a new one.
if(d->APELocation < 0) {
if(d->ID3v1Location >= 0)
d->APELocation = d->ID3v1Location;
else
d->APELocation = length();
}
const ByteVector data = APETag()->render();
insert(data, d->APELocation, d->APEOriginalSize);
if(d->ID3v1Location >= 0)
d->ID3v1Location += (static_cast<long>(data.size()) - d->APEOriginalSize);
d->APEOriginalSize = data.size();
}
else {
// APE tag is empty. Remove the old one.
2019-11-14 00:33:13 +01:00
strip(APE, false);
2016-07-19 16:58:52 +02:00
}
}
return true;
2012-10-28 02:12:18 +02:00
}
ID3v2::Tag *MPEG::File::ID3v2Tag(bool create)
{
return d->tag.access<ID3v2::Tag>(ID3v2Index, create);
}
ID3v1::Tag *MPEG::File::ID3v1Tag(bool create)
{
return d->tag.access<ID3v1::Tag>(ID3v1Index, create);
}
APE::Tag *MPEG::File::APETag(bool create)
{
return d->tag.access<APE::Tag>(APEIndex, create);
}
bool MPEG::File::strip(int tags)
{
return strip(tags, true);
}
bool MPEG::File::strip(int tags, bool freeMemory)
{
if(readOnly()) {
debug("MPEG::File::strip() - Cannot strip tags from a read only file.");
return false;
}
2016-07-19 16:58:52 +02:00
if((tags & ID3v2) && d->ID3v2Location >= 0) {
2012-10-28 02:12:18 +02:00
removeBlock(d->ID3v2Location, d->ID3v2OriginalSize);
2016-07-19 16:58:52 +02:00
if(d->APELocation >= 0)
d->APELocation -= d->ID3v2OriginalSize;
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->ID3v2OriginalSize;
2012-10-28 02:12:18 +02:00
d->ID3v2Location = -1;
d->ID3v2OriginalSize = 0;
if(freeMemory)
d->tag.set(ID3v2Index, 0);
}
2016-07-19 16:58:52 +02:00
if((tags & ID3v1) && d->ID3v1Location >= 0) {
2012-10-28 02:12:18 +02:00
truncate(d->ID3v1Location);
2016-07-19 16:58:52 +02:00
2012-10-28 02:12:18 +02:00
d->ID3v1Location = -1;
if(freeMemory)
d->tag.set(ID3v1Index, 0);
}
2016-07-19 16:58:52 +02:00
if((tags & APE) && d->APELocation >= 0) {
2012-10-28 02:12:18 +02:00
removeBlock(d->APELocation, d->APEOriginalSize);
2016-07-19 16:58:52 +02:00
if(d->ID3v1Location >= 0)
d->ID3v1Location -= d->APEOriginalSize;
2012-10-28 02:12:18 +02:00
d->APELocation = -1;
2016-07-19 16:58:52 +02:00
d->APEOriginalSize = 0;
2012-10-28 02:12:18 +02:00
if(freeMemory)
d->tag.set(APEIndex, 0);
}
return true;
}
void MPEG::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory)
{
d->ID3v2FrameFactory = factory;
}
long MPEG::File::nextFrameOffset(long position)
{
2018-06-06 22:47:08 +02:00
ByteVector frameSyncBytes(2, '\0');
2012-10-28 02:12:18 +02:00
while(true) {
seek(position);
2018-06-06 22:47:08 +02:00
const ByteVector buffer = readBlock(bufferSize());
if(buffer.isEmpty())
2012-10-28 02:12:18 +02:00
return -1;
2018-06-06 22:47:08 +02:00
for(unsigned int i = 0; i < buffer.size(); ++i) {
frameSyncBytes[0] = frameSyncBytes[1];
frameSyncBytes[1] = buffer[i];
if(isFrameSync(frameSyncBytes)) {
const Header header(this, position + i - 1, true);
if(header.isValid())
return position + i - 1;
}
2012-10-28 02:12:18 +02:00
}
2018-06-06 22:47:08 +02:00
position += bufferSize();
2012-10-28 02:12:18 +02:00
}
}
long MPEG::File::previousFrameOffset(long position)
{
2018-06-06 22:47:08 +02:00
ByteVector frameSyncBytes(2, '\0');
2012-10-28 02:12:18 +02:00
2018-06-06 22:47:08 +02:00
while(position > 0) {
const long bufferLength = std::min<long>(position, bufferSize());
position -= bufferLength;
2012-10-28 02:12:18 +02:00
seek(position);
2018-06-06 22:47:08 +02:00
const ByteVector buffer = readBlock(bufferLength);
for(int i = buffer.size() - 1; i >= 0; --i) {
frameSyncBytes[1] = frameSyncBytes[0];
frameSyncBytes[0] = buffer[i];
if(isFrameSync(frameSyncBytes)) {
const Header header(this, position + i, true);
if(header.isValid())
return position + i + header.frameLength();
}
2012-10-28 02:12:18 +02:00
}
}
2018-06-06 22:47:08 +02:00
2012-10-28 02:12:18 +02:00
return -1;
}
long MPEG::File::firstFrameOffset()
{
long position = 0;
2016-07-19 16:58:52 +02:00
if(hasID3v2Tag())
2012-10-28 02:12:18 +02:00
position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize();
return nextFrameOffset(position);
}
long MPEG::File::lastFrameOffset()
{
2016-07-19 16:58:52 +02:00
long position;
if(hasAPETag())
position = d->APELocation - 1;
else if(hasID3v1Tag())
position = d->ID3v1Location - 1;
else
position = length();
return previousFrameOffset(position);
2012-10-28 02:12:18 +02:00
}
bool MPEG::File::hasID3v1Tag() const
{
2016-07-19 16:58:52 +02:00
return (d->ID3v1Location >= 0);
}
bool MPEG::File::hasID3v2Tag() const
{
2016-07-19 16:58:52 +02:00
return (d->ID3v2Location >= 0);
}
bool MPEG::File::hasAPETag() const
{
2016-07-19 16:58:52 +02:00
return (d->APELocation >= 0);
}
2012-10-28 02:12:18 +02:00
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
2015-11-24 19:36:24 +01:00
void MPEG::File::read(bool readProperties)
2012-10-28 02:12:18 +02:00
{
// Look for an ID3v2 tag
2016-07-19 16:58:52 +02:00
d->ID3v2Location = findID3v2();
2012-10-28 02:12:18 +02:00
if(d->ID3v2Location >= 0) {
d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory));
d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize();
}
// Look for an ID3v1 tag
2016-07-19 16:58:52 +02:00
d->ID3v1Location = Utils::findID3v1(this);
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
if(d->ID3v1Location >= 0)
2012-10-28 02:12:18 +02:00
d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location));
// Look for an APE tag
2016-07-19 16:58:52 +02:00
d->APELocation = Utils::findAPE(this, d->ID3v1Location);
2012-10-28 02:12:18 +02:00
if(d->APELocation >= 0) {
2016-07-19 16:58:52 +02:00
d->tag.set(APEIndex, new APE::Tag(this, d->APELocation));
2012-10-28 02:12:18 +02:00
d->APEOriginalSize = APETag()->footer()->completeTagSize();
2016-07-19 16:58:52 +02:00
d->APELocation = d->APELocation + APE::Footer::size() - d->APEOriginalSize;
2012-10-28 02:12:18 +02:00
}
if(readProperties)
2015-11-24 19:36:24 +01:00
d->properties = new Properties(this);
2012-10-28 02:12:18 +02:00
// Make sure that we have our default tag types available.
ID3v2Tag(true);
ID3v1Tag(true);
}
2016-07-19 16:58:52 +02:00
long MPEG::File::findID3v2()
2012-10-28 02:12:18 +02:00
{
2016-07-19 16:58:52 +02:00
if(!isValid())
return -1;
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
// An ID3v2 tag or MPEG frame is most likely be at the beginning of the file.
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
const ByteVector headerID = ID3v2::Header::fileIdentifier();
2012-10-28 02:12:18 +02:00
2016-07-19 16:58:52 +02:00
seek(0);
2018-06-06 22:47:08 +02:00
if(readBlock(headerID.size()) == headerID)
return 0;
2012-10-28 02:12:18 +02:00
2018-06-06 22:47:08 +02:00
const Header firstHeader(this, 0, true);
if(firstHeader.isValid())
2016-07-19 16:58:52 +02:00
return -1;
2012-10-28 02:12:18 +02:00
2018-06-06 22:47:08 +02:00
// Look for an ID3v2 tag until reaching the first valid MPEG frame.
2012-10-28 02:12:18 +02:00
2018-06-06 22:47:08 +02:00
ByteVector frameSyncBytes(2, '\0');
ByteVector tagHeaderBytes(3, '\0');
long position = 0;
2012-10-28 02:12:18 +02:00
2018-06-06 22:47:08 +02:00
while(true) {
seek(position);
const ByteVector buffer = readBlock(bufferSize());
if(buffer.isEmpty())
return -1;
2012-10-28 02:12:18 +02:00
2018-06-06 22:47:08 +02:00
for(unsigned int i = 0; i < buffer.size(); ++i) {
frameSyncBytes[0] = frameSyncBytes[1];
frameSyncBytes[1] = buffer[i];
if(isFrameSync(frameSyncBytes)) {
const Header header(this, position + i - 1, true);
if(header.isValid())
return -1;
}
2012-10-28 02:12:18 +02:00
2018-06-06 22:47:08 +02:00
tagHeaderBytes[0] = tagHeaderBytes[1];
tagHeaderBytes[1] = tagHeaderBytes[2];
tagHeaderBytes[2] = buffer[i];
if(tagHeaderBytes == headerID)
return position + i - 2;
}
2012-10-28 02:12:18 +02:00
2018-06-06 22:47:08 +02:00
position += bufferSize();
}
2012-10-28 02:12:18 +02:00
}