/*
 * EXIF metadata parser
 * Copyright (c) 2013 Thilo Borgmann <thilo.borgmann _at_ mail.de>
 * Copyright (c) 2024-2025 Leo Izen <leo.izen@gmail.com>
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/**
 * @file
 * EXIF metadata parser
 * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
 * @author Leo Izen <leo.izen@gmail.com>
 */

#ifndef AVCODEC_EXIF_H
#define AVCODEC_EXIF_H

#include <stddef.h>
#include <stdint.h>

#include "libavutil/buffer.h"
#include "libavutil/dict.h"
#include "libavutil/rational.h"

/** Data type identifiers for TIFF tags */
enum AVTiffDataType {
    AV_TIFF_BYTE = 1,
    AV_TIFF_STRING,
    AV_TIFF_SHORT,
    AV_TIFF_LONG,
    AV_TIFF_RATIONAL,
    AV_TIFF_SBYTE,
    AV_TIFF_UNDEFINED,
    AV_TIFF_SSHORT,
    AV_TIFF_SLONG,
    AV_TIFF_SRATIONAL,
    AV_TIFF_FLOAT,
    AV_TIFF_DOUBLE,
    AV_TIFF_IFD,
};

enum AVExifHeaderMode {
    /**
     * The TIFF header starts with 0x49492a00, or 0x4d4d002a.
     * This one is used internally by FFmpeg.
     */
    AV_EXIF_TIFF_HEADER,
    /** skip the TIFF header, assume little endian */
    AV_EXIF_ASSUME_LE,
    /** skip the TIFF header, assume big endian */
    AV_EXIF_ASSUME_BE,
    /** The first four bytes point to the actual start, then it's AV_EXIF_TIFF_HEADER */
    AV_EXIF_T_OFF,
    /** The first six bytes contain "Exif\0\0", then it's AV_EXIF_TIFF_HEADER */
    AV_EXIF_EXIF00,
};

typedef struct AVExifEntry AVExifEntry;

typedef struct AVExifMetadata {
    /* array of EXIF metadata entries */
    AVExifEntry *entries;
    /* number of entries in this array */
    unsigned int count;
    /* size of the buffer, used for av_fast_realloc */
    unsigned int size;
} AVExifMetadata;

struct AVExifEntry {
    uint16_t id;
    enum AVTiffDataType type;
    uint32_t count;

    /*
     * These are for IFD-style MakerNote
     * entries which occur after a fixed
     * offset rather than at the start of
     * the entry. The ifd_lead field contains
     * the leading bytes which typically
     * identify the type of MakerNote.
     */
    uint32_t ifd_offset;
    uint8_t *ifd_lead;

    /*
     * An array of entries of size count
     * Unless it's an IFD, in which case
     * it's not an array and count = 1
     */
    union {
        void *ptr;
        int64_t *sint;
        uint64_t *uint;
        double *dbl;
        char *str;
        uint8_t *ubytes;
        int8_t *sbytes;
        AVRational *rat;
        AVExifMetadata ifd;
    } value;
};

/**
 * Retrieves the tag name associated with the provided tag ID.
 * If the tag ID is unknown, NULL is returned.
 *
 * For example, av_exif_get_tag_name(0x112) returns "Orientation".
 */
const char *av_exif_get_tag_name(uint16_t id);

/**
 * Retrieves the tag ID associated with the provided tag string name.
 * If the tag name is unknown, a negative number is returned. Otherwise
 * it always fits inside a uint16_t integer.
 *
 * For example, av_exif_get_tag_id("Orientation") returns 274 (0x0112).
 */
int32_t av_exif_get_tag_id(const char *name);

/**
  * Add an entry to the provided EXIF metadata struct. If one already exists with the provided
  * ID, it will set the existing one to have the other information provided. Otherwise, it
  * will allocate a new entry.
  *
  * This function reallocates ifd->entries using av_fast_realloc and allocates (using av_malloc)
  * a new value member of the entry, then copies the contents of value into that buffer.
 */
int av_exif_set_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, enum AVTiffDataType type,
                      uint32_t count, const uint8_t *ifd_lead, uint32_t ifd_offset, const void *value);

