diff --git a/core/libs/dmetadata/dmetadata_ffmpeg.cpp b/core/libs/dmetadata/dmetadata_ffmpeg.cpp index 8c361763ae..c509a7bf12 100644 --- a/core/libs/dmetadata/dmetadata_ffmpeg.cpp +++ b/core/libs/dmetadata/dmetadata_ffmpeg.cpp @@ -1,1613 +1,1611 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-02-26 * Description : metadata extraction with FFMpeg (libav) * * References : * * FFMpeg metadata review: https://wiki.multimedia.cx/index.php/FFmpeg_Metadata * FFMpeg MP4 parser : https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/mov.c#L298 * Exiv2 XMP video : https://github.com/Exiv2/exiv2/blob/master/src/properties.cpp#L1331 * Exiv2 RIFF tags : https://github.com/Exiv2/exiv2/blob/master/src/riffvideo.cpp#L83 * Apple metadata desc : https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html * Matroska metadata desc: https://matroska.org/technical/specs/tagging/index.html * FFMpeg metadata writer: https://github.com/kritzikratzi/ofxAvCodec/blob/master/src/ofxAvUtils.cpp#L61 * * FFMpeg tags names origin: * * Generic : common tags generated by FFMpeg codecs. * RIFF files : Resource Interchange File Format tags (as AVI). * MKV files : Matroska container tags. * QT files : Quicktime container tags (Apple). * * Copyright (C) 2018 by Gilles Caulier * * This program 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, or (at your option) * any later version. * * This program 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. * * ============================================================ */ #include "dmetadata.h" // C Ansi includes #include // Qt includes -#include -#include #include #include #include #include // KDE includes #include // Local incudes #include "captionvalues.h" #include "digikam_debug.h" #include "digikam_config.h" #ifdef HAVE_MEDIAPLAYER // Libav includes extern "C" { #include #include #include #include } #endif namespace Digikam { /** Search first occurrence of string in 'map' with keys given by 'lst'. * Return the string match. * If 'xmpTags' is not empty, register XMP tags value with string. */ QString s_setXmpTagStringFromEntry(DMetadata* const meta, const QStringList& lst, const DMetadata::MetaDataMap& map, const QStringList& xmpTags=QStringList()) { foreach (const QString& tag, lst) { DMetadata::MetaDataMap::const_iterator it = map.find(tag); if (it != map.end()) { if (meta && // Protection. !xmpTags.isEmpty()) // If xmpTags is empty, we only return the matching value from the map. { foreach (const QString& tag, xmpTags) { // Only register the tag value if it doesn't exists yet. if (meta->getXmpTagString(tag.toLatin1().data()).isNull()) { meta->setXmpTagString(tag.toLatin1().data(), it.value()); } } } return it.value(); } } return QString(); } QStringList s_keywordsSeparation(const QString& data) { QStringList keywords = data.split(QLatin1String("/")); if (keywords.isEmpty()) { keywords = data.split(QLatin1String(",")); if (keywords.isEmpty()) { keywords = data.split(QLatin1String(" ")); } } return keywords; } qint64 s_secondsSinceJanuary1904(const QDateTime dt) { QDateTime dt1904(QDate(1904, 1, 1), QTime(0, 0, 0)); return dt1904.secsTo(dt); } #ifdef HAVE_MEDIAPLAYER QString s_convertFFMpegFormatToXMP(int format) { QString data; switch (format) { case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_U8P: data = QLatin1String("8Int"); break; case AV_SAMPLE_FMT_S16: case AV_SAMPLE_FMT_S16P: data = QLatin1String("16Int"); break; case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P: data = QLatin1String("32Int"); break; case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP: data = QLatin1String("32Float"); break; case AV_SAMPLE_FMT_DBL: // Not supported by XMP spec. case AV_SAMPLE_FMT_DBLP: // Not supported by XMP spec. case AV_SAMPLE_FMT_S64: // Not supported by XMP spec. case AV_SAMPLE_FMT_S64P: // Not supported by XMP spec. case AV_SAMPLE_FMT_NONE: case AV_SAMPLE_FMT_NB: default: data = QLatin1String("Other"); break; // NOTE: where are 'Compressed' and 'Packed' type from XMP spec into FFMPEG ? } return data; } DMetadata::MetaDataMap s_extractFFMpegMetadataEntriesFromDictionary(AVDictionary* const dict) { AVDictionaryEntry* entry = 0; DMetadata::MetaDataMap meta; do { entry = av_dict_get(dict, "", entry, AV_DICT_IGNORE_SUFFIX); if (entry) { if (QString::fromUtf8(entry->key) != QLatin1String("creation_time") || QDateTime::fromString(QString::fromUtf8(entry->value), Qt::ISODate).toMSecsSinceEpoch() != 0) { meta.insert(QString::fromUtf8(entry->key), QString::fromUtf8(entry->value)); } } } while (entry); return meta; } #endif bool DMetadata::loadUsingFFmpeg(const QString& filePath) { #ifdef HAVE_MEDIAPLAYER qCDebug(DIGIKAM_METAENGINE_LOG) << "Parse metadada with FFMpeg:" << filePath; av_register_all(); AVFormatContext* fmt_ctx = avformat_alloc_context(); int ret = avformat_open_input(&fmt_ctx, filePath.toUtf8().data(), NULL, NULL); if (ret < 0) { qCDebug(DIGIKAM_METAENGINE_LOG) << "avformat_open_input error: " << ret; return false; } ret = avformat_find_stream_info(fmt_ctx, NULL); if (ret < 0) { qCDebug(DIGIKAM_METAENGINE_LOG) << "avform_find_stream_info error: " << ret; return false; } QString data; setXmpTagString("Xmp.video.duration", QString::number((int)(1000.0 * (double)fmt_ctx->duration / (double)AV_TIME_BASE))); setXmpTagString("Xmp.xmpDM.duration", QString::number((int)(1000.0 * (double)fmt_ctx->duration / (double)AV_TIME_BASE))); if (fmt_ctx->bit_rate > 0) setXmpTagString("Xmp.video.MaxBitRate", QString::number(fmt_ctx->bit_rate)); setXmpTagString("Xmp.video.StreamCount", QString::number(fmt_ctx->nb_streams)); // To only register one video, one audio stream, and one subtitle stream in XMP metadata. bool vstream = false; bool astream = false; bool sstream = false; for (uint i = 0 ; i < fmt_ctx->nb_streams ; i++) { const AVStream* const stream = fmt_ctx->streams[i]; AVCodecParameters* const codec = stream->codecpar; // ----------------------------------------- // Audio stream parsing // ----------------------------------------- if (!astream && codec->codec_type == AVMEDIA_TYPE_AUDIO) { astream = true; const char* cname = avcodec_get_name(codec->codec_id); setXmpTagString("Xmp.audio.Codec", QString::fromUtf8(cname)); setXmpTagString("Xmp.audio.CodecDescription", QString::fromUtf8(avcodec_descriptor_get_by_name(cname)->long_name)); setXmpTagString("Xmp.audio.SampleRate", QString::number(codec->sample_rate)); setXmpTagString("Xmp.xmpDM.audioSampleRate", QString::number(codec->sample_rate)); // See XMP Dynamic Media properties from Adobe. // Audio Channel type is a limited untranslated string choice depending of amount of audio channels data = QString(); switch (codec->channels) { case 0: break; case 1: data = QLatin1String("Mono"); break; case 2: data = QLatin1String("Stereo"); break; case 6: data = QLatin1String("5.1"); break; case 8: data = QLatin1String("7.1"); break; case 16: data = QLatin1String("16 Channel"); break; default: data = QLatin1String("Other"); break; } if (!data.isEmpty()) { setXmpTagString("Xmp.audio.ChannelType", data); setXmpTagString("Xmp.xmpDM.audioChannelType", data); } setXmpTagString("Xmp.audio.Format", QString::fromUtf8(av_get_sample_fmt_name((AVSampleFormat)codec->format))); // See XMP Dynamic Media properties from Adobe. // Audio Sample type is a limited untranslated string choice depending of amount of audio samples data = s_convertFFMpegFormatToXMP(codec->format); if (!data.isEmpty()) { setXmpTagString("Xmp.audio.SampleType", data); setXmpTagString("Xmp.xmpDM.audioSampleType", data); } // -------------- MetaDataMap ameta = s_extractFFMpegMetadataEntriesFromDictionary(stream->metadata); qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg audio stream metadata entries :"; qCDebug(DIGIKAM_METAENGINE_LOG) << ameta; qCDebug(DIGIKAM_METAENGINE_LOG) << "-----------------------------------------"; // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("language"), // Generic. ameta, QStringList() << QLatin1String("Xmp.audio.TrackLang")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("creation_time"), // Generic. ameta); if (!data.isEmpty()) { QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime(); setXmpTagString("Xmp.audio.TrackCreateDate", QString::number(s_secondsSinceJanuary1904(dt))); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("handler_name"), // Generic. ameta, QStringList() << QLatin1String("Xmp.audio.HandlerDescription")); } // ----------------------------------------- // Video stream parsing // ----------------------------------------- if (!vstream && codec->codec_type == AVMEDIA_TYPE_VIDEO) { vstream = true; const char* cname = avcodec_get_name(codec->codec_id); setXmpTagString("Xmp.video.Codec", QString::fromUtf8(cname)); setXmpTagString("Xmp.video.CodecDescription", QString::fromUtf8(avcodec_descriptor_get_by_name(cname)->long_name)); setXmpTagString("Xmp.video.Format", QString::fromUtf8(av_get_pix_fmt_name((AVPixelFormat)codec->format))); // Store in this tag the full description off FFMPEG video color space. setXmpTagString("Xmp.video.ColorMode", QString::fromUtf8(av_color_space_name((AVColorSpace)codec->color_space))); VIDEOCOLORMODEL cm = VIDEOCOLORMODEL_OTHER; switch (codec->color_space) { case AVCOL_SPC_RGB: cm = VIDEOCOLORMODEL_SRGB; break; case AVCOL_SPC_BT470BG: case AVCOL_SPC_SMPTE170M: case AVCOL_SPC_SMPTE240M: cm = VIDEOCOLORMODEL_BT601; break; case AVCOL_SPC_BT709: cm = VIDEOCOLORMODEL_BT709; break; case AVCOL_SPC_UNSPECIFIED: case AVCOL_SPC_RESERVED: case AVCOL_SPC_NB: cm = VIDEOCOLORMODEL_UNKNOWN; break; default: break; } // See XMP Dynamic Media properties from Adobe. // Video Color Space is a limited untranslated string choice depending of video color space value. data = videoColorModelToString(cm); if (!data.isEmpty()) { setXmpTagString("Xmp.video.ColorSpace", data); setXmpTagString("Xmp.xmpDM.videoColorSpace", data); } // ---------- QString fo; switch (codec->field_order) { case AV_FIELD_PROGRESSIVE: fo = QLatin1String("Progressive"); break; case AV_FIELD_TT: // Top coded first, top displayed first case AV_FIELD_BT: // Bottom coded first, top displayed first fo = QLatin1String("Upper"); break; case AV_FIELD_BB: // Bottom coded first, bottom displayed first case AV_FIELD_TB: // Top coded first, bottom displayed first fo = QLatin1String("Lower"); break; default: break; } if (!fo.isEmpty()) { setXmpTagString("Xmp.xmpDM.FieldOrder", fo); } // ---------- QString aspectRatio; int frameRate = -1.0; if (codec->sample_aspect_ratio.num != 0) // Check if undefined by ffmpeg { AVRational displayAspectRatio; av_reduce(&displayAspectRatio.num, &displayAspectRatio.den, codec->width * (int64_t)codec->sample_aspect_ratio.num, codec->height * (int64_t)codec->sample_aspect_ratio.den, 1024 * 1024); aspectRatio = QString::fromLatin1("%1/%2").arg(displayAspectRatio.num) .arg(displayAspectRatio.den); } else if (codec->height) { aspectRatio = QString::fromLatin1("%1/%2").arg(codec->width) .arg(codec->height); } if (stream->avg_frame_rate.den) { frameRate = (double)stream->avg_frame_rate.num / (double)stream->avg_frame_rate.den; } setXmpTagString("Xmp.video.Width", QString::number(codec->width)); setXmpTagString("Xmp.video.FrameWidth", QString::number(codec->width)); setXmpTagString("Xmp.video.SourceImageWidth", QString::number(codec->width)); setXmpTagString("Xmp.video.Height", QString::number(codec->height)); setXmpTagString("Xmp.video.FrameHeight", QString::number(codec->height)); setXmpTagString("Xmp.video.SourceImageHeight", QString::number(codec->height)); setXmpTagString("Xmp.video.FrameSize", QString::fromLatin1("w:%1, h:%2, unit:pixels").arg(codec->width).arg(codec->height)); setXmpTagString("Xmp.xmpDM.videoFrameSize", QString::fromLatin1("w:%1, h:%2, unit:pixels").arg(codec->width).arg(codec->height)); // Backport size in Exif and Iptc setImageDimensions(QSize(codec->width, codec->height)); if (!aspectRatio.isEmpty()) { setXmpTagString("Xmp.video.AspectRatio", aspectRatio); setXmpTagString("Xmp.xmpDM.videoPixelAspectRatio", aspectRatio); } if (frameRate != -1.0) { setXmpTagString("Xmp.video.FrameRate", QString::number(frameRate)); // See XMP Dynamic Media properties from Adobe. // Video Color Space is a limited untranslated string choice depending of video frame rate. // https://documentation.apple.com/en/finalcutpro/usermanual/index.html#chapter=D%26section=4%26tasks=true data = QLatin1String("Other"); if (frameRate == 24.0) data = QLatin1String("24"); else if (frameRate == 23.98 || frameRate == 29.97 || frameRate == 30.0 || frameRate == 59.94) data = QLatin1String("NTSC"); else if (frameRate == 25 || frameRate == 50) data = QLatin1String("PAL"); setXmpTagString("Xmp.xmpDM.videoFrameRate", data); } setXmpTagString("Xmp.video.BitDepth", QString::number(codec->bits_per_coded_sample)); // See XMP Dynamic Media properties from Adobe. // Video Pixel Depth is a limited untranslated string choice depending of amount of samples format. data = s_convertFFMpegFormatToXMP(codec->format); if (!data.isEmpty()) setXmpTagString("Xmp.xmpDM.videoPixelDepth", data); // ----------------------------------------- MetaDataMap vmeta = s_extractFFMpegMetadataEntriesFromDictionary(stream->metadata); qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg video stream metadata entries :"; qCDebug(DIGIKAM_METAENGINE_LOG) << vmeta; qCDebug(DIGIKAM_METAENGINE_LOG) << "-----------------------------------------"; // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("rotate"), // Generic. vmeta); if (!data.isEmpty()) { bool b = false; int val = data.toInt(&b); ImageOrientation ori = ORIENTATION_UNSPECIFIED; if (b) { switch (val) { case 0: ori = ORIENTATION_NORMAL; break; case 90: ori = ORIENTATION_ROT_90; break; case 180: ori = ORIENTATION_ROT_180; break; case 270: ori = ORIENTATION_ROT_270; break; default: break; } setXmpTagString("Xmp.video.Orientation", QString::number(ori)); // Backport orientation in Exif setImageOrientation(ori); } } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("language") // Generic. << QLatin1String("ILNG") // RIFF files. << QLatin1String("LANG"), // RIFF files. vmeta, QStringList() << QLatin1String("Xmp.video.Language")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("creation_time") // Generic. << QLatin1String("_STATISTICS_WRITING_DATE_UTC"), // MKV files. vmeta); if (!data.isEmpty()) { QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime(); setXmpTagString("Xmp.video.TrackCreateDate", QString::number(s_secondsSinceJanuary1904(dt))); setXmpTagString("Xmp.xmpDM.shotDate", dt.toString()); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("handler_name"), // Generic. vmeta, QStringList() << QLatin1String("Xmp.video.HandlerDescription")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TVER") // RIFF files. << QLatin1String("_STATISTICS_WRITING_APP"), // MKV files. vmeta, QStringList() << QLatin1String("Xmp.video.SoftwareVersion")); } // ----------------------------------------- // Subtitle stream parsing // ----------------------------------------- if (!sstream && codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { sstream = true; const char* cname = avcodec_get_name(codec->codec_id); setXmpTagString("Xmp.video.SubTCodec", QString::fromUtf8(cname)); setXmpTagString("Xmp.video.SubTCodecInfo", QString::fromUtf8(avcodec_descriptor_get_by_name(cname)->long_name)); // ----------------------------------------- MetaDataMap smeta = s_extractFFMpegMetadataEntriesFromDictionary(stream->metadata); qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg subtitle stream metadata entries :"; qCDebug(DIGIKAM_METAENGINE_LOG) << smeta; qCDebug(DIGIKAM_METAENGINE_LOG) << "--------------------------------------------"; // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("subtitle") // Generic. << QLatin1String("title"), // Generic. smeta, QStringList() << QLatin1String("Xmp.video.Subtitle")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("language"), // Generic. smeta, QStringList() << QLatin1String("Xmp.video.SubTLang")); } } // ----------------------------------------- // Root container parsing // ----------------------------------------- MetaDataMap rmeta = s_extractFFMpegMetadataEntriesFromDictionary(fmt_ctx->metadata); qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg root container metadata entries :"; qCDebug(DIGIKAM_METAENGINE_LOG) << rmeta; qCDebug(DIGIKAM_METAENGINE_LOG) << "------------------------------------------"; // ---------------------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("major_brand"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.MajorBrand")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("compatible_brands"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.CompatibleBrands")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("minor_version"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.MinorVersion")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("keywords") // Generic. << QLatin1String("IMIT") // RIFF files. << QLatin1String("KEYWORDS") // MKV files. << QLatin1String("com.apple.quicktime.keywords"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.InfoText")); if (!data.isEmpty()) { QStringList keywords = s_keywordsSeparation(data); if (!keywords.isEmpty()) { setXmpKeywords(keywords); setIptcKeywords(QStringList(), keywords); } } // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("category") // Generic. << QLatin1String("ISBJ") // RIFF files. << QLatin1String("SUBJECT"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Subject")); if (!data.isEmpty()) { QStringList categories = s_keywordsSeparation(data); if (!categories.isEmpty()) { setXmpSubCategories(categories); setIptcSubCategories(QStringList(), categories); } } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("premiere_version") // Generic. << QLatin1String("quicktime_version") // Generic. << QLatin1String("ISFT") // Riff files << QLatin1String("com.apple.quicktime.software"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.SoftwareVersion")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("firmware") // Generic. << QLatin1String("com.apple.proapps.serialno"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.FirmwareVersion")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("composer") // Generic. << QLatin1String("COMPOSER"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.Composer") << QLatin1String("Xmp.xmpDM.composer")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("com.apple.quicktime.displayname"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Name")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("playback_requirements"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.Requirements")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("lyrics"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.Lyrics")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("filename"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.FileName")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("disk"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.xmpDM.discNumber")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("performers"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.Performers")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("producer") // Generic. << QLatin1String("PRODUCER") // MKV files. << QLatin1String("com.apple.quicktime.producer"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Producer")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("artist") // Generic. << QLatin1String("album_artist") // Generic. << QLatin1String("original_artist") // Generic. << QLatin1String("com.apple.quicktime.artist") // QT files. << QLatin1String("IART") // RIFF files. << QLatin1String("ARTIST") // MKV files. << QLatin1String("author") // Generic. << QLatin1String("com.apple.quicktime.author"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Artist") << QLatin1String("Xmp.xmpDM.artist")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("director") // Generic. << QLatin1String("DIRC") // RIFF files. << QLatin1String("DIRECTOR") // MKV files. << QLatin1String("com.apple.quicktime.director"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Director") << QLatin1String("Xmp.xmpDM.director")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("media_type") // Generic. << QLatin1String("IMED") // RIFF files. << QLatin1String("ORIGINAL_MEDIA_TYPE"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Medium")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("grouping"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.Grouping")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("BPS"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.MaxBitRate")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISRC"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.ISRCCode")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("CONTENT_TYPE"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.ExtendedContentDescription")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("FPS"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.videoFrameRate") << QLatin1String("Xmp.xmpDM.FrameRate")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("encoder") // Generic. << QLatin1String("ENCODER"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Encoder")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("com.apple.proapps.clipID"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.FileID")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("original_source") // Generic. << QLatin1String("ISRC") // Riff files << QLatin1String("com.apple.proapps.cameraName"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Source")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("original_format") // Generic. << QLatin1String("com.apple.proapps.originalFormat"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Format")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("rating") // Generic. << QLatin1String("IRTD") // RIFF files. << QLatin1String("RATE") // RIFF files. << QLatin1String("RATING") // MKV files. << QLatin1String("com.apple.quicktime.rating.user"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Rating") << QLatin1String("Xmp.video.Rate")); if (!data.isEmpty()) { // Backport rating in Exif and Iptc bool b = false; int rating = data.toInt(&b); if (b) { setImageRating(rating); } } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("make") // Generic. << QLatin1String("com.apple.quicktime.make"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Make")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("model") // Generic. << QLatin1String("com.apple.quicktime.model"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Model") << QLatin1String("Xmp.xmpDM.cameraModel")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("URL") // Generic. << QLatin1String("TURL"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.URL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("title") // Generic. << QLatin1String("INAM") // RIFF files. << QLatin1String("TITL") // RIFF files. << QLatin1String("TITLE") // MKV files. << QLatin1String("com.apple.quicktime.title"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Title") << QLatin1String("Xmp.xmpDM.shotName")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("copyright") // Generic. << QLatin1String("ICOP") // RIFF files. << QLatin1String("COPYRIGHT") // MKV files. << QLatin1String("com.apple.quicktime.copyright"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Copyright") << QLatin1String("Xmp.xmpDM.copyright")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("comment") // Generic. << QLatin1String("description") // Generic. << QLatin1String("CMNT") // Riff Files. << QLatin1String("COMN") // Riff Files. << QLatin1String("ICMT") // Riff Files. << QLatin1String("COMMENT") // MKV Files. << QLatin1String("DESCRIPTION") // MKV Files. << QLatin1String("com.apple.quicktime.description"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Comment") << QLatin1String("Xmp.xmpDM.logComment")); if (!data.isEmpty()) { // Backport comment in Exif and Iptc CaptionsMap capMap; MetaEngine::AltLangMap comMap; comMap.insert(QLatin1String("x-default"), data); capMap.setData(comMap, MetaEngine::AltLangMap(), QString(), MetaEngine::AltLangMap()); setImageComments(capMap); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("synopsis") // Generic. << QLatin1String("SUMMARY") // MKV files. << QLatin1String("SYNOPSIS"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Information")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("lyrics") // Generic. << QLatin1String("LYRICS"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.xmpDM.lyrics")); // -------------- for (int i = 1 ; i <= 9 ; i++) { s_setXmpTagStringFromEntry(this, QStringList() << QString::fromLatin1("IAS%1").arg(i), // RIFF files. rmeta, QStringList() << QString::fromLatin1("Xmp.video.Edit%1").arg(i)); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("encoded_by") // Generic. << QLatin1String("CODE") // RIFF files. << QLatin1String("IECN") // RIFF files. << QLatin1String("ENCODED_BY"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.EncodedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("DISP"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.SchemeTitle")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("AGES") // RIFF files. << QLatin1String("ICRA") // MKV files. << QLatin1String("LAW_RATING"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.Rated")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IBSU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.BaseURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICAS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.DefaultStream")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICDS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.CostumeDesigner")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICMS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Commissioned")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICNM"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Cinematographer")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICNT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Country")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IARL"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.ArchivalLocation")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICRP"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Cropped")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IDIM"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Dimensions")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IDPI"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.DotsPerInch")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IDST") // RIFF files. << QLatin1String("DISTRIBUTED_BY"), // MKV files. rmeta, QStringList() << QLatin1String("Xmp.video.DistributedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IEDT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.EditedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IENG"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Engineer") << QLatin1String("Xmp.xmpDM.engineer")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IKEY"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.PerformerKeywords")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ILGT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Lightness")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ILGU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.LogoURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ILIU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.LogoIconURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IMBI"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.InfoBannerImage")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IMBU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.InfoBannerURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IMIU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.InfoURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IMUS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.MusicBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IPDS"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.ProductionDesigner")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IPLT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.NumOfColors")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IPRD"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Product")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IPRO"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.ProducedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IRIP"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.RippedBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISGN"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.SecondaryGenre")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISHP"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Sharpness")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISRF"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.SourceForm")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISTD"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.ProductionStudio")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ISTR") // RIFF files. << QLatin1String("STAR"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Starring")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ITCH"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Technician")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IWMU"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.WatermarkURL")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("IWRI"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.WrittenBy")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("PRT1"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Part")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("PRT2"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.NumOfParts")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("STAT"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Statistics")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TAPE"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.TapeName")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TCDO"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.EndTimecode")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TCOD"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.StartTimecode")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TLEN"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Length")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("TORG"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.Organization")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("VMAJ"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.VegasVersionMajor")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("VMIN"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.VegasVersionMinor")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("LOCA"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.LocationInfo") << QLatin1String("Xmp.xmpDM.shotLocation")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("album") // Generic. << QLatin1String("com.apple.quicktime.album"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Album") << QLatin1String("Xmp.xmpDM.album")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("genre") // Generic. << QLatin1String("GENR") // RIFF files. << QLatin1String("IGNR") // RIFF files. << QLatin1String("GENRE") // MKV files. << QLatin1String("com.apple.quicktime.genre"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.Genre") << QLatin1String("Xmp.xmpDM.genre")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("track") // Generic. << QLatin1String("TRCK"), // RIFF files. rmeta, QStringList() << QLatin1String("Xmp.video.TrackNumber") << QLatin1String("Xmp.xmpDM.trackNumber")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("year") // Generic. << QLatin1String("YEAR") // RIFF files. << QLatin1String("com.apple.quicktime.year"), rmeta, QStringList() << QLatin1String("Xmp.video.Year")); // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("ICRD") // Riff files << QLatin1String("DATE_DIGITIZED"), // MKV files rmeta, QStringList() << QLatin1String("Xmp.video.DateTimeDigitized")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("creation_time") // Generic. << QLatin1String("DTIM") // RIFF files. << QLatin1String("DATE_RECORDED") // MKV files. << QLatin1String("com.apple.quicktime.creationdate"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.DateTimeOriginal")); if (!data.isEmpty()) { // Backport date in Exif and Iptc. QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime(); setImageDateTime(dt, true); } // -------------- s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("edit_date"), // Generic. rmeta, QStringList() << QLatin1String("Xmp.video.ModificationDate") << QLatin1String("Xmp.xmpDM.videoModDate")); // -------------- data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("date") // Generic. << QLatin1String("DATE_RELEASED"), // MKV files. rmeta); if (!data.isEmpty()) { QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime(); setXmpTagString("Xmp.video.MediaCreateDate", QString::number(s_secondsSinceJanuary1904(dt))); } // -------------- // GPS info as string. ex: "+44.8511-000.6229/" // Defined in ISO 6709:2008. // Notes: altitude can be passed as 3rd values. // each value is separated from others by '-' or '+'. // '/' is always the terminaison character. data = s_setXmpTagStringFromEntry(this, QStringList() << QLatin1String("location") // Generic. << QLatin1String("RECORDING_LOCATION") // MKV files. << QLatin1String("com.apple.quicktime.location.ISO6709"), // QT files. rmeta, QStringList() << QLatin1String("Xmp.video.GPSCoordinates") << QLatin1String("Xmp.xmpDM.shotLocation")); if (!data.isEmpty()) { // Backport location to Exif. QList digits; for (int i = 0 ; i < data.length() ; i++) { QChar c = data[i]; if (c == QLatin1Char('+') || c == QLatin1Char('-') || c == QLatin1Char('/')) { digits << i; } } QString coord; double lattitude = 0.0; double longitude = 0.0; double altitude = 0.0; bool b1 = false; bool b2 = false; bool b3 = false; if (digits.size() > 1) { coord = data.mid(digits[0], digits[1] - digits[0]); lattitude = coord.toDouble(&b1); } if (digits.size() > 2) { coord = data.mid(digits[1], digits[2] - digits[1]); longitude = coord.toDouble(&b2); } if (digits.size() > 3) { coord = data.mid(digits[2], digits[3] - digits[2]); altitude = coord.toDouble(&b3); } if (b1 && b2) { if (b3) { // All GPS values are available. setGPSInfo(altitude, lattitude, longitude); setXmpTagString("Xmp.video.GPSAltitude", getXmpTagString("Xmp.exif.GPSAltitude")); setXmpTagString("Xmp.exif.GPSAltitude", getXmpTagString("Xmp.exif.GPSAltitude")); } else { // No altitude available. double* alt = 0; setGPSInfo(alt, lattitude, longitude); } setXmpTagString("Xmp.video.GPSLatitude", getXmpTagString("Xmp.exif.GPSLatitude")); setXmpTagString("Xmp.video.GPSLongitude", getXmpTagString("Xmp.exif.GPSLongitude")); setXmpTagString("Xmp.video.GPSMapDatum", getXmpTagString("Xmp.exif.GPSMapDatum")); setXmpTagString("Xmp.video.GPSVersionID", getXmpTagString("Xmp.exif.GPSVersionID")); setXmpTagString("Xmp.exif.GPSLatitude", getXmpTagString("Xmp.exif.GPSLatitude")); setXmpTagString("Xmp.exif.GPSLongitude", getXmpTagString("Xmp.exif.GPSLongitude")); setXmpTagString("Xmp.exif.GPSMapDatum", getXmpTagString("Xmp.exif.GPSMapDatum")); setXmpTagString("Xmp.exif.GPSVersionID", getXmpTagString("Xmp.exif.GPSVersionID")); } } avformat_close_input(&fmt_ctx); QFileInfo fi(filePath); if (getXmpTagString("Xmp.video.FileName").isNull()) setXmpTagString("Xmp.video.FileName", fi.fileName()); if (getXmpTagString("Xmp.video.FileSize").isNull()) setXmpTagString("Xmp.video.FileSize", QString::number(fi.size() / (1024*1024))); if (getXmpTagString("Xmp.video.FileType").isNull()) setXmpTagString("Xmp.video.FileType", fi.suffix()); if (getXmpTagString("Xmp.video.MimeType").isNull()) setXmpTagString("Xmp.video.MimeType", QMimeDatabase().mimeTypeForFile(filePath).name()); return true; #else Q_UNUSED(filePath); return false; #endif } QString DMetadata::videoColorModelToString(VIDEOCOLORMODEL videoColorModel) { QString cs; switch (videoColorModel) { case VIDEOCOLORMODEL_SRGB: cs = QLatin1String("sRGB"); break; case VIDEOCOLORMODEL_BT601: cs = QLatin1String("CCIR-601"); break; case VIDEOCOLORMODEL_BT709: cs = QLatin1String("CCIR-709"); break; case VIDEOCOLORMODEL_OTHER: cs = QLatin1String("Other"); break; default: // VIDEOCOLORMODEL_UNKNOWN break; } return cs; } } // namespace Digikam