<!DOCTYPE html><html><head><title></title><style type="text/css">p.MsoNormal,p.MsoNoSpacing{margin:0}</style></head><body><div><br></div><div><br></div><div>On Mon, Sep 14, 2020, at 13:09, Kartik Ohri wrote:<br></div><blockquote type="cite" id="qt" style=""><div dir="ltr"><div dir="ltr">On Mon, Sep 14, 2020 at 4:27 PM Thomas Guillem <<a href="mailto:thomas@gllm.fr">thomas@gllm.fr</a>> wrote:<br></div><div class="qt-gmail_quote"><blockquote class="qt-gmail_quote" style="margin-top:0px;margin-right:0px;margin-bottom:0px;margin-left:0.8ex;border-left-color:rgb(204, 204, 204);border-left-style:solid;border-left-width:1px;padding-left:1ex;"><div><br></div><div><br></div><div>On Fri, Sep 11, 2020, at 21:33, Kartik Ohri wrote:<br></div><div> > The module is written using Rust API and serves as<br></div><div> > a proof of concept for the same.<br></div><div> > ---<br></div><div> >  modules/demux/Makefile.am                     |  26 +++<br></div><div> >  modules/demux/playlist/cuesheet.c             | 192 ++++++++++++++++++<br></div><div> >  modules/demux/playlist/cuesheet/Cargo.toml    |  13 ++<br></div><div> >  modules/demux/playlist/cuesheet/cbindgen.toml |  16 ++<br></div><div> >  modules/demux/playlist/cuesheet/src/<a href="http://capi.rs" rel="noreferrer" target="_blank">capi.rs</a>   | 122 +++++++++++<br></div><div> >  modules/demux/playlist/cuesheet/src/<a href="http://config.rs" rel="noreferrer" target="_blank">config.rs</a> | 106 ++++++++++<br></div><div> >  modules/demux/playlist/cuesheet/src/<a href="http://lib.rs" rel="noreferrer" target="_blank">lib.rs</a>    |   5 +<br></div><div> >  modules/demux/playlist/cuesheet/src/<a href="http://parse.rs" rel="noreferrer" target="_blank">parse.rs</a>  |  75 +++++++<br></div><div> >  8 files changed, 555 insertions(+)<br></div><div> >  create mode 100644 modules/demux/playlist/cuesheet.c<br></div><div> >  create mode 100644 modules/demux/playlist/cuesheet/Cargo.toml<br></div><div> >  create mode 100644 modules/demux/playlist/cuesheet/cbindgen.toml<br></div><div> >  create mode 100644 modules/demux/playlist/cuesheet/src/<a href="http://capi.rs" rel="noreferrer" target="_blank">capi.rs</a><br></div><div> >  create mode 100644 modules/demux/playlist/cuesheet/src/<a href="http://config.rs" rel="noreferrer" target="_blank">config.rs</a><br></div><div> >  create mode 100644 modules/demux/playlist/cuesheet/src/<a href="http://lib.rs" rel="noreferrer" target="_blank">lib.rs</a><br></div><div> >  create mode 100644 modules/demux/playlist/cuesheet/src/<a href="http://parse.rs" rel="noreferrer" target="_blank">parse.rs</a><br></div><div> > <br></div><div> > diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am<br></div><div> > index 675b2d10af..31591a768b 100644<br></div><div> > --- a/modules/demux/Makefile.am<br></div><div> > +++ b/modules/demux/Makefile.am<br></div><div> > @@ -1,3 +1,4 @@<br></div><div> > +<br></div><div> >  demuxdir = $(pluginsdir)/demux<br></div><div> >  demux_LTLIBRARIES =<br></div><div> >  <br></div><div> > @@ -256,6 +257,31 @@ libplaylist_plugin_la_SOURCES = \<br></div><div> >       demux/playlist/playlist.c demux/playlist/playlist.h<br></div><div> >  demux_LTLIBRARIES += <a href="http://libplaylist_plugin.la" rel="noreferrer" target="_blank">libplaylist_plugin.la</a><br></div><div> >  <br></div><div> > +if BUILD_RUST<br></div><div> > +CARGO_C = cargo capi install --release<br></div><div> > +RUST_RUNTIME_LIBS = -ldl -lrt -lpthread -lgcc_s -lc -lm -lrt -lpthread <br></div><div> > -lutil<br></div><div> > +libcuesheet_rust_plugin_la_SOURCES = \<br></div><div> > +     demux/playlist/cuesheet/src/<a href="http://capi.rs" rel="noreferrer" target="_blank">capi.rs</a> \<br></div><div> > +     demux/playlist/cuesheet/src/<a href="http://config.rs" rel="noreferrer" target="_blank">config.rs</a> \<br></div><div> > +     demux/playlist/cuesheet/src/<a href="http://lib.rs" rel="noreferrer" target="_blank">lib.rs</a> \<br></div><div> > +     demux/playlist/cuesheet/src/<a href="http://parse.rs" rel="noreferrer" target="_blank">parse.rs</a><br></div><div> > +<br></div><div> > +abs_src_dir = $(shell readlink -f $(srcdir))<br></div><div> > +<br></div><div> > +@abs_top_builddir@/modules/demux/playlist/cuesheet/cuesheet.h: <br></div><div> > $(libcuesheet_rust_plugin_la_SOURCES)<br></div><div> > +     mkdir -p demux/playlist/cuesheet && cd demux/playlist/cuesheet && \<br></div><div> > +     $(CARGO_C) --library-type=staticlib --destdir=. --includedir=.. <br></div><div> > --libdir=. \<br></div><div> > +     --manifest-path=$(abs_src_dir)/demux/playlist/cuesheet/Cargo.toml<br></div><div> > +<br></div><div> > +BUILT_SOURCES += <br></div><div> > @abs_top_builddir@/modules/demux/playlist/cuesheet/cuesheet.h<br></div><div> > +CUESHEET_LIBS = <br></div><div> > @abs_top_builddir@/modules/demux/playlist/cuesheet/libcuesheet.a<br></div><div> > +CUESHEET_LIBS += $(RUST_RUNTIME_LIBS)<br></div><div> > +libcuesheet_plugin_la_SOURCES = demux/playlist/cuesheet.c <br></div><div> > @abs_top_builddir@/modules/demux/playlist/cuesheet/cuesheet.h<br></div><div> > +libcuesheet_plugin_la_LIBADD = $(CUESHEET_LIBS)<br></div><div> > +<br></div><div> > +demux_LTLIBRARIES += <a href="http://libcuesheet_plugin.la" rel="noreferrer" target="_blank">libcuesheet_plugin.la</a><br></div><div> > +endif<br></div><div> > +<br></div><div> >  libts_plugin_la_SOURCES = demux/mpeg/ts.c demux/mpeg/ts.h \<br></div><div> >          demux/mpeg/ts_pid.h demux/mpeg/ts_pid_fwd.h <br></div><div> > demux/mpeg/ts_pid.c \<br></div><div> >          demux/mpeg/ts_psi.h demux/mpeg/ts_psi.c \<br></div><div> > diff --git a/modules/demux/playlist/cuesheet.c <br></div><div> > b/modules/demux/playlist/cuesheet.c<br></div><div> > new file mode 100644<br></div><div> > index 0000000000..9634396b88<br></div><div> > --- /dev/null<br></div><div> > +++ b/modules/demux/playlist/cuesheet.c<br></div><div> > @@ -0,0 +1,192 @@<br></div><div> > +/*****************************************************************************<br></div><div> > + * cuesheet.c : cue sheet playlist import format<br></div><div> > + <br></div><div> > *****************************************************************************<br></div><div> > + * Copyright (C) 2020 VLC authors and VideoLAN<br></div><div> > + *<br></div><div> > + * Authors: Kartik Ohri <<a href="mailto:kartikohri13@gmail.com" target="_blank">kartikohri13@gmail.com</a>><br></div><div> > + *<br></div><div> > + * This program is free software; you can redistribute it and/or <br></div><div> > modify it<br></div><div> > + * under the terms of the GNU Lesser General Public License as <br></div><div> > published by<br></div><div> > + * the Free Software Foundation; either version 2.1 of the License, or<br></div><div> > + * (at your option) any later version.<br></div><div> > + *<br></div><div> > + * This program is distributed in the hope that it will be useful,<br></div><div> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of<br></div><div> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br></div><div> > + * GNU Lesser General Public License for more details.<br></div><div> > + *<br></div><div> > + * You should have received a copy of the GNU Lesser General Public <br></div><div> > License<br></div><div> > + * along with this program; if not, write to the Free Software <br></div><div> > Foundation,<br></div><div> > + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.<br></div><div> > + <br></div><div> > *****************************************************************************/<br></div><div> > +<br></div><div> > +#ifdef HAVE_CONFIG_H<br></div><div> > +# include "config.h"<br></div><div> > +#endif<br></div><div> > +<br></div><div> > +#include <string.h><br></div><div> > +<br></div><div> > +#include <vlc_common.h><br></div><div> > +#include <vlc_plugin.h><br></div><div> > +#include <vlc_access.h><br></div><div> > +#include <vlc_url.h><br></div><div> > +<br></div><div> > +#include "cuesheet/cuesheet.h"<br></div><div> > +#include "playlist.h"<br></div><div> > +<br></div><div> > +typedef struct {<br></div><div> > +    struct CCuesheet *cuesheet;<br></div><div> > +} cuesheet_sys_t;<br></div><div> > +<br></div><div> > +int Import_Cue_Sheet(vlc_object_t *);<br></div><div> > +static int ReadDir(stream_t *, input_item_node_t *);<br></div><div> > +static char* rust_string_to_c_string(char*);<br></div><div> > +static char* get_cuesheet_property(CCuesheet *, const char*);<br></div><div> > +static char* get_cuesheet_comment(CCuesheet *, const char*);<br></div><div> > +static char* get_track_property(CCuesheet *, const char*, int);<br></div><div> > +<br></div><div> > +int Import_Cue_Sheet( vlc_object_t *p_this )<br></div><div> > +{<br></div><div> > +    stream_t *p_demux = (stream_t *) p_this;<br></div><div> > +    CHECK_FILE ( p_demux );<br></div><div> > +<br></div><div> > +    if ( !stream_IsMimeType ( p_demux->s, "application/x-cue" )<br></div><div> > +    && !stream_HasExtension( p_demux->s, ".cue" ))<br></div><div> > +        return VLC_EGENERIC;<br></div><div> > +<br></div><div> > +    p_demux->pf_control = access_vaDirectoryControlHelper;<br></div><div> > +    p_demux->pf_readdir = ReadDir;<br></div><div> > +    return VLC_SUCCESS;<br></div><div> > +}<br></div><div> > +<br></div><div> > +/**<br></div><div> > + * free should not be called on strings allocated by rust compiler. <br></div><div> > There is no<br></div><div> > + * way to ensure this without duplicating the string before passing it <br></div><div> > to<br></div><div> > + * input_item_t.<br></div><div> > + */<br></div><div> > +static char* rust_string_to_c_string(char *psz_rust) {<br></div><div> > +    if (!psz_rust)<br></div><div> > +        return NULL;<br></div><div> > +    char *psz_c = strdup(psz_rust);<br></div><div> > +    unref_cuesheet_string(psz_rust);<br></div><div> > +    return psz_c;<br></div><div> > +}<br></div><div> > +<br></div><div> > +static char* get_cuesheet_property(CCuesheet *cuesheet, const char* <br></div><div> > psz_property) {<br></div><div> > +    char* psz_val = cuesheet_get_property(cuesheet, psz_property);<br></div><div> > +    return rust_string_to_c_string(psz_val);<br></div><div> > +}<br></div><div> > +<br></div><div> > +static char* get_cuesheet_comment(CCuesheet *cuesheet, const char* <br></div><div> > psz_property) {<br></div><div> > +    char* psz_val = cuesheet_get_comment_value(cuesheet, psz_property);<br></div><div> > +    return rust_string_to_c_string(psz_val);<br></div><div> > +}<br></div><div> > +<br></div><div> > +static char* get_track_property(CCuesheet *cuesheet, const char* <br></div><div> > psz_property, int track_num) {<br></div><div> > +    char* psz_val = cuesheet_get_track_property(cuesheet, track_num, <br></div><div> > psz_property);<br></div><div> > +    return rust_string_to_c_string(psz_val);<br></div><div> > +}<br></div><div> > +<br></div><div> > +static int ReadDir(stream_t* p_demux, input_item_node_t* p_subitems)<br></div><div> > +{<br></div><div> > +    int i_ret = VLC_EGENERIC;<br></div><div> > +    CCuesheet *cuesheet = cuesheet_from_demux(p_demux->s);<br></div><div> > +    if(!cuesheet)<br></div><div> > +        return i_ret;<br></div><div> > +<br></div><div> > +    int tracks = cuesheet_get_tracks_number(cuesheet);<br></div><div> > +    int idx = 0;<br></div><div> > +    char *psz_val = NULL;<br></div><div> > +    char *ppsz_option[2];<br></div><div> > +<br></div><div> > +    char *psz_album = get_cuesheet_property(cuesheet, "TITLE");<br></div><div> > +    char *psz_album_artist = get_cuesheet_property(cuesheet, <br></div><div> > "PERFORMER");<br></div><div> > +    char *psz_description = get_cuesheet_comment(cuesheet, "COMMENT");<br></div><div> > +    char *psz_genre = get_cuesheet_comment(cuesheet, "GENRE");<br></div><div> > +    char *psz_date = get_cuesheet_comment(cuesheet, "DATE");<br></div><div> > +<br></div><div> > +    char *psz_audio_file = get_cuesheet_property(cuesheet, <br></div><div> > "FILE_NAME");<br></div><div> > +    if (!psz_audio_file)<br></div><div> > +        goto end;<br></div><div> > +<br></div><div> > +    char *psz_audio_file_uri = vlc_uri_encode(psz_audio_file);<br></div><div> > +    char *psz_url = vlc_uri_resolve(p_demux->psz_url, <br></div><div> > psz_audio_file_uri);<br></div><div> > +    free(psz_audio_file);<br></div><div> > +    free(psz_audio_file_uri);<br></div><div> > +<br></div><div> > +    while ( idx < tracks )<br></div><div> > +    {<br></div><div> > +        int i_options = 0;<br></div><div> > +        input_item_t *p_item;<br></div><div> > +<br></div><div> > +        psz_val = get_track_property(cuesheet, "TITLE", idx);<br></div><div> > +        if (psz_val)<br></div><div> > +        {<br></div><div> > +            p_item = input_item_New(psz_url, psz_val);<br></div><div> > +            p_item->i_type = ITEM_TYPE_FILE;<br></div><div> > +            free(psz_val);<br></div><div> > +        }<br></div><div> > +        else<br></div><div> > +            goto end;<br></div><div> > +<br></div><div> > +        if (psz_album)<br></div><div> > +            input_item_SetAlbum(p_item, psz_album);<br></div><div> > +<br></div><div> > +        if (psz_album_artist)<br></div><div> > +            input_item_SetAlbumArtist(p_item, psz_album_artist);<br></div><div> > +<br></div><div> > +        if (psz_description)<br></div><div> > +            input_item_SetDescription(p_item, psz_description);<br></div><div> > +<br></div><div> > +        if (psz_date)<br></div><div> > +            input_item_SetDate(p_item, psz_date);<br></div><div> > +<br></div><div> > +        if (psz_genre)<br></div><div> > +            input_item_SetGenre(p_item, psz_genre);<br></div><div> > +<br></div><div> > +        psz_val = get_track_property(cuesheet, "PERFORMER", idx);<br></div><div> > +        if (psz_val)<br></div><div> > +        {<br></div><div> > +            input_item_SetArtist(p_item, psz_val);<br></div><div> > +            free(psz_val);<br></div><div> > +        }<br></div><div> > +<br></div><div> > +        int i_time = cuesheet_get_track_start(cuesheet, idx);<br></div><div> > +        if (asprintf(ppsz_option, ":start-time=%d", i_time))<br></div><div> > +            i_options++;<br></div><div> > +<br></div><div> > +        if (idx < tracks - 1)<br></div><div> > +        {<br></div><div> > +            i_time = cuesheet_get_track_start(cuesheet, idx + 1);<br></div><div> > +            if (asprintf(ppsz_option + i_options, ":stop-time=%d", <br></div><div> > i_time))<br></div><div> > +                i_options++;<br></div><div> > +        }<br></div><div> > +<br></div><div> > +        input_item_AddOptions(p_item, i_options, (const char <br></div><div> > **)ppsz_option, VLC_INPUT_OPTION_TRUSTED);<br></div><div> > +        input_item_node_AppendItem(p_subitems, p_item);<br></div><div> > +        input_item_Release(p_item);<br></div><div> > +        idx++;<br></div><div> > +    }<br></div><div> > +    <br></div><div> > +    i_ret = VLC_SUCCESS;<br></div><div> > + <br></div><div> > + end:<br></div><div> > +    free(psz_album);<br></div><div> > +    free(psz_album_artist);<br></div><div> > +    free(psz_description);<br></div><div> > +    free(psz_genre);<br></div><div> > +    free(psz_date);<br></div><div> > +    unref_cuesheet(cuesheet);<br></div><div> > +    return i_ret;<br></div><div> > +}<br></div><div> > +<br></div><div> > +vlc_module_begin()<br></div><div> > +    add_shortcut( "playlist" )<br></div><div> > +    set_category( CAT_INPUT )<br></div><div> > +    set_subcategory( SUBCAT_INPUT_DEMUX )<br></div><div> > +    set_description ( N_("Cue Sheet importer") )<br></div><div> > +    set_capability ( "stream_filter", 320 )<br></div><div> > +    set_callback ( Import_Cue_Sheet )<br></div><div> > +vlc_module_end()<br></div><div> > +<br></div><div> > +<br></div><div> > diff --git a/modules/demux/playlist/cuesheet/Cargo.toml <br></div><div> > b/modules/demux/playlist/cuesheet/Cargo.toml<br></div><div> > new file mode 100644<br></div><div> > index 0000000000..597076aa13<br></div><div> > --- /dev/null<br></div><div> > +++ b/modules/demux/playlist/cuesheet/Cargo.toml<br></div><div> > @@ -0,0 +1,13 @@<br></div><div> > +[package]<br></div><div> > +name = "cuesheet"<br></div><div> > +version = "0.1.0"<br></div><div> > +authors = ["Kartik Ohri <<a href="mailto:kartikohri13@gmail.com" target="_blank">kartikohri13@gmail.com</a>>"]<br></div><div> > +edition = "2018"<br></div><div> > +license = "LGPL-2.1-or-later"<br></div><div> > +<br></div><div> > +[dependencies]<br></div><div> > +vlccore-rs = {path = "../../../../src/vlccore-rs"}<br></div><div> > +vlccore-sys = {path = "../../../../src/vlccore-sys"}<br></div><div> > +<br></div><div> > +[lib]<br></div><div> > +name = "cuesheet"<br></div><div> > \ No newline at end of file<br></div><div> > diff --git a/modules/demux/playlist/cuesheet/cbindgen.toml <br></div><div> > b/modules/demux/playlist/cuesheet/cbindgen.toml<br></div><div> > new file mode 100644<br></div><div> > index 0000000000..bc8fd14ae8<br></div><div> > --- /dev/null<br></div><div> > +++ b/modules/demux/playlist/cuesheet/cbindgen.toml<br></div><div> > @@ -0,0 +1,16 @@<br></div><div> > +header = "// SPDX-License-Identifier: LGPL-3.0-or-later"<br></div><div> <br></div><div> Why this license? I don't think such license is acceptable in VLC (impossible to use on iOS for example).<br></div><div> <br></div></blockquote><div>That was a mistake on my part. I had only seen that other modules use LGPL but forgot to check which version. I checked the version and see that libVLC uses LGPL-2.1. Is changing to that fine or some other license should be used ?<br></div></div></div></blockquote><div><br></div><div>This license is fine, yes. Thanks !<br></div><div><br></div><blockquote type="cite" id="qt" style=""><div dir="ltr"><div class="qt-gmail_quote"><div> <br></div><blockquote class="qt-gmail_quote" style="margin-top:0px;margin-right:0px;margin-bottom:0px;margin-left:0.8ex;border-left-color:rgb(204, 204, 204);border-left-style:solid;border-left-width:1px;padding-left:1ex;"><div>> +sys_includes = ["stddef.h", "stdint.h", "stdlib.h"]<br></div><div> > +no_includes = true<br></div><div> > +include_guard = "CUESHEET_H"<br></div><div> > +tab_width = 4<br></div><div> > +language = "C"<br></div><div> > +documentation_style = "C"<br></div><div> > +style = "type"<br></div><div> > +cpp_compat = true<br></div><div> > +<br></div><div> > +[export]<br></div><div> > +item_types = ["enums", "structs", "unions", "typedefs", "opaque", <br></div><div> > "functions"]<br></div><div> > +<br></div><div> > +[enum]<br></div><div> > +rename_variants = "ScreamingSnakeCase"<br></div><div> > +prefix_with_name = true<br></div><div> > \ No newline at end of file<br></div><div> > diff --git a/modules/demux/playlist/cuesheet/src/<a href="http://capi.rs" rel="noreferrer" target="_blank">capi.rs</a> <br></div><div> > b/modules/demux/playlist/cuesheet/src/<a href="http://capi.rs" rel="noreferrer" target="_blank">capi.rs</a><br></div><div> > new file mode 100644<br></div><div> > index 0000000000..cd8daac2ba<br></div><div> > --- /dev/null<br></div><div> > +++ b/modules/demux/playlist/cuesheet/src/<a href="http://capi.rs" rel="noreferrer" target="_blank">capi.rs</a><br></div><div> > @@ -0,0 +1,122 @@<br></div><div> > +use crate::config::Cuesheet;<br></div><div> > +use std::convert::TryFrom;<br></div><div> > +use std::ffi::{CStr, CString};<br></div><div> > +use std::io::{BufRead, BufReader};<br></div><div> > +use std::mem::ManuallyDrop;<br></div><div> > +use std::os::raw::{c_char, c_int};<br></div><div> > +use std::ptr::null_mut;<br></div><div> > +<br></div><div> > +use vlccore_rs::stream;<br></div><div> > +use vlccore_sys::stream::stream_t;<br></div><div> > +<br></div><div> > +pub struct CCuesheet {<br></div><div> > +    cuesheet: Cuesheet,<br></div><div> > +}<br></div><div> > +<br></div><div> > +#[no_mangle]<br></div><div> > +pub extern "C" fn cuesheet_from_demux(s: *mut stream_t) -> *mut <br></div><div> > CCuesheet {<br></div><div> > +    let mut stream = s.into();<br></div><div> > +    let ptr = rust_cuesheet_from_demux(&mut stream)<br></div><div> > +        .map(|cuesheet| Box::new(CCuesheet { cuesheet }))<br></div><div> > +        .map(|c_cuesheet| Box::into_raw(c_cuesheet))<br></div><div> > +        .unwrap_or(null_mut());<br></div><div> > +    let _ = ManuallyDrop::new(stream);<br></div><div> > +    ptr<br></div><div> > +}<br></div><div> > +<br></div><div> > +fn rust_cuesheet_from_demux(stream: &mut stream::Stream) -> <br></div><div> > Option<Cuesheet> {<br></div><div> > +    let mut cuesheet = Cuesheet::default();<br></div><div> > +    let reader = BufReader::new(stream);<br></div><div> > +    for line in reader.lines() {<br></div><div> > +        match line {<br></div><div> > +            Ok(l) => cuesheet.process_line(l.as_str())?,<br></div><div> > +            Err(_) => return None,<br></div><div> > +        }<br></div><div> > +    }<br></div><div> > +    Some(cuesheet)<br></div><div> > +}<br></div><div> > +<br></div><div> > +#[no_mangle]<br></div><div> > +pub unsafe extern "C" fn cuesheet_get_property(<br></div><div> > +    c_cuesheet: *mut CCuesheet,<br></div><div> > +    line: *const c_char,<br></div><div> > +) -> *mut c_char {<br></div><div> > +    let cuesheet = &(*c_cuesheet).cuesheet;<br></div><div> > +<br></div><div> > +    CStr::from_ptr(line)<br></div><div> > +        .to_str()<br></div><div> > +        .ok()<br></div><div> > +        .and_then(|line| cuesheet.get_property(line))<br></div><div> > +        .map(|property| convert_string_to_ptr(property.to_string()))<br></div><div> > +        .unwrap_or(null_mut())<br></div><div> > +}<br></div><div> > +<br></div><div> > +#[no_mangle]<br></div><div> > +pub unsafe extern "C" fn cuesheet_get_comment_value(<br></div><div> > +    c_cuesheet: *mut CCuesheet,<br></div><div> > +    line: *const c_char,<br></div><div> > +) -> *mut c_char {<br></div><div> > +    let cuesheet = &(*c_cuesheet).cuesheet;<br></div><div> > +<br></div><div> > +    CStr::from_ptr(line)<br></div><div> > +        .to_str()<br></div><div> > +        .ok()<br></div><div> > +        .and_then(|line| cuesheet.comments.get(line))<br></div><div> > +        .map(|comment| convert_string_to_ptr(comment.to_string()))<br></div><div> > +        .unwrap_or(null_mut())<br></div><div> > +}<br></div><div> > +<br></div><div> > +#[no_mangle]<br></div><div> > +pub unsafe extern "C" fn cuesheet_get_tracks_number(c_cuesheet: *mut <br></div><div> > CCuesheet) -> c_int {<br></div><div> > +    (*c_cuesheet).cuesheet.tracks.len() as i32<br></div><div> > +}<br></div><div> > +<br></div><div> > +#[no_mangle]<br></div><div> > +pub unsafe extern "C" fn cuesheet_get_track_property(<br></div><div> > +    c_cuesheet: *mut CCuesheet,<br></div><div> > +    index: c_int,<br></div><div> > +    line: *const c_char,<br></div><div> > +) -> *mut c_char {<br></div><div> > +    let cuesheet = &(*c_cuesheet).cuesheet;<br></div><div> > +<br></div><div> > +    usize::try_from(index)<br></div><div> > +        .ok()<br></div><div> > +        .and_then(|index| cuesheet.get_track_at_index(index))<br></div><div> > +        .zip(CStr::from_ptr(line).to_str().ok())<br></div><div> > +        .and_then(|(track, property)| track.get_property(property))<br></div><div> > +        .map(|value| convert_string_to_ptr(value.to_string()))<br></div><div> > +        .unwrap_or(null_mut())<br></div><div> > +}<br></div><div> > +<br></div><div> > +#[no_mangle]<br></div><div> > +pub unsafe extern "C" fn cuesheet_get_track_start(<br></div><div> > +    c_cuesheet: *mut CCuesheet,<br></div><div> > +    index: c_int,<br></div><div> > +) -> c_int {<br></div><div> > +    let cuesheet = &(*c_cuesheet).cuesheet;<br></div><div> > +<br></div><div> > +    usize::try_from(index)<br></div><div> > +        .ok()<br></div><div> > +        .and_then(|index| cuesheet.get_track_at_index(index))<br></div><div> > +        .map(|track| track.begin_time_in_seconds())<br></div><div> > +        .unwrap_or(-1)<br></div><div> > +}<br></div><div> > +<br></div><div> > +unsafe fn convert_string_to_ptr(string: String) -> *mut c_char {<br></div><div> > +    if string.is_empty() {<br></div><div> > +        return std::ptr::null_mut();<br></div><div> > +    }<br></div><div> > +    CString::new(string)<br></div><div> > +        .map(|ptr| ptr.into_raw())<br></div><div> > +        .unwrap_or(null_mut())<br></div><div> > +}<br></div><div> > +<br></div><div> > +#[no_mangle]<br></div><div> > +pub unsafe extern "C" fn unref_cuesheet_string(text: *mut c_char) {<br></div><div> > +    let _ = CString::from_raw(text);<br></div><div> > +}<br></div><div> > +<br></div><div> > +#[no_mangle]<br></div><div> > +pub unsafe extern "C" fn unref_cuesheet(cuesheet: *mut CCuesheet) {<br></div><div> > +    let _ = Box::from_raw(cuesheet);<br></div><div> > +}<br></div><div> > diff --git a/modules/demux/playlist/cuesheet/src/<a href="http://config.rs" rel="noreferrer" target="_blank">config.rs</a> <br></div><div> > b/modules/demux/playlist/cuesheet/src/<a href="http://config.rs" rel="noreferrer" target="_blank">config.rs</a><br></div><div> > new file mode 100644<br></div><div> > index 0000000000..24ebbbc7a4<br></div><div> > --- /dev/null<br></div><div> > +++ b/modules/demux/playlist/cuesheet/src/<a href="http://config.rs" rel="noreferrer" target="_blank">config.rs</a><br></div><div> > @@ -0,0 +1,106 @@<br></div><div> > +use std::borrow::Borrow;<br></div><div> > +use std::collections::HashMap;<br></div><div> > +<br></div><div> > +#[derive(Clone, Debug)]<br></div><div> > +pub struct CuesheetTrack {<br></div><div> > +    pub item_type: String,<br></div><div> > +    pub item_position: u8,<br></div><div> > +    pub item_performer: String,<br></div><div> > +    pub item_title: String,<br></div><div> > +    pub item_begin_duration: String,<br></div><div> > +}<br></div><div> > +<br></div><div> > +impl Default for CuesheetTrack {<br></div><div> > +    fn default() -> Self {<br></div><div> > +        CuesheetTrack {<br></div><div> > +            item_type: "".to_owned(),<br></div><div> > +            item_position: 0,<br></div><div> > +            item_performer: "".to_string(),<br></div><div> > +            item_title: "".to_string(),<br></div><div> > +            item_begin_duration: "".to_string(),<br></div><div> > +        }<br></div><div> > +    }<br></div><div> > +}<br></div><div> > +<br></div><div> > +fn add_to_start_time(<br></div><div> > +    start_time: i32,<br></div><div> > +    time_to_add: Option<&str>,<br></div><div> > +    conversion_factor: i32,<br></div><div> > +) -> Option<i32> {<br></div><div> > +    let val = time_to_add?.parse::<u32>().ok()?;<br></div><div> > +    Some(start_time + val as i32 * conversion_factor)<br></div><div> > +}<br></div><div> > +<br></div><div> > +impl CuesheetTrack {<br></div><div> > +    pub fn begin_time_in_seconds(&self) -> i32 {<br></div><div> > +        let start: &str = &self.item_begin_duration;<br></div><div> > +        let mut duration_parts: Vec<&str> = start.split(":").collect();<br></div><div> > +<br></div><div> > +        let frame_rate = 75;<br></div><div> > +        let convert_min_to_s = 60;<br></div><div> > +<br></div><div> > +        duration_parts<br></div><div> > +            .pop()<br></div><div> > +            .and_then(|part| part.parse::<u32>().ok())<br></div><div> > +            .map(|part| part / frame_rate)<br></div><div> > +            .and_then(|start| add_to_start_time(start as i32, <br></div><div> > duration_parts.pop(), 1))<br></div><div> > +            .and_then(|start| {<br></div><div> > +                add_to_start_time(start as i32, duration_parts.pop(), <br></div><div> > convert_min_to_s)<br></div><div> > +            })<br></div><div> > +            .unwrap_or(-1)<br></div><div> > +    }<br></div><div> > +<br></div><div> > +    pub fn get_property(&self, key: &str) -> Option<&str> {<br></div><div> > +        match key.to_ascii_lowercase().as_str() {<br></div><div> > +            "type" => Some(self.item_type.as_str()),<br></div><div> > +            "begin_duration" => <br></div><div> > Some(self.item_begin_duration.as_str()),<br></div><div> > +            "performer" => Some(self.item_performer.as_str()),<br></div><div> > +            "title" => Some(self.item_title.as_str()),<br></div><div> > +            _ => None,<br></div><div> > +        }<br></div><div> > +    }<br></div><div> > +}<br></div><div> > +<br></div><div> > +#[derive(Clone, Debug)]<br></div><div> > +pub struct Cuesheet {<br></div><div> > +    pub file_name: String,<br></div><div> > +    pub file_type: String,<br></div><div> > +    pub tracks: Vec<CuesheetTrack>,<br></div><div> > +    pub performer: String,<br></div><div> > +    pub title: String,<br></div><div> > +    pub comments: HashMap<String, String>,<br></div><div> > +    pub is_processing_tracks: bool,<br></div><div> > +}<br></div><div> > +<br></div><div> > +impl Default for Cuesheet {<br></div><div> > +    fn default() -> Self {<br></div><div> > +        Cuesheet {<br></div><div> > +            file_name: "".to_string(),<br></div><div> > +            file_type: "".to_string(),<br></div><div> > +            tracks: Vec::new(),<br></div><div> > +            performer: "".to_string(),<br></div><div> > +            title: "".to_string(),<br></div><div> > +            comments: HashMap::new(),<br></div><div> > +            is_processing_tracks: false,<br></div><div> > +        }<br></div><div> > +    }<br></div><div> > +}<br></div><div> > +<br></div><div> > +impl Cuesheet {<br></div><div> > +    pub fn get_track_at_index(&self, index: usize) -> <br></div><div> > Option<&CuesheetTrack> {<br></div><div> > +        if index >= self.tracks.len() {<br></div><div> > +            return None;<br></div><div> > +        }<br></div><div> > +        Some(self.tracks[index].borrow())<br></div><div> > +    }<br></div><div> > +<br></div><div> > +    pub fn get_property(&self, key: &str) -> Option<&str> {<br></div><div> > +        match key.to_ascii_lowercase().as_str() {<br></div><div> > +            "file_name" => Some(self.file_name.as_str()),<br></div><div> > +            "file_type" => Some(self.file_type.as_str()),<br></div><div> > +            "performer" => Some(self.performer.as_str()),<br></div><div> > +            "title" => Some(self.title.as_str()),<br></div><div> > +            _ => None,<br></div><div> > +        }<br></div><div> > +    }<br></div><div> > +}<br></div><div> > diff --git a/modules/demux/playlist/cuesheet/src/<a href="http://lib.rs" rel="noreferrer" target="_blank">lib.rs</a> <br></div><div> > b/modules/demux/playlist/cuesheet/src/<a href="http://lib.rs" rel="noreferrer" target="_blank">lib.rs</a><br></div><div> > new file mode 100644<br></div><div> > index 0000000000..c43541e34f<br></div><div> > --- /dev/null<br></div><div> > +++ b/modules/demux/playlist/cuesheet/src/<a href="http://lib.rs" rel="noreferrer" target="_blank">lib.rs</a><br></div><div> > @@ -0,0 +1,5 @@<br></div><div> > +mod config;<br></div><div> > +mod parse;<br></div><div> > +<br></div><div> > +#[cfg(cargo_c)]<br></div><div> > +mod capi;<br></div><div> > diff --git a/modules/demux/playlist/cuesheet/src/<a href="http://parse.rs" rel="noreferrer" target="_blank">parse.rs</a> <br></div><div> > b/modules/demux/playlist/cuesheet/src/<a href="http://parse.rs" rel="noreferrer" target="_blank">parse.rs</a><br></div><div> > new file mode 100644<br></div><div> > index 0000000000..b570638325<br></div><div> > --- /dev/null<br></div><div> > +++ b/modules/demux/playlist/cuesheet/src/<a href="http://parse.rs" rel="noreferrer" target="_blank">parse.rs</a><br></div><div> > @@ -0,0 +1,75 @@<br></div><div> > +use crate::config::*;<br></div><div> > +<br></div><div> > +pub fn parse_line(line: &str) -> Vec<String> {<br></div><div> > +    let characters = line.trim().chars();<br></div><div> > +    let mut parts: Vec<String> = Vec::new();<br></div><div> > +    let mut inside_quotes: bool = false;<br></div><div> > +    let mut temp_str: String = String::new();<br></div><div> > +    for char in characters {<br></div><div> > +        if char == '"' {<br></div><div> > +            inside_quotes = !inside_quotes;<br></div><div> > +        } else if inside_quotes {<br></div><div> > +            temp_str.push(char);<br></div><div> > +        } else if char.is_whitespace() {<br></div><div> > +            parts.push(temp_str.clone());<br></div><div> > +            temp_str.clear();<br></div><div> > +        } else {<br></div><div> > +            temp_str.push(char);<br></div><div> > +        }<br></div><div> > +    }<br></div><div> > +    parts.push(temp_str.clone());<br></div><div> > +    parts<br></div><div> > +}<br></div><div> > +<br></div><div> > +impl Cuesheet {<br></div><div> > +    pub fn process_line(&mut self, line: &str) -> Option<()> {<br></div><div> > +        let parts = parse_line(line);<br></div><div> > +        let keyword = parts[0].as_str();<br></div><div> > +        if !self.is_processing_tracks {<br></div><div> > +            match keyword {<br></div><div> > +                "FILE" => {<br></div><div> > +                    self.file_name = parts[1].to_owned();<br></div><div> > +                    self.file_type = parts[2].to_owned();<br></div><div> > +                }<br></div><div> > +                "TITLE" => self.title = parts[1].to_owned(),<br></div><div> > +                "PERFORMER" => self.performer = parts[1].to_owned(),<br></div><div> > +                "REM" => {<br></div><div> > +                    self.comments<br></div><div> > +                        .insert(parts[1].to_owned().to_lowercase(), <br></div><div> > parts[2].to_owned());<br></div><div> > +                }<br></div><div> > +                "TRACK" => self.is_processing_tracks = true,<br></div><div> > +                _ => {}<br></div><div> > +            };<br></div><div> > +        }<br></div><div> > +        if self.is_processing_tracks {<br></div><div> > +            if keyword == "TRACK" {<br></div><div> > +                let mut track = CuesheetTrack::default();<br></div><div> > +                track.item_position = <br></div><div> > parts[1].to_owned().parse::<u8>().ok()?;<br></div><div> > +                track.item_type = parts[2].to_owned();<br></div><div> > +                self.tracks.push(track);<br></div><div> > +            } else {<br></div><div> > +                self.tracks.last_mut()?.process_track(line);<br></div><div> > +            }<br></div><div> > +        }<br></div><div> > +        Some(())<br></div><div> > +    }<br></div><div> > +}<br></div><div> > +<br></div><div> > +impl CuesheetTrack {<br></div><div> > +    fn process_track(&mut self, line: &str) -> Option<()> {<br></div><div> > +        let parts = parse_line(line);<br></div><div> > +        let keyword = parts[0].as_str();<br></div><div> > +        match keyword {<br></div><div> > +            "TITLE" => self.item_title = parts[1].to_owned(),<br></div><div> > +            "PERFORMER" => self.item_performer = parts[1].to_owned(),<br></div><div> > +            "INDEX" => {<br></div><div> > +                let index_position = <br></div><div> > parts[1].to_owned().parse::<u8>().ok()?;<br></div><div> > +                if index_position == 1 {<br></div><div> > +                    self.item_begin_duration = parts[2].to_owned();<br></div><div> > +                }<br></div><div> > +            }<br></div><div> > +            _ => {}<br></div><div> > +        };<br></div><div> > +        Some(())<br></div><div> > +    }<br></div><div> > +}<br></div><div> > -- <br></div><div> > 2.25.1<br></div><div> > <br></div><div> > _______________________________________________<br></div><div> > vlc-devel mailing list<br></div><div> > To unsubscribe or modify your subscription options:<br></div><div> > <a href="https://mailman.videolan.org/listinfo/vlc-devel" rel="noreferrer" target="_blank">https://mailman.videolan.org/listinfo/vlc-devel</a><br></div><div> _______________________________________________<br></div><div> vlc-devel mailing list<br></div><div> To unsubscribe or modify your subscription options:<br></div><div> <a href="https://mailman.videolan.org/listinfo/vlc-devel" rel="noreferrer" target="_blank">https://mailman.videolan.org/listinfo/vlc-devel</a><br></div></blockquote></div></div><div>_______________________________________________<br></div><div>vlc-devel mailing list<br></div><div>To unsubscribe or modify your subscription options:<br></div><div><a href="https://mailman.videolan.org/listinfo/vlc-devel">https://mailman.videolan.org/listinfo/vlc-devel</a><br></div></blockquote><div><br></div></body></html>