From 32892e729e7fffc491b72bfb9a677c4f1deb3885 Mon Sep 17 00:00:00 2001 From: "byte[]" Date: Tue, 4 Feb 2020 18:32:59 -0500 Subject: [PATCH] media validator --- Makefile | 37 ++++++++++++++++++++ build/.gitignore | 3 ++ src/stat.c | 80 ++++++++++++++++++++++++++++++++++++++++++ src/validation.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ src/validation.h | 9 +++++ 5 files changed, 220 insertions(+) create mode 100644 Makefile create mode 100644 build/.gitignore create mode 100644 src/stat.c create mode 100644 src/validation.c create mode 100644 src/validation.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c9f0abb --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +RM := rm -f +CC := gcc +CFLAGS := -O3 -Wall -Isrc -I/usr/include/ffmpeg +LIBS := -lavformat -lavutil -lavcodec +LDFLAGS := +INSTALL ?= install +PREFIX ?= /usr/local + +COMMON_OBJECTS := build/validation.o +MEDIASTAT_OBJECTS := build/stat.o + +# Phony rules +.PHONY: all mediastat clean + +all: mediastat + +install: all + $(INSTALL) build/mediastat $(PREFIX)/bin/mediastat + +uninstall: + $(RM) -rf $(PREFIX)/bin/mediastat + +mediastat: build/mediastat + +clean: + $(RM) $(COMMON_OBJECTS) $(MEDIASTAT_OBJECTS) + +# Build rules +build/mediastat: $(COMMON_OBJECTS) $(MEDIASTAT_OBJECTS) + $(CC) $^ $(LDFLAGS) $(LIBS) -o $@ + +build/%.o: src/%.c + $(CC) $(CFLAGS) -MMD -c $< -o $@ + +# Declare dependencies +-include $(COMMON_OBJECTS:.o=.d) +-include $(MEDIASTAT_OBJECTS:.o=.d) diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 0000000..94548af --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,3 @@ +* +*/ +!.gitignore diff --git a/src/stat.c b/src/stat.c new file mode 100644 index 0000000..1c32b5e --- /dev/null +++ b/src/stat.c @@ -0,0 +1,80 @@ +#include +#include + +#include +#include +#include + +#include "validation.h" + +int main(int argc, char *argv[]) +{ + AVFormatContext *format = NULL; + struct stat statbuf; + AVPacket pkt; + + if (argc != 2) { + printf("No input specified\n"); + return -1; + } + + av_log_set_level(AV_LOG_QUIET); + + if (stat(argv[1], &statbuf) != 0) { + printf("Couldn't read file\n"); + return -1; + } + + if (avformat_open_input(&format, argv[1], NULL, NULL) != 0) { + printf("Couldn't read file\n"); + return -1; + } + + if (!mediatools_validate_video(format)) { + // Error is printed by validation function + return -1; + } + + if (av_seek_frame(format, -1, 0, AVSEEK_FLAG_BACKWARD) != 0) { + printf("Couldn't read file\n"); + return -1; + } + + int vstream_idx = av_find_best_stream(format, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); + if (vstream_idx < 0) { + printf("Couldn't read file\n"); + return -1; + } + + uint64_t frames = 0; + int64_t last_pts = 0; + int last_stream = 0; + + while (av_read_frame(format, &pkt) >= 0) { + int64_t new_pts = pkt.pts + pkt.duration; + + if (last_pts < new_pts) { + last_pts = new_pts; + last_stream = pkt.stream_index; + } + + if (pkt.stream_index == vstream_idx) + ++frames; + + av_packet_unref(&pkt); + } + + AVRational tb = format->streams[last_stream]->time_base; + AVRational dur = av_mul_q(av_make_q(last_pts, 1), tb); + + if (!mediatools_validate_duration(dur)) + return -1; + + AVCodecParameters *vpar = format->streams[vstream_idx]->codecpar; + + printf("%ld %lu %d %d %d %d\n", statbuf.st_size, frames, vpar->width, vpar->height, dur.num, dur.den); + + avformat_close_input(&format); + + return 0; +} diff --git a/src/validation.c b/src/validation.c new file mode 100644 index 0000000..a537c0f --- /dev/null +++ b/src/validation.c @@ -0,0 +1,91 @@ +#include +#include +#include + +#include "validation.h" + +const AVRational t_1h = { 3600, 1 }; +const AVRational t_0h = { 0, 1 }; + +int mediatools_validate_video(AVFormatContext *format) +{ + uint64_t num_vstreams = 0; + uint64_t num_astreams = 0; + int64_t vstream_idx = -1; + int64_t astream_idx = -1; + + for (size_t i = 0; i < format->nb_streams; ++i) { + if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + num_vstreams++; + vstream_idx = i; + } + if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + num_astreams++; + astream_idx = i; + } + } + + if (num_vstreams != 1) { + printf("Found %lu video streams (must be 1)\n", num_vstreams); + return false; + } + + if (num_astreams > 1) { + printf("Found %lu audio streams (must be 0 or 1)\n", num_astreams); + return false; + } + + AVInputFormat *iformat = format->iformat; + AVCodecParameters *vpar = format->streams[vstream_idx]->codecpar; + AVCodecParameters *apar = NULL; + + if (astream_idx != -1) + apar = format->streams[astream_idx]->codecpar; + + if (strstr(iformat->name, "matroska")) { + switch (vpar->codec_id) { + default: + printf("Bad video codec for WebM container (must be VP8, or VP9)\n"); + return false; + case AV_CODEC_ID_VP8: + case AV_CODEC_ID_VP9: + ; + } + + if (apar) { + switch (apar->codec_id) { + default: + printf("Bad audio codec for WebM container (must be Opus or Vorbis)\n"); + return false; + case AV_CODEC_ID_VORBIS: + case AV_CODEC_ID_OPUS: + ; + } + } + } else { + printf("Unknown input format\n"); + return false; + } + + if (vpar->width < 32 || vpar->width > 4096) { + printf("Bad width %d (must be 32..4096)\n", vpar->width); + return false; + } + + if (vpar->height < 32 || vpar->height > 4096) { + printf("Bad height %d (must be 32..4096)\n", vpar->height); + return false; + } + + return true; +} + +int mediatools_validate_duration(AVRational dur) +{ + if (av_cmp_q(dur, t_0h) < 0 || av_cmp_q(dur, t_1h) > 0) { + printf("Bad duration (must be 0..1 hour)\n"); + return false; + } + + return true; +} diff --git a/src/validation.h b/src/validation.h new file mode 100644 index 0000000..0e1ef77 --- /dev/null +++ b/src/validation.h @@ -0,0 +1,9 @@ +#ifndef _VALIDATION_H_DEFINED +#define _VALIDATION_H_DEFINED + +#include + +int mediatools_validate_video(AVFormatContext *format); +int mediatools_validate_duration(AVRational dur); + +#endif