| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2012-2014 Vishesh Handa <me@vhanda.in> |
| 3 | |
| 4 | Code adapted from Strigi FFmpeg Analyzer - |
| 5 | SPDX-FileCopyrightText: 2010 Evgeny Egorochkin <phreedom.stdin@gmail.com> |
| 6 | SPDX-FileCopyrightText: 2011 Tirtha Chatterjee <tirtha.p.chatterjee@gmail.com> |
| 7 | |
| 8 | SPDX-License-Identifier: LGPL-2.1-or-later |
| 9 | */ |
| 10 | |
| 11 | |
| 12 | #include "ffmpegextractor.h" |
| 13 | #include "kfilemetadata_debug.h" |
| 14 | |
| 15 | #ifdef __cplusplus |
| 16 | #define __STDC_CONSTANT_MACROS |
| 17 | #ifdef _STDINT_H |
| 18 | #undef _STDINT_H |
| 19 | #endif |
| 20 | # include <stdint.h> |
| 21 | #endif |
| 22 | |
| 23 | extern "C" { |
| 24 | #include <libavcodec/avcodec.h> |
| 25 | #include <libavformat/avformat.h> |
| 26 | #include <libavutil/avutil.h> |
| 27 | #include <libavutil/dict.h> |
| 28 | #include <libavutil/pixdesc.h> |
| 29 | } |
| 30 | |
| 31 | using namespace KFileMetaData; |
| 32 | |
| 33 | FFmpegExtractor::(QObject* parent) |
| 34 | : ExtractorPlugin(parent) |
| 35 | { |
| 36 | } |
| 37 | |
| 38 | const QStringList supportedMimeTypes = { |
| 39 | QStringLiteral("video/mp2t" ), |
| 40 | QStringLiteral("video/mp4" ), |
| 41 | QStringLiteral("video/mpeg" ), |
| 42 | QStringLiteral("video/ogg" ), |
| 43 | QStringLiteral("video/quicktime" ), |
| 44 | QStringLiteral("video/vnd.avi" ), |
| 45 | QStringLiteral("video/webm" ), |
| 46 | QStringLiteral("video/x-flv" ), |
| 47 | QStringLiteral("video/x-matroska" ), |
| 48 | QStringLiteral("video/x-ms-asf" ), |
| 49 | QStringLiteral("video/x-ms-wmv" ), |
| 50 | QStringLiteral("video/x-msvideo" ), |
| 51 | }; |
| 52 | |
| 53 | QStringList FFmpegExtractor::() const |
| 54 | { |
| 55 | return supportedMimeTypes; |
| 56 | } |
| 57 | |
| 58 | void FFmpegExtractor::(ExtractionResult* result) |
| 59 | { |
| 60 | AVFormatContext* fmt_ctx = nullptr; |
| 61 | |
| 62 | #if LIBAVFORMAT_VERSION_MAJOR < 58 |
| 63 | av_register_all(); |
| 64 | #endif |
| 65 | |
| 66 | QByteArray arr = result->inputUrl().toUtf8(); |
| 67 | |
| 68 | fmt_ctx = avformat_alloc_context(); |
| 69 | if (int ret = avformat_open_input(ps: &fmt_ctx, url: arr.data(), fmt: nullptr, options: nullptr)) { |
| 70 | qCWarning(KFILEMETADATA_LOG) << "avformat_open_input error: " << ret; |
| 71 | return; |
| 72 | } |
| 73 | |
| 74 | int ret = avformat_find_stream_info(ic: fmt_ctx, options: nullptr); |
| 75 | if (ret < 0) { |
| 76 | qCWarning(KFILEMETADATA_LOG) << "avform_find_stream_info error: " << ret; |
| 77 | return; |
| 78 | } |
| 79 | |
| 80 | result->addType(type: Type::Video); |
| 81 | |
| 82 | if (result->inputFlags() & ExtractionResult::ExtractMetaData) { |
| 83 | int totalSecs = fmt_ctx->duration / AV_TIME_BASE; |
| 84 | int bitrate = fmt_ctx->bit_rate; |
| 85 | |
| 86 | result->add(property: Property::Duration, value: totalSecs); |
| 87 | result->add(property: Property::BitRate, value: bitrate); |
| 88 | |
| 89 | const int index_stream = av_find_default_stream_index(s: fmt_ctx); |
| 90 | if (index_stream >= 0) { |
| 91 | AVStream* stream = fmt_ctx->streams[index_stream]; |
| 92 | |
| 93 | const AVCodecParameters* codec = stream->codecpar; |
| 94 | |
| 95 | if (codec->codec_type == AVMEDIA_TYPE_VIDEO) { |
| 96 | result->add(property: Property::Width, value: codec->width); |
| 97 | result->add(property: Property::Height, value: codec->height); |
| 98 | |
| 99 | AVRational avSampleAspectRatio = av_guess_sample_aspect_ratio(format: fmt_ctx, stream, frame: nullptr); |
| 100 | AVRational avDisplayAspectRatio; |
| 101 | av_reduce(dst_num: &avDisplayAspectRatio.num, dst_den: &avDisplayAspectRatio.den, |
| 102 | num: codec->width * avSampleAspectRatio.num, |
| 103 | den: codec->height * avSampleAspectRatio.den, |
| 104 | max: 1024*1024); |
| 105 | double displayAspectRatio = avDisplayAspectRatio.num; |
| 106 | if (avDisplayAspectRatio.den) { |
| 107 | displayAspectRatio /= avDisplayAspectRatio.den; |
| 108 | } |
| 109 | if (displayAspectRatio) { |
| 110 | result->add(property: Property::AspectRatio, value: displayAspectRatio); |
| 111 | } |
| 112 | |
| 113 | AVRational avFrameRate = av_guess_frame_rate(ctx: fmt_ctx, stream, frame: nullptr); |
| 114 | double frameRate = avFrameRate.num; |
| 115 | if (avFrameRate.den) { |
| 116 | frameRate /= avFrameRate.den; |
| 117 | } |
| 118 | if (frameRate) { |
| 119 | result->add(property: Property::FrameRate, value: frameRate); |
| 120 | } |
| 121 | |
| 122 | result->add(property: Property::VideoCodec, value: QString::fromUtf8(utf8: avcodec_get_name(id: codec->codec_id))); |
| 123 | |
| 124 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt: (AVPixelFormat)codec->format); |
| 125 | if (desc) { |
| 126 | result->add(property: Property::PixelFormat, value: QString::fromUtf8(utf8: desc->name)); |
| 127 | } |
| 128 | |
| 129 | result->add(property: Property::ColorSpace, value: QString::fromUtf8(utf8: av_color_space_name(space: codec->color_space))); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | const auto audio_index_stream = av_find_best_stream(ic: fmt_ctx, type: AVMEDIA_TYPE_AUDIO, wanted_stream_nb: -1, related_stream: -1, decoder_ret: nullptr, flags: 0); |
| 134 | if (audio_index_stream >= 0) { |
| 135 | AVStream* stream = fmt_ctx->streams[audio_index_stream]; |
| 136 | |
| 137 | const AVCodecParameters* codec = stream->codecpar; |
| 138 | result->add(property: Property::AudioCodec, value: QString::fromUtf8(utf8: avcodec_get_name(id: codec->codec_id))); |
| 139 | } |
| 140 | |
| 141 | AVDictionary* dict = fmt_ctx->metadata; |
| 142 | // In Ogg, the internal comment metadata headers are attached to a single content stream. |
| 143 | // By convention, it's the first logical bitstream occuring. |
| 144 | if (!dict && fmt_ctx->nb_streams > 0) { |
| 145 | dict = fmt_ctx->streams[0]->metadata; |
| 146 | } |
| 147 | |
| 148 | AVDictionaryEntry* entry; |
| 149 | |
| 150 | entry = av_dict_get(m: dict, key: "title" , prev: nullptr, flags: 0); |
| 151 | if (entry) { |
| 152 | result->add(property: Property::Title, value: QString::fromUtf8(utf8: entry->value)); |
| 153 | } |
| 154 | |
| 155 | |
| 156 | entry = av_dict_get(m: dict, key: "author" , prev: nullptr, flags: 0); |
| 157 | if (entry) { |
| 158 | result->add(property: Property::Author, value: QString::fromUtf8(utf8: entry->value)); |
| 159 | } |
| 160 | |
| 161 | entry = av_dict_get(m: dict, key: "copyright" , prev: nullptr, flags: 0); |
| 162 | if (entry) { |
| 163 | result->add(property: Property::Copyright, value: QString::fromUtf8(utf8: entry->value)); |
| 164 | } |
| 165 | |
| 166 | entry = av_dict_get(m: dict, key: "comment" , prev: nullptr, flags: 0); |
| 167 | if (entry) { |
| 168 | result->add(property: Property::Comment, value: QString::fromUtf8(utf8: entry->value)); |
| 169 | } |
| 170 | |
| 171 | entry = av_dict_get(m: dict, key: "album" , prev: nullptr, flags: 0); |
| 172 | if (entry) { |
| 173 | result->add(property: Property::Album, value: QString::fromUtf8(utf8: entry->value)); |
| 174 | } |
| 175 | |
| 176 | entry = av_dict_get(m: dict, key: "genre" , prev: nullptr, flags: 0); |
| 177 | if (entry) { |
| 178 | result->add(property: Property::Genre, value: QString::fromUtf8(utf8: entry->value)); |
| 179 | } |
| 180 | |
| 181 | entry = av_dict_get(m: dict, key: "track" , prev: nullptr, flags: 0); |
| 182 | if (entry) { |
| 183 | QString value = QString::fromUtf8(utf8: entry->value); |
| 184 | |
| 185 | bool ok = false; |
| 186 | int track = value.toInt(ok: &ok); |
| 187 | if (ok && track) { |
| 188 | result->add(property: Property::TrackNumber, value: track); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | entry = av_dict_get(m: dict, key: "year" , prev: nullptr, flags: 0); |
| 193 | if (entry) { |
| 194 | int year = QString::fromUtf8(utf8: entry->value).toInt(); |
| 195 | result->add(property: Property::ReleaseYear, value: year); |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | avformat_close_input(s: &fmt_ctx); |
| 200 | } |
| 201 | |
| 202 | #include "moc_ffmpegextractor.cpp" |
| 203 | |