315 lines
15 KiB
C++
315 lines
15 KiB
C++
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "metadatamodel.h"
|
|
#include "exiv2wrapper.h"
|
|
|
|
#include <QDir>
|
|
#include <QDebug>
|
|
#include <QDateTime>
|
|
#include <QFileInfo>
|
|
#include <QImageReader>
|
|
|
|
MetadataModel::MetadataModel(QObject *parent)
|
|
: QAbstractItemModel(parent)
|
|
{
|
|
|
|
}
|
|
|
|
MetadataModel::~MetadataModel()
|
|
{
|
|
|
|
}
|
|
|
|
void MetadataModel::setFile(const QString &imageFilePath)
|
|
{
|
|
QFileInfo fileInfo(imageFilePath);
|
|
// It'll be fine if we don't re-use the image reader we pass to the graphics scene for now.
|
|
QImageReader imgReader(imageFilePath);
|
|
imgReader.setAutoTransform(true);
|
|
imgReader.setDecideFormatFromContent(true);
|
|
|
|
const QString & itemTypeString = tr("%1 File").arg(QString(imgReader.format().toUpper()));
|
|
const QString & sizeString = QLocale().formattedDataSize(fileInfo.size());
|
|
const QString & birthTimeString = QLocale().toString(fileInfo.birthTime(), QLocale::LongFormat);
|
|
const QString & lastModifiedTimeString = QLocale().toString(fileInfo.lastModified(), QLocale::LongFormat);
|
|
const QString & imageDimensionsString = imageSize(imgReader.size());
|
|
const QString & imageRatioString = imageSizeRatio(imgReader.size());
|
|
|
|
appendSection(QStringLiteral("Description"), tr("Description", "Section name."));
|
|
appendSection(QStringLiteral("Origin"), tr("Origin", "Section name."));
|
|
appendSection(QStringLiteral("Image"), tr("Image", "Section name."));
|
|
appendSection(QStringLiteral("Camera"), tr("Camera", "Section name."));
|
|
appendSection(QStringLiteral("AdvancedPhoto"), tr("Advanced photo", "Section name."));
|
|
appendSection(QStringLiteral("GPS"), tr("GPS", "Section name."));
|
|
appendSection(QStringLiteral("File"), tr("File", "Section name."));
|
|
|
|
if (imgReader.supportsOption(QImageIOHandler::Size)) {
|
|
appendProperty(QStringLiteral("Image"), QStringLiteral("Image.Dimensions"),
|
|
tr("Dimensions"), imageDimensionsString);
|
|
appendProperty(QStringLiteral("Image"), QStringLiteral("Image.SizeRatio"),
|
|
tr("Aspect ratio"), imageRatioString);
|
|
}
|
|
if (imgReader.supportsAnimation() && imgReader.imageCount() > 1) {
|
|
appendProperty(QStringLiteral("Image"), QStringLiteral("Image.FrameCount"),
|
|
tr("Frame count"), QString::number(imgReader.imageCount()));
|
|
}
|
|
|
|
appendProperty(QStringLiteral("File"), QStringLiteral("File.Name"),
|
|
tr("Name"), fileInfo.fileName());
|
|
appendProperty(QStringLiteral("File"), QStringLiteral("File.ItemType"),
|
|
tr("Item type"), itemTypeString);
|
|
appendProperty(QStringLiteral("File"), QStringLiteral("File.Path"),
|
|
tr("Folder path"), QDir::toNativeSeparators(fileInfo.path()));
|
|
appendProperty(QStringLiteral("File"), QStringLiteral("File.Size"),
|
|
tr("Size"), sizeString);
|
|
appendProperty(QStringLiteral("File"), QStringLiteral("File.CreatedTime"),
|
|
tr("Date created"), birthTimeString);
|
|
appendProperty(QStringLiteral("File"), QStringLiteral("File.LastModified"),
|
|
tr("Date modified"), lastModifiedTimeString);
|
|
|
|
Exiv2Wrapper wrapper;
|
|
if (wrapper.load(imageFilePath)) {
|
|
wrapper.cacheSections();
|
|
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Description"),
|
|
QStringLiteral("Xmp.dc.title"), tr("Title"), true);
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Description"),
|
|
QStringLiteral("Exif.Image.ImageDescription"), tr("Subject"), true);
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Description"),
|
|
QStringLiteral("Exif.Image.Rating"), tr("Rating"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Description"),
|
|
QStringLiteral("Xmp.dc.subject"), tr("Tags"));
|
|
appendPropertyIfNotEmpty(QStringLiteral("Description"), QStringLiteral("Description.Comments"),
|
|
tr("Comments"), wrapper.comment());
|
|
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Origin"),
|
|
QStringLiteral("Exif.Image.Artist"), tr("Authors"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Origin"),
|
|
QStringLiteral("Exif.Photo.DateTimeOriginal"), tr("Date taken"));
|
|
// FIXME: We may fetch the same type of metadata from different metadata collection.
|
|
// Current implementation is not pretty and may need to do a rework...
|
|
// appendExivPropertyIfExist(wrapper, QStringLiteral("Origin"),
|
|
// QStringLiteral("Xmp.xmp.CreatorTool"), tr("Program name"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Origin"),
|
|
QStringLiteral("Exif.Image.Software"), tr("Program name"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Origin"),
|
|
QStringLiteral("Exif.Image.Copyright"), tr("Copyright"));
|
|
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Image"),
|
|
QStringLiteral("Exif.Image.XResolution"), tr("Horizontal resolution"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Image"),
|
|
QStringLiteral("Exif.Image.YResolution"), tr("Vertical resolution"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Image"),
|
|
QStringLiteral("Exif.Image.ResolutionUnit"), tr("Resolution unit"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Image"),
|
|
QStringLiteral("Exif.Photo.ColorSpace"), tr("Colour representation"));
|
|
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Image.Make"), tr("Camera maker"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Image.Model"), tr("Camera model"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Photo.FNumber"), tr("F-stop"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Photo.ExposureTime"), tr("Exposure time"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Photo.ISOSpeedRatings"), tr("ISO speed"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Photo.ExposureBiasValue"), tr("Exposure bias"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Photo.FocalLength"), tr("Focal length"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Photo.MaxApertureValue"), tr("Max aperture"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Photo.MeteringMode"), tr("Metering mode"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Photo.Flash"), tr("Flash mode"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"),
|
|
QStringLiteral("Exif.Photo.FocalLengthIn35mmFilm"), tr("35mm focal length"));
|
|
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"),
|
|
QStringLiteral("Exif.Photo.LensModel"), tr("Lens model"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"),
|
|
QStringLiteral("Exif.Photo.BrightnessValue"), tr("Brightness"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"),
|
|
QStringLiteral("Exif.Photo.ExposureProgram"), tr("Exposure program"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"),
|
|
QStringLiteral("Exif.Photo.Saturation"), tr("Saturation"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"),
|
|
QStringLiteral("Exif.Photo.Sharpness"), tr("Sharpness"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"),
|
|
QStringLiteral("Exif.Photo.WhiteBalance"), tr("White balance"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"),
|
|
QStringLiteral("Exif.Photo.DigitalZoomRatio"), tr("Digital zoom"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"),
|
|
QStringLiteral("Exif.Photo.ExifVersion"), tr("EXIF version"));
|
|
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("GPS"),
|
|
QStringLiteral("Exif.GPSInfo.GPSLatitudeRef"), tr("Latitude reference"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("GPS"),
|
|
QStringLiteral("Exif.GPSInfo.GPSLatitude"), tr("Latitude"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("GPS"),
|
|
QStringLiteral("Exif.GPSInfo.GPSLongitudeRef"), tr("Longitude reference"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("GPS"),
|
|
QStringLiteral("Exif.GPSInfo.GPSLongitude"), tr("Longitude"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("GPS"),
|
|
QStringLiteral("Exif.GPSInfo.GPSAltitudeRef"), tr("Altitude reference"));
|
|
appendExivPropertyIfExist(wrapper, QStringLiteral("GPS"),
|
|
QStringLiteral("Exif.GPSInfo.GPSAltitude"), tr("Altitude"));
|
|
|
|
}
|
|
}
|
|
|
|
QString MetadataModel::imageSize(const QSize &size)
|
|
{
|
|
QString imageSize;
|
|
|
|
if (size.isValid()) {
|
|
imageSize = tr("%1 x %2").arg(QString::number(size.width()), QString::number(size.height()));
|
|
} else {
|
|
imageSize = QLatin1Char('-');
|
|
}
|
|
|
|
return imageSize;
|
|
}
|
|
|
|
int simplegcd(int a, int b) {
|
|
return b == 0 ? a : simplegcd(b, a % b);
|
|
}
|
|
|
|
QString MetadataModel::imageSizeRatio(const QSize &size)
|
|
{
|
|
if (!size.isValid()) {
|
|
return QStringLiteral("-");
|
|
}
|
|
int gcd = simplegcd(size.width(), size.height());
|
|
return tr("%1 : %2").arg(QString::number(size.width() / gcd), QString::number(size.height() / gcd));
|
|
}
|
|
|
|
bool MetadataModel::appendSection(const QString §ionKey, QStringView sectionDisplayName)
|
|
{
|
|
if (m_sections.contains(sectionKey)) {
|
|
return false;
|
|
}
|
|
|
|
m_sections.append(sectionKey);
|
|
m_sectionProperties[sectionKey] = qMakePair<QString, QList<QString> >(sectionDisplayName.toString(), {});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MetadataModel::appendPropertyIfNotEmpty(const QString §ionKey, const QString &propertyKey, const QString &propertyDisplayName, const QString &propertyValue)
|
|
{
|
|
if (propertyValue.isEmpty()) return false;
|
|
|
|
return appendProperty(sectionKey, propertyKey, propertyDisplayName, propertyValue);
|
|
}
|
|
|
|
bool MetadataModel::appendProperty(const QString §ionKey, const QString &propertyKey, QStringView propertyDisplayName, QStringView propertyValue)
|
|
{
|
|
if (!m_sections.contains(sectionKey)) {
|
|
return false;
|
|
}
|
|
|
|
QList<QString> & propertyList = m_sectionProperties[sectionKey].second;
|
|
if (!propertyList.contains(propertyKey)) {
|
|
propertyList.append(propertyKey);
|
|
}
|
|
|
|
m_properties[propertyKey] = qMakePair<QString, QString>(propertyDisplayName.toString(), propertyValue.toString());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MetadataModel::appendExivPropertyIfExist(const Exiv2Wrapper &wrapper, const QString §ionKey, const QString &exiv2propertyKey, const QString &propertyDisplayName, bool isXmpString)
|
|
{
|
|
const QString & value = wrapper.value(exiv2propertyKey);
|
|
if (!value.isEmpty()) {
|
|
appendProperty(sectionKey, exiv2propertyKey,
|
|
propertyDisplayName.isEmpty() ? wrapper.label(exiv2propertyKey) : propertyDisplayName,
|
|
isXmpString ? Exiv2Wrapper::XmpValue(value) : value);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QModelIndex MetadataModel::index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
if (!hasIndex(row, column, parent)) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
if (!parent.isValid()) {
|
|
return createIndex(row, column, RowType::SectionRow);
|
|
} else {
|
|
// internalid param: row means nth section it belongs to.
|
|
return createIndex(row, column, RowType::PropertyRow + parent.row());
|
|
}
|
|
}
|
|
|
|
QModelIndex MetadataModel::parent(const QModelIndex &child) const
|
|
{
|
|
if (!child.isValid()) {
|
|
return QModelIndex();
|
|
}
|
|
|
|
if (child.internalId() == RowType::SectionRow) {
|
|
return QModelIndex();
|
|
} else {
|
|
return createIndex(child.internalId() - RowType::PropertyRow, 0, SectionRow);
|
|
}
|
|
}
|
|
|
|
int MetadataModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (!parent.isValid()) {
|
|
return m_sections.count();
|
|
}
|
|
|
|
if (parent.internalId() == RowType::SectionRow) {
|
|
const QString & sectionKey = m_sections[parent.row()];
|
|
return m_sectionProperties[sectionKey].second.count();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int MetadataModel::columnCount(const QModelIndex &) const
|
|
{
|
|
// Always key(display name) and value.
|
|
return 2;
|
|
}
|
|
|
|
QVariant MetadataModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return QVariant();
|
|
}
|
|
|
|
if (role != Qt::DisplayRole) {
|
|
return QVariant();
|
|
}
|
|
|
|
if (index.internalId() == RowType::SectionRow) {
|
|
return (index.column() == 0) ? m_sectionProperties[m_sections[index.row()]].first
|
|
: QVariant();
|
|
} else {
|
|
int sectionIndex = index.internalId() - RowType::PropertyRow;
|
|
const QString & sectionKey = m_sections[sectionIndex];
|
|
const QList<QString> & propertyList = m_sectionProperties[sectionKey].second;
|
|
return (index.column() == 0) ? m_properties[propertyList[index.row()]].first
|
|
: m_properties[propertyList[index.row()]].second;
|
|
}
|
|
}
|
|
|
|
QVariant MetadataModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (orientation == Qt::Vertical || role != Qt::DisplayRole) {
|
|
return QVariant();
|
|
}
|
|
|
|
return section == 0 ? tr("Property") : tr("Value");
|
|
}
|