Commit 3fd5e710 authored by Urs Fleisch's avatar Urs Fleisch
Browse files

Support ID3v2 chapter and table of contents frames (CHAP, CTOC).

parent e8f5e800
......@@ -994,6 +994,53 @@ void FrameCollection::dump() const
}
#endif
/**
* Create a frame collection from a list of subframe fields.
*
* The given subframe fields must start with a Frame::ID_Subframe field with
* the frame name as its value, followed by the fields of the frame. More
* subframes may follow.
*
* @param begin iterator to begin of subframes
* @param end iterator after end of subframes
*
* @return frames constructed from subframe fields.
*/
FrameCollection FrameCollection::fromSubframes(
Frame::FieldList::const_iterator begin,
Frame::FieldList::const_iterator end)
{
FrameCollection frames;
Frame frame;
int index = 0;
for (Frame::FieldList::const_iterator it = begin;
it != end;
++it) {
const Frame::Field& fld = *it;
if (fld.m_id == Frame::ID_Subframe) {
if (frame.getType() != Frame::FT_UnknownFrame) {
frame.setValueFromFieldList();
frames.insert(frame);
frame = Frame();
}
QString name = fld.m_value.toString();
if (!name.isEmpty()) {
frame.setExtendedType(Frame::ExtendedType(name));
frame.setIndex(index++);
}
} else {
if (frame.getType() != Frame::FT_UnknownFrame) {
frame.fieldList().append(fld);
}
}
}
if (frame.getType() != Frame::FT_UnknownFrame) {
frame.setValueFromFieldList();
frames.insert(frame);
}
return frames;
}
/**
* Constructor.
......
......@@ -133,7 +133,10 @@ public:
ID_Seller,
// Additional field for METADATA_BLOCK_PICTURE
ID_ImageProperties
ID_ImageProperties,
// Type of subframe in CTOC and CHAP frames
ID_Subframe
};
/** Text encoding for fields of type ID_TextEnc. */
......@@ -970,6 +973,21 @@ public:
return s_quickAccessFrames;
}
/**
* Create a frame collection from a list of subframe fields.
*
* The given subframe fields must start with a Frame::ID_Subframe field with
* the frame name as its value, followed by the fields of the frame. More
* subframes may follow.
*
* @param begin iterator to begin of subframes
* @param end iterator after end of subframes
*
* @return frames constructed from subframe fields.
*/
static FrameCollection fromSubframes(Frame::FieldList::const_iterator begin,
Frame::FieldList::const_iterator end);
private:
/**
* Search for a frame only by name.
......
......@@ -1278,6 +1278,17 @@ void TaggedFile::closeFileHandle()
{
}
/**
* Add a suitable field list for the frame if missing.
* If a frame is created, its field list is empty. This method will create
* a field list appropriate for the frame type and tagged file type if no
* field list exists. The default implementation does nothing.
* @param frame frame where field list is added
*/
void TaggedFile::addFieldList(Frame&) const
{
}
/**
* Set frames in tag 2.
*
......
......@@ -562,6 +562,15 @@ public:
*/
virtual void closeFileHandle();
/**
* Add a suitable field list for the frame if missing.
* If a frame is created, its field list is empty. This method will create
* a field list appropriate for the frame type and tagged file type if no
* field list exists. The default implementation does nothing.
* @param frame frame where field list is added
*/
virtual void addFieldList(Frame& frame) const;
/**
* Set frames in tag 1.
*
......
......@@ -50,6 +50,9 @@
#include "iplatformtools.h"
#include "timeeventmodel.h"
#include "timeeventeditor.h"
#include "chaptereditor.h"
#include "tableofcontentseditor.h"
#include "subframeseditor.h"
#include "pictureframe.h"
/** QTextEdit with label above */
......@@ -629,6 +632,111 @@ protected:
TimeEventEditor* m_editor;
};
/** Control to edit a subframe */
class SubframeFieldControl : public Mp3FieldControl {
public:
/**
* Constructor.
*/
SubframeFieldControl(IPlatformTools* platformTools,
Kid3Application* app, const TaggedFile* taggedFile,
Frame::FieldList& fields,
Frame::FieldList::iterator begin,
Frame::FieldList::iterator end);
/**
* Destructor.
*/
virtual ~SubframeFieldControl();
/**
* Update field from data in field control.
*/
virtual void updateTag();
/**
* Create widget to edit field data.
*
* @param parent parent widget
*
* @return widget to edit field data.
*/
virtual QWidget* createWidget(QWidget* parent);
private:
IPlatformTools* m_platformTools;
Kid3Application* m_app;
const TaggedFile* m_taggedFile;
Frame::FieldList& m_fields;
Frame::FieldList::iterator m_begin;
Frame::FieldList::iterator m_end;
SubframesEditor* m_editor;
};
/** Control to edit a chapter */
class ChapterFieldControl : public Mp3FieldControl {
public:
/**
* Constructor.
* @param field field to edit
*/
ChapterFieldControl(Frame::Field& field);
/**
* Destructor.
*/
virtual ~ChapterFieldControl();
/**
* Update field from data in field control.
*/
virtual void updateTag();
/**
* Create widget to edit field data.
*
* @param parent parent widget
*
* @return widget to edit field data.
*/
virtual QWidget* createWidget(QWidget* parent);
private:
ChapterEditor* m_editor;
};
/** Control to edit table of contents. */
class TableOfContentsFieldControl : public Mp3FieldControl {
public:
/**
* Constructor.
* @param field field to edit
*/
TableOfContentsFieldControl(Frame::Field& field);
/**
* Destructor.
*/
virtual ~TableOfContentsFieldControl();
/**
* Update field from data in field control.
*/
virtual void updateTag();
/**
* Create widget to edit field data.
*
* @param parent parent widget
*
* @return widget to edit field data.
*/
virtual QWidget* createWidget(QWidget* parent);
private:
TableOfContentsEditor* m_editor;
};
/**
* Constructor.
......@@ -972,6 +1080,155 @@ QWidget* TimeEventFieldControl::createWidget(QWidget* parent)
return m_editor;
}
/**
* Constructor.
*/
SubframeFieldControl::SubframeFieldControl(
IPlatformTools* platformTools, Kid3Application* app,
const TaggedFile* taggedFile, Frame::FieldList& fields,
Frame::FieldList::iterator begin, Frame::FieldList::iterator end) :
Mp3FieldControl(*begin), m_platformTools(platformTools), m_app(app),
m_taggedFile(taggedFile), m_fields(fields), m_begin(begin), m_end(end),
m_editor(0)
{
}
/**
* Destructor.
*/
SubframeFieldControl::~SubframeFieldControl()
{
}
/**
* Update field from data in field control.
*/
void SubframeFieldControl::updateTag()
{
if (m_editor) {
FrameCollection frames;
m_editor->getFrames(frames);
m_fields.erase(m_begin, m_end);
Frame::Field field;
field.m_id = Frame::ID_Subframe;
for (FrameCollection::iterator it = frames.begin();
it != frames.end();
++it) {
field.m_value = it->getExtendedType().getName();
m_fields.append(field);
m_fields.append(it->getFieldList());
}
}
}
/**
* Create widget to edit field data.
*
* @param parent parent widget
*
* @return widget to edit field data.
*/
QWidget* SubframeFieldControl::createWidget(QWidget* parent) {
m_editor = new SubframesEditor(m_platformTools, m_app, m_taggedFile, parent);
FrameCollection frames = FrameCollection::fromSubframes(m_begin, m_end);
m_editor->setFrames(frames);
return m_editor;
}
/**
* Constructor.
* @param field field to edit
*/
ChapterFieldControl::ChapterFieldControl(Frame::Field& field) :
Mp3FieldControl(field), m_editor(0)
{
}
/**
* Destructor.
*/
ChapterFieldControl::~ChapterFieldControl()
{
}
/**
* Update field from data in field control.
*/
void ChapterFieldControl::updateTag()
{
if (m_editor) {
quint32 startTimeMs, endTimeMs, startOffset, endOffset;
m_editor->getValues(startTimeMs, endTimeMs, startOffset, endOffset);
QVariantList lst;
lst << startTimeMs << endTimeMs << startOffset << endOffset;
m_field.m_value = lst;
}
}
/**
* Create widget to edit field data.
*
* @param parent parent widget
*
* @return widget to edit field data.
*/
QWidget* ChapterFieldControl::createWidget(QWidget* parent) {
m_editor = new ChapterEditor(parent);
QVariantList lst = m_field.m_value.toList();
if (lst.size() >= 4) {
m_editor->setValues(lst.at(0).toUInt(), lst.at(1).toUInt(),
lst.at(2).toUInt(), lst.at(3).toUInt());
}
return m_editor;
}
/**
* Constructor.
* @param field field to edit
*/
TableOfContentsFieldControl::TableOfContentsFieldControl(Frame::Field& field) :
Mp3FieldControl(field), m_editor(0)
{
}
/**
* Destructor.
*/
TableOfContentsFieldControl::~TableOfContentsFieldControl()
{
}
/**
* Update field from data in field control.
*/
void TableOfContentsFieldControl::updateTag()
{
if (m_editor) {
bool isTopLevel, isOrdered;
QStringList elements = m_editor->getValues(isTopLevel, isOrdered);
QVariantList lst;
lst << isTopLevel << isOrdered << elements;
m_field.m_value = lst;
}
}
/**
* Create widget to edit field data.
*
* @param parent parent widget
*
* @return widget to edit field data.
*/
QWidget* TableOfContentsFieldControl::createWidget(QWidget* parent) {
m_editor = new TableOfContentsEditor(parent);
QVariantList lst = m_field.m_value.toList();
if (lst.size() >= 3) {
m_editor->setValues(lst.at(0).toBool(), lst.at(1).toBool(),
lst.at(2).toStringList());
}
return m_editor;
}
/**
* Constructor.
......@@ -1041,6 +1298,7 @@ void EditFrameFieldsDialog::setFrame(const Frame& frame,
qDeleteAll(m_fieldcontrols);
m_fieldcontrols.clear();
bool subframeMissing = false;
for (Frame::FieldList::iterator fldIt = m_fields.begin();
fldIt != m_fields.end();
......@@ -1049,6 +1307,15 @@ void EditFrameFieldsDialog::setFrame(const Frame& frame,
if (fld.m_id == Frame::ID_ImageProperties)
continue;
if (fld.m_id == Frame::ID_Subframe) {
SubframeFieldControl* subframeCtl =
new SubframeFieldControl(m_platformTools, m_app, taggedFile,
m_fields, fldIt, m_fields.end());
m_fieldcontrols.append(subframeCtl);
subframeMissing = false;
break;
}
switch (fld.m_value.type()) {
case QVariant::Int:
case QVariant::UInt:
......@@ -1111,6 +1378,15 @@ void EditFrameFieldsDialog::setFrame(const Frame& frame,
m_platformTools, m_app, fld, m_fields, taggedFile,
TimeEventModel::EventTimingCodes);
m_fieldcontrols.append(timeEventCtl);
} else if (frameName.startsWith(QLatin1String("CHAP"))) {
ChapterFieldControl* chapCtl = new ChapterFieldControl(fld);
m_fieldcontrols.append(chapCtl);
subframeMissing = true;
} else if (frameName.startsWith(QLatin1String("CTOC"))) {
TableOfContentsFieldControl* tocCtl =
new TableOfContentsFieldControl(fld);
m_fieldcontrols.append(tocCtl);
subframeMissing = true;
} else {
qDebug("Unexpected QVariantList in field %d", fld.m_id);
}
......@@ -1122,6 +1398,14 @@ void EditFrameFieldsDialog::setFrame(const Frame& frame,
}
}
if (subframeMissing) {
// Add an empty subframe so that subframes can be added
SubframeFieldControl* subframeCtl =
new SubframeFieldControl(m_platformTools, m_app, taggedFile,
m_fields, m_fields.end(), m_fields.end());
m_fieldcontrols.append(subframeCtl);
}
// Handle case for frames without fields, just a value.
m_valueField.m_id = Frame::ID_Text;
if (m_fields.isEmpty()) {
......
......@@ -13,6 +13,9 @@ set(widgets_SRCS
widgets/enumdelegate.cpp
widgets/eventcodedelegate.cpp
widgets/timestampdelegate.cpp
widgets/chaptereditor.cpp
widgets/tableofcontentseditor.cpp
widgets/subframeseditor.cpp
)
set(widgets_MOC_HDRS
......@@ -26,4 +29,7 @@ set(widgets_MOC_HDRS
widgets/stringlistedit.h
widgets/timeeventeditor.h
widgets/timestampdelegate.h
widgets/chaptereditor.h
widgets/tableofcontentseditor.h
widgets/subframeseditor.h
)
/**
* \file chaptereditor.cpp
* Editor for chapter frames.
*
* \b Project: Kid3
* \author Urs Fleisch
* \date 18 Sep 2015
*
* Copyright (C) 2015 Urs Fleisch
*
* This file is part of Kid3.
*
* Kid3 is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Kid3 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "chaptereditor.h"
#include <QFormLayout>
#include <QLineEdit>
#include <QTimeEdit>
#include <QTime>
/**
* Constructor.
*
* @param parent parent widget
*/
ChapterEditor::ChapterEditor(QWidget* parent) :
QWidget(parent)
{
setObjectName(QLatin1String("ChapterEditor"));
QFormLayout* formatLayout = new QFormLayout(this);
formatLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
QString timeFormat(QLatin1String("hh:mm:ss.zzz"));
QString inputMask(QLatin1String("HHHHHHHH"));
m_startTimeEdit = new QTimeEdit;
m_startTimeEdit->setDisplayFormat(timeFormat);
m_endTimeEdit = new QTimeEdit;
m_endTimeEdit->setDisplayFormat(timeFormat);
m_startOffsetEdit = new QLineEdit;
m_startOffsetEdit->setInputMask(inputMask);
m_endOffsetEdit = new QLineEdit;
m_endOffsetEdit->setInputMask(inputMask);
formatLayout->addRow(tr("Start time"), m_startTimeEdit);
formatLayout->addRow(tr("End time"), m_endTimeEdit);
formatLayout->addRow(tr("Start offset"), m_startOffsetEdit);
formatLayout->addRow(tr("End offset"), m_endOffsetEdit);
}
/**
* Destructor.
*/
ChapterEditor::~ChapterEditor()
{
}
/**
* Set start and end time of chapter.
* @param startTimeMs start time in ms
* @param endTimeMs end time in ms
* @param startOffset offset of first byte of chapter in file,
* ignored if all ones
* @param endOffset offset of byte after last chapter byte,
* ignored if all ones
*/
void ChapterEditor::setValues(quint32 startTimeMs, quint32 endTimeMs,
quint32 startOffset, quint32 endOffset)
{
const QTime zeroTime(0, 0);
QTime startTime = zeroTime.addMSecs(startTimeMs);
m_startTimeEdit->setTime(startTime);
QTime endTime = zeroTime.addMSecs(endTimeMs);
m_endTimeEdit->setTime(endTime);
m_startOffsetEdit->setText(QString::number(startOffset, 16).toUpper());
m_endOffsetEdit->setText(QString::number(endOffset, 16).toUpper());
}
/**
* Get start and end time of chapter.
* @param startTimeMs the start time in ms is returned here
* @param endTimeMs the end time in ms is returned here
* @param startOffset the offset of the first byte of chapter in file is
* returned here, ignored if all ones
* @param endOffset the offset of the byte after the last chapter byte is
* returned here, ignored if all ones
*/
void ChapterEditor::getValues(quint32& startTimeMs, quint32& endTimeMs,
quint32& startOffset, quint32& endOffset) const
{
const QTime zeroTime(0, 0);
startTimeMs = zeroTime.msecsTo(m_startTimeEdit->time());
endTimeMs = zeroTime.msecsTo(m_endTimeEdit->time());
bool ok;
startOffset = m_startOffsetEdit->text().toUInt(&ok, 16);
if (!ok) {
startOffset = 0xffffffff;
}
endOffset = m_endOffsetEdit->text().toUInt(&ok, 16);
if (!ok) {
endOffset = 0xffffffff;
}
}
/**
* \file chaptereditor.h
* Editor for chapter frames.
*
* \b Project: Kid3
* \author Urs Fleisch
* \date 18 Sep 2015
*
* Copyright (C) 2015 Urs Fleisch
*
* This file is part of Kid3.
*
* Kid3 is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Kid3 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CHAPTEREDITOR_H
#define CHAPTEREDITOR_H
#include <QWidget>