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

Kartik Ohri kartikohri13 at gmail.com
Wed Aug 19 08:12:29 CEST 2020


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.

>   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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20200819/dcecd501/attachment.html>


More information about the vlc-devel mailing list