demux_playlist: add --autocreate-playlist-{video,audio,image}-exts

This commit is contained in:
Kacper Michajłow 2024-07-16 17:02:52 +02:00
parent bb9b862f0c
commit c54ad6933b
7 changed files with 157 additions and 23 deletions

View File

@ -0,0 +1,4 @@
add `--autocreate-playlist`
add `--autocreate-playlist-video-exts`
add `--autocreate-playlist-audio-exts`
add `--autocreate-playlist-image-exts`

View File

@ -4139,6 +4139,33 @@ Demuxer
all. The default is ``auto``, which behaves like ``recursive`` with all. The default is ``auto``, which behaves like ``recursive`` with
``--shuffle``, and like ``lazy`` otherwise. ``--shuffle``, and like ``lazy`` otherwise.
``--autocreate-playlist=<no|any|exts|same>``
When opening a local file, act as if the parent directory is opened and
create a playlist automatically.
:no: Load a single file (default).
:any: Create a playlist from the parent directory with any file type.
:exts: Create a playlist from the parent directory with files matching
extensions from any list ``autocreate-playlist-*-exts``.
:same: Create a playlist from the parent directory with files matching the
same category as the currently loaded file. One of the
``autocreate-playlist-*-exts`` is selected based on the input file
and only files with matching extensions are added to the playlist.
If the input file itself is not matched to any extension list,
the playlist is not autogenerated.
``--autocreate-playlist-video-exts=<ext-list>``
Video file extension list that is used to match files when using
``--autocreate-playlist=<exts|same>``.
``--autocreate-playlist-audio-exts=<ext-list>``
Audio file extension list that is used to match files when using
``--autocreate-playlist=<exts|same>``.
``--autocreate-playlist-image-exts=<ext-list>``
Image file extension list that is used to match files when using
``--autocreate-playlist=<exts|same>``.
Input Input
----- -----

View File

@ -72,6 +72,7 @@ struct playlist {
bool current_was_replaced; bool current_was_replaced;
bool playlist_completed; bool playlist_completed;
bool playlist_started; bool playlist_started;
bool autocreated;
uint64_t id_alloc; uint64_t id_alloc;
}; };

View File

@ -57,6 +57,7 @@ extern const demuxer_desc_t demuxer_desc_mf;
extern const demuxer_desc_t demuxer_desc_matroska; extern const demuxer_desc_t demuxer_desc_matroska;
extern const demuxer_desc_t demuxer_desc_lavf; extern const demuxer_desc_t demuxer_desc_lavf;
extern const demuxer_desc_t demuxer_desc_playlist; extern const demuxer_desc_t demuxer_desc_playlist;
extern const demuxer_desc_t demuxer_desc_directory;
extern const demuxer_desc_t demuxer_desc_disc; extern const demuxer_desc_t demuxer_desc_disc;
extern const demuxer_desc_t demuxer_desc_rar; extern const demuxer_desc_t demuxer_desc_rar;
extern const demuxer_desc_t demuxer_desc_libarchive; extern const demuxer_desc_t demuxer_desc_libarchive;
@ -64,10 +65,10 @@ extern const demuxer_desc_t demuxer_desc_null;
extern const demuxer_desc_t demuxer_desc_timeline; extern const demuxer_desc_t demuxer_desc_timeline;
static const demuxer_desc_t *const demuxer_list[] = { static const demuxer_desc_t *const demuxer_list[] = {
&demuxer_desc_directory,
&demuxer_desc_disc, &demuxer_desc_disc,
&demuxer_desc_edl, &demuxer_desc_edl,
&demuxer_desc_cue, &demuxer_desc_cue,
&demuxer_desc_playlist,
&demuxer_desc_rawaudio, &demuxer_desc_rawaudio,
&demuxer_desc_rawvideo, &demuxer_desc_rawvideo,
&demuxer_desc_matroska, &demuxer_desc_matroska,
@ -76,6 +77,7 @@ static const demuxer_desc_t *const demuxer_list[] = {
#endif #endif
&demuxer_desc_lavf, &demuxer_desc_lavf,
&demuxer_desc_mf, &demuxer_desc_mf,
&demuxer_desc_playlist,
&demuxer_desc_null, &demuxer_desc_null,
NULL NULL
}; };
@ -117,7 +119,8 @@ const struct m_sub_options demux_conf = {
{"demuxer-backward-playback-step", OPT_DOUBLE(back_seek_size), {"demuxer-backward-playback-step", OPT_DOUBLE(back_seek_size),
M_RANGE(0, DBL_MAX)}, M_RANGE(0, DBL_MAX)},
{"metadata-codepage", OPT_STRING(meta_cp)}, {"metadata-codepage", OPT_STRING(meta_cp)},
{"autocreate-playlist", OPT_BOOL(autocreate_playlist)}, {"autocreate-playlist", OPT_CHOICE(autocreate_playlist,
{"no", 0}, {"any", 1}, {"exts", 2}, {"same", 3})},
{0} {0}
}, },
.size = sizeof(struct demux_opts), .size = sizeof(struct demux_opts),

