diff --git a/core/libs/metadataengine/dmetadata/dmetadata_video.cpp b/core/libs/metadataengine/dmetadata/dmetadata_video.cpp index 0c92459974..eceb7dbc9b 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_video.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_video.cpp @@ -1,1669 +1,1678 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2018-02-26 * Description : item metadata interface - video helpers. * * 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 // KDE includes #include // Local includes #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(QLatin1Char('/')); if (keywords.isEmpty()) { keywords = data.split(QLatin1Char(',')); if (keywords.isEmpty()) { keywords = data.split(QLatin1Char(' ')); } } 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) { QString entryValue = QString::fromUtf8(entry->value); if (QString::fromUtf8(entry->key) == QLatin1String("creation_time")) { if (entryValue.endsWith(QLatin1Char('Z'), Qt::CaseInsensitive)) { entryValue.chop(1); } if (QDateTime::fromString(entryValue, Qt::ISODate).toMSecsSinceEpoch() == 0) { continue; } } meta.insert(QString::fromUtf8(entry->key), entryValue); } } while (entry); return meta; } #endif bool DMetadata::loadUsingFFmpeg(const QString& filePath) { #ifdef HAVE_MEDIAPLAYER qCDebug(DIGIKAM_METAENGINE_LOG) << "Parse metadada with FFMpeg:" << filePath; #if LIBAVFORMAT_VERSION_MAJOR < 58 av_register_all(); #endif 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]; + + if (!stream) + continue; + AVCodecParameters* const codec = stream->codecpar; + if (!codec) + continue; + + const char* cname = avcodec_get_name(codec->codec_id); + + if (QLatin1String(cname) == QLatin1String("none")) + continue; + // ----------------------------------------- // Audio stream parsing // ----------------------------------------- if (!astream && codec->codec_type == AVMEDIA_TYPE_AUDIO) { - astream = true; - const char* cname = avcodec_get_name(codec->codec_id); + astream = true; 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); + vstream = true; 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 setItemDimensions(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 setItemOrientation(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); + sstream = true; 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) { setItemRating(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()); setItemComments(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; } VideoInfoContainer DMetadata::getVideoInformation() const { VideoInfoContainer videoInfo; if (hasXmp()) { if (videoInfo.aspectRatio.isEmpty()) { videoInfo.aspectRatio = getMetadataField(MetadataInfo::AspectRatio).toString(); } if (videoInfo.audioBitRate.isEmpty()) { videoInfo.audioBitRate = getXmpTagString("Xmp.audio.SampleRate"); } if (videoInfo.audioChannelType.isEmpty()) { videoInfo.audioChannelType = getXmpTagString("Xmp.audio.ChannelType"); } if (videoInfo.audioCodec.isEmpty()) { videoInfo.audioCodec = getXmpTagString("Xmp.audio.Codec"); } if (videoInfo.duration.isEmpty()) { videoInfo.duration = getXmpTagString("Xmp.video.duration"); } if (videoInfo.frameRate.isEmpty()) { videoInfo.frameRate = getXmpTagString("Xmp.video.FrameRate"); } if (videoInfo.videoCodec.isEmpty()) { videoInfo.videoCodec = getXmpTagString("Xmp.video.Codec"); } } return videoInfo; } } // namespace Digikam diff --git a/core/libs/threadimageio/video/videodecoder.cpp b/core/libs/threadimageio/video/videodecoder.cpp index dcb2cb4515..eae994486e 100644 --- a/core/libs/threadimageio/video/videodecoder.cpp +++ b/core/libs/threadimageio/video/videodecoder.cpp @@ -1,267 +1,271 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2016-04-21 * Description : video thumbnails extraction based on ffmpeg * * Copyright (C) 2010 by Dirk Vanden Boer * Copyright (C) 2016-2018 by Maik Qualmann * Copyright (C) 2016-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 "videodecoder.h" #include "videodecoder_p.h" // Local includes #include "digikam_debug.h" namespace Digikam { VideoDecoder::VideoDecoder(const QString& filename) : d(new Private) { initialize(filename); } VideoDecoder::~VideoDecoder() { destroy(); delete d; } void VideoDecoder::initialize(const QString& filename) { d->lastWidth = -1; d->lastHeight = -1; d->lastPixfmt = AV_PIX_FMT_NONE; #if LIBAVFORMAT_VERSION_MAJOR < 58 av_register_all(); #endif #if LIBAVCODEC_VERSION_MAJOR < 58 avcodec_register_all(); #endif if (avformat_open_input(&d->pFormatContext, filename.toUtf8().data(), NULL, NULL) != 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Could not open input file: " << filename; return; } if (avformat_find_stream_info(d->pFormatContext, 0) < 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Could not find stream information"; return; } - d->initializeVideo(); + if (!d->initializeVideo()) + { + return; + } + d->pFrame = av_frame_alloc(); if (d->pFrame) { d->initialized = true; } } bool VideoDecoder::getInitialized() const { return d->initialized; } void VideoDecoder::destroy() { d->deleteFilterGraph(); if (d->pVideoCodecContext) { avcodec_close(d->pVideoCodecContext); d->pVideoCodecContext = 0; } if (d->pFormatContext) { avformat_close_input(&d->pFormatContext); d->pFormatContext = 0; } if (d->pPacket) { av_packet_unref(d->pPacket); delete d->pPacket; d->pPacket = 0; } if (d->pFrame) { av_frame_free(&d->pFrame); d->pFrame = 0; } if (d->pFrameBuffer) { av_free(d->pFrameBuffer); d->pFrameBuffer = 0; } } QString VideoDecoder::getCodec() const { QString codecName; if (d->pVideoCodec) { codecName = QLatin1String(d->pVideoCodec->name); } return codecName; } int VideoDecoder::getWidth() const { if (d->pVideoCodecContext) { return d->pVideoCodecContext->width; } return -1; } int VideoDecoder::getHeight() const { if (d->pVideoCodecContext) { return d->pVideoCodecContext->height; } return -1; } int VideoDecoder::getDuration() const { if (d->pFormatContext) { return static_cast(d->pFormatContext->duration / AV_TIME_BASE); } return 0; } void VideoDecoder::seek(int timeInSeconds) { if (!d->allowSeek) { return; } qint64 timestamp = AV_TIME_BASE * static_cast(timeInSeconds); if (timestamp < 0) { timestamp = 0; } int ret = av_seek_frame(d->pFormatContext, -1, timestamp, 0); if (ret >= 0) { avcodec_flush_buffers(d->pVideoCodecContext); } else { qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed"; return; } int keyFrameAttempts = 0; bool gotFrame = 0; do { int count = 0; gotFrame = 0; while (!gotFrame && count < 20) { d->getVideoPacket(); gotFrame = d->decodeVideoPacket(); count++; } keyFrameAttempts++; } while ((!gotFrame || !d->pFrame->key_frame) && keyFrameAttempts < 200); if (gotFrame == 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed"; } } bool VideoDecoder::decodeVideoFrame() const { bool frameFinished = false; while (!frameFinished && d->getVideoPacket()) { frameFinished = d->decodeVideoPacket(); } if (!frameFinished) { qDebug(DIGIKAM_GENERAL_LOG) << "decodeVideoFrame() failed: frame not finished"; } return frameFinished; } void VideoDecoder::getScaledVideoFrame(int scaledSize, bool maintainAspectRatio, VideoFrame& videoFrame) { if (d->pFrame->interlaced_frame) { d->processFilterGraph(d->pFrame, d->pFrame, d->pVideoCodecContext->pix_fmt, d->pVideoCodecContext->width, d->pVideoCodecContext->height); } int scaledWidth, scaledHeight; d->convertAndScaleFrame(AV_PIX_FMT_RGB24, scaledSize, maintainAspectRatio, scaledWidth, scaledHeight); videoFrame.width = scaledWidth; videoFrame.height = scaledHeight; videoFrame.lineSize = d->pFrame->linesize[0]; videoFrame.frameData.clear(); videoFrame.frameData.resize(videoFrame.lineSize * videoFrame.height); memcpy((&(videoFrame.frameData.front())), d->pFrame->data[0], videoFrame.lineSize * videoFrame.height); } } // namespace Digikam diff --git a/core/libs/threadimageio/video/videodecoder_p.cpp b/core/libs/threadimageio/video/videodecoder_p.cpp index ea634eec9a..836a667464 100644 --- a/core/libs/threadimageio/video/videodecoder_p.cpp +++ b/core/libs/threadimageio/video/videodecoder_p.cpp @@ -1,429 +1,432 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2016-04-21 * Description : video thumbnails extraction based on ffmpeg * * Copyright (C) 2010 by Dirk Vanden Boer * Copyright (C) 2016-2018 by Maik Qualmann * Copyright (C) 2016-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 "videodecoder_p.h" // Local includes #include "digikam_debug.h" namespace Digikam { VideoDecoder::Private::Private() { videoStream = -1; pFormatContext = 0; pVideoCodecContext = 0; pVideoCodecParameters = 0; pVideoCodec = 0; pVideoStream = 0; pFrame = 0; pFrameBuffer = 0; pPacket = 0; allowSeek = true; initialized = false; bufferSinkContext = 0; bufferSourceContext = 0; filterGraph = 0; filterFrame = 0; lastWidth = 0; lastHeight = 0; lastPixfmt = AV_PIX_FMT_NONE; } VideoDecoder::Private::~Private() { } void VideoDecoder::Private::createAVFrame(AVFrame** const avFrame, quint8** const frameBuffer, int width, int height, AVPixelFormat format) { *avFrame = av_frame_alloc(); int numBytes = av_image_get_buffer_size(format, width, height, 1); *frameBuffer = reinterpret_cast(av_malloc(numBytes)); av_image_fill_arrays((*avFrame)->data, (*avFrame)->linesize, *frameBuffer, format, width, height, 1); } -void VideoDecoder::Private::initializeVideo() +bool VideoDecoder::Private::initializeVideo() { for (unsigned int i = 0 ; i < pFormatContext->nb_streams ; i++) { if (pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { pVideoStream = pFormatContext->streams[i]; videoStream = i; break; } } if (videoStream == -1) { qDebug(DIGIKAM_GENERAL_LOG) << "Could not find video stream"; - return; + return false; } pVideoCodecParameters = pFormatContext->streams[videoStream]->codecpar; pVideoCodec = avcodec_find_decoder(pVideoCodecParameters->codec_id); if (pVideoCodec == 0) { // set to 0, otherwise avcodec_close(d->pVideoCodecContext) crashes pVideoCodecContext = 0; qDebug(DIGIKAM_GENERAL_LOG) << "Video Codec not found"; - return; + return false; } pVideoCodecContext = avcodec_alloc_context3(pVideoCodec); avcodec_parameters_to_context(pVideoCodecContext, pVideoCodecParameters); if (avcodec_open2(pVideoCodecContext, pVideoCodec, 0) < 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Could not open video codec"; + return false; } + + return true; } bool VideoDecoder::Private::decodeVideoPacket() const { if (pPacket->stream_index != videoStream) { return false; } av_frame_unref(pFrame); int frameFinished = 0; #if LIBAVCODEC_VERSION_MAJOR < 53 int bytesDecoded = avcodec_decode_video(pVideoCodecContext, pFrame, &frameFinished, pPacket->data, pPacket->size); #else int bytesDecoded = decodeVideoNew(pVideoCodecContext, pFrame, &frameFinished, pPacket); #endif if (bytesDecoded < 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Failed to decode video frame: bytesDecoded < 0"; } return (frameFinished > 0); } int VideoDecoder::Private::decodeVideoNew(AVCodecContext* const avContext, AVFrame* const avFrame, int* gotFrame, AVPacket* const avPacket) const { int ret = 0; *gotFrame = 0; if (avPacket) { ret = avcodec_send_packet(avContext, avPacket); // In particular, we don't expect AVERROR(EAGAIN), because we read all // decoded frames with avcodec_receive_frame() until done. if (ret < 0) { return (ret == AVERROR_EOF ? 0 : ret); } } ret = avcodec_receive_frame(avContext, avFrame); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { return ret; } if (ret >= 0) { *gotFrame = 1; } return 0; } bool VideoDecoder::Private::getVideoPacket() { bool framesAvailable = true; bool frameDecoded = false; int attempts = 0; if (pPacket) { av_packet_unref(pPacket); delete pPacket; } pPacket = new AVPacket(); while (framesAvailable && !frameDecoded && (attempts++ < 1000)) { framesAvailable = (av_read_frame(pFormatContext, pPacket) >= 0); if (framesAvailable) { frameDecoded = (pPacket->stream_index == videoStream); if (!frameDecoded) { av_packet_unref(pPacket); } } } return frameDecoded; } void VideoDecoder::Private::deleteFilterGraph() { if (filterGraph) { av_frame_free(&filterFrame); avfilter_graph_free(&filterGraph); filterGraph = 0; } } bool VideoDecoder::Private::initFilterGraph(enum AVPixelFormat pixfmt, int width, int height) { AVFilterInOut* inputs = 0; AVFilterInOut* outputs = 0; deleteFilterGraph(); filterGraph = avfilter_graph_alloc(); QByteArray arguments("buffer="); arguments += "video_size=" + QByteArray::number(width) + 'x' + QByteArray::number(height) + ':'; arguments += "pix_fmt=" + QByteArray::number(pixfmt) + ':'; arguments += "time_base=1/1:pixel_aspect=0/1[in];"; arguments += "[in]yadif[out];"; arguments += "[out]buffersink"; int ret = avfilter_graph_parse2(filterGraph, arguments.constData(), &inputs, &outputs); if (ret < 0) { qWarning(DIGIKAM_GENERAL_LOG) << "Unable to parse filter graph"; return false; } if (inputs || outputs) { return -1; } ret = avfilter_graph_config(filterGraph, nullptr); if (ret < 0) { qWarning(DIGIKAM_GENERAL_LOG) << "Unable to validate filter graph"; return false; } bufferSourceContext = avfilter_graph_get_filter(filterGraph, "Parsed_buffer_0"); bufferSinkContext = avfilter_graph_get_filter(filterGraph, "Parsed_buffersink_2"); if (!bufferSourceContext || !bufferSinkContext) { qWarning(DIGIKAM_GENERAL_LOG) << "Unable to get source or sink"; return false; } filterFrame = av_frame_alloc(); lastWidth = width; lastHeight = height; lastPixfmt = pixfmt; return true; } bool VideoDecoder::Private::processFilterGraph(AVFrame* const dst, const AVFrame* const src, enum AVPixelFormat pixfmt, int width, int height) { if (!filterGraph || width != lastWidth || height != lastHeight || pixfmt != lastPixfmt) { if (!initFilterGraph(pixfmt, width, height)) { return false; } } memcpy(filterFrame->data, src->data, sizeof(src->data)); memcpy(filterFrame->linesize, src->linesize, sizeof(src->linesize)); filterFrame->width = width; filterFrame->height = height; filterFrame->format = pixfmt; int ret = av_buffersrc_add_frame(bufferSourceContext, filterFrame); if (ret < 0) { return false; } ret = av_buffersink_get_frame(bufferSinkContext, filterFrame); if (ret < 0) { return false; } av_image_copy(dst->data, dst->linesize, (const uint8_t**)filterFrame->data, filterFrame->linesize, pixfmt, width, height); av_frame_unref(filterFrame); return true; } void VideoDecoder::Private::convertAndScaleFrame(AVPixelFormat format, int scaledSize, bool maintainAspectRatio, int& scaledWidth, int& scaledHeight) { AVPixelFormat pVideoCodecContextPixFormat; pVideoCodecContextPixFormat = pVideoCodecContext->pix_fmt; #if LIBAVUTIL_VERSION_MAJOR > 55 switch (pVideoCodecContextPixFormat) { case AV_PIX_FMT_YUVJ420P: pVideoCodecContextPixFormat = AV_PIX_FMT_YUV420P; break; case AV_PIX_FMT_YUVJ422P: pVideoCodecContextPixFormat = AV_PIX_FMT_YUV422P; break; case AV_PIX_FMT_YUVJ444P: pVideoCodecContextPixFormat = AV_PIX_FMT_YUV444P; break; case AV_PIX_FMT_YUVJ440P: pVideoCodecContextPixFormat = AV_PIX_FMT_YUV440P; break; default: break; } #endif calculateDimensions(scaledSize, maintainAspectRatio, scaledWidth, scaledHeight); SwsContext* const scaleContext = sws_getContext(pVideoCodecContext->width, pVideoCodecContext->height, pVideoCodecContextPixFormat, scaledWidth, scaledHeight, format, SWS_BICUBIC, NULL, NULL, NULL); if (!scaleContext) { qDebug(DIGIKAM_GENERAL_LOG) << "Failed to create resize context"; return; } AVFrame* convertedFrame = 0; uint8_t* convertedFrameBuffer = 0; createAVFrame(&convertedFrame, &convertedFrameBuffer, scaledWidth, scaledHeight, format); sws_scale(scaleContext, pFrame->data, pFrame->linesize, 0, pVideoCodecContext->height, convertedFrame->data, convertedFrame->linesize); sws_freeContext(scaleContext); av_frame_free(&pFrame); av_free(pFrameBuffer); pFrame = convertedFrame; pFrameBuffer = convertedFrameBuffer; } void VideoDecoder::Private::calculateDimensions(int squareSize, bool maintainAspectRatio, int& destWidth, int& destHeight) { if (!maintainAspectRatio) { destWidth = squareSize; destHeight = squareSize; } else { int srcWidth = pVideoCodecContext->width; int srcHeight = pVideoCodecContext->height; int ascpectNominator = pVideoCodecContext->sample_aspect_ratio.num; int ascpectDenominator = pVideoCodecContext->sample_aspect_ratio.den; if (ascpectNominator != 0 && ascpectDenominator != 0) { srcWidth = srcWidth * ascpectNominator / ascpectDenominator; } if (srcWidth > srcHeight) { destWidth = squareSize; destHeight = static_cast(static_cast(squareSize) / srcWidth * srcHeight); } else { destWidth = static_cast(static_cast(squareSize) / srcHeight * srcWidth); destHeight = squareSize; } } } } // namespace Digikam diff --git a/core/libs/threadimageio/video/videodecoder_p.h b/core/libs/threadimageio/video/videodecoder_p.h index f0c79e38c7..b7747b3df9 100644 --- a/core/libs/threadimageio/video/videodecoder_p.h +++ b/core/libs/threadimageio/video/videodecoder_p.h @@ -1,118 +1,118 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2016-04-21 * Description : video thumbnails extraction based on ffmpeg * * Copyright (C) 2010 by Dirk Vanden Boer * Copyright (C) 2016-2018 by Maik Qualmann * Copyright (C) 2016-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. * * ============================================================ */ #ifndef DIGIKAM_VIDEO_DECODER_PRIVATE_H #define DIGIKAM_VIDEO_DECODER_PRIVATE_H #include "videodecoder.h" // FFMpeg includes extern "C" { #include #include #include #include #include #include #include } namespace Digikam { class Q_DECL_HIDDEN VideoDecoder::Private { public: explicit Private(); ~Private(); public: int videoStream; AVFormatContext* pFormatContext; AVCodecContext* pVideoCodecContext; AVCodecParameters* pVideoCodecParameters; AVCodec* pVideoCodec; AVStream* pVideoStream; AVFrame* pFrame; quint8* pFrameBuffer; AVPacket* pPacket; bool allowSeek; bool initialized; AVFilterContext* bufferSinkContext; AVFilterContext* bufferSourceContext; AVFilterGraph* filterGraph; AVFrame* filterFrame; int lastWidth; int lastHeight; enum AVPixelFormat lastPixfmt; public: - void initializeVideo(); + bool initializeVideo(); bool getVideoPacket(); bool decodeVideoPacket() const; void convertAndScaleFrame(AVPixelFormat format, int scaledSize, bool maintainAspectRatio, int& scaledWidth, int& scaledHeight); bool processFilterGraph(AVFrame* const dst, const AVFrame* const src, enum AVPixelFormat pixfmt, int width, int height); void deleteFilterGraph(); private: bool initFilterGraph(enum AVPixelFormat pixfmt, int width, int height); void calculateDimensions(int squareSize, bool maintainAspectRatio, int& destWidth, int& destHeight); void createAVFrame(AVFrame** const avFrame, quint8** const frameBuffer, int width, int height, AVPixelFormat format); // cppcheck-suppress unusedPrivateFunction int decodeVideoNew(AVCodecContext* const avContext, AVFrame* const avFrame, int* gotFrame, AVPacket* const avPacket) const; }; } // namespace Digikam #endif // DIGIKAM_VIDEO_DECODER_PRIVATE_H