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 | |