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

Kartik Ohri kartikohri13 at gmail.com
Tue Aug 18 22:37:47 CEST 2020


The purpose of this module is to serve as a Proof Of Concept
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
+
 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
+
+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);
+    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);
+        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
new file mode 100644
index 0000000000..96ef6c0b94
--- /dev/null
+++ b/modules/demux/playlist/cuesheet/.gitignore
@@ -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
+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 )
 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



More information about the vlc-devel mailing list