remove video specific stuff, base off of video, and add support for HTML
Some checks are pending
docker / Docker (push) Waiting to run
test / Test (push) Waiting to run

This commit is contained in:
Failure 2024-07-30 02:34:01 -07:00
parent 3d03f45eac
commit f348bb92e3
166 changed files with 85 additions and 1430 deletions

View file

@ -71,7 +71,6 @@ RUN go mod download
COPY . . COPY . .
RUN if [ "$TARGETARCH" = "amd64" ]; then go test ./...; fi
RUN go build -o ${GOPATH}/bin/imagorvideo ./cmd/imagorvideo/main.go RUN go build -o ${GOPATH}/bin/imagorvideo ./cmd/imagorvideo/main.go
FROM debian:bookworm-slim FROM debian:bookworm-slim

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
imagorvideoextended "git.cef.icu/CEF/imagorextended"
"github.com/cshum/imagor/config" "github.com/cshum/imagor/config"
"github.com/cshum/imagor/config/awsconfig" "github.com/cshum/imagor/config/awsconfig"
"github.com/cshum/imagor/config/gcloudconfig" "github.com/cshum/imagor/config/gcloudconfig"
@ -12,6 +13,7 @@ import (
func main() { func main() {
var server = config.CreateServer( var server = config.CreateServer(
os.Args[1:], os.Args[1:],
imagorvideoextended.Config,
imagorvideo.Config, imagorvideo.Config,
vipsconfig.WithVips, vipsconfig.WithVips,
awsconfig.WithAWS, awsconfig.WithAWS,

View file

@ -1,4 +1,4 @@
package imagorvideo package imagorvideoextended
import ( import (
"flag" "flag"
@ -9,14 +9,10 @@ import (
// Config imagorvideo config.Option // Config imagorvideo config.Option
func Config(fs *flag.FlagSet, cb func() (*zap.Logger, bool)) imagor.Option { func Config(fs *flag.FlagSet, cb func() (*zap.Logger, bool)) imagor.Option {
var ( var (
ffmpegFallbackImage = fs.String("ffmpeg-fallback-image", "",
"FFmpeg fallback image on processing error. Supports image path enabled by loaders or storages")
logger, isDebug = cb() logger, isDebug = cb()
) )
return imagor.WithProcessors( return imagor.WithProcessors(
NewProcessor( NewProcessor(
WithFallbackImage(*ffmpegFallbackImage),
WithLogger(logger), WithLogger(logger),
WithDebug(isDebug), WithDebug(isDebug),
), ),

View file

@ -1,4 +1,4 @@
package imagorvideo package imagorvideoextended
import ( import (
"github.com/cshum/imagor" "github.com/cshum/imagor"

View file

@ -1,55 +0,0 @@
package ffmpeg
// #include "ffmpeg.h"
import "C"
import (
"github.com/cshum/imagor/vips/pointer"
"io"
"reflect"
"unsafe"
)
//export goPacketRead
func goPacketRead(opaque unsafe.Pointer, buffer *C.uint8_t, bufSize C.int) C.int {
ctx, ok := pointer.Restore(opaque).(*AVContext)
if !ok || ctx.reader == nil {
return C.int(ErrUnknown)
}
size := int(bufSize)
sh := &reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(buffer)),
Len: size,
Cap: size,
}
buf := *(*[]byte)(unsafe.Pointer(sh))
n, err := ctx.reader.Read(buf)
if err == io.EOF {
if n == 0 {
return C.int(ErrEOF)
}
} else if err != nil {
return C.int(ErrUnknown)
}
return C.int(n)
}
//export goPacketSeek
func goPacketSeek(opaque unsafe.Pointer, offset C.int64_t, whence C.int) C.int64_t {
ctx, ok := pointer.Restore(opaque).(*AVContext)
if !ok || ctx.seeker == nil {
return C.int64_t(ErrUnknown)
}
if whence == C.AVSEEK_SIZE {
return C.int64_t(ctx.size)
}
n, err := ctx.seeker.Seek(int64(offset), int(whence))
if err != nil {
return C.int64_t(ErrUnknown)
}
return C.int64_t(n)
}
//export goAVLoggingHandler
func goAVLoggingHandler(level C.int, cstr *C.char) {
log(AVLogLevel(level), C.GoString(cstr))
}

View file

@ -1,38 +0,0 @@
package ffmpeg
// #include "ffmpeg.h"
import "C"
type avError int
// AV Error enum
const (
ErrNoMem = avError(-C.ENOMEM)
ErrEOF = avError(C.AVERROR_EOF)
ErrUnknown = avError(C.AVERROR_UNKNOWN)
ErrDecoderNotFound = avError(C.AVERROR_DECODER_NOT_FOUND)
ErrInvalidData = avError(C.AVERROR_INVALIDDATA)
ErrTooBig = avError(C.ERR_TOO_BIG)
)
func (e avError) errorString() string {
switch e {
case ErrNoMem:
return "cannot allocate memory"
case ErrTooBig:
return "video or cover art size exceeds maximum allowed dimensions"
case ErrEOF:
return "end of file"
case ErrDecoderNotFound:
return "decoder not found"
case ErrInvalidData:
return "invalid data found when processing input"
default:
return "unknown error occurred"
}
}
// Error implements error interface
func (e avError) Error() string {
return "ffmpeg: " + e.errorString()
}

View file

@ -1,15 +0,0 @@
package ffmpeg
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestErrors(t *testing.T) {
assert.Equal(t, "ffmpeg: cannot allocate memory", ErrNoMem.Error())
assert.Equal(t, "ffmpeg: end of file", ErrEOF.Error())
assert.Equal(t, "ffmpeg: unknown error occurred", ErrUnknown.Error())
assert.Equal(t, "ffmpeg: decoder not found", ErrDecoderNotFound.Error())
assert.Equal(t, "ffmpeg: invalid data found when processing input", ErrInvalidData.Error())
assert.Equal(t, "ffmpeg: video or cover art size exceeds maximum allowed dimensions", ErrTooBig.Error())
}

View file

@ -1,413 +0,0 @@
#include "ffmpeg.h"
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void free_format_context(AVFormatContext *fmt_ctx) {
if (!fmt_ctx) {
return;
}
av_free(fmt_ctx->pb->buffer);
avio_context_free(&fmt_ctx->pb);
avformat_close_input(&fmt_ctx);
}
int allocate_format_context(AVFormatContext **fmt_ctx) {
AVFormatContext *ctx = NULL;
if (!(ctx = avformat_alloc_context())) {
return AVERROR(ENOMEM);
}
*fmt_ctx = ctx;
return 0;
}
int create_format_context(AVFormatContext *fmt_ctx, void* opaque, int flags) {
int err = 0;
uint8_t *avio_buffer = NULL;
AVIOContext *avio_ctx = NULL;
if (!(avio_buffer = av_malloc(BUFFER_SIZE))) {
avformat_free_context(fmt_ctx);
return AVERROR(ENOMEM);
}
void *reader = NULL;
void *seeker = NULL;
int write_flag = 0;
int seekable = 0;
if (flags & READ_PACKET_FLAG) {
reader = goPacketRead;
}
if (flags & SEEK_PACKET_FLAG) {
seeker = goPacketSeek;
seekable = 1;
}
if (!(avio_ctx = avio_alloc_context(avio_buffer, BUFFER_SIZE, write_flag, opaque, reader, NULL, seeker))) {
av_free(avio_buffer);
avformat_free_context(fmt_ctx);
return AVERROR(ENOMEM);
}
fmt_ctx->pb = avio_ctx;
fmt_ctx->pb->seekable = seekable;
err = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
if (err < 0) {
av_free(avio_ctx->buffer);
avio_context_free(&avio_ctx);
free_format_context(fmt_ctx);
return err;
}
err = pthread_mutex_lock(&mutex);
if (err < 0) {
free_format_context(fmt_ctx);
return err;
}
err = avformat_find_stream_info(fmt_ctx, NULL);
int muErr = pthread_mutex_unlock(&mutex);
if (err < 0 || muErr < 0) {
free_format_context(fmt_ctx);
if (muErr < 0) {
return muErr;
}
}
return err;
}
static int get_orientation(AVStream *video_stream) {
uint8_t *display_matrix = av_stream_get_side_data(video_stream, AV_PKT_DATA_DISPLAYMATRIX, NULL);
double theta = 0;
if (display_matrix) {
theta = -av_display_rotation_get((int32_t *) display_matrix);
}
theta -= 360 * floor(theta / 360 + 0.9 / 360);
int rot = (int) (90 * round(theta / 90)) % 360;
switch (rot) {
case 90:
return 6;
case 180:
return 3;
case 270:
return 8;
default:
return 1;
};
}
void get_metadata(AVFormatContext *fmt_ctx, char **artist, char **title) {
AVDictionaryEntry *tag = NULL;
if ((tag = av_dict_get(fmt_ctx->metadata, "artist", NULL, 0))) {
*artist = tag->value;
}
if ((tag = av_dict_get(fmt_ctx->metadata, "title", NULL, 0))) {
*title = tag->value;
}
}
int find_streams(AVFormatContext *fmt_ctx, AVStream **video_stream, int *orientation) {
int video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
int audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
int video_audio = 0;
if (audio_stream_index >= 0) {
video_audio |= HAS_AUDIO_STREAM;
}
if (video_stream_index >= 0) {
video_audio |= HAS_VIDEO_STREAM;
} else {
if (video_audio) {
return video_audio;
}
return AVERROR_STREAM_NOT_FOUND;
}
*video_stream = fmt_ctx->streams[video_stream_index];
*orientation = get_orientation(*video_stream);
return video_audio;
}
static int open_codec(AVCodecContext *codec_ctx, AVCodec *codec) {
int err = pthread_mutex_lock(&mutex);
if (err < 0) {
return err;
}
err = avcodec_open2(codec_ctx, codec, NULL);
int muErr = pthread_mutex_unlock(&mutex);
if (muErr < 0) {
return muErr;
}
return err;
}
int create_codec_context(AVStream *video_stream, AVCodecContext **dec_ctx) {
AVCodec *dec = NULL;
AVCodecParameters *par = video_stream->codecpar;
if (par->codec_id == AV_CODEC_ID_VP8) {
dec = avcodec_find_decoder_by_name("libvpx");
} else if (par->codec_id == AV_CODEC_ID_VP9) {
dec = avcodec_find_decoder_by_name("libvpx-vp9");
}
if (!dec) {
dec = avcodec_find_decoder(par->codec_id);
}
if (dec == NULL) {
return AVERROR_DECODER_NOT_FOUND;
}
if (par->format == -1) {
return AVERROR_INVALIDDATA;
}
if (av_get_bits_per_pixel(av_pix_fmt_desc_get(par->format)) * par->height * par->width > 1 << 30) {
return ERR_TOO_BIG;
}
if (!(*dec_ctx = avcodec_alloc_context3(dec))) {
return AVERROR(ENOMEM);
}
int err = avcodec_parameters_to_context(*dec_ctx, par);
if (err < 0) {
avcodec_free_context(dec_ctx);
return err;
}
err = open_codec(*dec_ctx, dec);
if (err < 0) {
avcodec_free_context(dec_ctx);
}
return err;
}
AVFrame *convert_frame_to_rgb(AVFrame *frame, int alpha) {
int output_fmt = alpha ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24;
struct SwsContext *sws_ctx = NULL;
AVFrame *output_frame = av_frame_alloc();
if (!output_frame) {
return output_frame;
}
output_frame->height = frame->height;
output_frame->width = frame->width;
output_frame->format = output_fmt;
if (av_frame_get_buffer(output_frame, 1) < 0) {
goto free;
}
if (output_fmt == frame->format) {
if (av_frame_copy(output_frame, frame) < 0) {
goto free;
}
goto done;
}
sws_ctx = sws_getContext(frame->width, frame->height, frame->format,
output_frame->width, output_frame->height, output_fmt,
SWS_LANCZOS | SWS_ACCURATE_RND, NULL, NULL, NULL);
if (!sws_ctx) {
goto free;
}
if (sws_scale(sws_ctx, (const uint8_t *const *) frame->data, frame->linesize, 0, frame->height, output_frame->data,
output_frame->linesize) != output_frame->height) {
goto free;
} else {
goto done;
}
free:
av_frame_free(&output_frame);
done:
if (sws_ctx) {
sws_freeContext(sws_ctx);
}
return output_frame;
}
AVPacket create_packet() {
AVPacket *pkt = av_packet_alloc();
pkt->data = NULL;
pkt->size = 0;
return *pkt;
}
int
obtain_next_frame(AVFormatContext *fmt_ctx, AVCodecContext *dec_ctx, int stream_index, AVPacket *pkt, AVFrame **frame) {
int err = 0, retry = 0;
if (!(*frame) && !(*frame = av_frame_alloc())) {
err = AVERROR(ENOMEM);
return err;
}
if ((err = avcodec_receive_frame(dec_ctx, *frame)) != AVERROR(EAGAIN)) {
return err;
}
while (1) {
if ((err = av_read_frame(fmt_ctx, pkt)) < 0) {
break;
}
if (pkt->stream_index != stream_index) {
av_packet_unref(pkt);
continue;
}
if ((err = avcodec_send_packet(dec_ctx, pkt)) < 0) {
if (retry++ >= 10) {
break;
}
continue;
}
if (!(*frame) && !(*frame = av_frame_alloc())) {
err = AVERROR(ENOMEM);
break;
}
err = avcodec_receive_frame(dec_ctx, *frame);
if (err >= 0 || err != AVERROR(EAGAIN)) {
break;
}
av_packet_unref(pkt);
}
if (pkt->buf) {
av_packet_unref(pkt);
}
return err;
}
ThumbContext *create_thumb_context(AVStream *stream, AVFrame *frame) {
ThumbContext *thumb_ctx = av_mallocz(sizeof *thumb_ctx);
if (!thumb_ctx) {
return thumb_ctx;
}
thumb_ctx->desc = av_pix_fmt_desc_get(frame->format);
int nb_frames = 100;
if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) {
nb_frames = 1;
} else if (stream->nb_frames && stream->nb_frames < 400) {
nb_frames = (int) (stream->nb_frames >> 2) + 1;
}
int frames_in_128mb = (1 << 30) / (av_get_bits_per_pixel(thumb_ctx->desc) * frame->height * frame->width);
thumb_ctx->max_frames = FFMIN(nb_frames, frames_in_128mb);
int i;
for (i = 0; i < thumb_ctx->desc->nb_components; i++) {
thumb_ctx->hist_size += 1 << thumb_ctx->desc->comp[i].depth;
}
thumb_ctx->median = av_calloc(thumb_ctx->hist_size, sizeof(double));
if (!thumb_ctx->median) {
av_free(thumb_ctx);
return NULL;
}
thumb_ctx->frames = av_malloc_array((size_t) thumb_ctx->max_frames, sizeof *thumb_ctx->frames);
if (!thumb_ctx->frames) {
av_free(thumb_ctx->median);
av_free(thumb_ctx);
return NULL;
}
for (i = 0; i < thumb_ctx->max_frames; i++) {
thumb_ctx->frames[i].frame = NULL;
thumb_ctx->frames[i].hist = av_calloc(thumb_ctx->hist_size, sizeof(int));
if (!thumb_ctx->frames[i].hist) {
for (i--; i >= 0; i--) {
av_free(thumb_ctx->frames[i].hist);
}
av_free(thumb_ctx->median);
av_free(thumb_ctx);
return NULL;
}
}
return thumb_ctx;
}
void free_thumb_context(ThumbContext *thumb_ctx) {
if (!thumb_ctx) {
return;
}
int i;
for (i = 0; i < thumb_ctx->n; i++) {
av_frame_free(&thumb_ctx->frames[i].frame);
av_free(thumb_ctx->frames[i].hist);
}
for (i = thumb_ctx->n; i < thumb_ctx->max_frames; i++) {
av_free(thumb_ctx->frames[i].hist);
}
av_free(thumb_ctx->median);
av_free(thumb_ctx->frames);
av_free(thumb_ctx);
}
static double root_mean_square_error(const int *hist, const double *median, size_t hist_size) {
int i;
double err, sum_sq_err = 0;
for (i = 0; i < hist_size; i++) {
err = median[i] - (double) hist[i];
sum_sq_err += err * err;
}
return sum_sq_err;
}
void populate_frame(ThumbContext *thumb_ctx, int n, AVFrame *frame) {
thumb_ctx->frames[n].frame = frame;
}
void populate_histogram(ThumbContext *thumb_ctx, int n, AVFrame *frame) {
const AVPixFmtDescriptor *desc = thumb_ctx->desc;
thumb_ctx->frames[n].frame = frame;
int *hist = thumb_ctx->frames[n].hist;
AVComponentDescriptor comp;
int w, h, plane, depth, mask, shift, step, height, width;
uint64_t flags;
uint8_t **data = frame->data;
int *linesize = frame->linesize;
for (int c = 0; c < desc->nb_components; c++) {
comp = desc->comp[c];
plane = comp.plane;
depth = comp.depth;
mask = (1 << depth) - 1;
shift = comp.shift;
step = comp.step;
flags = desc->flags;
width = !(desc->log2_chroma_w) || (c != 1 && c != 2) ? frame->width : AV_CEIL_RSHIFT(frame->width,
desc->log2_chroma_w);
height = !(desc->log2_chroma_h) || (c != 1 && c != 2) ? frame->height : AV_CEIL_RSHIFT(frame->height,
desc->log2_chroma_h);
for (h = 0; h < height; h++) {
w = width;
if (flags & AV_PIX_FMT_FLAG_BITSTREAM) {
const uint8_t *p = data[plane] + h * linesize[plane] + (comp.offset >> 3);
shift = 8 - depth - (comp.offset & 7);
while (w--) {
int val = (*p >> shift) & mask;
shift -= step;
p -= shift >> 3;
shift &= 7;
(*(hist + val))++;
}
} else {
const uint8_t *p = data[plane] + h * linesize[plane] + comp.offset;
int is_8bit = shift + depth <= 8;
if (is_8bit)
p += (flags & AV_PIX_FMT_FLAG_BE) != 0;
while (w--) {
int val = is_8bit ? *p :
flags & AV_PIX_FMT_FLAG_BE ? AV_RB16(p) : AV_RL16(p);
val = (val >> shift) & mask;
p += step;
(*(hist + val))++;
}
}
}
hist += 1 << depth;
}
}
int find_best_frame_index(ThumbContext *thumb_ctx) {
int i, j, n = 0, m = thumb_ctx->n, *hist = NULL;
double *median = thumb_ctx->median;
for (j = 0; j < m; j++) {
hist = thumb_ctx->frames[j].hist;
for (i = 0; i < thumb_ctx->hist_size; i++) {
median[i] += (double) hist[i] / m;
}
}
struct thumb_frame *t_frame = NULL;
double min_sum_sq_err = DBL_MAX, sum_sq_err = 0;
for (i = 0; i < thumb_ctx->n; i++) {
t_frame = thumb_ctx->frames + i;
sum_sq_err = root_mean_square_error(t_frame->hist, thumb_ctx->median, thumb_ctx->hist_size);
if (sum_sq_err < min_sum_sq_err) {
min_sum_sq_err = sum_sq_err;
n = i;
}
}
return n;
}
AVFrame *select_frame(ThumbContext *thumb_ctx, int n) {
return thumb_ctx->frames[n].frame;
}

View file

@ -1,401 +0,0 @@
package ffmpeg
// #cgo pkg-config: libavformat libavutil libavcodec libswscale
// #cgo CFLAGS: -std=c11
// #cgo LDFLAGS: -lm
// #include "ffmpeg.h"
import "C"
import (
"github.com/cshum/imagor/vips/pointer"
"io"
"math"
"time"
"unsafe"
)
const (
readPacketFlag = 1
seekPacketFlag = 2
hasVideo = 1
hasAudio = 2
)
// Metadata AV metadata
type Metadata struct {
Orientation int `json:"orientation"`
Duration int `json:"duration,omitempty"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
Title string `json:"title,omitempty"`
Artist string `json:"artist,omitempty"`
FPS float64 `json:"fps,omitempty"`
HasVideo bool `json:"has_video"`
HasAudio bool `json:"has_audio"`
}
// AVContext manages lifecycle of AV contexts and reader stream
type AVContext struct {
opaque unsafe.Pointer
reader io.Reader
seeker io.Seeker
formatContext *C.AVFormatContext
stream *C.AVStream
codecContext *C.AVCodecContext
thumbContext *C.ThumbContext
selectedIndex C.int
selectedDuration time.Duration
frame *C.AVFrame
durationInFormat bool
orientation int
size int64
duration time.Duration
availableIndex C.int
availableDuration time.Duration
width, height int
title, artist string
hasVideo, hasAudio bool
closed bool
}
// LoadAVContext load and create AVContext from reader stream
func LoadAVContext(reader io.Reader, size int64) (*AVContext, error) {
av := &AVContext{
reader: reader,
size: size,
selectedIndex: -1,
}
if seeker, ok := reader.(io.Seeker); ok {
av.seeker = seeker
}
flags := C.int(readPacketFlag)
if av.seeker != nil {
flags |= seekPacketFlag
}
if err := createFormatContext(av, flags); err != nil {
return nil, err
}
if !av.hasVideo {
return av, nil
}
return av, createDecoder(av)
}
// ProcessFrames triggers frame processing
// limit under max num of frames if maxFrames > 0
func (av *AVContext) ProcessFrames(maxFrames int) (err error) {
if av.formatContext == nil || av.codecContext == nil {
return ErrDecoderNotFound
}
if av.thumbContext == nil {
return createThumbContext(av, C.int(maxFrames))
}
return
}
// SelectFrame triggers frame processing and select specific frame index
func (av *AVContext) SelectFrame(n int) (err error) {
nn := C.int(n - 1)
if av.thumbContext != nil && nn > av.availableIndex {
nn = av.availableIndex
}
av.selectedIndex = nn
return av.ProcessFrames(-1)
}
func (av *AVContext) positionToDuration(f float64) time.Duration {
return time.Duration(float64(av.duration) * math.Max(math.Min(f, 1), 0))
}
func (av *AVContext) SelectPosition(f float64) (err error) {
return av.SelectDuration(av.positionToDuration(f))
}
// SelectDuration seeks to keyframe before the specified duration
// then process frames to find precise duration
func (av *AVContext) SelectDuration(ts time.Duration) (err error) {
if ts > 0 {
av.selectedDuration = ts
if err = av.SeekDuration(ts); err != nil {
return
}
return av.ProcessFrames(-1)
} else {
return av.SelectFrame(1)
}
}
// SeekPosition seeks to keyframe before specified position percentage between 0 and 1
// then process frames to find precise position
func (av *AVContext) SeekPosition(f float64) error {
return av.SeekDuration(av.positionToDuration(f))
}
// SeekDuration seeks to keyframe before the specified duration
func (av *AVContext) SeekDuration(ts time.Duration) error {
if av.formatContext == nil || av.codecContext == nil {
return ErrDecoderNotFound
}
return seekDuration(av, ts)
}
// Export frame to RGB or RGBA buffer
func (av *AVContext) Export(bands int) (buf []byte, err error) {
if err = av.ProcessFrames(-1); err != nil {
return
}
if bands < 3 || bands > 4 {
bands = 4
}
if err = convertFrameToRGB(av, bands); err != nil {
return
}
return exportBuffer(av, bands)
}
// Close AVContext objects
func (av *AVContext) Close() {
closeAVContext(av)
}
// Metadata AV metadata
func (av *AVContext) Metadata() *Metadata {
var fps float64
if av.stream != nil {
fps = float64(av.stream.r_frame_rate.num) / float64(av.stream.r_frame_rate.den)
}
return &Metadata{
Orientation: av.orientation,
Duration: int(av.duration / time.Millisecond),
Width: av.width,
Height: av.height,
Title: av.title,
Artist: av.artist,
FPS: fps,
HasVideo: av.hasVideo,
HasAudio: av.hasAudio,
}
}
func closeAVContext(av *AVContext) {
if !av.closed {
if av.frame != nil {
C.av_frame_free(&av.frame)
}
if av.thumbContext != nil {
C.free_thumb_context(av.thumbContext)
}
if av.codecContext != nil {
C.avcodec_free_context(&av.codecContext)
}
if av.formatContext != nil {
C.free_format_context(av.formatContext)
}
pointer.Unref(av.opaque)
av.closed = true
}
}
func createFormatContext(av *AVContext, callbackFlags C.int) error {
intErr := C.allocate_format_context(&av.formatContext)
if intErr < 0 {
return avError(intErr)
}
av.opaque = pointer.Save(av)
intErr = C.create_format_context(av.formatContext, av.opaque, callbackFlags)
if intErr < 0 {
pointer.Unref(av.opaque)
return avError(intErr)
}
metadata(av)
duration(av)
err := findStreams(av)
if err != nil {
C.free_format_context(av.formatContext)
pointer.Unref(av.opaque)
}
return err
}
func metadata(av *AVContext) {
var artist, title *C.char
C.get_metadata(av.formatContext, &artist, &title)
av.artist = C.GoString(artist)
av.title = C.GoString(title)
}
func duration(av *AVContext) {
if av.formatContext.duration > 0 {
av.durationInFormat = true
av.duration = time.Duration(1000 * av.formatContext.duration)
}
}
func findStreams(av *AVContext) error {
var orientation C.int
err := C.find_streams(av.formatContext, &av.stream, &orientation)
if err < 0 {
return avError(err)
}
av.hasVideo = err&hasVideo != 0
av.hasAudio = err&hasAudio != 0
if av.hasVideo {
av.width = int(av.stream.codecpar.width)
av.height = int(av.stream.codecpar.height)
av.orientation = int(orientation)
}
return nil
}
func createDecoder(av *AVContext) error {
err := C.create_codec_context(av.stream, &av.codecContext)
if err < 0 {
return avError(err)
}
return nil
}
func seekDuration(av *AVContext, ts time.Duration) error {
tts := C.int64_t(ts.Milliseconds()) * C.AV_TIME_BASE / 1000
err := C.av_seek_frame(av.formatContext, C.int(-1), tts, C.AVSEEK_FLAG_BACKWARD)
C.avcodec_flush_buffers(av.codecContext)
if err < 0 {
return avError(err)
}
return nil
}
func incrementDuration(av *AVContext, frame *C.AVFrame, i C.int) {
av.availableIndex = i
if frame.pts != C.AV_NOPTS_VALUE {
ptsToNano := C.int64_t(1000000000 * av.stream.time_base.num / av.stream.time_base.den)
newDuration := time.Duration(frame.pts * ptsToNano)
av.availableDuration = newDuration
if !av.durationInFormat && newDuration > av.duration {
av.duration = newDuration
}
}
}
func populateFrames(av *AVContext, frames <-chan *C.AVFrame) <-chan struct{} {
done := make(chan struct{})
var isSelected = av.selectedIndex > -1
go func() {
var n C.int
if !isSelected {
for frame := range frames {
C.populate_histogram(av.thumbContext, n, frame)
n++
}
} else {
for frame := range frames {
C.populate_frame(av.thumbContext, n, frame)
n++
}
}
av.thumbContext.n = n
close(done)
}()
return done
}
func createThumbContext(av *AVContext, maxFrames C.int) error {
pkt := C.create_packet()
var frame *C.AVFrame
err := C.obtain_next_frame(av.formatContext, av.codecContext, av.stream.index, &pkt, &frame)
if err >= 0 {
incrementDuration(av, frame, 0)
av.thumbContext = C.create_thumb_context(av.stream, frame)
if av.thumbContext == nil {
err = C.int(ErrNoMem)
}
}
if err < 0 {
if pkt.buf != nil {
C.av_packet_unref(&pkt)
}
if frame != nil {
C.av_frame_free(&frame)
}
return avError(err)
}
n := av.thumbContext.max_frames
if maxFrames > 0 && n > maxFrames {
n = maxFrames
}
if av.selectedIndex > -1 && n > av.selectedIndex+1 {
n = av.selectedIndex + 1
}
if av.selectedDuration > 0 && av.selectedIndex < 0 {
av.selectedIndex = 0
}
frames := make(chan *C.AVFrame, n)
done := populateFrames(av, frames)
frames <- frame
if pkt.buf != nil {
C.av_packet_unref(&pkt)
}
return populateThumbContext(av, frames, n, done)
}
func populateThumbContext(av *AVContext, frames chan *C.AVFrame, n C.int, done <-chan struct{}) error {
pkt := C.create_packet()
var frame *C.AVFrame
var err C.int
for i := C.int(1); i < n; i++ {
err = C.obtain_next_frame(av.formatContext, av.codecContext, av.stream.index, &pkt, &frame)
if err < 0 {
break
}
incrementDuration(av, frame, i)
frames <- frame
frame = nil
if av.selectedDuration > 0 {
if av.availableDuration <= av.selectedDuration {
av.selectedIndex = i
} else {
break
}
}
}
if av.selectedIndex > av.availableIndex {
av.selectedIndex = av.availableIndex
}
close(frames)
if pkt.buf != nil {
C.av_packet_unref(&pkt)
}
if frame != nil {
C.av_frame_free(&frame)
}
<-done
if err != 0 && err != C.int(ErrEOF) {
return avError(err)
}
if av.selectedIndex < 0 {
av.selectedIndex = C.find_best_frame_index(av.thumbContext)
}
return nil
}
func convertFrameToRGB(av *AVContext, bands int) error {
var alpha int
if bands == 4 {
alpha = 1
}
av.frame = C.convert_frame_to_rgb(
C.select_frame(av.thumbContext, av.selectedIndex), C.int(alpha))
if av.frame == nil {
return ErrNoMem
}
return nil
}
func exportBuffer(av *AVContext, bands int) ([]byte, error) {
if av.frame == nil {
return nil, ErrInvalidData
}
size := av.height * av.width * bands
buf := C.GoBytes(unsafe.Pointer(av.frame.data[0]), C.int(size))
return buf, nil
}

View file

@ -1,69 +0,0 @@
#include <math.h>
#include <pthread.h>
#include <float.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/file.h>
#include <libavutil/pixfmt.h>
#include <libswscale/swscale.h>
#include <libavutil/pixdesc.h>
#include <libavutil/intreadwrite.h>
#include <libavutil/imgutils.h>
#include <libavutil/display.h>
#define BUFFER_SIZE 1 << 12
#define READ_PACKET_FLAG 1
#define SEEK_PACKET_FLAG 2
#define HAS_VIDEO_STREAM 1
#define HAS_AUDIO_STREAM 2
#define ERR_TOO_BIG FFERRTAG('H','M','M','M')
struct thumb_frame {
AVFrame *frame;
int *hist;
};
typedef struct ThumbContext {
int n, max_frames;
struct thumb_frame *frames;
double *median;
const AVPixFmtDescriptor *desc;
size_t hist_size;
} ThumbContext;
int allocate_format_context(AVFormatContext **fmt_ctx);
int create_format_context(AVFormatContext *fmt_ctx, void* opaque, int callbacks);
void free_format_context(AVFormatContext *fmt_ctx);
void get_metadata(AVFormatContext *fmt_ctx, char **artist, char **title);
int find_streams(AVFormatContext *fmt_ctx, AVStream **video_stream, int *orientation);
int create_codec_context(AVStream *video_stream, AVCodecContext **dec_ctx);
AVFrame *convert_frame_to_rgb(AVFrame *frame, int alpha);
AVPacket create_packet();
int
obtain_next_frame(AVFormatContext *fmt_ctx, AVCodecContext *dec_ctx, int stream_index, AVPacket *pkt, AVFrame **frame);
ThumbContext *create_thumb_context(AVStream *stream, AVFrame *frame);
void free_thumb_context(ThumbContext *thumb_ctx);
int find_best_frame_index(ThumbContext *thumb_ctx);
AVFrame *select_frame(ThumbContext *thumb_ctx, int i);
void populate_frame(ThumbContext *thumb_ctx, int n, AVFrame *frame);
void populate_histogram(ThumbContext *thumb_ctx, int n, AVFrame *frame);
extern int goPacketRead(void *opaque, uint8_t *buf, int buf_size);
extern int64_t goPacketSeek(void *opaque, int64_t seek, int whence);

View file

@ -1,188 +0,0 @@
package ffmpeg
import (
"encoding/json"
"fmt"
"github.com/cshum/imagor/vips"
"github.com/cshum/imagor/vips/pointer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"io"
"os"
"reflect"
"strings"
"testing"
"time"
)
var files = []string{
"everybody-betray-me.mkv",
"alpha-webm.webm",
"schizo.flv",
"macabre.mp4",
"schizo_0.mp4",
"schizo_90.mp4",
"schizo_180.mp4",
"schizo_270.mp4",
"with_cover.mp3",
}
var noVideo = []string{
"no_cover.mp3",
}
var baseDir = "../testdata/"
func TestAVContext(t *testing.T) {
vips.Startup(nil)
SetFFmpegLogLevel(AVLogDebug)
logger := zap.NewExample()
SetLogging(nil)
log(AVLogDebug, "nop logging")
SetLogging(func(level AVLogLevel, message string) {
message = strings.TrimSuffix(message, "\n")
switch level {
case AVLogTrace, AVLogDebug, AVLogVerbose:
logger.Debug("ffmpeg", zap.String("log", message))
case AVLogInfo:
logger.Info("ffmpeg", zap.String("log", message))
case AVLogWarning, AVLogError, AVLogFatal, AVLogPanic:
logger.Warn("ffmpeg", zap.String("log", message))
}
})
require.NoError(t, os.MkdirAll(baseDir+"golden/meta", 0755))
require.NoError(t, os.MkdirAll(baseDir+"golden/export", 0755))
t.Parallel()
for _, filename := range files {
for _, n := range []int{-1, 1, 5, 10, 9999, 99999} {
name := filename
if n > -1 {
name = fmt.Sprintf("%s-%d", filename, n)
}
t.Run(name, func(t *testing.T) {
path := baseDir + filename
reader, err := os.Open(path)
require.NoError(t, err)
stats, err := os.Stat(path)
require.NoError(t, err)
av, err := LoadAVContext(reader, stats.Size())
meta := av.Metadata()
metaBuf, err := json.Marshal(meta)
require.NoError(t, err)
goldenFile := baseDir + "golden/meta/" + name + ".meta.json"
if curr, err := os.ReadFile(goldenFile); err == nil {
assert.Equal(t, string(curr), string(metaBuf))
} else {
require.NoError(t, os.WriteFile(goldenFile, metaBuf, 0666))
}
require.NoError(t, err)
defer av.Close()
if n == 10 {
require.NoError(t, av.ProcessFrames(n))
} else if n == 99999 {
require.NoError(t, av.SelectDuration(time.Second))
} else if n == 9999 {
require.NoError(t, av.SelectPosition(0.7))
} else if n == 1 {
require.NoError(t, av.SelectDuration(0))
} else if n == 5 {
require.NoError(t, av.SelectFrame(n))
} else {
require.NoError(t, av.SeekPosition(0.7))
}
bands := 4
if n == 99999 {
bands = 999
}
buf, err := av.Export(bands)
require.NoError(t, err)
if bands > 4 {
bands = 4
}
img, err := vips.LoadImageFromMemory(buf, meta.Width, meta.Height, bands)
require.NoError(t, err)
buf, err = img.ExportJpeg(nil)
require.NoError(t, err)
goldenFile = baseDir + "golden/export/" + name + ".jpg"
if curr, err := os.ReadFile(goldenFile); err == nil {
assert.True(t, reflect.DeepEqual(curr, buf))
} else {
require.NoError(t, os.WriteFile(goldenFile, buf, 0666))
}
})
}
}
}
func TestNoVideo(t *testing.T) {
require.NoError(t, os.MkdirAll(baseDir+"golden/meta", 0755))
require.NoError(t, os.MkdirAll(baseDir+"golden/export", 0755))
for _, filename := range noVideo {
for i := 0; i < 2; i++ {
t.Run(fmt.Sprintf("%s-%d", filename, i), func(t *testing.T) {
path := baseDir + filename
reader, err := os.Open(path)
require.NoError(t, err)
stats, err := os.Stat(path)
require.NoError(t, err)
av, err := LoadAVContext(reader, stats.Size())
require.NoError(t, err)
defer av.Close()
require.Equal(t, ErrDecoderNotFound, av.ProcessFrames(-1))
meta := av.Metadata()
metaBuf, err := json.Marshal(meta)
require.NoError(t, err)
goldenFile := baseDir + "golden/meta/" + filename + ".meta.json"
if curr, err := os.ReadFile(goldenFile); err == nil {
assert.Equal(t, string(curr), string(metaBuf))
} else {
require.NoError(t, os.WriteFile(goldenFile, metaBuf, 0666))
}
if i == 0 {
buf, err := av.Export(3)
require.Empty(t, buf)
assert.Equal(t, ErrDecoderNotFound, err)
} else {
assert.Equal(t, ErrDecoderNotFound, av.SelectFrame(1))
}
})
}
}
}
func TestCorrupted(t *testing.T) {
filename := "macabre.mp4"
path := baseDir + filename
file, err := os.Open(path)
require.NoError(t, err)
reader := &readCloser{
Reader: io.LimitReader(file, 1024),
Closer: file,
}
stats, err := os.Stat(path)
require.NoError(t, err)
av, err := LoadAVContext(reader, stats.Size())
require.Equal(t, ErrInvalidData, err)
require.Empty(t, av)
}
func TestCorruptedOpaque(t *testing.T) {
filename := "macabre.mp4"
path := baseDir + filename
reader, err := os.Open(path)
require.NoError(t, err)
stats, err := os.Stat(path)
require.NoError(t, err)
av, err := LoadAVContext(reader, stats.Size())
require.NoError(t, err)
defer av.Close()
pointer.Unref(av.opaque)
err = av.ProcessFrames(-1)
assert.Equal(t, ErrUnknown, err)
}
type readCloser struct {
io.Reader
io.Closer
}

View file

@ -1,12 +0,0 @@
#include "logging.h"
void goavLogCallback(void *class_ptr, int level, const char *fmt, va_list vl) {
char line[LINE_SZ];
int print_prefix = 1;
av_log_format_line(class_ptr, level, fmt, vl, line, LINE_SZ, &print_prefix);
goAVLoggingHandler(level, line);
}
void goavLogSetup() {
av_log_set_callback(goavLogCallback);
}

View file

@ -1,55 +0,0 @@
package ffmpeg
// #include "ffmpeg.h"
// #include "logging.h"
import "C"
import "sync"
// AVLogLevel defines the ffmpeg threshold for dumping information to stderr.
type AVLogLevel int
// AVLogLevel enum
const (
AVLogQuiet AVLogLevel = (iota - 1) * 8
AVLogPanic
AVLogFatal
AVLogError
AVLogWarning
AVLogInfo
AVLogVerbose
AVLogDebug
AVLogTrace
)
var (
currentLoggingHandlerFunction = noopLoggingHandler
currentLoggingVerbosity AVLogLevel
onceLogging sync.Once
)
// SetFFmpegLogLevel allows you to change the log level from the default (AVLogInfo).
func SetFFmpegLogLevel(logLevel AVLogLevel) {
C.av_log_set_level(C.int(logLevel))
currentLoggingVerbosity = logLevel
}
type LoggingHandlerFunction func(messageLevel AVLogLevel, message string)
// SetLogging set AV logging handler
func SetLogging(handler LoggingHandlerFunction) {
onceLogging.Do(func() {
C.goavLogSetup()
})
if handler != nil {
currentLoggingHandlerFunction = handler
}
}
func noopLoggingHandler(_ AVLogLevel, _ string) {
}
func log(level AVLogLevel, message string) {
if level <= currentLoggingVerbosity {
currentLoggingHandlerFunction(level, message)
}
}

View file

@ -1,11 +0,0 @@
#include <stdarg.h>
#define LINE_SZ 1024
extern void goAVLoggingHandler(int level, char *str);
extern void av_log_set_callback(void (*callback)(void *, int, const char *, va_list));
extern void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl, char *line, int line_size, int *print_prefix);
void goavLogCallback(void *class_ptr, int level, const char *fmt, va_list vl);
void goavLogSetup();

5
go.mod
View file

@ -1,4 +1,4 @@
module github.com/cshum/imagorvideo module git.cef.icu/CEF/imagorextended
go 1.21 go 1.21
@ -6,6 +6,7 @@ toolchain go1.21.1
require ( require (
github.com/cshum/imagor v1.4.13 github.com/cshum/imagor v1.4.13
github.com/cshum/imagorvideo v0.4.13
github.com/gabriel-vasile/mimetype v1.4.4 github.com/gabriel-vasile/mimetype v1.4.4
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
@ -18,6 +19,8 @@ require (
cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect
cloud.google.com/go/iam v1.1.11 // indirect cloud.google.com/go/iam v1.1.11 // indirect
cloud.google.com/go/storage v1.43.0 // indirect cloud.google.com/go/storage v1.43.0 // indirect
github.com/antchfx/htmlquery v1.3.2 // indirect
github.com/antchfx/xpath v1.3.1 // indirect
github.com/aws/aws-sdk-go v1.54.20 // indirect github.com/aws/aws-sdk-go v1.54.20 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect

27
go.sum
View file

@ -16,6 +16,10 @@ cloud.google.com/go/pubsub v1.40.0/go.mod h1:BVJI4sI2FyXp36KFKvFwcfDRDfR8MiLT8mM
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/antchfx/htmlquery v1.3.2 h1:85YdttVkR1rAY+Oiv/nKI4FCimID+NXhDn82kz3mEvs=
github.com/antchfx/htmlquery v1.3.2/go.mod h1:1mbkcEgEarAokJiWhTfr4hR06w/q2ZZjnYLrDt6CTUk=
github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk=
github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/aws/aws-sdk-go v1.54.20 h1:FZ2UcXya7bUkvkpf7TaPmiL7EubK0go1nlXGLRwEsoo= github.com/aws/aws-sdk-go v1.54.20 h1:FZ2UcXya7bUkvkpf7TaPmiL7EubK0go1nlXGLRwEsoo=
github.com/aws/aws-sdk-go v1.54.20/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go v1.54.20/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -27,6 +31,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cshum/imagor v1.4.13 h1:BFcSpsTUOJj+Wv5SzDeXa8bhsT/Ehw7EcrFD0UTdpmU= github.com/cshum/imagor v1.4.13 h1:BFcSpsTUOJj+Wv5SzDeXa8bhsT/Ehw7EcrFD0UTdpmU=
github.com/cshum/imagor v1.4.13/go.mod h1:LHxXgks6Y06GzEHitnlO8vcD5gznxIHWPdvGsnlGpMo= github.com/cshum/imagor v1.4.13/go.mod h1:LHxXgks6Y06GzEHitnlO8vcD5gznxIHWPdvGsnlGpMo=
github.com/cshum/imagorvideo v0.4.13 h1:tn+TmdPMvS00XndPX8j/BUMyebErkxXgpJ9CLxNvROE=
github.com/cshum/imagorvideo v0.4.13/go.mod h1:y2g2GQst6b1X+jvAcxhDusTcYYGJqp+fSRcUo3l+PxU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -129,6 +135,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
@ -151,6 +158,7 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -159,12 +167,17 @@ golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -173,16 +186,27 @@ golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
@ -192,8 +216,11 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw= google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw=
google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag=

View file

@ -1,4 +1,4 @@
package imagorvideo package imagorvideoextended
import "go.uber.org/zap" import "go.uber.org/zap"

View file

@ -1,16 +1,14 @@
package imagorvideo package imagorvideoextended
import ( import (
"context" "context"
"github.com/antchfx/htmlquery"
"github.com/cshum/imagor" "github.com/cshum/imagor"
"github.com/cshum/imagor/imagorpath" "github.com/cshum/imagor/imagorpath"
"github.com/cshum/imagorvideo/ffmpeg"
"github.com/gabriel-vasile/mimetype" "github.com/gabriel-vasile/mimetype"
"go.uber.org/zap" "go.uber.org/zap"
"io" "io"
"strconv"
"strings" "strings"
"time"
) )
// Processor for imagorvideo that implements imagor.Processor interface // Processor for imagorvideo that implements imagor.Processor interface
@ -33,22 +31,6 @@ func NewProcessor(options ...Option) *Processor {
// Startup implements imagor.Processor interface // Startup implements imagor.Processor interface
func (p *Processor) Startup(_ context.Context) error { func (p *Processor) Startup(_ context.Context) error {
ffmpeg.SetLogging(func(level ffmpeg.AVLogLevel, message string) {
message = strings.TrimSuffix(message, "\n")
switch level {
case ffmpeg.AVLogTrace, ffmpeg.AVLogDebug, ffmpeg.AVLogVerbose:
p.Logger.Debug("ffmpeg", zap.String("log", message))
case ffmpeg.AVLogInfo:
p.Logger.Info("ffmpeg", zap.String("log", message))
case ffmpeg.AVLogWarning, ffmpeg.AVLogError, ffmpeg.AVLogFatal, ffmpeg.AVLogPanic:
p.Logger.Warn("ffmpeg", zap.String("log", message))
}
})
if p.Debug {
ffmpeg.SetFFmpegLogLevel(ffmpeg.AVLogDebug)
} else {
ffmpeg.SetFFmpegLogLevel(ffmpeg.AVLogError)
}
return nil return nil
} }
@ -75,15 +57,15 @@ func (p *Processor) Process(ctx context.Context, in *imagor.Blob, params imagorp
} }
} }
}() }()
var filters imagorpath.Filters
var mime = mimetype.Detect(in.Sniff()) var mime = mimetype.Detect(in.Sniff())
if typ := mime.String(); !strings.HasPrefix(typ, "video/") &&
!strings.HasPrefix(typ, "audio/") { if typ := mime.String(); !strings.HasPrefix(typ, "text/html") {
// forward identical for non video nor audio // forward identical for non video nor audio
err = imagor.ErrForward{Params: params} err = imagor.ErrForward{Params: params}
out = in out = in
return return
} }
rs, size, err := in.NewReadSeeker() rs, size, err := in.NewReadSeeker()
if err != nil { if err != nil {
return return
@ -100,95 +82,61 @@ func (p *Processor) Process(ctx context.Context, in *imagor.Blob, params imagorp
return return
} }
} }
av, err := ffmpeg.LoadAVContext(rs, size) all, err := in.ReadAll()
if err != nil { if err != nil {
return return nil, err
} }
defer av.Close()
meta := av.Metadata() doc, err := htmlquery.Parse(strings.NewReader(string(all[:])))
if params.Meta { meta := Metadata{
out = imagor.NewBlobFromJsonMarshal(Metadata{
Format: strings.TrimPrefix(mime.Extension(), "."), Format: strings.TrimPrefix(mime.Extension(), "."),
ContentType: mime.String(), Title: "",
Metadata: meta, Description: "",
}) Image: "",
return
} }
bands := 3 metaTags := htmlquery.Find(doc, "//meta[@property]")
for _, filter := range params.Filters { for _, metaTag := range metaTags {
switch filter.Name { var property = htmlquery.SelectAttr(metaTag, "property")
case "format": var val = htmlquery.SelectAttr(metaTag, "content")
switch strings.ToLower(filter.Args) {
case "webp", "png", "gif": switch property {
switch mime.Extension() { case "og:image":
case ".webm", ".flv", ".mov", ".avi": fallthrough
bands = 4 case "twitter:image:src":
meta.Image = val
break
case "og:title":
fallthrough
case "twitter:title":
meta.Title = val
break
case "twitter:description":
fallthrough
case "og:description":
meta.Description = val
} }
} }
case "frame": if meta.Title == "" {
if ts, e := time.ParseDuration(filter.Args); e == nil { title := htmlquery.FindOne(doc, "//title")
if err = av.SelectDuration(ts); err != nil { if title != nil {
return meta.Title = htmlquery.InnerText(title)
} } else {
} else if f, e := strconv.ParseFloat(filter.Args, 64); e == nil { meta.Title = in.FilePath()
if strings.Contains(filter.Args, ".") {
if err = av.SelectPosition(f); err != nil {
return
}
} else if n := int(f); n >= 1 {
if err = av.SelectFrame(n); err != nil {
return
}
}
}
case "seek":
if ts, e := time.ParseDuration(filter.Args); e == nil {
if err = av.SeekDuration(ts); err != nil {
return
}
} else if f, e := strconv.ParseFloat(filter.Args, 64); e == nil {
if err = av.SeekPosition(f); err != nil {
return
}
}
case "max_frames":
n, _ := strconv.Atoi(filter.Args)
if err = av.ProcessFrames(n); err != nil {
return
}
} }
} }
switch meta.Orientation { out = imagor.NewBlobFromJsonMarshal(meta)
case 3:
filters = append(filters, imagorpath.Filter{Name: "orient", Args: "180"})
case 6:
filters = append(filters, imagorpath.Filter{Name: "orient", Args: "270"})
case 8:
filters = append(filters, imagorpath.Filter{Name: "orient", Args: "90"})
}
buf, err := av.Export(bands)
if err != nil || len(buf) == 0 {
if err == nil {
err = imagor.ErrUnsupportedFormat
}
return
}
out = imagor.NewBlobFromMemory(buf, meta.Width, meta.Height, bands)
if len(filters) > 0 {
params.Filters = append(params.Filters, filters...)
params.Path = imagorpath.GeneratePath(params)
}
err = imagor.ErrForward{Params: params}
return return
} }
// Metadata imagorvideo metadata // Metadata imagorvideo metadata
type Metadata struct { type Metadata struct {
Format string `json:"format"` Format string `json:"format"`
ContentType string `json:"content_type"` Title string `json:"title"`
*ffmpeg.Metadata Description string `json:"description"`
Image string `json:"image"`
} }
var transPixel = []byte("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B") var transPixel = []byte("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B")

View file

@ -1,4 +1,4 @@
package imagorvideo package imagorvideoextended
import ( import (
"context" "context"

Binary file not shown.

BIN
testdata/black.jpg vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

BIN
testdata/demo.jpg vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

BIN
testdata/demo.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

BIN
testdata/demo2.jpg vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

BIN
testdata/demo3.jpg vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View file

@ -1 +0,0 @@
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":12040,"width":720,"height":576,"fps":25,"has_video":true,"has_audio":false}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":7407,"width":640,"height":480,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":3925,"width":492,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":0,"duration":13536,"has_video":false,"has_audio":true}

View file

@ -1 +0,0 @@
{"orientation":1,"duration":2560,"width":480,"height":360,"fps":29.97002997002997,"has_video":true,"has_audio":true}

Some files were not shown because too many files have changed in this diff Show more