diff options
Diffstat (limited to 'demux/demux_mf.c')
-rw-r--r-- | demux/demux_mf.c | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/demux/demux_mf.c b/demux/demux_mf.c new file mode 100644 index 0000000..8f7cb70 --- /dev/null +++ b/demux/demux_mf.c @@ -0,0 +1,373 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "osdep/io.h" + +#include "mpv_talloc.h" +#include "common/msg.h" +#include "options/options.h" +#include "options/m_config.h" +#include "options/path.h" +#include "misc/ctype.h" + +#include "stream/stream.h" +#include "demux.h" +#include "stheader.h" +#include "codec_tags.h" + +#define MF_MAX_FILE_SIZE (1024 * 1024 * 256) + +typedef struct mf { + struct mp_log *log; + struct sh_stream *sh; + int curr_frame; + int nr_of_files; + char **names; + // optional + struct stream **streams; +} mf_t; + + +static void mf_add(mf_t *mf, const char *fname) +{ + char *entry = talloc_strdup(mf, fname); + MP_TARRAY_APPEND(mf, mf->names, mf->nr_of_files, entry); +} + +static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename) +{ + struct mp_log *log = d->log; + int error_count = 0; + int count = 0; + + mf_t *mf = talloc_zero(talloc_ctx, mf_t); + mf->log = log; + + if (filename[0] == '@') { + struct stream *s = stream_create(filename + 1, + d->stream_origin | STREAM_READ, d->cancel, d->global); + if (s) { + while (1) { + char buf[512]; + int len = stream_read_peek(s, buf, sizeof(buf)); + if (!len) + break; + bstr data = (bstr){buf, len}; + int pos = bstrchr(data, '\n'); + data = bstr_splice(data, 0, pos < 0 ? data.len : pos + 1); + bstr fname = bstr_strip(data); + if (fname.len) { + if (bstrchr(fname, '\0') >= 0) { + mp_err(log, "invalid filename\n"); + break; + } + char *entry = bstrto0(mf, fname); + if (!mp_path_exists(entry)) { + mp_verbose(log, "file not found: '%s'\n", entry); + } else { + MP_TARRAY_APPEND(mf, mf->names, mf->nr_of_files, entry); + } + } + stream_seek_skip(s, stream_tell(s) + data.len); + } + free_stream(s); + + mp_info(log, "number of files: %d\n", mf->nr_of_files); + goto exit_mf; + } + mp_info(log, "%s is not indirect filelist\n", filename + 1); + } + + if (strchr(filename, ',')) { + mp_info(log, "filelist: %s\n", filename); + bstr bfilename = bstr0(filename); + + while (bfilename.len) { + bstr bfname; + bstr_split_tok(bfilename, ",", &bfname, &bfilename); + char *fname2 = bstrdup0(mf, bfname); + + if (!mp_path_exists(fname2)) + mp_verbose(log, "file not found: '%s'\n", fname2); + else { + mf_add(mf, fname2); + } + talloc_free(fname2); + } + mp_info(log, "number of files: %d\n", mf->nr_of_files); + + goto exit_mf; + } + + size_t fname_avail = strlen(filename) + 32; + char *fname = talloc_size(mf, fname_avail); + +#if HAVE_GLOB + if (!strchr(filename, '%')) { + // append * if none present + snprintf(fname, fname_avail, "%s%c", filename, + strchr(filename, '*') ? 0 : '*'); + mp_info(log, "search expr: %s\n", fname); + + glob_t gg; + if (glob(fname, 0, NULL, &gg)) { + talloc_free(mf); + return NULL; + } + + for (int i = 0; i < gg.gl_pathc; i++) { + if (mp_path_isdir(gg.gl_pathv[i])) + continue; + mf_add(mf, gg.gl_pathv[i]); + } + mp_info(log, "number of files: %d\n", mf->nr_of_files); + globfree(&gg); + goto exit_mf; + } +#endif + + // We're using arbitrary user input as printf format with 1 int argument. + // Any format which uses exactly 1 int argument would be valid, but for + // simplicity we reject all conversion specifiers except %% and simple + // integer specifier: %[.][NUM]d where NUM is 1-3 digits (%.d is valid) + const char *f = filename; + int MAXDIGS = 3, nspec = 0, c; + bool bad_spec = false; + + while (nspec < 2 && (c = *f++)) { + if (c != '%') + continue; + + if (*f == '%') { + // '%%', which ends up as an explicit % in the output. + // Skipping forwards as it doesn't require further attention. + f++; + continue; + } + + // Now c == '%' and *f != '%', thus we have entered territory of format + // specifiers which we are interested in. + nspec++; + + if (*f == '.') + f++; + + for (int ndig = 0; mp_isdigit(*f) && ndig < MAXDIGS; ndig++, f++) + /* no-op */; + + if (*f != 'd') { + bad_spec = true; // not int, or beyond our validation capacity + break; + } + + // *f is 'd' + f++; + } + + // nspec==0 (zero specifiers) is rejected because fname wouldn't advance. + if (bad_spec || nspec != 1) { + mp_err(log, "unsupported expr format: '%s'\n", filename); + goto exit_mf; + } + + mp_info(log, "search expr: %s\n", filename); + + while (error_count < 5) { + if (snprintf(fname, fname_avail, filename, count++) >= fname_avail) { + mp_err(log, "format result too long: '%s'\n", filename); + goto exit_mf; + } + if (!mp_path_exists(fname)) { + error_count++; + mp_verbose(log, "file not found: '%s'\n", fname); + } else { + mf_add(mf, fname); + } + } + + mp_info(log, "number of files: %d\n", mf->nr_of_files); + +exit_mf: + return mf; +} + +static mf_t *open_mf_single(void *talloc_ctx, struct mp_log *log, char *filename) +{ + mf_t *mf = talloc_zero(talloc_ctx, mf_t); + mf->log = log; + mf_add(mf, filename); + return mf; +} + +static void demux_seek_mf(demuxer_t *demuxer, double seek_pts, int flags) +{ + mf_t *mf = demuxer->priv; + double newpos = seek_pts * mf->sh->codec->fps; + if (flags & SEEK_FACTOR) + newpos = seek_pts * (mf->nr_of_files - 1); + if (flags & SEEK_FORWARD) { + newpos = ceil(newpos); + } else { + newpos = MPMIN(floor(newpos), mf->nr_of_files - 1); + } + mf->curr_frame = MPCLAMP((int)newpos, 0, mf->nr_of_files); +} + +static bool demux_mf_read_packet(struct demuxer *demuxer, + struct demux_packet **pkt) +{ + mf_t *mf = demuxer->priv; + if (mf->curr_frame >= mf->nr_of_files) + return false; + bool ok = false; + + struct stream *entry_stream = NULL; + if (mf->streams) + entry_stream = mf->streams[mf->curr_frame]; + struct stream *stream = entry_stream; + if (!stream) { + char *filename = mf->names[mf->curr_frame]; + if (filename) { + stream = stream_create(filename, demuxer->stream_origin | STREAM_READ, + demuxer->cancel, demuxer->global); + } + } + + if (stream) { + stream_seek(stream, 0); + bstr data = stream_read_complete(stream, NULL, MF_MAX_FILE_SIZE); + if (data.len) { + demux_packet_t *dp = new_demux_packet(data.len); + if (dp) { + memcpy(dp->buffer, data.start, data.len); + dp->pts = mf->curr_frame / mf->sh->codec->fps; + dp->keyframe = true; + dp->stream = mf->sh->index; + *pkt = dp; + ok = true; + } + } + talloc_free(data.start); + } + + if (stream && stream != entry_stream) + free_stream(stream); + + mf->curr_frame++; + + if (!ok) + MP_ERR(demuxer, "error reading image file\n"); + + return true; +} + +static const char *probe_format(mf_t *mf, char *type, enum demux_check check) +{ + if (check > DEMUX_CHECK_REQUEST) + return NULL; + char *org_type = type; + if (!type || !type[0]) { + char *p = strrchr(mf->names[0], '.'); + if (p) + type = p + 1; + } + const char *codec = mp_map_type_to_image_codec(type); + if (codec) + return codec; + if (check == DEMUX_CHECK_REQUEST) { + if (!org_type) { + MP_ERR(mf, "file type was not set! (try --mf-type=ext)\n"); + } else { + MP_ERR(mf, "--mf-type set to an unknown codec!\n"); + } + } + return NULL; +} + +static int demux_open_mf(demuxer_t *demuxer, enum demux_check check) +{ + mf_t *mf; + + if (strncmp(demuxer->stream->url, "mf://", 5) == 0 && + demuxer->stream->info && strcmp(demuxer->stream->info->name, "mf") == 0) + { + mf = open_mf_pattern(demuxer, demuxer, demuxer->stream->url + 5); + } else { + mf = open_mf_single(demuxer, demuxer->log, demuxer->stream->url); + int bog = 0; + MP_TARRAY_APPEND(mf, mf->streams, bog, demuxer->stream); + } + + if (!mf || mf->nr_of_files < 1) + goto error; + + const char *codec = mp_map_mimetype_to_video_codec(demuxer->stream->mime_type); + if (!codec || (demuxer->opts->mf_type && demuxer->opts->mf_type[0])) + codec = probe_format(mf, demuxer->opts->mf_type, check); + if (!codec) + goto error; + + mf->curr_frame = 0; + + // create a new video stream header + struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO); + if (mf->nr_of_files == 1) { + MP_VERBOSE(demuxer, "Assuming this is an image format.\n"); + sh->image = true; + } + + struct mp_codec_params *c = sh->codec; + c->codec = codec; + c->disp_w = 0; + c->disp_h = 0; + c->fps = demuxer->opts->mf_fps; + c->reliable_fps = true; + + demux_add_sh_stream(demuxer, sh); + + mf->sh = sh; + demuxer->priv = (void *)mf; + demuxer->seekable = true; + demuxer->duration = mf->nr_of_files / mf->sh->codec->fps; + + return 0; + +error: + return -1; +} + +static void demux_close_mf(demuxer_t *demuxer) +{ +} + +const demuxer_desc_t demuxer_desc_mf = { + .name = "mf", + .desc = "image files (mf)", + .read_packet = demux_mf_read_packet, + .open = demux_open_mf, + .close = demux_close_mf, + .seek = demux_seek_mf, +}; |