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

Steve Lhomme robux4 at ycbcr.xyz
Wed Aug 19 07:15:15 CEST 2020


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.

> 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.

> +
>   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.

> +
> +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.

> +    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.

> +        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.

> 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 ?

> +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.

>   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
> 


More information about the vlc-devel mailing list