1/*
2 * AviSynth/AvxSynth support
3 * Copyright (c) 2012 AvxSynth Team.
4 *
5 * This file is part of FFmpeg
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21#include "libavutil/internal.h"
22#include "libavcodec/internal.h"
23#include "avformat.h"
24#include "internal.h"
25
26/* Enable function pointer definitions for runtime loading. */
27#define AVSC_NO_DECLSPEC
28
29/* Platform-specific directives for AviSynth vs AvxSynth. */
30#ifdef _WIN32
31  #include <windows.h>
32  #undef EXTERN_C
33  #include "compat/avisynth/avisynth_c.h"
34  #include "compat/avisynth/avisynth_c_25.h"
35  #define AVISYNTH_LIB "avisynth"
36  #define USING_AVISYNTH
37#else
38  #include <dlfcn.h>
39  #include "compat/avisynth/avxsynth_c.h"
40    #if defined (__APPLE__)
41      #define AVISYNTH_LIB "libavxsynth.dylib"
42    #else
43      #define AVISYNTH_LIB "libavxsynth.so"
44    #endif
45
46  #define LoadLibrary(x) dlopen(x, RTLD_NOW | RTLD_GLOBAL)
47  #define GetProcAddress dlsym
48  #define FreeLibrary dlclose
49#endif
50
51typedef struct AviSynthLibrary {
52    void *library;
53#define AVSC_DECLARE_FUNC(name) name ## _func name
54    AVSC_DECLARE_FUNC(avs_bit_blt);
55    AVSC_DECLARE_FUNC(avs_clip_get_error);
56    AVSC_DECLARE_FUNC(avs_create_script_environment);
57    AVSC_DECLARE_FUNC(avs_delete_script_environment);
58    AVSC_DECLARE_FUNC(avs_get_audio);
59    AVSC_DECLARE_FUNC(avs_get_error);
60    AVSC_DECLARE_FUNC(avs_get_frame);
61    AVSC_DECLARE_FUNC(avs_get_version);
62    AVSC_DECLARE_FUNC(avs_get_video_info);
63    AVSC_DECLARE_FUNC(avs_invoke);
64    AVSC_DECLARE_FUNC(avs_release_clip);
65    AVSC_DECLARE_FUNC(avs_release_value);
66    AVSC_DECLARE_FUNC(avs_release_video_frame);
67    AVSC_DECLARE_FUNC(avs_take_clip);
68#undef AVSC_DECLARE_FUNC
69} AviSynthLibrary;
70
71typedef struct AviSynthContext {
72    AVS_ScriptEnvironment *env;
73    AVS_Clip *clip;
74    const AVS_VideoInfo *vi;
75
76    /* avisynth_read_packet_video() iterates over this. */
77    int n_planes;
78    const int *planes;
79
80    int curr_stream;
81    int curr_frame;
82    int64_t curr_sample;
83
84    int error;
85
86    /* Linked list pointers. */
87    struct AviSynthContext *next;
88} AviSynthContext;
89
90static const int avs_planes_packed[1] = { 0 };
91static const int avs_planes_grey[1]   = { AVS_PLANAR_Y };
92static const int avs_planes_yuv[3]    = { AVS_PLANAR_Y, AVS_PLANAR_U,
93                                          AVS_PLANAR_V };
94
95/* A conflict between C++ global objects, atexit, and dynamic loading requires
96 * us to register our own atexit handler to prevent double freeing. */
97static AviSynthLibrary avs_library;
98static int avs_atexit_called        = 0;
99
100/* Linked list of AviSynthContexts. An atexit handler destroys this list. */
101static AviSynthContext *avs_ctx_list = NULL;
102
103static av_cold void avisynth_atexit_handler(void);
104
105static av_cold int avisynth_load_library(void)
106{
107    avs_library.library = LoadLibrary(AVISYNTH_LIB);
108    if (!avs_library.library)
109        return AVERROR_UNKNOWN;
110
111#define LOAD_AVS_FUNC(name, continue_on_fail)                          \
112        avs_library.name =                                             \
113            (void *)GetProcAddress(avs_library.library, #name);        \
114        if (!continue_on_fail && !avs_library.name)                    \
115            goto fail;
116
117    LOAD_AVS_FUNC(avs_bit_blt, 0);
118    LOAD_AVS_FUNC(avs_clip_get_error, 0);
119    LOAD_AVS_FUNC(avs_create_script_environment, 0);
120    LOAD_AVS_FUNC(avs_delete_script_environment, 0);
121    LOAD_AVS_FUNC(avs_get_audio, 0);
122    LOAD_AVS_FUNC(avs_get_error, 1); // New to AviSynth 2.6
123    LOAD_AVS_FUNC(avs_get_frame, 0);
124    LOAD_AVS_FUNC(avs_get_version, 0);
125    LOAD_AVS_FUNC(avs_get_video_info, 0);
126    LOAD_AVS_FUNC(avs_invoke, 0);
127    LOAD_AVS_FUNC(avs_release_clip, 0);
128    LOAD_AVS_FUNC(avs_release_value, 0);
129    LOAD_AVS_FUNC(avs_release_video_frame, 0);
130    LOAD_AVS_FUNC(avs_take_clip, 0);
131#undef LOAD_AVS_FUNC
132
133    atexit(avisynth_atexit_handler);
134    return 0;
135
136fail:
137    FreeLibrary(avs_library.library);
138    return AVERROR_UNKNOWN;
139}
140
141/* Note that avisynth_context_create and avisynth_context_destroy
142 * do not allocate or free the actual context! That is taken care of
143 * by libavformat. */
144static av_cold int avisynth_context_create(AVFormatContext *s)
145{
146    AviSynthContext *avs = s->priv_data;
147    int ret;
148
149    if (!avs_library.library)
150        if (ret = avisynth_load_library())
151            return ret;
152
153    avs->env = avs_library.avs_create_script_environment(3);
154    if (avs_library.avs_get_error) {
155        const char *error = avs_library.avs_get_error(avs->env);
156        if (error) {
157            av_log(s, AV_LOG_ERROR, "%s\n", error);
158            return AVERROR_UNKNOWN;
159        }
160    }
161
162    if (!avs_ctx_list) {
163        avs_ctx_list = avs;
164    } else {
165        avs->next    = avs_ctx_list;
166        avs_ctx_list = avs;
167    }
168
169    return 0;
170}
171
172static av_cold void avisynth_context_destroy(AviSynthContext *avs)
173{
174    if (avs_atexit_called)
175        return;
176
177    if (avs == avs_ctx_list) {
178        avs_ctx_list = avs->next;
179    } else {
180        AviSynthContext *prev = avs_ctx_list;
181        while (prev->next != avs)
182            prev = prev->next;
183        prev->next = avs->next;
184    }
185
186    if (avs->clip) {
187        avs_library.avs_release_clip(avs->clip);
188        avs->clip = NULL;
189    }
190    if (avs->env) {
191        avs_library.avs_delete_script_environment(avs->env);
192        avs->env = NULL;
193    }
194}
195
196static av_cold void avisynth_atexit_handler(void)
197{
198    AviSynthContext *avs = avs_ctx_list;
199
200    while (avs) {
201        AviSynthContext *next = avs->next;
202        avisynth_context_destroy(avs);
203        avs = next;
204    }
205    FreeLibrary(avs_library.library);
206
207    avs_atexit_called = 1;
208}
209
210/* Create AVStream from audio and video data. */
211static int avisynth_create_stream_video(AVFormatContext *s, AVStream *st)
212{
213    AviSynthContext *avs = s->priv_data;
214    int planar = 0; // 0: packed, 1: YUV, 2: Y8
215
216    st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
217    st->codec->codec_id   = AV_CODEC_ID_RAWVIDEO;
218    st->codec->width      = avs->vi->width;
219    st->codec->height     = avs->vi->height;
220
221    st->time_base         = (AVRational) { avs->vi->fps_denominator,
222                                           avs->vi->fps_numerator };
223    st->avg_frame_rate    = (AVRational) { avs->vi->fps_numerator,
224                                           avs->vi->fps_denominator };
225    st->start_time        = 0;
226    st->duration          = avs->vi->num_frames;
227    st->nb_frames         = avs->vi->num_frames;
228
229    switch (avs->vi->pixel_type) {
230#ifdef USING_AVISYNTH
231    case AVS_CS_YV24:
232        st->codec->pix_fmt = AV_PIX_FMT_YUV444P;
233        planar             = 1;
234        break;
235    case AVS_CS_YV16:
236        st->codec->pix_fmt = AV_PIX_FMT_YUV422P;
237        planar             = 1;
238        break;
239    case AVS_CS_YV411:
240        st->codec->pix_fmt = AV_PIX_FMT_YUV411P;
241        planar             = 1;
242        break;
243    case AVS_CS_Y8:
244        st->codec->pix_fmt = AV_PIX_FMT_GRAY8;
245        planar             = 2;
246        break;
247#endif
248    case AVS_CS_BGR24:
249        st->codec->pix_fmt = AV_PIX_FMT_BGR24;
250        break;
251    case AVS_CS_BGR32:
252        st->codec->pix_fmt = AV_PIX_FMT_RGB32;
253        break;
254    case AVS_CS_YUY2:
255        st->codec->pix_fmt = AV_PIX_FMT_YUYV422;
256        break;
257    case AVS_CS_YV12:
258        st->codec->pix_fmt = AV_PIX_FMT_YUV420P;
259        planar             = 1;
260        break;
261    case AVS_CS_I420: // Is this even used anywhere?
262        st->codec->pix_fmt = AV_PIX_FMT_YUV420P;
263        planar             = 1;
264        break;
265    default:
266        av_log(s, AV_LOG_ERROR,
267               "unknown AviSynth colorspace %d\n", avs->vi->pixel_type);
268        avs->error = 1;
269        return AVERROR_UNKNOWN;
270    }
271
272    switch (planar) {
273    case 2: // Y8
274        avs->n_planes = 1;
275        avs->planes   = avs_planes_grey;
276        break;
277    case 1: // YUV
278        avs->n_planes = 3;
279        avs->planes   = avs_planes_yuv;
280        break;
281    default:
282        avs->n_planes = 1;
283        avs->planes   = avs_planes_packed;
284    }
285    return 0;
286}
287
288static int avisynth_create_stream_audio(AVFormatContext *s, AVStream *st)
289{
290    AviSynthContext *avs = s->priv_data;
291
292    st->codec->codec_type  = AVMEDIA_TYPE_AUDIO;
293    st->codec->sample_rate = avs->vi->audio_samples_per_second;
294    st->codec->channels    = avs->vi->nchannels;
295    st->time_base          = (AVRational) { 1,
296                                            avs->vi->audio_samples_per_second };
297    st->duration           = avs->vi->num_audio_samples;
298
299    switch (avs->vi->sample_type) {
300    case AVS_SAMPLE_INT8:
301        st->codec->codec_id = AV_CODEC_ID_PCM_U8;
302        break;
303    case AVS_SAMPLE_INT16:
304        st->codec->codec_id = AV_CODEC_ID_PCM_S16LE;
305        break;
306    case AVS_SAMPLE_INT24:
307        st->codec->codec_id = AV_CODEC_ID_PCM_S24LE;
308        break;
309    case AVS_SAMPLE_INT32:
310        st->codec->codec_id = AV_CODEC_ID_PCM_S32LE;
311        break;
312    case AVS_SAMPLE_FLOAT:
313        st->codec->codec_id = AV_CODEC_ID_PCM_F32LE;
314        break;
315    default:
316        av_log(s, AV_LOG_ERROR,
317               "unknown AviSynth sample type %d\n", avs->vi->sample_type);
318        avs->error = 1;
319        return AVERROR_UNKNOWN;
320    }
321    return 0;
322}
323
324static int avisynth_create_stream(AVFormatContext *s)
325{
326    AviSynthContext *avs = s->priv_data;
327    AVStream *st;
328    int ret;
329    int id = 0;
330
331    if (avs_has_video(avs->vi)) {
332        st = avformat_new_stream(s, NULL);
333        if (!st)
334            return AVERROR_UNKNOWN;
335        st->id = id++;
336        if (ret = avisynth_create_stream_video(s, st))
337            return ret;
338    }
339    if (avs_has_audio(avs->vi)) {
340        st = avformat_new_stream(s, NULL);
341        if (!st)
342            return AVERROR_UNKNOWN;
343        st->id = id++;
344        if (ret = avisynth_create_stream_audio(s, st))
345            return ret;
346    }
347    return 0;
348}
349
350static int avisynth_open_file(AVFormatContext *s)
351{
352    AviSynthContext *avs = s->priv_data;
353    AVS_Value arg, val;
354    int ret;
355#ifdef USING_AVISYNTH
356    char filename_ansi[MAX_PATH * 4];
357    wchar_t filename_wc[MAX_PATH * 4];
358#endif
359
360    if (ret = avisynth_context_create(s))
361        return ret;
362
363#ifdef USING_AVISYNTH
364    /* Convert UTF-8 to ANSI code page */
365    MultiByteToWideChar(CP_UTF8, 0, s->filename, -1, filename_wc, MAX_PATH * 4);
366    WideCharToMultiByte(CP_THREAD_ACP, 0, filename_wc, -1, filename_ansi,
367                        MAX_PATH * 4, NULL, NULL);
368    arg = avs_new_value_string(filename_ansi);
369#else
370    arg = avs_new_value_string(s->filename);
371#endif
372    val = avs_library.avs_invoke(avs->env, "Import", arg, 0);
373    if (avs_is_error(val)) {
374        av_log(s, AV_LOG_ERROR, "%s\n", avs_as_error(val));
375        ret = AVERROR_UNKNOWN;
376        goto fail;
377    }
378    if (!avs_is_clip(val)) {
379        av_log(s, AV_LOG_ERROR, "AviSynth script did not return a clip\n");
380        ret = AVERROR_UNKNOWN;
381        goto fail;
382    }
383
384    avs->clip = avs_library.avs_take_clip(val, avs->env);
385    avs->vi   = avs_library.avs_get_video_info(avs->clip);
386
387    /* Release the AVS_Value as it will go out of scope. */
388    avs_library.avs_release_value(val);
389
390    if (ret = avisynth_create_stream(s))
391        goto fail;
392
393    return 0;
394
395fail:
396    avisynth_context_destroy(avs);
397    return ret;
398}
399
400static void avisynth_next_stream(AVFormatContext *s, AVStream **st,
401                                 AVPacket *pkt, int *discard)
402{
403    AviSynthContext *avs = s->priv_data;
404
405    avs->curr_stream++;
406    avs->curr_stream %= s->nb_streams;
407
408    *st = s->streams[avs->curr_stream];
409    if ((*st)->discard == AVDISCARD_ALL)
410        *discard = 1;
411    else
412        *discard = 0;
413
414    return;
415}
416
417/* Copy AviSynth clip data into an AVPacket. */
418static int avisynth_read_packet_video(AVFormatContext *s, AVPacket *pkt,
419                                      int discard)
420{
421    AviSynthContext *avs = s->priv_data;
422    AVS_VideoFrame *frame;
423    unsigned char *dst_p;
424    const unsigned char *src_p;
425    int n, i, plane, rowsize, planeheight, pitch, bits;
426    const char *error;
427
428    if (avs->curr_frame >= avs->vi->num_frames)
429        return AVERROR_EOF;
430
431    /* This must happen even if the stream is discarded to prevent desync. */
432    n = avs->curr_frame++;
433    if (discard)
434        return 0;
435
436#ifdef USING_AVISYNTH
437    /* Define the bpp values for the new AviSynth 2.6 colorspaces.
438     * Since AvxSynth doesn't have these functions, special-case
439     * it in order to avoid implicit declaration errors. */
440
441    if (avs_is_yv24(avs->vi))
442        bits = 24;
443    else if (avs_is_yv16(avs->vi))
444        bits = 16;
445    else if (avs_is_yv411(avs->vi))
446        bits = 12;
447    else if (avs_is_y8(avs->vi))
448        bits = 8;
449    else
450#endif
451        bits = avs_bits_per_pixel(avs->vi);
452
453    /* Without the cast to int64_t, calculation overflows at about 9k x 9k
454     * resolution. */
455    pkt->size = (((int64_t)avs->vi->width *
456                  (int64_t)avs->vi->height) * bits) / 8;
457    if (!pkt->size)
458        return AVERROR_UNKNOWN;
459
460    if (av_new_packet(pkt, pkt->size) < 0)
461        return AVERROR(ENOMEM);
462
463    pkt->pts      = n;
464    pkt->dts      = n;
465    pkt->duration = 1;
466    pkt->stream_index = avs->curr_stream;
467
468    frame = avs_library.avs_get_frame(avs->clip, n);
469    error = avs_library.avs_clip_get_error(avs->clip);
470    if (error) {
471        av_log(s, AV_LOG_ERROR, "%s\n", error);
472        avs->error = 1;
473        av_packet_unref(pkt);
474        return AVERROR_UNKNOWN;
475    }
476
477    dst_p = pkt->data;
478    for (i = 0; i < avs->n_planes; i++) {
479        plane = avs->planes[i];
480        src_p = avs_get_read_ptr_p(frame, plane);
481        pitch = avs_get_pitch_p(frame, plane);
482
483#ifdef USING_AVISYNTH
484        if (avs_library.avs_get_version(avs->clip) == 3) {
485            rowsize     = avs_get_row_size_p_25(frame, plane);
486            planeheight = avs_get_height_p_25(frame, plane);
487        } else {
488            rowsize     = avs_get_row_size_p(frame, plane);
489            planeheight = avs_get_height_p(frame, plane);
490        }
491#else
492        rowsize     = avs_get_row_size_p(frame, plane);
493        planeheight = avs_get_height_p(frame, plane);
494#endif
495
496        /* Flip RGB video. */
497        if (avs_is_rgb24(avs->vi) || avs_is_rgb(avs->vi)) {
498            src_p = src_p + (planeheight - 1) * pitch;
499            pitch = -pitch;
500        }
501
502        avs_library.avs_bit_blt(avs->env, dst_p, rowsize, src_p, pitch,
503                                 rowsize, planeheight);
504        dst_p += rowsize * planeheight;
505    }
506
507    avs_library.avs_release_video_frame(frame);
508    return 0;
509}
510
511static int avisynth_read_packet_audio(AVFormatContext *s, AVPacket *pkt,
512                                      int discard)
513{
514    AviSynthContext *avs = s->priv_data;
515    AVRational fps, samplerate;
516    int samples;
517    int64_t n;
518    const char *error;
519
520    if (avs->curr_sample >= avs->vi->num_audio_samples)
521        return AVERROR_EOF;
522
523    fps.num        = avs->vi->fps_numerator;
524    fps.den        = avs->vi->fps_denominator;
525    samplerate.num = avs->vi->audio_samples_per_second;
526    samplerate.den = 1;
527
528    if (avs_has_video(avs->vi)) {
529        if (avs->curr_frame < avs->vi->num_frames)
530            samples = av_rescale_q(avs->curr_frame, samplerate, fps) -
531                      avs->curr_sample;
532        else
533            samples = av_rescale_q(1, samplerate, fps);
534    } else {
535        samples = 1000;
536    }
537
538    /* After seeking, audio may catch up with video. */
539    if (samples <= 0) {
540        pkt->size = 0;
541        pkt->data = NULL;
542        return 0;
543    }
544
545    if (avs->curr_sample + samples > avs->vi->num_audio_samples)
546        samples = avs->vi->num_audio_samples - avs->curr_sample;
547
548    /* This must happen even if the stream is discarded to prevent desync. */
549    n                 = avs->curr_sample;
550    avs->curr_sample += samples;
551    if (discard)
552        return 0;
553
554    pkt->size = avs_bytes_per_channel_sample(avs->vi) *
555                samples * avs->vi->nchannels;
556    if (!pkt->size)
557        return AVERROR_UNKNOWN;
558
559    if (av_new_packet(pkt, pkt->size) < 0)
560        return AVERROR(ENOMEM);
561
562    pkt->pts      = n;
563    pkt->dts      = n;
564    pkt->duration = samples;
565    pkt->stream_index = avs->curr_stream;
566
567    avs_library.avs_get_audio(avs->clip, pkt->data, n, samples);
568    error = avs_library.avs_clip_get_error(avs->clip);
569    if (error) {
570        av_log(s, AV_LOG_ERROR, "%s\n", error);
571        avs->error = 1;
572        av_packet_unref(pkt);
573        return AVERROR_UNKNOWN;
574    }
575    return 0;
576}
577
578static av_cold int avisynth_read_header(AVFormatContext *s)
579{
580    int ret;
581
582    // Calling library must implement a lock for thread-safe opens.
583    if (ret = avpriv_lock_avformat())
584        return ret;
585
586    if (ret = avisynth_open_file(s)) {
587        avpriv_unlock_avformat();
588        return ret;
589    }
590
591    avpriv_unlock_avformat();
592    return 0;
593}
594
595static int avisynth_read_packet(AVFormatContext *s, AVPacket *pkt)
596{
597    AviSynthContext *avs = s->priv_data;
598    AVStream *st;
599    int discard = 0;
600    int ret;
601
602    if (avs->error)
603        return AVERROR_UNKNOWN;
604
605    /* If either stream reaches EOF, try to read the other one before
606     * giving up. */
607    avisynth_next_stream(s, &st, pkt, &discard);
608    if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
609        ret = avisynth_read_packet_video(s, pkt, discard);
610        if (ret == AVERROR_EOF && avs_has_audio(avs->vi)) {
611            avisynth_next_stream(s, &st, pkt, &discard);
612            return avisynth_read_packet_audio(s, pkt, discard);
613        }
614    } else {
615        ret = avisynth_read_packet_audio(s, pkt, discard);
616        if (ret == AVERROR_EOF && avs_has_video(avs->vi)) {
617            avisynth_next_stream(s, &st, pkt, &discard);
618            return avisynth_read_packet_video(s, pkt, discard);
619        }
620    }
621
622    return ret;
623}
624
625static av_cold int avisynth_read_close(AVFormatContext *s)
626{
627    if (avpriv_lock_avformat())
628        return AVERROR_UNKNOWN;
629
630    avisynth_context_destroy(s->priv_data);
631    avpriv_unlock_avformat();
632    return 0;
633}
634
635static int avisynth_read_seek(AVFormatContext *s, int stream_index,
636                              int64_t timestamp, int flags)
637{
638    AviSynthContext *avs = s->priv_data;
639    AVStream *st;
640    AVRational fps, samplerate;
641
642    if (avs->error)
643        return AVERROR_UNKNOWN;
644
645    fps        = (AVRational) { avs->vi->fps_numerator,
646                                avs->vi->fps_denominator };
647    samplerate = (AVRational) { avs->vi->audio_samples_per_second, 1 };
648
649    st = s->streams[stream_index];
650    if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
651        /* AviSynth frame counts are signed int. */
652        if ((timestamp >= avs->vi->num_frames) ||
653            (timestamp > INT_MAX)              ||
654            (timestamp < 0))
655            return AVERROR_EOF;
656        avs->curr_frame = timestamp;
657        if (avs_has_audio(avs->vi))
658            avs->curr_sample = av_rescale_q(timestamp, samplerate, fps);
659    } else {
660        if ((timestamp >= avs->vi->num_audio_samples) || (timestamp < 0))
661            return AVERROR_EOF;
662        /* Force frame granularity for seeking. */
663        if (avs_has_video(avs->vi)) {
664            avs->curr_frame  = av_rescale_q(timestamp, fps, samplerate);
665            avs->curr_sample = av_rescale_q(avs->curr_frame, samplerate, fps);
666        } else {
667            avs->curr_sample = timestamp;
668        }
669    }
670
671    return 0;
672}
673
674AVInputFormat ff_avisynth_demuxer = {
675    .name           = "avisynth",
676    .long_name      = NULL_IF_CONFIG_SMALL("AviSynth script"),
677    .priv_data_size = sizeof(AviSynthContext),
678    .read_header    = avisynth_read_header,
679    .read_packet    = avisynth_read_packet,
680    .read_close     = avisynth_read_close,
681    .read_seek      = avisynth_read_seek,
682    .extensions     = "avs",
683};
684