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
23extern "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
31using namespace KFileMetaData;
32
33FFmpegExtractor::FFmpegExtractor(QObject* parent)
34 : ExtractorPlugin(parent)
35{
36}
37
38const 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
53QStringList FFmpegExtractor::mimetypes() const
54{
55 return supportedMimeTypes;
56}
57
58void FFmpegExtractor::extract(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

source code of kfilemetadata/src/extractors/ffmpegextractor.cpp