From b4de9fb0fb1d1f93c1c67a5d122d4c78933e8cec Mon Sep 17 00:00:00 2001 From: "byte[]" Date: Mon, 18 May 2020 20:37:19 -0400 Subject: [PATCH] add mediathumb utility --- Makefile | 16 ++++++-- src/png.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ src/png.h | 8 ++++ src/stat.c | 13 +++++- src/thumb.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 src/png.c create mode 100644 src/png.h create mode 100644 src/thumb.c diff --git a/Makefile b/Makefile index f3c0be5..784eb8f 100644 --- a/Makefile +++ b/Makefile @@ -1,33 +1,41 @@ RM := rm -f CFLAGS := -O3 -Wall -Isrc -I/usr/include/ffmpeg -LIBS := -lavformat -lavutil -lavcodec +LIBS := -lavformat -lavutil -lavcodec -lswscale LDFLAGS := INSTALL ?= install PREFIX ?= /usr/local -COMMON_OBJECTS := build/validation.o +COMMON_OBJECTS := build/validation.o build/png.o MEDIASTAT_OBJECTS := build/stat.o +MEDIATHUMB_OBJECTS := build/thumb.o # Phony rules .PHONY: all mediastat clean -all: mediastat +all: mediastat mediathumb install: all $(INSTALL) build/mediastat $(PREFIX)/bin/mediastat + $(INSTALL) build/mediathumb $(PREFIX)/bin/mediathumb uninstall: $(RM) -rf $(PREFIX)/bin/mediastat + $(RM) -rf $(PREFIX)/bin/mediathumb mediastat: build/mediastat +mediathumb: build/mediathumb + clean: - $(RM) $(COMMON_OBJECTS) $(MEDIASTAT_OBJECTS) + $(RM) $(COMMON_OBJECTS) $(MEDIASTAT_OBJECTS)$(MEDIATHUMB_OBJECTS) # Build rules build/mediastat: $(COMMON_OBJECTS) $(MEDIASTAT_OBJECTS) $(CC) $^ $(LDFLAGS) $(LIBS) -o $@ +build/mediathumb: $(COMMON_OBJECTS) $(MEDIATHUMB_OBJECTS) + $(CC) $^ $(LDFLAGS) $(LIBS) -o $@ + build/%.o: src/%.c $(CC) $(CFLAGS) -MMD -c $< -o $@ diff --git a/src/png.c b/src/png.c new file mode 100644 index 0000000..aa7f21d --- /dev/null +++ b/src/png.c @@ -0,0 +1,106 @@ +#include +#include +#include "png.h" + +int mediatools_write_frame_to_png(AVFrame *in_frame, const char *path) +{ + struct SwsContext *sws_ctx = NULL; + AVFormatContext *format = NULL; + AVOutputFormat *png = NULL; + AVCodecContext *vctx = NULL; + AVStream *vstream = NULL; + AVFrame *out_frame = NULL; + AVCodec *vcodec = NULL; + AVPacket pkt = { 0 }; + + int ret = -1; + + png = av_guess_format(NULL, NULL, "image/png"); + vcodec = avcodec_find_encoder(AV_CODEC_ID_APNG); + vctx = avcodec_alloc_context3(vcodec); + format = avformat_alloc_context(); + out_frame = av_frame_alloc(); + + if (!format || !vctx || !vcodec || !png || !out_frame) + goto error; + + format->oformat = png; + format->url = av_strdup(path); + + vstream = avformat_new_stream(format, NULL); + if (!vstream) + goto error; + + if (png->flags & AVFMT_GLOBALHEADER) + vctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + vctx->time_base = (AVRational) { 1, 1 }; + vctx->width = in_frame->width; + vctx->height = in_frame->height; + vctx->pix_fmt = AV_PIX_FMT_RGBA; + + if (avcodec_parameters_from_context(vstream->codecpar, vctx) < 0) + goto error; + + if (avcodec_open2(vctx, vcodec, NULL) < 0) + goto error; + + avio_open(&format->pb, path, AVIO_FLAG_WRITE); + + if (avformat_write_header(format, NULL) < 0) + goto error; + + av_init_packet(&pkt); + + out_frame->format = AV_PIX_FMT_RGBA; + out_frame->width = in_frame->width; + out_frame->height = in_frame->height; + + if (av_frame_get_buffer(out_frame, 0) < 0) + goto error; + + if (av_frame_make_writable(out_frame) < 0) + goto error; + + // Rewrite frame into correct color format + + sws_ctx = sws_getContext(in_frame->width, in_frame->height, in_frame->format, out_frame->width, out_frame->height, AV_PIX_FMT_RGBA, SWS_LANCZOS, NULL, NULL, NULL); + if (!sws_ctx) + goto error; + + sws_scale(sws_ctx, (const uint8_t **)in_frame->data, in_frame->linesize, 0, in_frame->height, out_frame->data, out_frame->linesize); + if (avcodec_send_frame(vctx, out_frame) < 0) + goto error; + if (avcodec_send_frame(vctx, NULL) < 0) + goto error; + if (avcodec_receive_packet(vctx, &pkt) < 0) + goto error; + + pkt.stream_index = vstream->index; + pkt.pts = 1; + pkt.dts = 1; + + if (av_write_frame(format, &pkt) < 0) + goto error; + + av_packet_unref(&pkt); + + if (av_write_trailer(format) < 0) + goto error; + + ret = 0; + +error: + if (sws_ctx) + sws_freeContext(sws_ctx); + if (format->pb) + avio_close(format->pb); + if (vctx) + avcodec_free_context(&vctx); + if (out_frame) + av_frame_free(&out_frame); + if (format) + avformat_free_context(format); + + return ret; +} diff --git a/src/png.h b/src/png.h new file mode 100644 index 0000000..fbb8a48 --- /dev/null +++ b/src/png.h @@ -0,0 +1,8 @@ +#ifndef _PNG_H_DEFINED +#define _PNG_H_DEFINED + +#include + +int mediatools_write_frame_to_png(AVFrame *in_frame, const char *path); + +#endif diff --git a/src/stat.c b/src/stat.c index 535280a..1c66407 100644 --- a/src/stat.c +++ b/src/stat.c @@ -7,6 +7,15 @@ #include "validation.h" +static int64_t start_time(AVStream *stream) +{ + if (stream->start_time == AV_NOPTS_VALUE) { + return 0; + } + + return stream->start_time; +} + int main(int argc, char *argv[]) { AVFormatContext *format = NULL; @@ -67,8 +76,8 @@ int main(int argc, char *argv[]) av_packet_unref(&pkt); } - AVRational tb = format->streams[last_stream]->time_base; - AVRational dur = av_mul_q(av_make_q(last_pts, 1), tb); + AVStream *stream = format->streams[last_stream]; + AVRational dur = av_mul_q(av_make_q(last_pts - start_time(stream), 1), stream->time_base); if (!mediatools_validate_duration(dur)) return -1; diff --git a/src/thumb.c b/src/thumb.c new file mode 100644 index 0000000..ecc7761 --- /dev/null +++ b/src/thumb.c @@ -0,0 +1,111 @@ +#include +#include +#include "png.h" + +int main(int argc, char *argv[]) +{ + AVFormatContext *format = NULL; + AVCodecContext *vctx = NULL; + AVStream *vstream = NULL; + AVCodec *vcodec = NULL; + AVFrame *frame = NULL; + AVPacket pkt; + + int found = 0; + + if (argc != 4) { + printf("Expected an input, a time, and an output\n"); + return -1; + } + + av_log_set_level(AV_LOG_QUIET); + + const char *input = argv[1]; + const char *output = argv[3]; + AVRational time = av_d2q(atof(argv[2]), INT_MAX); + + if (avformat_open_input(&format, input, NULL, NULL) != 0) { + printf("Couldn't read file\n"); + return -1; + } + + if (avformat_find_stream_info(format, NULL) < 0) { + printf("Couldn't read file \n"); + return -1; + } + + int vstream_idx = av_find_best_stream(format, AVMEDIA_TYPE_VIDEO, -1, -1, &vcodec, 0); + if (vstream_idx < 0) { + printf("Couldn't read file\n"); + return -1; + } + + vstream = format->streams[vstream_idx]; + + // Set up decoding context + vctx = avcodec_alloc_context3(vcodec); + if (!vctx) { + printf("Couldn't read file\n"); + return -1; + } + + if (avcodec_parameters_to_context(vctx, vstream->codecpar) < 0) { + printf("Couldn't read file\n"); + return -1; + } + + if (avcodec_open2(vctx, vcodec, NULL) < 0) { + printf("Couldn't read file\n"); + return -1; + } + + frame = av_frame_alloc(); + if (!frame) { + printf("Couldn't read file\n"); + return -1; + } + + av_seek_frame(format, -1, 0, AVSEEK_FLAG_BACKWARD); + + // Loop until we get to the first video frame past the intended pts, + // decoding all video frames along the way. + + while (!found && av_read_frame(format, &pkt) >= 0) { + if (pkt.stream_index == vstream_idx) { + AVRational cur_time = { pkt.pts, vstream->time_base.den }; + AVRational next_time = { pkt.pts + pkt.duration, vstream->time_base.den }; + + if (avcodec_send_packet(vctx, &pkt) != 0 || avcodec_receive_frame(vctx, frame) != 0) { + // Decoder returned an error + printf("Couldn't read file\n"); + return -1; + } + + // If this is the first frame past the requested time or the + // current frame contains the requested time, pick this frame. + + if (av_cmp_q(cur_time, time) >= 0 || (av_cmp_q(cur_time, time) <= 0 && av_cmp_q(next_time, time) >= 0)) { + found = 1; + + // Found the frame; write to the provided file + if (mediatools_write_frame_to_png(frame, output) < 0) { + printf("Couldn't read file\n"); + return -1; + } + } + } + + av_packet_unref(&pkt); + } + + if (!found) { + printf("Couldn't read file\n"); + return -1; + } + + av_frame_free(&frame); + avcodec_free_context(&vctx); + avformat_close_input(&format); + + return 0; +}