View File

@ -86,7 +86,7 @@ struct demux_opts {
double back_seek_size; double back_seek_size;
char *meta_cp; char *meta_cp;
bool force_retry_eof; bool force_retry_eof;
bool autocreate_playlist; int autocreate_playlist;
}; };
#define SEEK_FACTOR (1 << 1) // argument is in range [0,1] #define SEEK_FACTOR (1 << 1) // argument is in range [0,1]
@ -212,7 +212,7 @@ struct demuxer_params {
bool stream_record; // if true, enable stream recording if option is set bool stream_record; // if true, enable stream recording if option is set
int stream_flags; int stream_flags;
struct stream *external_stream; // if set, use this, don't open or close streams struct stream *external_stream; // if set, use this, don't open or close streams
bool has_playlist; bool allow_playlist_create;
// result // result
bool demuxer_failed; bool demuxer_failed;
}; };

View File

@ -42,9 +42,20 @@ enum dir_mode {
DIR_IGNORE, DIR_IGNORE,
}; };
enum autocreate_mode {
AUTO_NONE = 0,
AUTO_VIDEO = 1 << 0,
AUTO_AUDIO = 1 << 1,
AUTO_IMAGE = 1 << 2,
AUTO_ANY = 1 << 3,
};
#define OPT_BASE_STRUCT struct demux_playlist_opts #define OPT_BASE_STRUCT struct demux_playlist_opts
struct demux_playlist_opts { struct demux_playlist_opts {
int dir_mode; int dir_mode;
char **autocreate_playlist_vid_exts;
char **autocreate_playlist_aud_exts;
char **autocreate_playlist_img_exts;
}; };
struct m_sub_options demux_playlist_conf = { struct m_sub_options demux_playlist_conf = {
@ -54,11 +65,29 @@ struct m_sub_options demux_playlist_conf = {
{"lazy", DIR_LAZY}, {"lazy", DIR_LAZY},
{"recursive", DIR_RECURSIVE}, {"recursive", DIR_RECURSIVE},
{"ignore", DIR_IGNORE})}, {"ignore", DIR_IGNORE})},
{"autocreate-playlist-video-exts",
OPT_STRINGLIST(autocreate_playlist_vid_exts)},
{"autocreate-playlist-audio-exts",
OPT_STRINGLIST(autocreate_playlist_aud_exts)},
{"autocreate-playlist-image-exts",
OPT_STRINGLIST(autocreate_playlist_img_exts)},
{0} {0}
}, },
.size = sizeof(struct demux_playlist_opts), .size = sizeof(struct demux_playlist_opts),
.defaults = &(const struct demux_playlist_opts){ .defaults = &(const struct demux_playlist_opts){
.dir_mode = DIR_AUTO, .dir_mode = DIR_AUTO,
.autocreate_playlist_vid_exts = (char *[]){
"3g2", "3gp", "avi", "flv", "m2ts", "m4v", "mj2", "mkv", "mov",
"mp4", "mpeg", "mpg", "ogv", "rmvb", "webm", "wmv", "y4m", NULL
},
.autocreate_playlist_aud_exts = (char *[]){
"aiff", "ape", "au", "flac", "m4a", "mka", "mp3", "oga", "ogg",
"ogm", "opus", "wav", "wma", NULL
},
.autocreate_playlist_img_exts = (char *[]){
"avif", "bmp", "gif", "j2k", "jp2", "jpeg", "jpg", "jxl", "png",
"svg", "tga", "tif", "tiff", "webp", NULL
},
}, },
}; };
@ -85,7 +114,7 @@ struct pl_parser {
bool force; bool force;
bool add_base; bool add_base;
bool line_allocated; bool line_allocated;
bool create_dir_playlist; int autocreate_playlist;
enum demux_check check_level; enum demux_check check_level;
struct stream *real_stream; struct stream *real_stream;
char *format; char *format;
@ -395,9 +424,36 @@ static int cmp_dir_entry(const void *a, const void *b)
} }
} }
static bool has_ext(bstr ext, char **list)
{
if (!list)
return false;
while (*list) {
if (!bstrcasecmp0(ext, *list++))
return true;
}
return false;
}
static bool test_autocreate_path(struct pl_parser *p, char *path, int autocreate)
{
if (autocreate & AUTO_ANY)
return true;
bstr ext = bstr_get_ext(bstr0(path));
if (autocreate & AUTO_VIDEO && has_ext(ext, p->opts->autocreate_playlist_vid_exts))
return true;
if (autocreate & AUTO_AUDIO && has_ext(ext, p->opts->autocreate_playlist_aud_exts))
return true;
if (autocreate & AUTO_IMAGE && has_ext(ext, p->opts->autocreate_playlist_img_exts))
return true;
return false;
}
// Return true if this was a readable directory. // Return true if this was a readable directory.
static bool scan_dir(struct pl_parser *p, char *path, static bool scan_dir(struct pl_parser *p, char *path,
struct stat *dir_stack, int num_dir_stack) struct stat *dir_stack, int num_dir_stack, int autocreate)
{ {
if (strlen(path) >= 8192 || num_dir_stack == MAX_DIR_STACK) if (strlen(path) >= 8192 || num_dir_stack == MAX_DIR_STACK)
return false; // things like mount bind loops return false; // things like mount bind loops
@ -452,10 +508,11 @@ static bool scan_dir(struct pl_parser *p, char *path,
if (dir_mode == DIR_RECURSIVE && dir_entries[n].is_dir) { if (dir_mode == DIR_RECURSIVE && dir_entries[n].is_dir) {
dir_stack[num_dir_stack] = dir_entries[n].st; dir_stack[num_dir_stack] = dir_entries[n].st;
char *file = dir_entries[n].path; char *file = dir_entries[n].path;
scan_dir(p, file, dir_stack, num_dir_stack + 1); scan_dir(p, file, dir_stack, num_dir_stack + 1, autocreate);
} }
else { else {
playlist_append_file(p->pl, dir_entries[n].path); if (autocreate == AUTO_NONE || test_autocreate_path(p, dir_entries[n].path, autocreate))
playlist_append_file(p->pl, dir_entries[n].path);
} }
} }
@ -466,11 +523,32 @@ static int parse_dir(struct pl_parser *p)
{ {
int ret = -1; int ret = -1;
struct stream *stream = p->real_stream; struct stream *stream = p->real_stream;
if (p->create_dir_playlist && p->real_stream->is_local_file && !p->real_stream->is_directory) { int autocreate = AUTO_NONE;
if (p->autocreate_playlist && p->real_stream->is_local_file && !p->real_stream->is_directory) {
bstr ext = bstr_get_ext(bstr0(p->real_stream->url));
switch (p->autocreate_playlist) {
case 1: // any
autocreate = AUTO_ANY;
break;
case 2: // exts
autocreate = AUTO_VIDEO | AUTO_AUDIO | AUTO_IMAGE;
break;
case 3: // same
if (has_ext(ext, p->opts->autocreate_playlist_vid_exts)) {
autocreate = AUTO_VIDEO;
} else if (has_ext(ext, p->opts->autocreate_playlist_aud_exts)) {
autocreate = AUTO_AUDIO;
} else if (has_ext(ext, p->opts->autocreate_playlist_img_exts)) {
autocreate = AUTO_IMAGE;
}
break;
}
int flags = STREAM_ORIGIN_DIRECT | STREAM_READ | STREAM_LOCAL_FS_ONLY | int flags = STREAM_ORIGIN_DIRECT | STREAM_READ | STREAM_LOCAL_FS_ONLY |
STREAM_LESS_NOISE; STREAM_LESS_NOISE;
bstr dir = mp_dirname(p->real_stream->url); bstr dir = mp_dirname(p->real_stream->url);
if (dir.len) if (!dir.len)
autocreate = AUTO_NONE;
if (autocreate != AUTO_NONE)
stream = stream_create(bstrdup0(p, dir), flags, NULL, p->global); stream = stream_create(bstrdup0(p, dir), flags, NULL, p->global);
} }
if (!stream->is_directory) if (!stream->is_directory)
@ -492,9 +570,10 @@ static int parse_dir(struct pl_parser *p)
talloc_free(opts); talloc_free(opts);
} }
scan_dir(p, path, dir_stack, 0); scan_dir(p, path, dir_stack, 0, autocreate);
p->add_base = false; p->add_base = false;
p->pl->autocreated = autocreate != AUTO_NONE;
ret = p->pl->num_entries > 0 ? 0 : -1; ret = p->pl->num_entries > 0 ? 0 : -1;
done: done:
@ -512,7 +591,12 @@ struct pl_format {
const char *const *mime_types; const char *const *mime_types;
}; };
static const struct pl_format formats[] = { static const struct pl_format dir_formats[] = {
{"directory", parse_dir},
{0},
};
static const struct pl_format playlist_formats[] = {
{"m3u", parse_m3u, {"m3u", parse_m3u,
MIME_TYPES("audio/mpegurl", "audio/x-mpegurl", "application/x-mpegurl")}, MIME_TYPES("audio/mpegurl", "audio/x-mpegurl", "application/x-mpegurl")},
{"ini", parse_ref_init}, {"ini", parse_ref_init},
@ -520,14 +604,14 @@ static const struct pl_format formats[] = {
MIME_TYPES("audio/x-scpls")}, MIME_TYPES("audio/x-scpls")},
{"url", parse_url}, {"url", parse_url},
{"txt", parse_txt}, {"txt", parse_txt},
{"directory", parse_dir}, {0},
}; };
static const struct pl_format *probe_pl(struct pl_parser *p) static const struct pl_format *probe_pl(struct pl_parser *p, const struct pl_format *fmts)
{ {
int64_t start = stream_tell(p->s); int64_t start = stream_tell(p->s);
for (int n = 0; n < MP_ARRAY_SIZE(formats); n++) { const struct pl_format *fmt = fmts;
const struct pl_format *fmt = &formats[n]; while (fmt->name) {
stream_seek(p->s, start); stream_seek(p->s, start);
if (check_mimetype(p->s, fmt->mime_types)) { if (check_mimetype(p->s, fmt->mime_types)) {
MP_VERBOSE(p, "forcing format by mime-type.\n"); MP_VERBOSE(p, "forcing format by mime-type.\n");
@ -536,10 +620,14 @@ static const struct pl_format *probe_pl(struct pl_parser *p)
} }
if (fmt->parse(p) >= 0) if (fmt->parse(p) >= 0)
return fmt; return fmt;
fmt++;
} }
return NULL; return NULL;
} }
extern const demuxer_desc_t demuxer_desc_playlist;
extern const demuxer_desc_t demuxer_desc_directory;
static int open_file(struct demuxer *demuxer, enum demux_check check) static int open_file(struct demuxer *demuxer, enum demux_check check)
{ {
if (!demuxer->access_references) if (!demuxer->access_references)
@ -565,9 +653,14 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
p->force = force; p->force = force;
p->check_level = check; p->check_level = check;
p->probing = true; p->probing = true;
p->create_dir_playlist = !demuxer->params->has_playlist && opts->autocreate_playlist; p->autocreate_playlist = demuxer->params->allow_playlist_create ? opts->autocreate_playlist : 0;
p->opts = mp_get_config_group(demuxer, demuxer->global, &demux_playlist_conf);
const struct pl_format *fmt = probe_pl(p); const struct pl_format *fmts = playlist_formats;
if (demuxer->desc == &demuxer_desc_directory)
fmts = dir_formats;
const struct pl_format *fmt = probe_pl(p, fmts);
free_stream(p->s); free_stream(p->s);
playlist_clear(p->pl); playlist_clear(p->pl);
if (!fmt) { if (!fmt) {
@ -579,7 +672,6 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
p->error = false; p->error = false;
p->s = demuxer->stream; p->s = demuxer->stream;
p->utf16 = stream_skip_bom(p->s); p->utf16 = stream_skip_bom(p->s);
p->opts = mp_get_config_group(demuxer, demuxer->global, &demux_playlist_conf);
bool ok = fmt->parse(p) >= 0 && !p->error; bool ok = fmt->parse(p) >= 0 && !p->error;
if (p->add_base) { if (p->add_base) {
bstr proto = mp_split_proto(bstr0(demuxer->filename), NULL); bstr proto = mp_split_proto(bstr0(demuxer->filename), NULL);
@ -600,6 +692,12 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
return ok ? 0 : -1; return ok ? 0 : -1;
} }
const demuxer_desc_t demuxer_desc_directory = {
.name = "directory",
.desc = "Playlist dir",
.open = open_file,
};
const demuxer_desc_t demuxer_desc_playlist = { const demuxer_desc_t demuxer_desc_playlist = {
.name = "playlist", .name = "playlist",
.desc = "Playlist file", .desc = "Playlist file",

View File

@ -881,6 +881,7 @@ int mp_add_external_file(struct MPContext *mpctx, char *filename,
struct demuxer_params params = { struct demuxer_params params = {
.is_top_level = true, .is_top_level = true,
.stream_flags = STREAM_ORIGIN_DIRECT, .stream_flags = STREAM_ORIGIN_DIRECT,
.allow_playlist_create = false,
}; };
switch (filter) { switch (filter) {
@ -1041,11 +1042,9 @@ void prepare_playlist(struct MPContext *mpctx, struct playlist *pl)
if (opts->playlist_pos >= 0) if (opts->playlist_pos >= 0)
pl->current = playlist_entry_from_index(pl, opts->playlist_pos); pl->current = playlist_entry_from_index(pl, opts->playlist_pos);
for (int i = 0; i < pl->num_entries; ++i) { for (int i = 0; i < pl->num_entries && pl->autocreated; ++i) {
if (!pl->entries[i]->playlist_path) if (!pl->entries[i]->playlist_path)
continue; continue;
// If playlist_path exists as an element in the playlist itself, it means
// playlist was autogenerated.
if (!strcmp(pl->entries[i]->filename, pl->entries[i]->playlist_path)) { if (!strcmp(pl->entries[i]->filename, pl->entries[i]->playlist_path)) {
pl->current = pl->entries[i]; pl->current = pl->entries[i];
break; break;
@ -1080,6 +1079,7 @@ static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl,
playlist_remove(mpctx->playlist, mpctx->playlist->current); playlist_remove(mpctx->playlist, mpctx->playlist->current);
if (new) if (new)
mpctx->playlist->current = new; mpctx->playlist->current = new;
mpctx->playlist->autocreated = pl->autocreated;
} else { } else {
MP_WARN(mpctx, "Empty playlist!\n"); MP_WARN(mpctx, "Empty playlist!\n");
} }
@ -1155,7 +1155,8 @@ static MP_THREAD_VOID open_demux_thread(void *ctx)
.stream_flags = mpctx->open_url_flags, .stream_flags = mpctx->open_url_flags,
.stream_record = true, .stream_record = true,
.is_top_level = true, .is_top_level = true,
.has_playlist = mpctx->playlist->num_entries > 1, .allow_playlist_create = mpctx->playlist->num_entries <= 1 &&
!mpctx->playlist->autocreated,
}; };
struct demuxer *demux = struct demuxer *demux =
demux_open_url(mpctx->open_url, &p, mpctx->open_cancel, mpctx->global); demux_open_url(mpctx->open_url, &p, mpctx->open_cancel, mpctx->global);