mirror of
https://github.com/simon987/libscan.git
synced 2025-04-05 04:22:58 +00:00
717 lines
23 KiB
C
717 lines
23 KiB
C
#include "media.h"
|
|
#include <ctype.h>
|
|
|
|
#define MIN_SIZE 32
|
|
#define AVIO_BUF_SIZE 8192
|
|
#define IS_VIDEO(fmt) (fmt->iformat->name && strcmp(fmt->iformat->name, "image2") != 0)
|
|
|
|
#define STORE_AS_IS ((void*)-1)
|
|
|
|
__always_inline
|
|
void *scale_frame(const AVCodecContext *decoder, const AVFrame *frame, int size) {
|
|
|
|
if (frame->pict_type == AV_PICTURE_TYPE_NONE) {
|
|
return NULL;
|
|
}
|
|
|
|
int dstW;
|
|
int dstH;
|
|
if (frame->width <= size && frame->height <= size) {
|
|
if (decoder->codec_id == AV_CODEC_ID_MJPEG || decoder->codec_id == AV_CODEC_ID_PNG) {
|
|
return STORE_AS_IS;
|
|
}
|
|
|
|
dstW = frame->width;
|
|
dstH = frame->height;
|
|
} else {
|
|
double ratio = (double) frame->width / frame->height;
|
|
if (frame->width > frame->height) {
|
|
dstW = size;
|
|
dstH = (int) (size / ratio);
|
|
} else {
|
|
dstW = (int) (size * ratio);
|
|
dstH = size;
|
|
}
|
|
}
|
|
|
|
if (dstW <= MIN_SIZE || dstH <= MIN_SIZE) {
|
|
return NULL;
|
|
}
|
|
|
|
AVFrame *scaled_frame = av_frame_alloc();
|
|
|
|
struct SwsContext *sws_ctx = sws_getContext(
|
|
decoder->width, decoder->height, decoder->pix_fmt,
|
|
dstW, dstH, AV_PIX_FMT_YUVJ420P,
|
|
SIST_SWS_ALGO, 0, 0, 0
|
|
);
|
|
|
|
int dst_buf_len = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, dstW, dstH, 1);
|
|
uint8_t *dst_buf = (uint8_t *) av_malloc(dst_buf_len * 2);
|
|
|
|
av_image_fill_arrays(scaled_frame->data, scaled_frame->linesize, dst_buf, AV_PIX_FMT_YUV420P, dstW, dstH, 1);
|
|
|
|
sws_scale(sws_ctx,
|
|
(const uint8_t *const *) frame->data, frame->linesize,
|
|
0, decoder->height,
|
|
scaled_frame->data, scaled_frame->linesize
|
|
);
|
|
|
|
scaled_frame->width = dstW;
|
|
scaled_frame->height = dstH;
|
|
scaled_frame->format = AV_PIX_FMT_YUV420P;
|
|
|
|
sws_freeContext(sws_ctx);
|
|
|
|
return scaled_frame;
|
|
}
|
|
|
|
typedef struct {
|
|
AVPacket *packet;
|
|
AVFrame *frame;
|
|
} frame_and_packet_t;
|
|
|
|
static void frame_and_packet_free(frame_and_packet_t *frame_and_packet) {
|
|
if (frame_and_packet->packet != NULL) {
|
|
av_packet_free(&frame_and_packet->packet);
|
|
}
|
|
|
|
if (frame_and_packet->frame != NULL) {
|
|
av_frame_free(&frame_and_packet->frame);
|
|
}
|
|
|
|
free(frame_and_packet->packet);
|
|
free(frame_and_packet);
|
|
}
|
|
|
|
__always_inline
|
|
static void read_subtitles(scan_media_ctx_t *ctx, AVFormatContext *pFormatCtx, int stream_idx, document_t *doc) {
|
|
|
|
text_buffer_t tex = text_buffer_create(-1);
|
|
|
|
AVPacket packet;
|
|
AVSubtitle subtitle;
|
|
|
|
AVCodec *subtitle_codec = avcodec_find_decoder(pFormatCtx->streams[stream_idx]->codecpar->codec_id);
|
|
AVCodecContext *decoder = avcodec_alloc_context3(subtitle_codec);
|
|
avcodec_parameters_to_context(decoder, pFormatCtx->streams[stream_idx]->codecpar);
|
|
avcodec_open2(decoder, subtitle_codec, NULL);
|
|
|
|
decoder->sub_text_format = FF_SUB_TEXT_FMT_ASS;
|
|
|
|
int got_sub;
|
|
|
|
while (1) {
|
|
int read_frame_ret = av_read_frame(pFormatCtx, &packet);
|
|
|
|
if (read_frame_ret != 0) {
|
|
break;
|
|
}
|
|
|
|
if (packet.stream_index != stream_idx) {
|
|
av_packet_unref(&packet);
|
|
continue;
|
|
}
|
|
|
|
avcodec_decode_subtitle2(decoder, &subtitle, &got_sub, &packet);
|
|
|
|
if (got_sub) {
|
|
for (int i = 0; i < subtitle.num_rects; i++) {
|
|
const char *text = subtitle.rects[i]->ass;
|
|
|
|
char *idx = strstr(text, "\\N");
|
|
if (idx != NULL && strlen(idx + 2) > 1) {
|
|
text_buffer_append_string0(&tex, idx + 2);
|
|
text_buffer_append_char(&tex, ' ');
|
|
}
|
|
}
|
|
avsubtitle_free(&subtitle);
|
|
}
|
|
|
|
av_packet_unref(&packet);
|
|
}
|
|
|
|
text_buffer_terminate_string(&tex);
|
|
|
|
APPEND_STR_META(doc, MetaContent, tex.dyn_buffer.buf)
|
|
text_buffer_destroy(&tex);
|
|
avcodec_free_context(&decoder);
|
|
}
|
|
|
|
__always_inline
|
|
static frame_and_packet_t *
|
|
read_frame(scan_media_ctx_t *ctx, AVFormatContext *pFormatCtx, AVCodecContext *decoder, int stream_idx,
|
|
document_t *doc) {
|
|
|
|
frame_and_packet_t *result = calloc(1, sizeof(frame_and_packet_t));
|
|
result->packet = av_packet_alloc();
|
|
result->frame = av_frame_alloc();
|
|
|
|
av_init_packet(result->packet);
|
|
|
|
int receive_ret = -EAGAIN;
|
|
while (receive_ret == -EAGAIN) {
|
|
// Get video frame
|
|
while (1) {
|
|
int read_frame_ret = av_read_frame(pFormatCtx, result->packet);
|
|
|
|
if (read_frame_ret != 0) {
|
|
if (read_frame_ret != AVERROR_EOF) {
|
|
CTX_LOG_WARNINGF(doc->filepath,
|
|
"(media.c) avcodec_read_frame() returned error code [%d] %s",
|
|
read_frame_ret, av_err2str(read_frame_ret)
|
|
)
|
|
}
|
|
frame_and_packet_free(result);
|
|
return NULL;
|
|
}
|
|
|
|
//Ignore audio/other frames
|
|
if (result->packet->stream_index != stream_idx) {
|
|
av_packet_unref(result->packet);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Feed it to decoder
|
|
int decode_ret = avcodec_send_packet(decoder, result->packet);
|
|
if (decode_ret != 0) {
|
|
CTX_LOG_ERRORF(doc->filepath,
|
|
"(media.c) avcodec_send_packet() returned error code [%d] %s",
|
|
decode_ret, av_err2str(decode_ret)
|
|
)
|
|
frame_and_packet_free(result);
|
|
return NULL;
|
|
}
|
|
|
|
receive_ret = avcodec_receive_frame(decoder, result->frame);
|
|
if (receive_ret == -EAGAIN && result->packet != NULL) {
|
|
av_packet_unref(result->packet);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void append_tag_meta_if_not_exists(scan_media_ctx_t *ctx, document_t *doc, AVDictionaryEntry *tag, enum metakey key) {
|
|
|
|
meta_line_t *meta = doc->meta_head;
|
|
while (meta != NULL) {
|
|
if (meta->key == key) {
|
|
CTX_LOG_DEBUGF(doc->filepath, "Ignoring duplicate tag: '%02x=%s' and '%02x=%s'",
|
|
key, meta->str_val, key, tag->value)
|
|
return;
|
|
}
|
|
meta = meta->next;
|
|
}
|
|
|
|
text_buffer_t tex = text_buffer_create(-1);
|
|
text_buffer_append_string0(&tex, tag->value);
|
|
text_buffer_terminate_string(&tex);
|
|
meta_line_t *meta_tag = malloc(sizeof(meta_line_t) + tex.dyn_buffer.cur);
|
|
meta_tag->key = key;
|
|
strcpy(meta_tag->str_val, tex.dyn_buffer.buf);
|
|
|
|
APPEND_META(doc, meta_tag)
|
|
text_buffer_destroy(&tex);
|
|
}
|
|
|
|
#define APPEND_TAG_META(keyname) \
|
|
APPEND_UTF8_META(doc, keyname, tag->value)
|
|
|
|
#define STRCPY_TOLOWER(dst, str) \
|
|
strncpy(dst, str, sizeof(dst)); \
|
|
char *ptr = dst; \
|
|
for (; *ptr; ++ptr) *ptr = (char) tolower(*ptr);
|
|
|
|
__always_inline
|
|
static void append_audio_meta(AVFormatContext *pFormatCtx, document_t *doc) {
|
|
|
|
AVDictionaryEntry *tag = NULL;
|
|
while ((tag = av_dict_get(pFormatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
|
char key[256];
|
|
STRCPY_TOLOWER(key, tag->key)
|
|
|
|
if (strcmp(key, "artist") == 0) {
|
|
APPEND_TAG_META(MetaArtist)
|
|
} else if (strcmp(key, "genre") == 0) {
|
|
APPEND_TAG_META(MetaGenre)
|
|
} else if (strcmp(key, "title") == 0) {
|
|
APPEND_TAG_META(MetaTitle)
|
|
} else if (strcmp(key, "album_artist") == 0) {
|
|
APPEND_TAG_META(MetaAlbumArtist)
|
|
} else if (strcmp(key, "album") == 0) {
|
|
APPEND_TAG_META(MetaAlbum)
|
|
} else if (strcmp(key, "comment") == 0) {
|
|
APPEND_TAG_META(MetaContent)
|
|
}
|
|
}
|
|
}
|
|
|
|
__always_inline
|
|
static void
|
|
append_video_meta(scan_media_ctx_t *ctx, AVFormatContext *pFormatCtx, AVFrame *frame, document_t *doc, int is_video) {
|
|
|
|
if (is_video) {
|
|
meta_line_t *meta_duration = malloc(sizeof(meta_line_t));
|
|
meta_duration->key = MetaMediaDuration;
|
|
meta_duration->long_val = pFormatCtx->duration / AV_TIME_BASE;
|
|
APPEND_META(doc, meta_duration)
|
|
|
|
meta_line_t *meta_bitrate = malloc(sizeof(meta_line_t));
|
|
meta_bitrate->key = MetaMediaBitrate;
|
|
meta_bitrate->long_val = pFormatCtx->bit_rate;
|
|
APPEND_META(doc, meta_bitrate)
|
|
}
|
|
|
|
AVDictionaryEntry *tag = NULL;
|
|
if (is_video) {
|
|
while ((tag = av_dict_get(pFormatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
|
char key[256];
|
|
STRCPY_TOLOWER(key, tag->key)
|
|
|
|
if (strcmp(key, "title") == 0) {
|
|
append_tag_meta_if_not_exists(ctx, doc, tag, MetaTitle);
|
|
} else if (strcmp(key, "comment") == 0) {
|
|
append_tag_meta_if_not_exists(ctx, doc, tag, MetaContent);
|
|
} else if (strcmp(key, "artist") == 0) {
|
|
append_tag_meta_if_not_exists(ctx, doc, tag, MetaArtist);
|
|
}
|
|
}
|
|
} else {
|
|
// EXIF metadata
|
|
while ((tag = av_dict_get(frame->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
|
char key[256];
|
|
STRCPY_TOLOWER(key, tag->key)
|
|
|
|
if (strcmp(key, "artist") == 0) {
|
|
append_tag_meta_if_not_exists(ctx, doc, tag, MetaArtist);
|
|
} else if (strcmp(key, "imagedescription") == 0) {
|
|
APPEND_TAG_META(MetaContent)
|
|
} else if (strcmp(key, "make") == 0) {
|
|
APPEND_TAG_META(MetaExifMake)
|
|
} else if (strcmp(key, "model") == 0) {
|
|
APPEND_TAG_META(MetaExifModel)
|
|
} else if (strcmp(key, "software") == 0) {
|
|
APPEND_TAG_META(MetaExifSoftware)
|
|
} else if (strcmp(key, "fnumber") == 0) {
|
|
APPEND_TAG_META(MetaExifFNumber)
|
|
} else if (strcmp(key, "focallength") == 0) {
|
|
APPEND_TAG_META(MetaExifFocalLength)
|
|
} else if (strcmp(key, "usercomment") == 0) {
|
|
APPEND_TAG_META(MetaExifUserComment)
|
|
} else if (strcmp(key, "isospeedratings") == 0) {
|
|
APPEND_TAG_META(MetaExifIsoSpeedRatings)
|
|
} else if (strcmp(key, "exposuretime") == 0) {
|
|
APPEND_TAG_META(MetaExifExposureTime)
|
|
} else if (strcmp(key, "datetime") == 0) {
|
|
APPEND_TAG_META(MetaExifDateTime)
|
|
} else if (strcmp(key, "gpslatitude") == 0) {
|
|
APPEND_TAG_META(MetaExifGpsLatitudeDMS)
|
|
} else if (strcmp(key, "gpslatituderef") == 0) {
|
|
APPEND_TAG_META(MetaExifGpsLatitudeRef)
|
|
} else if (strcmp(key, "gpslongitude") == 0) {
|
|
APPEND_TAG_META(MetaExifGpsLongitudeDMS)
|
|
} else if (strcmp(key, "gpslongituderef") == 0) {
|
|
APPEND_TAG_META(MetaExifGpsLongitudeRef)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void parse_media_format_ctx(scan_media_ctx_t *ctx, AVFormatContext *pFormatCtx, document_t *doc) {
|
|
|
|
int video_stream = -1;
|
|
int audio_stream = -1;
|
|
int subtitle_stream = -1;
|
|
|
|
avformat_find_stream_info(pFormatCtx, NULL);
|
|
|
|
for (int i = (int) pFormatCtx->nb_streams - 1; i >= 0; i--) {
|
|
AVStream *stream = pFormatCtx->streams[i];
|
|
|
|
if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
|
if (audio_stream == -1) {
|
|
const AVCodecDescriptor *desc = avcodec_descriptor_get(stream->codecpar->codec_id);
|
|
|
|
if (desc != NULL) {
|
|
APPEND_STR_META(doc, MetaMediaAudioCodec, desc->name)
|
|
}
|
|
|
|
append_audio_meta(pFormatCtx, doc);
|
|
audio_stream = i;
|
|
}
|
|
} else if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
|
|
|
if (video_stream == -1) {
|
|
const AVCodecDescriptor *desc = avcodec_descriptor_get(stream->codecpar->codec_id);
|
|
|
|
if (desc != NULL) {
|
|
APPEND_STR_META(doc, MetaMediaVideoCodec, desc->name)
|
|
}
|
|
|
|
meta_line_t *meta_w = malloc(sizeof(meta_line_t));
|
|
meta_w->key = MetaWidth;
|
|
meta_w->int_val = stream->codecpar->width;
|
|
APPEND_META(doc, meta_w)
|
|
|
|
meta_line_t *meta_h = malloc(sizeof(meta_line_t));
|
|
meta_h->key = MetaHeight;
|
|
meta_h->int_val = stream->codecpar->height;
|
|
APPEND_META(doc, meta_h)
|
|
|
|
video_stream = i;
|
|
}
|
|
} else if (stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
|
|
subtitle_stream = i;
|
|
}
|
|
}
|
|
|
|
if (subtitle_stream != -1 && ctx->read_subtitles) {
|
|
read_subtitles(ctx, pFormatCtx, subtitle_stream, doc);
|
|
|
|
// Reset stream
|
|
if (video_stream != -1) {
|
|
av_seek_frame(pFormatCtx, video_stream, 0, 0);
|
|
}
|
|
}
|
|
|
|
if (video_stream != -1 && ctx->tn_size > 0) {
|
|
AVStream *stream = pFormatCtx->streams[video_stream];
|
|
|
|
if (stream->codecpar->width <= MIN_SIZE || stream->codecpar->height <= MIN_SIZE) {
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
return;
|
|
}
|
|
|
|
// Decoder
|
|
AVCodec *video_codec = avcodec_find_decoder(stream->codecpar->codec_id);
|
|
AVCodecContext *decoder = avcodec_alloc_context3(video_codec);
|
|
avcodec_parameters_to_context(decoder, stream->codecpar);
|
|
avcodec_open2(decoder, video_codec, NULL);
|
|
|
|
//Seek
|
|
if (stream->nb_frames > 1 && stream->codecpar->codec_id != AV_CODEC_ID_GIF) {
|
|
int seek_ret;
|
|
for (int i = 20; i >= 0; i--) {
|
|
seek_ret = av_seek_frame(pFormatCtx, video_stream,
|
|
stream->duration * 0.10, 0);
|
|
if (seek_ret == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
frame_and_packet_t *frame_and_packet = read_frame(ctx, pFormatCtx, decoder, video_stream, doc);
|
|
if (frame_and_packet == NULL) {
|
|
avcodec_free_context(&decoder);
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
return;
|
|
}
|
|
|
|
append_video_meta(ctx, pFormatCtx, frame_and_packet->frame, doc, IS_VIDEO(pFormatCtx));
|
|
|
|
// Scale frame
|
|
AVFrame *scaled_frame = scale_frame(decoder, frame_and_packet->frame, ctx->tn_size);
|
|
|
|
if (scaled_frame == NULL) {
|
|
frame_and_packet_free(frame_and_packet);
|
|
avcodec_free_context(&decoder);
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
return;
|
|
}
|
|
|
|
if (scaled_frame == STORE_AS_IS) {
|
|
APPEND_TN_META(doc, frame_and_packet->frame->width, frame_and_packet->frame->height)
|
|
ctx->store((char *) doc->path_md5, sizeof(doc->path_md5), (char *) frame_and_packet->packet->data,
|
|
frame_and_packet->packet->size);
|
|
} else {
|
|
// Encode frame to jpeg
|
|
AVCodecContext *jpeg_encoder = alloc_jpeg_encoder(scaled_frame->width, scaled_frame->height,
|
|
ctx->tn_qscale);
|
|
avcodec_send_frame(jpeg_encoder, scaled_frame);
|
|
|
|
AVPacket jpeg_packet;
|
|
av_init_packet(&jpeg_packet);
|
|
avcodec_receive_packet(jpeg_encoder, &jpeg_packet);
|
|
|
|
// Save thumbnail
|
|
APPEND_TN_META(doc, scaled_frame->width, scaled_frame->height)
|
|
ctx->store((char *) doc->path_md5, sizeof(doc->path_md5), (char *) jpeg_packet.data, jpeg_packet.size);
|
|
|
|
avcodec_free_context(&jpeg_encoder);
|
|
av_packet_unref(&jpeg_packet);
|
|
av_free(*scaled_frame->data);
|
|
av_frame_free(&scaled_frame);
|
|
}
|
|
|
|
frame_and_packet_free(frame_and_packet);
|
|
avcodec_free_context(&decoder);
|
|
}
|
|
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
}
|
|
|
|
void parse_media_filename(scan_media_ctx_t *ctx, const char *filepath, document_t *doc) {
|
|
|
|
AVFormatContext *pFormatCtx = avformat_alloc_context();
|
|
if (pFormatCtx == NULL) {
|
|
CTX_LOG_ERROR(doc->filepath, "(media.c) Could not allocate context with avformat_alloc_context()")
|
|
return;
|
|
}
|
|
int res = avformat_open_input(&pFormatCtx, filepath, NULL, NULL);
|
|
if (res < 0) {
|
|
CTX_LOG_ERRORF(doc->filepath, "(media.c) avformat_open_input() returned [%d] %s", res, av_err2str(res))
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
return;
|
|
}
|
|
|
|
parse_media_format_ctx(ctx, pFormatCtx, doc);
|
|
}
|
|
|
|
int vfile_read(void *ptr, uint8_t *buf, int buf_size) {
|
|
struct vfile *f = ptr;
|
|
|
|
int ret = f->read(f, buf, buf_size);
|
|
|
|
if (ret == 0) {
|
|
return AVERROR_EOF;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
typedef struct {
|
|
struct stat info;
|
|
FILE *file;
|
|
void *buf;
|
|
} memfile_t;
|
|
|
|
int memfile_read(void *ptr, uint8_t *buf, int buf_size) {
|
|
memfile_t *mem = ptr;
|
|
|
|
size_t ret = fread(buf, 1, buf_size, mem->file);
|
|
|
|
if (ret == 0 && feof(mem->file)) {
|
|
return AVERROR_EOF;
|
|
}
|
|
|
|
return buf_size;
|
|
}
|
|
|
|
long memfile_seek(void *ptr, long offset, int whence) {
|
|
memfile_t *mem = ptr;
|
|
|
|
if (whence == 0x10000) {
|
|
return mem->info.st_size;
|
|
}
|
|
|
|
int ret = fseek(mem->file, offset, whence);
|
|
if (ret != 0) {
|
|
return AVERROR_EOF;
|
|
}
|
|
|
|
return ftell(mem->file);
|
|
}
|
|
|
|
int memfile_open(vfile_t *f, memfile_t *mem) {
|
|
mem->info = f->info;
|
|
|
|
mem->buf = malloc(mem->info.st_size);
|
|
if (mem->buf == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
int ret = f->read(f, mem->buf, mem->info.st_size);
|
|
mem->file = fmemopen(mem->buf, mem->info.st_size, "rb");
|
|
|
|
return (ret == mem->info.st_size && mem->file != NULL) ? 0 : -1;
|
|
}
|
|
|
|
int memfile_open_buf(void *buf, size_t buf_len, memfile_t *mem) {
|
|
mem->info.st_size = buf_len;
|
|
|
|
mem->buf = buf;
|
|
mem->file = fmemopen(mem->buf, mem->info.st_size, "rb");
|
|
|
|
return mem->file != NULL ? 0 : -1;
|
|
}
|
|
|
|
void memfile_close(memfile_t *mem) {
|
|
if (mem->buf != NULL) {
|
|
free(mem->buf);
|
|
fclose(mem->file);
|
|
}
|
|
}
|
|
|
|
void parse_media_vfile(scan_media_ctx_t *ctx, struct vfile *f, document_t *doc) {
|
|
|
|
AVFormatContext *pFormatCtx = avformat_alloc_context();
|
|
if (pFormatCtx == NULL) {
|
|
CTX_LOG_ERROR(doc->filepath, "(media.c) Could not allocate context with avformat_alloc_context()")
|
|
return;
|
|
}
|
|
|
|
unsigned char *buffer = (unsigned char *) av_malloc(AVIO_BUF_SIZE);
|
|
AVIOContext *io_ctx = NULL;
|
|
memfile_t memfile = {{}, 0, 0};
|
|
|
|
if (f->info.st_size <= ctx->max_media_buffer) {
|
|
int ret = memfile_open(f, &memfile);
|
|
if (ret == 0) {
|
|
CTX_LOG_DEBUGF(f->filepath, "Loading media file in memory (%ldB)", f->info.st_size)
|
|
io_ctx = avio_alloc_context(buffer, AVIO_BUF_SIZE, 0, &memfile, memfile_read, NULL, memfile_seek);
|
|
}
|
|
}
|
|
|
|
if (io_ctx == NULL) {
|
|
CTX_LOG_DEBUGF(f->filepath, "Reading media file without seek support", f->info.st_size)
|
|
io_ctx = avio_alloc_context(buffer, AVIO_BUF_SIZE, 0, f, vfile_read, NULL, NULL);
|
|
}
|
|
|
|
pFormatCtx->pb = io_ctx;
|
|
|
|
int res = avformat_open_input(&pFormatCtx, f->filepath, NULL, NULL);
|
|
if (res < 0) {
|
|
if (res != -5) {
|
|
CTX_LOG_ERRORF(doc->filepath, "(media.c) avformat_open_input() returned [%d] %s", res, av_err2str(res))
|
|
}
|
|
av_free(io_ctx->buffer);
|
|
memfile_close(&memfile);
|
|
avio_context_free(&io_ctx);
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
return;
|
|
}
|
|
|
|
parse_media_format_ctx(ctx, pFormatCtx, doc);
|
|
av_free(io_ctx->buffer);
|
|
avio_context_free(&io_ctx);
|
|
memfile_close(&memfile);
|
|
}
|
|
|
|
void parse_media(scan_media_ctx_t *ctx, vfile_t *f, document_t *doc) {
|
|
|
|
if (f->is_fs_file) {
|
|
parse_media_filename(ctx, f->filepath, doc);
|
|
} else {
|
|
parse_media_vfile(ctx, f, doc);
|
|
}
|
|
}
|
|
|
|
void init_media() {
|
|
av_log_set_level(AV_LOG_QUIET);
|
|
}
|
|
|
|
int store_image_thumbnail(scan_media_ctx_t *ctx, void *buf, size_t buf_len, document_t *doc, const char *url) {
|
|
memfile_t memfile;
|
|
AVIOContext *io_ctx = NULL;
|
|
|
|
AVFormatContext *pFormatCtx = avformat_alloc_context();
|
|
if (pFormatCtx == NULL) {
|
|
CTX_LOG_ERROR(doc->filepath, "(media.c) Could not allocate context with avformat_alloc_context()")
|
|
return FALSE;
|
|
}
|
|
|
|
unsigned char *buffer = (unsigned char *) av_malloc(AVIO_BUF_SIZE);
|
|
|
|
int ret = memfile_open_buf(buf, buf_len, &memfile);
|
|
if (ret == 0) {
|
|
CTX_LOG_DEBUGF(doc->filepath, "Loading media file in memory (%ldB)", buf_len)
|
|
io_ctx = avio_alloc_context(buffer, AVIO_BUF_SIZE, 0, &memfile, memfile_read, NULL, memfile_seek);
|
|
} else {
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
av_free(io_ctx->buffer);
|
|
avio_context_free(&io_ctx);
|
|
fclose(memfile.file);
|
|
return FALSE;
|
|
}
|
|
|
|
pFormatCtx->pb = io_ctx;
|
|
|
|
int res = avformat_open_input(&pFormatCtx, url, NULL, NULL);
|
|
if (res != 0) {
|
|
av_free(io_ctx->buffer);
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
avio_context_free(&io_ctx);
|
|
fclose(memfile.file);
|
|
return FALSE;
|
|
}
|
|
|
|
AVStream *stream = pFormatCtx->streams[0];
|
|
|
|
// Decoder
|
|
AVCodec *video_codec = avcodec_find_decoder(stream->codecpar->codec_id);
|
|
AVCodecContext *decoder = avcodec_alloc_context3(video_codec);
|
|
avcodec_parameters_to_context(decoder, stream->codecpar);
|
|
avcodec_open2(decoder, video_codec, NULL);
|
|
|
|
frame_and_packet_t *frame_and_packet = read_frame(ctx, pFormatCtx, decoder, 0, doc);
|
|
if (frame_and_packet == NULL) {
|
|
avcodec_free_context(&decoder);
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
av_free(io_ctx->buffer);
|
|
avio_context_free(&io_ctx);
|
|
fclose(memfile.file);
|
|
return FALSE;
|
|
}
|
|
|
|
// Scale frame
|
|
AVFrame *scaled_frame = scale_frame(decoder, frame_and_packet->frame, ctx->tn_size);
|
|
|
|
if (scaled_frame == NULL) {
|
|
frame_and_packet_free(frame_and_packet);
|
|
avcodec_free_context(&decoder);
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
av_free(io_ctx->buffer);
|
|
avio_context_free(&io_ctx);
|
|
fclose(memfile.file);
|
|
return FALSE;
|
|
}
|
|
|
|
if (scaled_frame == STORE_AS_IS) {
|
|
APPEND_TN_META(doc, frame_and_packet->frame->width, frame_and_packet->frame->height)
|
|
ctx->store((char *) doc->path_md5, sizeof(doc->path_md5), (char *) frame_and_packet->packet->data,
|
|
frame_and_packet->packet->size);
|
|
} else {
|
|
// Encode frame to jpeg
|
|
AVCodecContext *jpeg_encoder = alloc_jpeg_encoder(scaled_frame->width, scaled_frame->height,
|
|
ctx->tn_qscale);
|
|
avcodec_send_frame(jpeg_encoder, scaled_frame);
|
|
|
|
AVPacket jpeg_packet;
|
|
av_init_packet(&jpeg_packet);
|
|
avcodec_receive_packet(jpeg_encoder, &jpeg_packet);
|
|
|
|
// Save thumbnail
|
|
APPEND_TN_META(doc, scaled_frame->width, scaled_frame->height)
|
|
ctx->store((char *) doc->path_md5, sizeof(doc->path_md5), (char *) jpeg_packet.data, jpeg_packet.size);
|
|
|
|
av_packet_unref(&jpeg_packet);
|
|
avcodec_free_context(&jpeg_encoder);
|
|
av_free(*scaled_frame->data);
|
|
av_frame_free(&scaled_frame);
|
|
}
|
|
|
|
frame_and_packet_free(frame_and_packet);
|
|
avcodec_free_context(&decoder);
|
|
|
|
avformat_close_input(&pFormatCtx);
|
|
avformat_free_context(pFormatCtx);
|
|
|
|
av_free(io_ctx->buffer);
|
|
avio_context_free(&io_ctx);
|
|
fclose(memfile.file);
|
|
|
|
return TRUE;
|
|
}
|