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.
 
 
 
 
 
 

557 lines
18 KiB

// SPDX-License-Identifier: LGPL-2.1-or-later
/*****************************************************************************
* preparser.c: preparse a media and return it's informations
*****************************************************************************
* 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 <vlc_preparser_ipc.h>
#include <vlc_vector.h>
#include <vlc_process.h>
#include <vlc_threads.h>
#include <vlc_fs.h>
#include <vlc_url.h>
#include <vlc_tls.h>
#include "preparser/cmdline.h"
#include "../lib/libvlc_internal.h"
struct preparser {
vlc_sem_t sem;
struct vlc_preparser_msg res_msg;
struct vlc_preparser_msg req_msg;
struct vlc_preparser_msg_serdes *serdes;
bool daemon;
int status;
struct vlc_tls *tls_in;
struct vlc_tls *tls_out;
};
/****************************************************************************
* parse Callbacks
*****************************************************************************/
static void parse_OnEnded(struct vlc_preparser_req *req, int status, void *userdata)
{
struct preparser *pp = userdata;
assert(pp != NULL);
assert(pp->serdes != NULL);
assert(pp->res_msg.type == VLC_PREPARSER_MSG_TYPE_RES);
assert(pp->res_msg.req_type == VLC_PREPARSER_MSG_REQ_TYPE_PARSE);
assert(req != NULL);
input_item_t *item = vlc_preparser_req_GetItem(req);
assert(item != NULL && item == pp->res_msg.res.item);
pp->res_msg.res.status = status;
pp->status = status;
vlc_preparser_msg_serdes_Serialize(pp->serdes, &pp->res_msg, pp->tls_out);
vlc_sem_post(&pp->sem);
}
static void parse_OnAttachmentsAdded(struct vlc_preparser_req *req,
input_attachment_t *const *array,
size_t count, void *userdata)
{
struct preparser *pp = userdata;
assert(pp != NULL);
assert(pp->res_msg.type == VLC_PREPARSER_MSG_TYPE_RES);
assert(pp->res_msg.req_type == VLC_PREPARSER_MSG_REQ_TYPE_PARSE);
assert(req != NULL);
input_item_t *item = vlc_preparser_req_GetItem(req);
assert(item != NULL && item == pp->res_msg.res.item);
assert(array != NULL);
for (size_t i = 0; i < count; i++) {
assert(array[i] != NULL);
input_attachment_t *a = vlc_input_attachment_Hold(array[i]);
vlc_vector_push(&pp->res_msg.res.attachments, a);
}
}
static void parse_OnSubtreeAdded(struct vlc_preparser_req *req,
input_item_node_t *subtree,
void *userdata)
{
struct preparser *pp = userdata;
assert(pp != NULL);
assert(pp->res_msg.type == VLC_PREPARSER_MSG_TYPE_RES);
assert(pp->res_msg.req_type == VLC_PREPARSER_MSG_REQ_TYPE_PARSE);
assert(req != NULL);
input_item_t *item = vlc_preparser_req_GetItem(req);
assert(item != NULL && item == pp->res_msg.res.item);
pp->res_msg.res.subtree = subtree;
}
/****************************************************************************
* thumbnailer Callback
*****************************************************************************/
static void thumbnailer_OnEnded(struct vlc_preparser_req *req, int status,
picture_t* thumbnail, void *userdata)
{
struct preparser *pp = userdata;
assert(pp != NULL);
assert(pp->serdes != NULL);
assert(pp->res_msg.type == VLC_PREPARSER_MSG_TYPE_RES);
assert(pp->res_msg.req_type == VLC_PREPARSER_MSG_REQ_TYPE_THUMBNAIL);
assert(req != NULL);
input_item_t *item = vlc_preparser_req_GetItem(req);
assert(item != NULL && item == pp->res_msg.res.item);
pp->res_msg.res.status = status;
if (status == 0) {
assert(thumbnail != NULL);
pp->res_msg.res.pic = picture_Hold(thumbnail);
}
vlc_preparser_msg_serdes_Serialize(pp->serdes, &pp->res_msg, NULL);
vlc_sem_post(&pp->sem);
}
/****************************************************************************
* thumbnailer_to_files Callback
*****************************************************************************/
static void thumbnailer_to_files_OnEnded(struct vlc_preparser_req *req,
int status, const bool *result_array,
size_t result_count, void *userdata)
{
struct preparser *pp = userdata;
assert(pp != NULL);
assert(pp->serdes != NULL);
assert(pp->res_msg.type == VLC_PREPARSER_MSG_TYPE_RES);
assert(pp->res_msg.req_type == VLC_PREPARSER_MSG_REQ_TYPE_THUMBNAIL_TO_FILES);
assert(req != NULL);
input_item_t *item = vlc_preparser_req_GetItem(req);
assert(item != NULL && item == pp->res_msg.res.item);
pp->res_msg.res.status = status;
vlc_vector_init(&pp->res_msg.res.result);
if (result_count != 0) {
assert(result_array != NULL);
vlc_vector_push_all(&pp->res_msg.res.result, result_array, result_count);
}
vlc_preparser_msg_serdes_Serialize(pp->serdes, &pp->res_msg, NULL);
vlc_sem_post(&pp->sem);
}
/****************************************************************************
* Preparser engine
*****************************************************************************/
static struct vlc_preparser_req *
preparser_req_PreparsePush(vlc_preparser_t *preparser,
struct preparser *pp)
{
assert(pp->req_msg.req_type == VLC_PREPARSER_MSG_REQ_TYPE_PARSE);
static const struct vlc_preparser_cbs cbs = {
.on_ended = parse_OnEnded,
.on_attachments_added = parse_OnAttachmentsAdded,
.on_subtree_added = parse_OnSubtreeAdded,
};
return vlc_preparser_Push(preparser, pp->res_msg.res.item,
pp->req_msg.req.options, &cbs, pp);
}
static struct vlc_preparser_req *
preparser_req_PreparseThumbnail(vlc_preparser_t *preparser,
struct preparser *pp)
{
assert(pp->req_msg.req_type == VLC_PREPARSER_MSG_REQ_TYPE_THUMBNAIL);
static const struct vlc_thumbnailer_cbs cbs = {
.on_ended = thumbnailer_OnEnded,
};
return vlc_preparser_GenerateThumbnail(preparser, pp->res_msg.res.item,
&pp->req_msg.req.arg,
&cbs, pp);
}
static struct vlc_preparser_req *
preparser_req_PreparseThumbnailToFiles(vlc_preparser_t *preparser,
struct preparser *pp)
{
assert(pp->req_msg.req_type == VLC_PREPARSER_MSG_REQ_TYPE_THUMBNAIL_TO_FILES);
static const struct vlc_thumbnailer_to_files_cbs cbs = {
.on_ended = thumbnailer_to_files_OnEnded,
};
return vlc_preparser_GenerateThumbnailToFiles(preparser,
pp->res_msg.res.item,
&pp->req_msg.req.arg,
pp->req_msg.req.outputs.data,
pp->req_msg.req.outputs.size,
&cbs, pp);
}
static int
preparser_req_Preparse(vlc_preparser_t *preparser, struct preparser *pp)
{
assert(preparser != NULL);
assert(pp != NULL);
assert(pp->req_msg.type == VLC_PREPARSER_MSG_TYPE_REQ);
assert(pp->req_msg.req.uri != NULL);
int ret = VLC_EGENERIC;
vlc_preparser_msg_Init(&pp->res_msg, VLC_PREPARSER_MSG_TYPE_RES,
pp->req_msg.req_type);
pp->res_msg.res.item = input_item_New(pp->req_msg.req.uri, NULL);
if (pp->res_msg.res.item == NULL) {
goto end;
}
struct vlc_preparser_req *req = NULL;
switch (pp->req_msg.req_type) {
case VLC_PREPARSER_MSG_REQ_TYPE_PARSE:
req = preparser_req_PreparsePush(preparser, pp);
break;
case VLC_PREPARSER_MSG_REQ_TYPE_THUMBNAIL:
req = preparser_req_PreparseThumbnail(preparser, pp);
break;
case VLC_PREPARSER_MSG_REQ_TYPE_THUMBNAIL_TO_FILES:
req = preparser_req_PreparseThumbnailToFiles(preparser, pp);
break;
default:
goto end;
}
if (req == NULL) {
if (pp->serdes != NULL) {
ret = vlc_preparser_msg_serdes_Serialize(pp->serdes, &pp->res_msg,
NULL);
}
} else {
ret = VLC_SUCCESS;
vlc_preparser_req_Release(req);
vlc_sem_wait(&pp->sem);
}
end:
vlc_preparser_msg_Clean(&pp->res_msg);
return ret;
}
/****************************************************************************
* Serdes Callbacks
*****************************************************************************/
static ssize_t write_cbs(const void *data, size_t size, void *userdata)
{
ssize_t ret = -1;
if (userdata == NULL) {
ret = write(STDOUT_FILENO, data, size);
return ret;
}
struct vlc_tls *tls = userdata;
ret = vlc_tls_Write(tls, data, size);
return ret;
}
static ssize_t read_cbs(void *data, size_t size, void *userdata)
{
ssize_t ret = -1;
if (userdata == NULL) {
ret = read(STDIN_FILENO, data, size);
return ret;
}
struct vlc_tls *tls = userdata;
ret = vlc_tls_Read(tls, data, size, false);
return ret;
}
/****************************************************************************
* Cmd line arg
*****************************************************************************/
static int
preparser_args_Preparse(vlc_preparser_t *preparser,
struct preparser *pp, char *uri,
struct preparser_args *args)
{
assert(preparser != NULL);
assert(pp != NULL);
assert(uri != NULL);
assert(args != NULL);
if (args->types & VLC_PREPARSER_TYPE_PARSE) {
int t = args->types & ~(VLC_PREPARSER_TYPE_THUMBNAIL |
VLC_PREPARSER_TYPE_THUMBNAIL_TO_FILES);
vlc_preparser_msg_Init(&pp->req_msg, VLC_PREPARSER_MSG_TYPE_REQ,
VLC_PREPARSER_MSG_REQ_TYPE_PARSE);
pp->req_msg.req.options = t;
pp->req_msg.req.uri = uri;
preparser_req_Preparse(preparser, pp);
pp->req_msg.req.uri = NULL;
if (pp->status != VLC_SUCCESS) {
fprintf(stderr, "Error while parsing '%s'\n", uri);
vlc_preparser_msg_Clean(&pp->req_msg);
return VLC_EGENERIC;
}
vlc_preparser_msg_Clean(&pp->req_msg);
}
if (args->types & VLC_PREPARSER_TYPE_THUMBNAIL) {
vlc_preparser_msg_Init(&pp->req_msg, VLC_PREPARSER_MSG_TYPE_REQ,
VLC_PREPARSER_MSG_REQ_TYPE_THUMBNAIL);
pp->req_msg.req.arg.seek.type = args->seek.type;
if (args->seek.type == VLC_THUMBNAILER_SEEK_TIME) {
pp->req_msg.req.arg.seek.time = args->seek.time;
} else if (args->seek.type == VLC_THUMBNAILER_SEEK_POS) {
pp->req_msg.req.arg.seek.pos = args->seek.pos;
}
pp->req_msg.req.arg.seek.speed = args->seek.speed;
pp->req_msg.req.arg.hw_dec = false;
pp->req_msg.req.uri = strdup(uri);
preparser_req_Preparse(preparser, pp);
pp->req_msg.req.uri = NULL;
if (pp->status != VLC_SUCCESS) {
fprintf(stderr, "Error while parsing '%s'\n", uri);
vlc_preparser_msg_Clean(&pp->req_msg);
return VLC_EGENERIC;
}
vlc_preparser_msg_Clean(&pp->req_msg);
}
if (args->types & VLC_PREPARSER_TYPE_THUMBNAIL_TO_FILES) {
vlc_preparser_msg_Init(&pp->req_msg, VLC_PREPARSER_MSG_TYPE_REQ,
VLC_PREPARSER_MSG_REQ_TYPE_THUMBNAIL_TO_FILES);
pp->req_msg.req.arg.seek.type = args->seek.type;
if (args->seek.type == VLC_THUMBNAILER_SEEK_TIME) {
pp->req_msg.req.arg.seek.time = args->seek.time;
} else if (args->seek.type == VLC_THUMBNAILER_SEEK_POS) {
pp->req_msg.req.arg.seek.pos = args->seek.pos;
}
pp->req_msg.req.arg.seek.speed = args->seek.speed;
pp->req_msg.req.arg.hw_dec = false;
struct vlc_thumbnailer_output out = {
.width = args->output.width,
.height = args->output.height,
.file_path = args->output.file_path,
.format = args->output.format,
.creat_mode = 0766,
.crop = args->output.crop,
};
vlc_vector_push(&pp->req_msg.req.outputs, out);
pp->req_msg.req.uri = strdup(uri);
preparser_req_Preparse(preparser, pp);
pp->req_msg.req.uri = NULL;
if (pp->status != VLC_SUCCESS) {
fprintf(stderr, "Error while parsing '%s'\n", uri);
vlc_preparser_msg_Clean(&pp->req_msg);
return VLC_EGENERIC;
}
vlc_preparser_msg_Clean(&pp->req_msg);
}
return VLC_SUCCESS;
}
static int
preparser_args_Loop(vlc_object_t *obj, vlc_preparser_t *preparser,
char *const *argv, int argc, struct preparser_args *args)
{
assert(preparser != NULL);
assert(argv != NULL);
assert(argc != 0);
assert(args != 0);
struct preparser pp;
vlc_sem_init(&pp.sem, 0);
pp.status = 0;
pp.daemon = false;
pp.tls_in = NULL;
pp.tls_out = NULL;
static const struct vlc_preparser_msg_serdes_cbs args_cbs = {
.write = write_cbs,
.read = NULL,
};
pp.serdes = vlc_preparser_msg_serdes_Create(obj, &args_cbs, false);
if (pp.serdes == NULL) {
return VLC_EGENERIC;
}
for (int i = 0; i < argc; i++) {
char *uri = NULL;
if (strstr(argv[i], "://" ) == NULL) {
uri = vlc_path2uri(argv[i], NULL);
} else {
uri = strdup(argv[i]);
}
if (uri == NULL) {
vlc_preparser_msg_serdes_Delete(pp.serdes);
return VLC_ENOMEM;
}
preparser_args_Preparse(preparser, &pp, uri, args);
free(uri);
}
vlc_preparser_msg_serdes_Delete(pp.serdes);
return VLC_SUCCESS;
}
/****************************************************************************
* Daemon
*****************************************************************************/
static int
preparser_daemon_Loop(vlc_object_t *obj, vlc_preparser_t *preparser)
{
assert(preparser != NULL);
struct preparser pp;
vlc_sem_init(&pp.sem, 0);
pp.status = 0;
pp.daemon = true;
static const struct vlc_preparser_msg_serdes_cbs deamon_cbs = {
.write = write_cbs,
.read = read_cbs,
};
pp.serdes = vlc_preparser_msg_serdes_Create(obj, &deamon_cbs, true);
if (pp.serdes == NULL) {
return VLC_EGENERIC;
}
#ifndef _WIN32
pp.tls_in = vlc_tls_SocketOpen(STDIN_FILENO);
if (pp.tls_in == NULL) {
vlc_preparser_msg_serdes_Delete(pp.serdes);
return VLC_EGENERIC;
}
pp.tls_out = vlc_tls_SocketOpen(STDOUT_FILENO);
if (pp.tls_out == NULL) {
pp.tls_in->ops->close(pp.tls_in);
vlc_preparser_msg_serdes_Delete(pp.serdes);
return VLC_EGENERIC;
}
#else
pp.tls_in = NULL;
pp.tls_out = NULL;
#endif
int status = VLC_SUCCESS;
while (status == VLC_SUCCESS) {
status = vlc_preparser_msg_serdes_Deserialize(pp.serdes, &pp.req_msg,
pp.tls_in);
if (status != VLC_SUCCESS) {
break;
}
if (pp.req_msg.req.uri != NULL) {
status = preparser_req_Preparse(preparser, &pp);
} else {
status = VLC_EGENERIC;
}
vlc_preparser_msg_Clean(&pp.req_msg);
}
if (pp.tls_in != NULL) {
pp.tls_in->ops->close(pp.tls_in);
}
if (pp.tls_out != NULL) {
pp.tls_out->ops->close(pp.tls_out);
}
vlc_preparser_msg_serdes_Delete(pp.serdes);
return status;
}
/****************************************************************************
* Main
*****************************************************************************/
const char vlc_module_name[] = "vlc-preparser";
/**
* Wait for new request and call preparser_Preparse.
*/
int main(int argc, char **argv)
{
#ifdef TOP_BUILDDIR
setenv("VLC_PLUGIN_PATH", TOP_BUILDDIR"/modules", 1);
setenv("VLC_DATA_PATH", TOP_SRCDIR"/share", 1);
setenv("VLC_LIB_PATH", TOP_BUILDDIR"/modules", 1);
#endif
#ifdef _WIN32
#include <fcntl.h>
setmode(STDIN_FILENO, O_BINARY);
setmode(STDOUT_FILENO, O_BINARY);
#endif
vlc_thread_set_name("vlc-preparser");
struct preparser_args args = {
.timeout = VLC_TICK_INVALID,
.types = 0,
.daemon = false,
.seek.type = VLC_THUMBNAILER_SEEK_NONE,
.seek.speed = VLC_THUMBNAILER_SEEK_FAST,
.output.file_path = NULL,
.output.format = VLC_THUMBNAILER_FORMAT_PNG,
.output.height = 0,
.output.width = 0,
.output.crop = false,
.verbosity = "-1",
};
int ret = preparser_cmdline_Parse(argc, argv, &args);
if (ret <= 0) {
return ret == 0 ? 0 : 1;
}
const char *libvlc_args[] = {
"--verbose", args.verbosity, "--vout=vdummy", "--aout=adummy",
"--text-renderer=tdummy",
};
libvlc_instance_t *vlc = libvlc_new(ARRAY_SIZE(libvlc_args), libvlc_args);
if (vlc == NULL) {
return 1;
}
vlc_object_t *obj = VLC_OBJECT(vlc->p_libvlc_int);
const struct vlc_preparser_cfg cfg = {
.types = args.types,
.max_parser_threads = 1,
.max_thumbnailer_threads = 1,
.timeout = args.timeout,
.external_process = false,
};
vlc_preparser_t *preparser = vlc_preparser_New(obj, &cfg);
if (preparser == NULL) {
libvlc_release(vlc);
return 1;
}
if (args.daemon) {
ret = preparser_daemon_Loop(obj, preparser);
} else {
ret = preparser_args_Loop(obj, preparser, argv + args.arg_idx,
argc - args.arg_idx, &args);
}
vlc_preparser_Delete(preparser);
libvlc_release(vlc);
return ret;
}