You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
489 lines
15 KiB
489 lines
15 KiB
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
/*****************************************************************************
|
|
* cmdline.c: preparser command line
|
|
*****************************************************************************
|
|
* Copyright © 2025 Videolabs, VideoLAN and VLC authors
|
|
*
|
|
* Authors: Gabriel Lafond Thenaille <gabriel@videolabs.io>
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
|
|
#include <vlc/vlc.h>
|
|
#include <vlc_common.h>
|
|
#include <vlc_preparser.h>
|
|
|
|
#include "cmdline.h"
|
|
|
|
#include "../src/config/vlc_getopt.h"
|
|
#include "../src/config/vlc_jaro_winkler.h"
|
|
|
|
static bool
|
|
opt_set_Timeout(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
char *endptr = NULL;
|
|
args->timeout = VLC_TICK_FROM_MS(strtoull(arg, &endptr, 0));
|
|
if (endptr != NULL && *endptr != '\0') {
|
|
fprintf(stderr, "Error: Invalid timeout `%s'\n", arg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_TimeoutTick(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
char *endptr = NULL;
|
|
args->timeout = strtoull(arg, &endptr, 0);
|
|
if (endptr != NULL && *endptr != '\0') {
|
|
fprintf(stderr, "Error: Invalid timeout `%s'\n", arg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_Types(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
|
|
char *endptr = NULL;
|
|
args->types = strtoll(arg, &endptr, 0);
|
|
if (endptr != NULL && *endptr != '\0') {
|
|
fprintf(stderr, "Error: Invalid type `%s'\n", arg);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_Type(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
if (!strcmp(arg, "parse")) {
|
|
args->types |= VLC_PREPARSER_TYPE_PARSE;
|
|
} else if (!strcmp(arg, "thumbnail") || !strcmp(arg, "jpg")) {
|
|
args->types |= VLC_PREPARSER_TYPE_THUMBNAIL;
|
|
} else if (!strcmp(arg, "thumbnail_to_files")) {
|
|
args->types |= VLC_PREPARSER_TYPE_THUMBNAIL_TO_FILES;
|
|
} else {
|
|
fprintf(stderr, "Error: Unknown preparser type `%s'\n", arg);
|
|
return false;
|
|
}
|
|
return true;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_Fetch(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
if (!strcmp(arg, "local")) {
|
|
args->types |= VLC_PREPARSER_TYPE_FETCHMETA_LOCAL;
|
|
} else if (!strcmp(arg, "net") || !strcmp(arg, "jpg")) {
|
|
args->types |= VLC_PREPARSER_TYPE_FETCHMETA_NET;
|
|
} else if (!strcmp(arg, "all")) {
|
|
args->types |= VLC_PREPARSER_TYPE_FETCHMETA_ALL;
|
|
} else {
|
|
fprintf(stderr, "Error: Unknown preparser fetching policy `%s'\n", arg);
|
|
return false;
|
|
}
|
|
return true;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_Daemon(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg == NULL);
|
|
|
|
args->daemon = true;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_SeekSpeed(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
if (!strcmp(arg, "precise")) {
|
|
args->seek.speed = VLC_THUMBNAILER_SEEK_PRECISE;
|
|
} else if (!strcmp(arg, "fast")) {
|
|
args->seek.speed = VLC_THUMBNAILER_SEEK_FAST;
|
|
} else {
|
|
fprintf(stderr, "Error: Unknown seek speed `%s'\n", arg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_SeekTime(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
if (args->seek.type != VLC_THUMBNAILER_SEEK_NONE) {
|
|
fprintf(stderr, "Error: --seek-time not compatible with --seek-pos\n");
|
|
return false;
|
|
}
|
|
|
|
char *endptr = NULL;
|
|
args->seek.time = VLC_TICK_FROM_MS(strtoull(arg, &endptr, 0));
|
|
if (endptr != NULL && *endptr != '\0') {
|
|
fprintf(stderr, "Error: Invalid time `%s'\n", arg);
|
|
return false;
|
|
}
|
|
args->seek.type = VLC_THUMBNAILER_SEEK_TIME;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_SeekPos(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
if (args->seek.type != VLC_THUMBNAILER_SEEK_NONE) {
|
|
fprintf(stderr, "Error: --seek-pos not compatible with --seek-time\n");
|
|
return false;
|
|
}
|
|
|
|
char *endptr = NULL;
|
|
args->seek.pos = strtod(arg, &endptr);
|
|
if (endptr != NULL && *endptr != '\0') {
|
|
fprintf(stderr, "Error: Invalid position `%s'\n", arg);
|
|
return false;
|
|
}
|
|
if (!(args->seek.pos > 0)) {
|
|
fprintf(stderr, "Error: Seek pos should be greater than 0\n");
|
|
return false;
|
|
}
|
|
args->seek.type = VLC_THUMBNAILER_SEEK_POS;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_OutputPath(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
if (args->output.file_path != NULL) {
|
|
fprintf(stderr, "Error: --output-path already setted\n");
|
|
return false;
|
|
}
|
|
|
|
args->output.file_path = arg;
|
|
if (args->output.file_path == NULL) {
|
|
fprintf(stderr, "Error: memory error\n");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_OutputWidth(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
if (args->output.width != 0) {
|
|
fprintf(stderr, "Error: --output-width already setted\n");
|
|
return false;
|
|
}
|
|
char *endptr = NULL;
|
|
args->output.width = strtoull(arg, &endptr, 0);
|
|
if (endptr != NULL && *endptr != '\0') {
|
|
fprintf(stderr, "Error: Invalid time `%s'\n", arg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_OutputHeight(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
if (args->output.height != 0) {
|
|
fprintf(stderr, "Error: --output-height already setted\n");
|
|
return false;
|
|
}
|
|
char *endptr = NULL;
|
|
args->output.height = strtoull(arg, &endptr, 0);
|
|
if (endptr != NULL && *endptr != '\0') {
|
|
fprintf(stderr, "Error: Invalid time `%s'\n", arg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_OutputFormat(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
if (!strcmp(arg, "png")) {
|
|
args->output.format = VLC_THUMBNAILER_FORMAT_PNG;
|
|
} else if (!strcmp(arg, "jpeg") || !strcmp(arg, "jpg")) {
|
|
args->output.format = VLC_THUMBNAILER_FORMAT_JPEG;
|
|
} else if (!strcmp(arg, "webp")) {
|
|
args->output.format = VLC_THUMBNAILER_FORMAT_WEBP;
|
|
} else {
|
|
fprintf(stderr, "Error: Unknown output format `%s'\n", arg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
opt_set_OutputCrop(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg == NULL);
|
|
|
|
args->output.crop = true;
|
|
return true;
|
|
}
|
|
|
|
static bool opt_set_Verbose(struct preparser_args *args, const char *arg)
|
|
{
|
|
assert(args != NULL);
|
|
assert(arg != NULL);
|
|
|
|
args->verbosity = arg;
|
|
return true;
|
|
}
|
|
|
|
struct preparser_opt {
|
|
const char *name;
|
|
const char *text;
|
|
const int type;
|
|
bool (*set)(struct preparser_args *args, const char *arg);
|
|
};
|
|
|
|
#define opt_type_bool 1
|
|
#define opt_add_bool(n, set_fn, description)\
|
|
{.name = n, .type = opt_type_bool, .text = description, .set = set_fn}
|
|
|
|
#define opt_type_integer 2
|
|
#define opt_add_integer(n, set_fn, description)\
|
|
{.name = n, .type = opt_type_integer, .text = description, .set = set_fn}
|
|
|
|
#define opt_type_string 3
|
|
#define opt_add_string(n, set_fn, description)\
|
|
{.name = n, .type = opt_type_string, .text = description, .set = set_fn}
|
|
|
|
static const struct preparser_opt options[] = {
|
|
opt_add_bool("help", NULL, "Print this help"),
|
|
opt_add_integer("timeout", opt_set_Timeout, "Preparser timeout (ms)"),
|
|
opt_add_integer("timeout-tick", opt_set_TimeoutTick, "Preparser timeout (vlc_tick_t)"),
|
|
opt_add_integer("types", opt_set_Types, "Preparser types"),
|
|
opt_add_string("type", opt_set_Type, "Preparser type (parse/thumbnail/thumbnail_to_files)"),
|
|
opt_add_string("fetch", opt_set_Fetch, "Preparser fetching (local/net/all)"),
|
|
opt_add_bool("daemon", opt_set_Daemon, "Start the preparser as a daemon reading request from the stdin"),
|
|
opt_add_bool(NULL, NULL, "thumbnail and thumbnail_to_files"),
|
|
opt_add_string("seek-speed", opt_set_SeekSpeed, "Set the seek speed (precise/fast)"),
|
|
opt_add_integer("seek-time", opt_set_SeekTime, "Set from where to seek (ms)"),
|
|
opt_add_integer("seek-pos", opt_set_SeekPos, "Set the seek position"),
|
|
opt_add_bool(NULL, NULL, "thumbnail_to_files"),
|
|
opt_add_string("output-path", opt_set_OutputPath, "Path of the thumbnail"),
|
|
opt_add_integer("output-width", opt_set_OutputWidth, "Width of the thumbnail"),
|
|
opt_add_integer("output-height", opt_set_OutputHeight, "Height of the thumbnail"),
|
|
opt_add_string("output-format", opt_set_OutputFormat, "Format of the thumbnail (png/jp[e]g/webp)"),
|
|
opt_add_bool("output-crop", opt_set_OutputCrop, "Crop the thumbnail"),
|
|
opt_add_integer("verbose", opt_set_Verbose, "Verbosity (0,1,2)"),
|
|
};
|
|
|
|
static const char vlc_preparser_usage[] = N_(
|
|
"Usage: %s [options] [url|path]\n"
|
|
"\n"
|
|
"URL syntax:\n"
|
|
" file:///path/file Plain media file\n"
|
|
" http://host[:port]/file HTTP URL\n"
|
|
" ftp://host[:port]/file FTP URL\n"
|
|
" mms://host[:port]/file MMS URL\n"
|
|
" screen:// Screen capture\n"
|
|
" dvd://[device] DVD device\n"
|
|
" vcd://[device] VCD device\n"
|
|
" cdda://[device] Audio CD device\n"
|
|
" udp://[[<source address>]@[<bind address>][:<bind port>]]\n"
|
|
" UDP stream sent by a streaming server\n"
|
|
" vlc://pause:<seconds> Pause the playlist for a certain time\n"
|
|
" vlc://quit Special item to quit VLC\n"
|
|
"\n"
|
|
);
|
|
|
|
static void
|
|
preparser_help(const char *arg0)
|
|
{
|
|
assert(arg0 != NULL);
|
|
|
|
fprintf(stderr, vlc_preparser_usage, arg0);
|
|
fprintf(stderr, "Options:\n");
|
|
size_t nopts = ARRAY_SIZE(options);
|
|
for (size_t i = 0; i < nopts; i++) {
|
|
if (options[i].name == NULL) {
|
|
fprintf(stderr, "\n %s\n", options[i].text);
|
|
continue;
|
|
}
|
|
const char *type = NULL;
|
|
switch (options[i].type) {
|
|
case opt_type_integer:
|
|
type = "<integer>";
|
|
break;
|
|
case opt_type_string:
|
|
type = "<string>";
|
|
break;
|
|
}
|
|
int ret = fprintf(stderr, " --%s %s", options[i].name,
|
|
type == NULL ? "" : type);
|
|
if (ret >= 33) {
|
|
ret = 0;
|
|
fprintf(stderr, "\n");
|
|
}
|
|
fprintf(stderr, "%*s%s\n", 33 - ret, "", options[i].text);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
|
|
int
|
|
preparser_cmdline_Parse(int argc, char *const *argv,
|
|
struct preparser_args *args)
|
|
{
|
|
assert(argc != 0);
|
|
assert(argv != NULL);
|
|
assert(args != NULL);
|
|
|
|
size_t noptions = ARRAY_SIZE(options);
|
|
struct vlc_option *opts = calloc(noptions + 1, sizeof(*opts));
|
|
for (size_t i = 0; i < noptions; i++) {
|
|
if (options[i].name == NULL) {
|
|
opts[i].name = "";
|
|
} else {
|
|
opts[i].name = options[i].name;
|
|
}
|
|
opts[i].has_arg = options[i].type == opt_type_bool ? false : true;
|
|
opts[i].flag = NULL;
|
|
opts[i].is_obsolete = false;
|
|
opts[i].val = 0;
|
|
}
|
|
|
|
bool error = false;
|
|
vlc_getopt_t state = {0};
|
|
int cmd = 0;
|
|
int longid = 0;
|
|
while (1) {
|
|
cmd = vlc_getopt_long(argc, argv, ":", opts, &longid, &state);
|
|
if (cmd == -1) {
|
|
break;
|
|
} else if (cmd == 0) {
|
|
if (options[longid].set == NULL) {
|
|
preparser_help(argv[0]);
|
|
break;
|
|
} else if (!options[longid].set(args, state.arg)) {
|
|
error = true;
|
|
fprintf(stderr, "For more information try --help\n");
|
|
break;
|
|
}
|
|
} else {
|
|
if (cmd == ':') {
|
|
if (state.opt != 0) {
|
|
fprintf(stderr,
|
|
"Error: Missing mandatory value for option -%c\n",
|
|
cmd);
|
|
} else {
|
|
fprintf(stderr,
|
|
"Error: Missing mandatory value for option %s\n",
|
|
argv[state.ind - 1]);
|
|
}
|
|
} else if (state.opt != 0) {
|
|
fprintf(stderr, "Error: Unknown option `-%c'\n", state.opt);
|
|
} else {
|
|
fprintf(stderr, "Error: Unknown option `%s'\n",
|
|
argv[state.ind - 1]);
|
|
|
|
/* suggestion matching */
|
|
float jw_filter = 0.8f;
|
|
float best_m = jw_filter;
|
|
float m = 0;
|
|
const char *best = NULL;
|
|
const char *jw_a = argv[state.ind - 1] + 2;
|
|
for (size_t i = 0; i < noptions; i++) {
|
|
if (opts[i].name == NULL) {
|
|
continue;
|
|
}
|
|
if (opts[i].is_obsolete)
|
|
continue;
|
|
const char *jw_b = opts[i].name;
|
|
if (vlc_jaro_winkler(jw_a, jw_b, &m) == 0) {
|
|
//ignore failed malloc calls
|
|
if (m > best_m || (!best && m >= jw_filter)) {
|
|
best = jw_b;
|
|
best_m = m;
|
|
}
|
|
}
|
|
}
|
|
if (best != NULL) {
|
|
fprintf( stderr, " Did you mean --%s?\n", best);
|
|
}
|
|
}
|
|
fprintf( stderr, "For more information try --help\n");
|
|
return -1;
|
|
}
|
|
}
|
|
free(opts);
|
|
|
|
if (error) {
|
|
return -1;
|
|
} else if (cmd != -1) {
|
|
return 0;
|
|
}
|
|
|
|
if (args->types == 0) {
|
|
args->types = VLC_PREPARSER_TYPE_PARSE;
|
|
}
|
|
|
|
args->arg_idx = state.ind;
|
|
if (!args->daemon) {
|
|
if (args->types == VLC_PREPARSER_TYPE_THUMBNAIL_TO_FILES) {
|
|
if (args->output.file_path == NULL) {
|
|
fprintf(stderr, "Error: with `--type thummbnail_to_files' the "
|
|
"option `--output-path' is mandatory!\n");
|
|
fprintf( stderr, "For more information try --help\n");
|
|
return -1;
|
|
}
|
|
}
|
|
if (args->arg_idx == argc) {
|
|
fprintf(stderr, "No media to parse!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|