/*
    SPDX-FileCopyrightText: 2012-2014 Vishesh Handa <me@vhanda.in>

    Code adapted from Strigi FFmpeg Analyzer -
    SPDX-FileCopyrightText: 2010 Evgeny Egorochkin <phreedom.stdin@gmail.com>
    SPDX-FileCopyrightText: 2011 Tirtha Chatterjee <tirtha.p.chatterjee@gmail.com>

    SPDX-License-Identifier: LGPL-2.1-or-later
*/


#include "ffmpegextractor.h"
#include "kfilemetadata_debug.h"

#ifdef __cplusplus
#define __STDC_CONSTANT_MACROS
#ifdef _STDINT_H
#undef _STDINT_H
#endif
# include <stdint.h>
#endif

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/dict.h>
#include <libavutil/pixdesc.h>
}

using namespace KFileMetaData;

FFmpegExtractor::FFmpegExtractor(QObject* parent)
    : ExtractorPlugin(parent)
{
}

const QStringList supportedMimeTypes = {
    QStringLiteral("video/mp2t"),
    QStringLiteral("video/mp4"),
    QStringLiteral("video/mpeg"),
    QStringLiteral("video/ogg"),
    QStringLiteral("video/quicktime"),
    QStringLiteral("video/vnd.avi"),
    QStringLiteral("video/webm"),
    QStringLiteral("video/x-flv"),
    QStringLiteral("video/x-matroska"),
    QStringLiteral("video/x-ms-asf"),
    QStringLiteral("video/x-ms-wmv"),
    QStringLiteral("video/x-msvideo"),
};

QStringList FFmpegExtractor::mimetypes() const
{
    return supportedMimeTypes;
}

void FFmpegExtractor::extract(ExtractionResult* result)
{
    AVFormatContext* fmt_ctx = nullptr;

#if LIBAVFORMAT_VERSION_MAJOR < 58
    av_register_all();
#endif

    fmt_ctx = avformat_alloc_context();
    if (!fmt_ctx) {
        qCWarning(KFILEMETADATA_LOG) << "Failed to allocate format context";
        return;
    }

    QByteArray arr = result->inputUrl().toUtf8();
    if (int ret = avformat_open_input(&fmt_ctx, arr.data(), nullptr, nullptr)) {
        qCWarning(KFILEMETADATA_LOG) << "avformat_open_input error: " << ret;
        // The AVFormatContext will be freed on failure, no need to release it explicitly
        return;
    }

    auto guard = qScopeGuard([&fmt_ctx]() {
        avformat_close_input(&fmt_ctx);
    });

    int ret = avformat_find_stream_info(fmt_ctx, nullptr);
    if (ret < 0) {
        qCWarning(KFILEMETADATA_LOG) << "avform_find_stream_info error: " << ret;
        return;
    }

    result->addType(Type::Video);

    if (result->inputFlags() & ExtractionResult::ExtractMetaData) {
        int totalSecs = fmt_ctx->duration / AV_TIME_BASE;
        int bitrate = fmt_ctx->bit_rate;

        result->add(Property::Duration, totalSecs);
        result->add(Property::BitRate, bitrate);

        const int index_stream = av_find_default_stream_index(fmt_ctx);
        if (index_stream >= 0) {
            AVStream* stream = fmt_ctx->streams[index_stream];

            const AVCodecParameters* codec = stream->codecpar;

            if (codec->codec_type == AVMEDIA_TYPE_VIDEO) {
                result->add(Property::Width, codec->width);
                result->add(Property::Height, codec->height);

                AVRational avSampleAspectRatio = av_guess_sample_aspect_ratio(fmt_ctx, stream, nullptr);
                AVRational avDisplayAspectRatio;
                av_reduce(&avDisplayAspectRatio.num, &avDisplayAspectRatio.den,
                          codec->width  * avSampleAspectRatio.num,
                          codec->height * avSampleAspectRatio.den,
                          1024*1024);
                double displayAspectRatio = avDisplayAspectRatio.num;
                if (avDisplayAspectRatio.den) {
                    displayAspectRatio /= avDisplayAspectRatio.den;
                }
                if (displayAspectRatio) {
                    result->add(Property::AspectRatio, displayAspectRatio);
                }

                AVRational avFrameRate = av_guess_frame_rate(fmt_ctx, stream, nullptr);
                double frameRate = avFrameRate.num;
                if (avFrameRate.den) {
                    frameRate /= avFrameRate.den;
                }
                if (frameRate) {
                    result->add(Property::FrameRate, frameRate);
                }

                result->add(Property::VideoCodec, QString::fromUtf8(avcodec_get_name(codec->codec_id)));

                const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get((AVPixelFormat)codec->format);
                if (desc) {
                    result->add(Property::PixelFormat, QString::fromUtf8(desc->name));
                }

                if (codec->color_space != AVCOL_SPC_UNSPECIFIED) {
                    result->add(Property::ColorSpace, QString::fromUtf8(av_color_space_name(codec->color_space)));
                }
            }
        }

        const auto audio_index_stream = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
        if (audio_index_stream >= 0) {
            AVStream* stream = fmt_ctx->streams[audio_index_stream];

            const AVCodecParameters* codec = stream->codecpar;
            result->add(Property::AudioCodec, QString::fromUtf8(avcodec_get_name(codec->codec_id)));
        }

        AVDictionary* dict = fmt_ctx->metadata;
        // In Ogg, the internal comment metadata headers are attached to a single content stream.
        // By convention, it's the first logical bitstream occuring.
        if (!dict && fmt_ctx->nb_streams > 0) {
            dict = fmt_ctx->streams[0]->metadata;
        }

        AVDictionaryEntry* entry;

        entry = av_dict_get(dict, "title", nullptr, 0);
        if (entry) {
            result->add(Property::Title, QString::fromUtf8(entry->value));
        }


        entry = av_dict_get(dict, "author", nullptr, 0);
        if (entry) {
            result->add(Property::Author, QString::fromUtf8(entry->value));
        }

        entry = av_dict_get(dict, "copyright", nullptr, 0);
        if (entry) {
            result->add(Property::Copyright, QString::fromUtf8(entry->value));
        }

        entry = av_dict_get(dict, "comment", nullptr, 0);
        if (entry) {
            result->add(Property::Comment, QString::fromUtf8(entry->value));
        }

        entry = av_dict_get(dict, "artist", nullptr, 0);
        if (entry) {
            result->add(Property::Artist, QString::fromUtf8(entry->value));
        }

        entry = av_dict_get(dict, "album", nullptr, 0);
        if (entry) {
            result->add(Property::Album, QString::fromUtf8(entry->value));
        }

        entry = av_dict_get(dict, "genre", nullptr, 0);
        if (entry) {
            result->add(Property::Genre, QString::fromUtf8(entry->value));
        }

        entry = av_dict_get(dict, "track", nullptr, 0);
        if (entry) {
            QString value = QString::fromUtf8(entry->value);

            bool ok = false;
            int track = value.toInt(&ok);
            if (ok && track) {
                result->add(Property::TrackNumber, track);
            }
        }

        entry = av_dict_get(dict, "year", nullptr, 0);
        if (entry) {
            int year = QString::fromUtf8(entry->value).toInt();
            result->add(Property::ReleaseYear, year);
        }
    }
}

#include "moc_ffmpegextractor.cpp"