/**
 * Also check subdirectories.
 */
#define AV_EXIF_FLAG_RECURSIVE (1 << 0)

/**
 * Get an entry with the tagged ID from the EXIF metadata struct. A pointer to the entry
 * will be written into *value.
 *
 * If the entry was present and returned successfully, a positive number is returned.
 *
 * The positive number is equal to 1 plus the offset at which the entry was found. If the
 * entry was found at the top level, this will be in the range of 1 to the number of entries
 * in the IFD (inclusive). If the entry was found in a sub-IFD, then it will be a number greater
 * than the number of entries in the top-level IFD.
 *
 * If the entry was not found, *value is left untouched and zero is returned.
 * If an error occurred, a negative AVERROR is returned.
 */
int av_exif_get_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int flags, AVExifEntry **value);

/**
 * Remove an entry from the provided EXIF metadata struct.
 *
 * If the entry was present and removed successfully, a positive number is returned.
 *
 * The positive number is equal to 1 plus the offset at which the entry was found. If the
 * entry was found at the top level, this will be in the range of 1 to the number of entries
 * in the IFD (inclusive). If the entry was found in a sub-IFD, then it will be a number greater
 * than the number of entries in the top-level IFD. This refers to the state of the IFD before
 * the entry was removed, e.g. if the last entry is removed, a 1 is returned, but the IFD has
 * no entries anymore.
 *
 * If the entry was not found, zero is returned and the IFD is left untouched.
 * If an error occurred, a negative AVERROR is returned.
 */
int av_exif_remove_entry(void *logctx, AVExifMetadata *ifd, uint16_t id, int flags);

/**
 * Decodes the EXIF data provided in the buffer and writes it into the
 * struct *ifd. If this function succeeds, the IFD is owned by the caller
 * and must be cleared after use by calling av_exif_free(); If this function
 * fails and returns a negative value, it will call av_exif_free(ifd) before
 * returning.
 */
int av_exif_parse_buffer(void *logctx, const uint8_t *data, size_t size,
                         AVExifMetadata *ifd, enum AVExifHeaderMode header_mode);

/**
 * Allocates a buffer using av_malloc of an appropriate size and writes the
 * EXIF data represented by ifd into that buffer.
 *
 * Upon error, *buffer will be NULL. The buffer becomes owned by the caller upon
 * success. The *buffer argument must be NULL before calling.
 */
int av_exif_write(void *logctx, const AVExifMetadata *ifd, AVBufferRef **buffer, enum AVExifHeaderMode header_mode);

/**
 * Frees all resources associated with the given EXIF metadata struct.
 * Does not free the pointer passed itself, in case it is stack-allocated.
 * The pointer passed to this function must be freed by the caller,
 * if it is heap-allocated. Passing NULL is permitted.
 */
void av_exif_free(AVExifMetadata *ifd);

/**
 * Recursively reads all tags from the IFD and stores them in the
 * provided metadata dictionary.
 */
int av_exif_ifd_to_dict(void *logctx, const AVExifMetadata *ifd, AVDictionary **metadata);

/**
 * Allocates a duplicate of the provided EXIF metadata struct. The caller owns
 * the duplicate and must free it with av_exif_free. Returns NULL if the duplication
 * process failed.
 */
AVExifMetadata *av_exif_clone_ifd(const AVExifMetadata *ifd);

/**
 * Convert a display matrix used by AV_FRAME_DATA_DISPLAYMATRIX
 * into an orientation constant used by EXIF's orientation tag.
 *
 * Returns an EXIF orientation between 1 and 8 (inclusive) depending
 * on the rotation and flip factors. Returns 0 if the matrix is singular.
 */
int av_exif_matrix_to_orientation(const int32_t *matrix);

/**
 * Convert an orientation constant used by EXIF's orientation tag
 * into a display matrix used by AV_FRAME_DATA_DISPLAYMATRIX.
 *
 * Returns 0 on success and negative if the orientation is invalid,
 * i.e. not between 1 and 8 (inclusive).
 */
int av_exif_orientation_to_matrix(int32_t *matrix, int orientation);

#endif /* AVCODEC_EXIF_H */
