[vlc-devel] [PATCH] Add Rust backed cue sheet parsing module

Thomas Guillem thomas at gllm.fr
Thu Aug 20 11:47:37 CEST 2020



On Wed, Aug 19, 2020, at 08:12, Kartik Ohri wrote:
> On Wed, Aug 19, 2020 at 10:45 AM Steve Lhomme <robux4 at ycbcr.xyz> wrote:
>> Hello,
>> 
>> On 2020-08-18 22:37, Kartik Ohri wrote:
>> > The purpose of this module is to serve as a Proof Of Concept
>> 
>> In general you should use [RFC] in the subject of your email, suggesting 
>> it's not meant to be merged as such.
>> 
> I will keep this in mind.
> 
>> > and an example on how Rust code can be integrated inside VLC.
>> > ---
>> >   configure.ac                                  |  12 ++
>> >   modules/demux/Makefile.am                     |  25 +++
>> >   modules/demux/playlist/cuesheet.c             | 155 ++++++++++++++++++
>> >   modules/demux/playlist/cuesheet/.gitignore    |   2 +
>> >   modules/demux/playlist/cuesheet/Cargo.toml    |  10 ++
>> >   modules/demux/playlist/cuesheet/cbindgen.toml |  16 ++
>> >   modules/demux/playlist/cuesheet/cuesheet.h    |  44 +++++
>> >   modules/demux/playlist/cuesheet/src/capi.rs   | 115 +++++++++++++
>> >   modules/demux/playlist/cuesheet/src/config.rs | 108 ++++++++++++
>> >   modules/demux/playlist/cuesheet/src/lib.rs    |  18 ++
>> >   modules/demux/playlist/cuesheet/src/parse.rs  |  69 ++++++++
>> >   modules/demux/playlist/playlist.c             |   4 +
>> >   modules/demux/playlist/playlist.h             |   2 +
>> >   13 files changed, 580 insertions(+)
>> >   create mode 100644 modules/demux/playlist/cuesheet.c
>> >   create mode 100644 modules/demux/playlist/cuesheet/.gitignore
>> >   create mode 100644 modules/demux/playlist/cuesheet/Cargo.toml
>> >   create mode 100644 modules/demux/playlist/cuesheet/cbindgen.toml
>> >   create mode 100644 modules/demux/playlist/cuesheet/cuesheet.h
>> >   create mode 100644 modules/demux/playlist/cuesheet/src/capi.rs
>> >   create mode 100644 modules/demux/playlist/cuesheet/src/config.rs
>> >   create mode 100644 modules/demux/playlist/cuesheet/src/lib.rs
>> >   create mode 100644 modules/demux/playlist/cuesheet/src/parse.rs
>> > 
>> > diff --git a/configure.ac b/configure.ac
>> > index a30e86bf37..93a45d57d6 100644
>> > --- a/configure.ac
>> > +++ b/configure.ac
>> > @@ -1886,6 +1886,18 @@ AS_IF([test "${enable_sout}" != "no"], [
>> >   ])
>> >   AM_CONDITIONAL([ENABLE_SOUT], [test "${enable_sout}" != "no"])
>> > 
>> > +dnl Rust Modules
>> > +AC_ARG_ENABLE([rust],
>> > +    AS_HELP_STRING([--enable-rust], [disable building Rust modules (default disabled)]))
>> > +AS_IF([test "${enable_rust}" = "yes"],
>> > +      [AC_DEFINE(ENABLE_RUST, 1, [Define to 1 for building rust modules.])])
>> > +AM_CONDITIONAL([BUILD_RUST], [test "${enable_rust}" = "yes"])
>> > +if test "${enable_rust}" = "yes"
>> > +then
>> > +    AC_CHECK_PROG(CARGO, [cargo], [yes], [no])
>> > +    AS_IF(test "x$CARGO" = "xno", [cargo not found. cargo is required to build rust modules])
>> > +fi
>> 
>> You probably don't want to enable building Rust if cargo is not found. 
>> So BUILD_RUST should also depend on this test.
>> 
> Yes, right. Will fix it.
>  
>> > +
>> >   dnl Lua modules
>> >   AC_ARG_ENABLE([lua],
>> >     AS_HELP_STRING([--disable-lua],
>> > diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am
>> > index 67ec086af9..a2c3018f71 100644
>> > --- a/modules/demux/Makefile.am
>> > +++ b/modules/demux/Makefile.am
>> > @@ -1,3 +1,4 @@
>> > +
>> >   demuxdir = $(pluginsdir)/demux
>> >   demux_LTLIBRARIES =
>> > 
>> > @@ -254,6 +255,30 @@ libplaylist_plugin_la_SOURCES = \
>> >       demux/playlist/wpl.c \
>> >       demux/playlist/xspf.c \
>> >       demux/playlist/playlist.c demux/playlist/playlist.h
>> > +
>> > +if BUILD_RUST
>> > +CARGO_C = cargo capi install --release
>> > +RUST_RUNTIME_LIBS = -ldl -lrt -lpthread -lgcc_s -lc -lm -lrt -lpthread -lutil
>> 
>> This should not be hardcoded. Some may come from the Rust+C linking, 
>> some from the library you are using. This should be settled via 
>> variables in configure.ac.
>> 
> Understood.
> 
>> > +
>> > +libcuesheet_rust_plugin_la_SOURCES = \
>> > +     demux/playlist/cuesheet/src/capi.rs \
>> > +     demux/playlist/cuesheet/src/config.rs \
>> > +     demux/playlist/cuesheet/src/lib.rs \
>> > +     demux/playlist/cuesheet/src/parse.rs
>> > +
>> > +abs_src_dir = $(shell readlink -f $(srcdir))
>> > +
>> > + at abs_top_builddir@/modules/demux/playlist/cuesheet/libcuesheet.a: $(libcuesheet_rust_plugin_la_SOURCES)
>> > +     mkdir -p demux/playlist/cuesheet && cd demux/playlist/cuesheet && \
>> > +     $(CARGO_C) --library-type=staticlib --destdir=. --includedir=.. --libdir=. \
>> > +     --manifest-path=$(abs_src_dir)/demux/playlist/cuesheet/Cargo.toml
>> > +
>> > +CUESHEET_LIBS = @abs_top_builddir@/modules/demux/playlist/cuesheet/libcuesheet.a
>> > +CUESHEET_LIBS += $(RUST_RUNTIME_LIBS)
>> > +libplaylist_plugin_la_SOURCES += demux/playlist/cuesheet.c demux/playlist/cuesheet/cuesheet.h
>> > +libplaylist_plugin_la_LIBADD = $(CUESHEET_LIBS)
>> > +endif
>> > +
>> >   demux_LTLIBRARIES += libplaylist_plugin.la
>> > 
>> >   libts_plugin_la_SOURCES = demux/mpeg/ts.c demux/mpeg/ts.h \
>> > diff --git a/modules/demux/playlist/cuesheet.c b/modules/demux/playlist/cuesheet.c
>> > new file mode 100644
>> > index 0000000000..9330b01079
>> > --- /dev/null
>> > +++ b/modules/demux/playlist/cuesheet.c
>> > @@ -0,0 +1,155 @@
>> > +/*****************************************************************************
>> > + * cuesheet.c : cue sheet playlist import format
>> > + *****************************************************************************
>> > + * Copyright (C) 2020 VLC authors and VideoLAN
>> > + *
>> > + * Authors: Kartik Ohri <kartikohri13 at gmail.com>
>> > + *
>> > + * This program 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.
>> > + *
>> > + * This program 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 this program; if not, write to the Free Software Foundation,
>> > + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
>> > + *****************************************************************************/
>> > +
>> > +#ifdef HAVE_CONFIG_H
>> > +# include "config.h"
>> > +#endif
>> > +
>> > +#include <string.h>
>> > +
>> > +#include <vlc_common.h>
>> > +#include <vlc_access.h>
>> > +#include <vlc_url.h>
>> > +
>> > +#include "cuesheet/cuesheet.h"
>> > +#include "playlist.h"
>> > +
>> > +typedef struct {
>> > +    struct CCuesheet *cuesheet;
>> > +} cuesheet_sys_t;
>> > +
>> > +static int ReadDir(stream_t *, input_item_node_t *);
>> > +static char* get_string_property(CCuesheet *, const char*, int);
>> > +
>> > +int Import_Cue_Sheet( vlc_object_t *p_this )
>> > +{
>> > +    stream_t *p_demux = (stream_t *) p_this;
>> > +    CHECK_FILE ( p_demux );
>> > +
>> > +    if ( !stream_IsMimeType ( p_demux->s, "application/x-cue" )
>> > +    && !stream_HasExtension( p_demux->s, ".cue" ))
>> > +        return VLC_EGENERIC;
>> > +
>> > +    cuesheet_sys_t *sys = (cuesheet_sys_t *) malloc(sizeof(*sys));
>> > +    sys->cuesheet = cuesheet_default();
>> > +    p_demux->p_sys = sys;
>> > +
>> > +    p_demux->pf_control = access_vaDirectoryControlHelper;
>> > +    p_demux->pf_readdir = ReadDir;
>> > +    return VLC_SUCCESS;
>> > +}
>> > +
>> > +/**
>> > + * free should not be called on strings allocated by rust compiler. There is no
>> > + * way to ensure this without duplicating the string before passing it to
>> > + * input_item_t. To avoid redundancy and duplication, use this method instead
>> > + * of directly using cuesheet_get_property or cuesheet_get_track_property.
>> > + * For getting the property using cuesheet_get_property pass a negative number as
>> > + * track_num. For using cuesheet_get_track_property, pass the track number for
>> > + * which you the property as track_num.
>> > + */
>> > +static char* get_string_property(CCuesheet *cuesheet, const char* property, int track_num) {
>> > +    const char* psz_val;
>> > +    if(track_num < 0)
>> > +        psz_val = cuesheet_get_property(cuesheet, property);
>> > +    else
>> > +        psz_val = cuesheet_get_track_property(cuesheet, track_num, property);
>> > +    if(!psz_val)
>> > +        return NULL;
>> > +    char *psz_c_val = strdup(psz_val);
>> 
>> It seems you use the return value to call other setter functions. You 
>> can probably just return the const char * and use it directly.
>> 
> I did this because the Rust documentation (https://doc.rust-lang.org/std/ffi/struct.CString.html#method.into_raw) mentions that free() should not be called on a string allocated by rust. If it is guaranteed that this will not happen for the current usage, it is safe to remove this block of code.
>   
>> > +    unref_cuesheet_string(psz_val);
>> > +    return psz_c_val;
>> > +}
>> > +
>> > +static int ReadDir(stream_t* p_demux, input_item_node_t* p_subitems)
>> > +{
>> > +    cuesheet_sys_t *sys = p_demux->p_sys;
>> > +    char *psz_line;
>> > +    while ((psz_line = vlc_stream_ReadLine(p_demux->s)) != NULL)
>> > +    {
>> > +        msg_Dbg(p_demux, "%s", psz_line);
>> 
>> Should probably be commented out in the end.
>> 
> Will fix this.
>  
>> > +        cuesheet_process_line(sys->cuesheet, psz_line);
>> > +        free(psz_line);
>> > +    }
>> > +    int tracks = cuesheet_get_tracks_number(sys->cuesheet);
>> > +    int idx = 0;
>> > +    const char *p_val = NULL;
>> > +    char *ppsz_option[2];
>> > +
>> > +    char *psz_audio_file = get_string_property(sys->cuesheet, "FILE_NAME", -1);
>> > +    if (!psz_audio_file)
>> > +        goto error;
>> > +
>> > +    char *psz_audio_file_uri = vlc_uri_encode(psz_audio_file);
>> > +    char *psz_url = vlc_uri_resolve(p_demux->psz_url, psz_audio_file_uri);
>> > +    free(psz_audio_file);
>> > +    free(psz_audio_file_uri);
>> > +
>> > +    while ( idx < tracks )
>> > +    {
>> > +        int i_options = 0;
>> > +        input_item_t *p_item;
>> > +
>> > +        p_val = get_string_property(sys->cuesheet, "TITLE", idx);
>> > +        if (p_val)
>> > +        {
>> > +            p_item = input_item_New(psz_url, p_val);
>> > +            p_item->i_type = ITEM_TYPE_FILE;
>> > +        }
>> > +        else
>> > +            goto error;
>> > +
>> > +        p_val = get_string_property(sys->cuesheet, "TITLE", -1);
>> > +        if (p_val)
>> > +            input_item_SetAlbum(p_item, p_val);
>> > +
>> > +        p_val = get_string_property(sys->cuesheet, "PERFORMER", -1);
>> > +        if (p_val)
>> > +            input_item_SetAlbumArtist(p_item, p_val);
>> > +
>> > +        p_val = get_string_property(sys->cuesheet, "PERFORMER", idx);
>> > +        if (p_val)
>> > +            input_item_SetArtist(p_item, p_val);
>> > +
>> > +        int i_time = cuesheet_get_track_start(sys->cuesheet, idx);
>> > +        if (asprintf(ppsz_option, ":start-time=%d", i_time))
>> > +            i_options++;
>> > +
>> > +        if (idx < tracks - 1)
>> > +        {
>> > +            i_time = cuesheet_get_track_start(sys->cuesheet, idx + 1);
>> > +            if (asprintf(ppsz_option + i_options, ":stop-time=%d", i_time))
>> > +                i_options++;
>> > +        }
>> > +
>> > +        input_item_AddOptions(p_item, i_options, (const char **)ppsz_option, VLC_INPUT_OPTION_TRUSTED);
>> > +        input_item_node_AppendItem(p_subitems, p_item);
>> > +        input_item_Release(p_item);
>> > +        idx++;
>> > +    }
>> > +    return VLC_SUCCESS;
>> > +error:
>> > +    return VLC_EGENERIC;
>> > +}
>> > +
>> > +
>> > +
>> > diff --git a/modules/demux/playlist/cuesheet/.gitignore b/modules/demux/playlist/cuesheet/.gitignore
>> 
>> This should not be merged here. Either a general rule in the topmost 
>> .gitignore or not at all.
>> 
> Understood. Will fix it.
>  
>> > new file mode 100644
>> > index 0000000000..96ef6c0b94
>> > --- /dev/null
>> > +++ b/modules/demux/playlist/cuesheet/.gitignore
>> 
>> Same here.
>> 
>> > @@ -0,0 +1,2 @@
>> > +/target
>> > +Cargo.lock
>> > diff --git a/modules/demux/playlist/cuesheet/Cargo.toml b/modules/demux/playlist/cuesheet/Cargo.toml
>> > new file mode 100644
>> > index 0000000000..3ce20f6480
>> > --- /dev/null
>> > +++ b/modules/demux/playlist/cuesheet/Cargo.toml
>> > @@ -0,0 +1,10 @@
>> > +[package]
>> > +name = "cuesheet"
>> > +version = "0.1.0"
>> > +authors = ["Kartik Ohri <kartikohri13 at gmail.com>"]
>> > +edition = "2018"
>> > +
>> > +[dependencies]
>> > +
>> > +[lib]
>> > +name = "cuesheet"
>> > \ No newline at end of file
>> > diff --git a/modules/demux/playlist/cuesheet/cbindgen.toml b/modules/demux/playlist/cuesheet/cbindgen.toml
>> > new file mode 100644
>> > index 0000000000..bc8fd14ae8
>> > --- /dev/null
>> > +++ b/modules/demux/playlist/cuesheet/cbindgen.toml
>> > @@ -0,0 +1,16 @@
>> > +header = "// SPDX-License-Identifier: LGPL-3.0-or-later"
>> > +sys_includes = ["stddef.h", "stdint.h", "stdlib.h"]
>> > +no_includes = true
>> > +include_guard = "CUESHEET_H"
>> > +tab_width = 4
>> 
>> Is this file used for text editors or also for building ?
>> 
> Yes, the file is required for building. This file is used to generate the C headers for the rust module in this case (cuesheet.h). I'd like to know the opinion on whether to commit the cuesheet.h file or not because it is auto-generated during the build time. If a cuesheet.h file (from a previous build or say from the git repository) already exists, it will be overridden.
>  
>> > +language = "C"
>> > +documentation_style = "C"
>> > +style = "type"
>> > +cpp_compat = true
>> > +
>> > +[export]
>> > +item_types = ["enums", "structs", "unions", "typedefs", "opaque", "functions"]
>> > +
>> > +[enum]
>> > +rename_variants = "ScreamingSnakeCase"
>> > +prefix_with_name = true
>> > \ No newline at end of file
>> > diff --git a/modules/demux/playlist/cuesheet/cuesheet.h b/modules/demux/playlist/cuesheet/cuesheet.h
>> > new file mode 100644
>> > index 0000000000..cb7dd970f3
>> > --- /dev/null
>> > +++ b/modules/demux/playlist/cuesheet/cuesheet.h
>> > @@ -0,0 +1,44 @@
>> > +// SPDX-License-Identifier: LGPL-3.0-or-later
>> > +
>> > +#ifndef CUESHEET_H
>> > +#define CUESHEET_H
>> > +
>> > +
>> > +#define CUESHEET_MAJOR 0
>> > +#define CUESHEET_MINOR 1
>> > +#define CUESHEET_PATCH 0
>> > +
>> > +
>> > +#include <stddef.h>
>> > +#include <stdint.h>
>> > +#include <stdlib.h>
>> > +
>> > +typedef struct CCuesheet CCuesheet;
>> > +
>> > +#ifdef __cplusplus
>> > +extern "C" {
>> > +#endif // __cplusplus
>> > +
>> > +CCuesheet *cuesheet_default(void);
>> > +
>> > +const char *cuesheet_get_comment_value(CCuesheet *c_cuesheet, const char *line);
>> > +
>> > +const char *cuesheet_get_property(CCuesheet *c_cuesheet, const char *line);
>> > +
>> > +const char *cuesheet_get_track_property(CCuesheet *c_cuesheet, int index, const char *line);
>> > +
>> > +int cuesheet_get_track_start(CCuesheet *c_cuesheet, int index);
>> > +
>> > +int cuesheet_get_tracks_number(CCuesheet *c_cuesheet);
>> > +
>> > +void cuesheet_process_line(CCuesheet *c_cuesheet, const char *line);
>> > +
>> > +void unref_cuesheet(CCuesheet *cuesheet);
>> > +
>> > +void unref_cuesheet_string(char *text);
>> > +
>> > +#ifdef __cplusplus
>> > +} // extern "C"
>> > +#endif // __cplusplus
>> > +
>> > +#endif /* CUESHEET_H */
>> > diff --git a/modules/demux/playlist/cuesheet/src/capi.rs b/modules/demux/playlist/cuesheet/src/capi.rs
>> > new file mode 100644
>> > index 0000000000..b5af473fe7
>> > --- /dev/null
>> > +++ b/modules/demux/playlist/cuesheet/src/capi.rs
>> > @@ -0,0 +1,115 @@
>> > +use crate::config::Cuesheet;
>> > +use crate::parse::process_line;
>> > +use std::os::raw::{c_char, c_int};
>> > +use std::ffi::{CStr, CString};
>> > +use std::convert::TryFrom;
>> > +
>> > +pub struct CCuesheet {
>> > +    cuesheet : Cuesheet
>> > +}
>> > +
>> > +#[no_mangle]
>> > +pub unsafe extern fn cuesheet_default() -> *mut CCuesheet {
>> > +    let cuesheet = Cuesheet::default();
>> > +    let c_cuesheet = Box::new(CCuesheet {cuesheet});
>> > +    Box::into_raw(c_cuesheet)
>> > +}
>> > +
>> > +#[no_mangle]
>> > +pub unsafe extern fn cuesheet_process_line(c_cuesheet : *mut CCuesheet, line : *const c_char) {
>> > +    let rust_line = CStr::from_ptr(line).to_str().to_owned().unwrap();
>> > +    process_line(&mut (*c_cuesheet).cuesheet, rust_line);
>> > +}
>> > +
>> > +#[no_mangle]
>> > +pub unsafe extern fn cuesheet_get_property(c_cuesheet : *mut CCuesheet, line : *const c_char)
>> > +    -> *const c_char {
>> > +    let rust_line = match CStr::from_ptr(line).to_str() {
>> > +        Ok(value) => value,
>> > +        Err(_) => return std::ptr::null()
>> > +    };
>> > +
>> > +    let cuesheet = & (*c_cuesheet).cuesheet;
>> > +
>> > +    let property_value = cuesheet.get_property(rust_line);
>> > +    if property_value.is_empty() {
>> > +        return std::ptr::null()
>> > +    }
>> > +    convert_string_to_ptr(property_value.to_string())
>> > +}
>> > +
>> > +#[no_mangle]
>> > +pub unsafe extern fn cuesheet_get_comment_value(c_cuesheet : *mut CCuesheet,
>> > +                                                line : *const c_char) -> *const c_char {
>> > +    let rust_line = match CStr::from_ptr(line).to_str() {
>> > +        Ok(value) => value,
>> > +        Err(_) => return std::ptr::null()
>> > +    };
>> > +
>> > +    let cuesheet = & (*c_cuesheet).cuesheet;
>> > +
>> > +    match cuesheet.comments.get(rust_line) {
>> > +        Some(value) => convert_string_to_ptr(value.to_string()),
>> > +        _ => std::ptr::null()
>> > +    }
>> > +}
>> > +
>> > +#[no_mangle]
>> > +pub unsafe extern fn cuesheet_get_tracks_number(c_cuesheet : *mut CCuesheet) -> c_int {
>> > +    (*c_cuesheet).cuesheet.tracks.len() as i32
>> > +}
>> > +
>> > +#[no_mangle]
>> > +pub unsafe extern fn cuesheet_get_track_property(c_cuesheet : *mut CCuesheet, index : c_int,
>> > +                                                 line : *const c_char) -> *const c_char {
>> > +    let rust_line = match CStr::from_ptr(line).to_str() {
>> > +        Ok(value) => value,
>> > +        Err(_) => return std::ptr::null()
>> > +    };
>> > +
>> > +    let cuesheet = & (*c_cuesheet).cuesheet;
>> > +    let u_index = match usize::try_from(index) {
>> > +        Ok(value) => value,
>> > +        Err(_) => return std::ptr::null()
>> > +    };
>> > +
>> > +    let track = match cuesheet.get_track_at_index(u_index) {
>> > +        Ok(value) => value,
>> > +        Err(_) => return std::ptr::null()
>> > +    };
>> > +
>> > +    let val = track.get_property(rust_line);
>> > +    if val.is_empty() {
>> > +        return std::ptr::null();
>> > +    }
>> > +    convert_string_to_ptr(val.to_string())
>> > +}
>> > +
>> > +unsafe fn convert_string_to_ptr(string: String) -> *mut c_char {
>> > +    CString::new(string).unwrap().into_raw()
>> > +}
>> > +
>> > +#[no_mangle]
>> > +pub unsafe extern fn cuesheet_get_track_start(c_cuesheet: *mut CCuesheet, index : c_int) -> c_int {
>> > +    let cuesheet = &(*c_cuesheet).cuesheet;
>> > +
>> > +    let u_index = match usize::try_from(index){
>> > +        Ok(value) => value,
>> > +        Err(_e) => return -1
>> > +    };
>> > +
>> > +    match cuesheet.get_track_at_index(u_index) {
>> > +        Ok(t) => t.begin_time_in_seconds(),
>> > +        Err(_) => return -1
>> > +    }
>> > +}
>> > +
>> > +#[no_mangle]
>> > +pub unsafe extern fn unref_cuesheet_string(text : *mut c_char) {
>> > +    let _temp = CString::from_raw(text);
>> > +}
>> > +
>> > +#[no_mangle]
>> > +pub unsafe extern fn unref_cuesheet(cuesheet : *mut CCuesheet) {
>> > +    let _temp = Box::from_raw(cuesheet);
>> > +}
>> > diff --git a/modules/demux/playlist/cuesheet/src/config.rs b/modules/demux/playlist/cuesheet/src/config.rs
>> > new file mode 100644
>> > index 0000000000..45292a3e3b
>> > --- /dev/null
>> > +++ b/modules/demux/playlist/cuesheet/src/config.rs
>> > @@ -0,0 +1,108 @@
>> > +use std::collections::HashMap;
>> > +use std::borrow::Borrow;
>> > +
>> > +#[derive(Clone, Debug)]
>> > +pub struct CuesheetTrack {
>> > +    pub item_type : String,
>> > +    pub item_position : u8,
>> > +    pub item_performer : String,
>> > +    pub item_title : String,
>> > +    pub item_begin_duration : String
>> > +}
>> > +
>> > +impl Default for CuesheetTrack {
>> > +    fn default() -> Self {
>> > +        CuesheetTrack {
>> > +            item_type: "".to_owned(),
>> > +            item_position: 0,
>> > +            item_performer: "".to_string(),
>> > +            item_title: "".to_string(),
>> > +            item_begin_duration: "".to_string()
>> > +        }
>> > +    }
>> > +}
>> > +
>> > +impl CuesheetTrack {
>> > +    pub fn begin_time_in_seconds(&self) -> i32 {
>> > +        let start: &str = &self.item_begin_duration;
>> > +        let mut duration_parts: Vec<&str> = start.split(":").collect();
>> > +
>> > +        let frame_rate = 75;
>> > +        let convert_min_to_s = 60;
>> > +        let mut start_time;
>> > +        let mut part;
>> > +
>> > +        part = match duration_parts.pop() {
>> > +            Some(t) => t.parse::<u32>().unwrap(),
>> > +            None => return -1
>> > +        };
>> > +        start_time = (part / frame_rate) as i32;
>> > +
>> > +        part = match duration_parts.pop() {
>> > +            Some(t) => t.parse::<u32>().unwrap(),
>> > +            None => return -1
>> > +        };
>> > +        start_time += part as i32;
>> > +
>> > +        part = match duration_parts.pop() {
>> > +            Some(t) => t.parse::<u32>().unwrap(),
>> > +            None => return -1
>> > +        };
>> > +        start_time += (part * convert_min_to_s) as i32;
>> > +        start_time
>> > +    }
>> > +
>> > +    pub fn get_property(&self, key : &str) -> &str {
>> > +        match key.to_ascii_lowercase().as_str() {
>> > +            "type" => self.item_type.as_str(),
>> > +            "begin_duration" => self.item_begin_duration.as_str(),
>> > +            "performer" => self.item_performer.as_str(),
>> > +            "title" => self.item_title.as_str(),
>> > +            _ => ""
>> > +        }
>> > +    }
>> > +}
>> > +
>> > +#[derive(Clone, Debug)]
>> > +pub struct Cuesheet {
>> > +    pub file_name : String,
>> > +    pub file_type : String,
>> > +    pub tracks : Vec<CuesheetTrack>,
>> > +    pub performer : String,
>> > +    pub title : String,
>> > +    pub comments : HashMap<String, String>,
>> > +    pub is_processing_tracks : bool
>> > +}
>> > +
>> > +impl Default for Cuesheet {
>> > +    fn default() -> Self {
>> > +        Cuesheet {
>> > +            file_name: "".to_string(),
>> > +            file_type: "".to_string(),
>> > +            tracks: Vec::new(),
>> > +            performer: "".to_string(),
>> > +            title: "".to_string(),
>> > +            comments: HashMap::new(),
>> > +            is_processing_tracks: false
>> > +        }
>> > +    }
>> > +}
>> > +
>> > +impl Cuesheet {
>> > +    pub fn get_track_at_index(&self, index : usize) -> Result<&CuesheetTrack, &'static str> {
>> > +        if index >= self.tracks.len() {
>> > +            return Err("Index Out of Bounds");
>> > +        }
>> > +        Ok(self.tracks[index].borrow())
>> > +    }
>> > +
>> > +    pub fn get_property(&self, key : &str) -> &str {
>> > +        match key.to_ascii_lowercase().as_str() {
>> > +            "file_name" => self.file_name.as_str(),
>> > +            "file_type" => self.file_type.as_str(),
>> > +            "performer" => self.performer.as_str(),
>> > +            "title" => self.title.as_str(),
>> > +            _ => ""
>> > +        }
>> > +    }
>> > +}
>> > \ No newline at end of file
>> > diff --git a/modules/demux/playlist/cuesheet/src/lib.rs b/modules/demux/playlist/cuesheet/src/lib.rs
>> > new file mode 100644
>> > index 0000000000..c768d23825
>> > --- /dev/null
>> > +++ b/modules/demux/playlist/cuesheet/src/lib.rs
>> > @@ -0,0 +1,18 @@
>> > +mod parse;
>> > +mod config;
>> > +
>> > +#[cfg(test)]
>> > +mod tests {
>> > +    use crate::parse::parse_line_alt;
>> > +    #[test]
>> > +    fn test_parse_line_alt() {
>> > +        let parts = parse_line_alt("FILE \"Live in Berlin\" MP3");
>> > +        assert_eq!(3, parts.len());
>> > +        assert_eq!("FILE", parts[0]);
>> > +        assert_eq!("Live in Berlin", parts[1]);
>> > +        assert_eq!("MP3", parts[2]);
>> > +    }
>> > +}
>> > +
>> > +#[cfg(cargo_c)]
>> > +mod capi;
>> > \ No newline at end of file
>> > diff --git a/modules/demux/playlist/cuesheet/src/parse.rs b/modules/demux/playlist/cuesheet/src/parse.rs
>> > new file mode 100644
>> > index 0000000000..c1e6e35a2a
>> > --- /dev/null
>> > +++ b/modules/demux/playlist/cuesheet/src/parse.rs
>> > @@ -0,0 +1,69 @@
>> > +use crate::config::*;
>> > +
>> > +pub(crate) fn parse_line_alt(line: &str) -> Vec<String> {
>> > +    let characters = line.trim().chars();
>> > +    let mut parts: Vec<String> = Vec::new();
>> > +    let mut inside_quotes: bool = false;
>> > +    let mut temp_str: String = String::new();
>> > +    for char in characters {
>> > +        if char == '"' {
>> > +            inside_quotes = !inside_quotes;
>> > +        } else if inside_quotes {
>> > +            temp_str.push(char);
>> > +        } else if char.is_whitespace() {
>> > +            parts.push(temp_str.clone());
>> > +            temp_str.clear();
>> > +        } else {
>> > +            temp_str.push(char);
>> > +        }
>> > +    }
>> > +    parts.push(temp_str.clone());
>> > +    parts
>> > +}
>> > +
>> > +pub fn process_line(cuesheet: &mut Cuesheet, line : &str) {
>> > +    let parts = parse_line_alt(line);
>> > +    let keyword = parts[0].as_str();
>> > +    if !cuesheet.is_processing_tracks {
>> > +        match keyword {
>> > +            "FILE" => {
>> > +                cuesheet.file_name = parts[1].to_owned();
>> > +                cuesheet.file_type = parts[2].to_owned();
>> > +            },
>> > +            "TITLE" => cuesheet.title = parts[1].to_owned(),
>> > +            "PERFORMER" => cuesheet.performer = parts[1].to_owned(),
>> > +            "REM" => {
>> > +                cuesheet.comments.insert(
>> > +                    parts[1].to_owned().to_lowercase(), parts[2].to_owned());
>> > +            },
>> > +            "TRACK" =>  cuesheet.is_processing_tracks = true,
>> > +            _ => {}
>> > +        };
>> > +    }
>> > +    if cuesheet.is_processing_tracks {
>> > +        if keyword == "TRACK" {
>> > +            let mut track = CuesheetTrack::default();
>> > +            track.item_position = parts[1].to_owned().parse::<u8>().unwrap();
>> > +            track.item_type = parts[2].to_owned();
>> > +            cuesheet.tracks.push(track);
>> > +        } else {
>> > +            process_track(cuesheet.tracks.last_mut().unwrap(), line);
>> > +        }
>> > +    }
>> > +}
>> > +
>> > +fn process_track(track: &mut CuesheetTrack, line : &str) {
>> > +    let parts = parse_line_alt(line);
>> > +    let keyword = parts[0].as_str();
>> > +    match keyword {
>> > +        "TITLE" => track.item_title = parts[1].to_owned(),
>> > +        "PERFORMER" => track.item_performer = parts[1].to_owned(),
>> > +        "INDEX" => {
>> > +            let index_position = parts[1].to_owned().parse::<u8>().unwrap();
>> > +            if index_position == 1 {
>> > +                track.item_begin_duration = parts[2].to_owned();
>> > +            }
>> > +        },
>> > +        _ => {}
>> > +    };
>> > +}
>> > \ No newline at end of file
>> > diff --git a/modules/demux/playlist/playlist.c b/modules/demux/playlist/playlist.c
>> > index d3cca7a502..d5142b7930 100644
>> > --- a/modules/demux/playlist/playlist.c
>> > +++ b/modules/demux/playlist/playlist.c
>> > @@ -130,6 +130,10 @@ vlc_module_begin ()
>> >           add_shortcut( "wpl" )
>> >           set_capability( "stream_filter", 310 )
>> >           set_callbacks( Import_WPL, Close_WPL )
>> > +    add_submodule ()
>> > +        set_description ( N_("Cue Sheet importer") )
>> > +        set_capability ( "stream_filter", 320 )
>> > +        set_callback ( Import_Cue_Sheet )
>> 
>> I suppose this module requires some (large) Rust runtime to be linked 
>> with it. I think it would be better in a separate module to avoid this 
>> dependency on the regular module.
>> 
> I understand the rationale but I did not completely understand what should be done. 

Remove your modification in playlist.c/.h

And create your separate module by adding in cuesheet.c

vlc_module_begin()
    add_shortcut( "playlist" )
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_DEMUX )
    set_description ( N_("Cue Sheet importer") )
    set_capability ( "stream_filter", 320 )
    set_callback ( Import_Cue_Sheet )
vlc_module_end()

You will need also to fix the makefile:

libplaylist_cue_plugin_la_SOURCES += demux/playlist/cuesheet.c demux/playlist/cuesheet/cuesheet.h
libplaylist_cue_plugin_la_LIBADD = $(CUESHEET_LIBS)

> 
>> >   vlc_module_end ()
>> > 
>> >   /**
>> > diff --git a/modules/demux/playlist/playlist.h b/modules/demux/playlist/playlist.h
>> > index 10a9135bbf..6daf77aebb 100644
>> > --- a/modules/demux/playlist/playlist.h
>> > +++ b/modules/demux/playlist/playlist.h
>> > @@ -61,6 +61,8 @@ int Import_WMS(vlc_object_t *);
>> >   int Import_WPL ( vlc_object_t * );
>> >   void Close_WPL ( vlc_object_t * );
>> > 
>> > +int Import_Cue_Sheet ( vlc_object_t * );
>> > +
>> >   #define GetCurrentItem(obj) ((obj)->p_input_item)
>> >   #define GetSource(obj) ((obj)->s)
>> > 
>> > --
>> > 2.25.1
>> > 
>> > _______________________________________________
>> > vlc-devel mailing list
>> > To unsubscribe or modify your subscription options:
>> > https://mailman.videolan.org/listinfo/vlc-devel
>> > 
>> _______________________________________________
>> vlc-devel mailing list
>> To unsubscribe or modify your subscription options:
>> https://mailman.videolan.org/listinfo/vlc-devel
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20200820/761bf941/attachment-0001.html>


More information about the vlc-devel mailing list