Implement REPL command to add current item to a m3u8 playlist
This commit is contained in:
parent
99f853e47d
commit
99a1372e47
6 changed files with 106 additions and 18 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1161,6 +1161,7 @@ dependencies = [
|
|||
"clap 3.2.25",
|
||||
"console",
|
||||
"lazy_static 1.4.0",
|
||||
"m3u8-rs",
|
||||
"muss-interpreter",
|
||||
"muss-player",
|
||||
]
|
||||
|
|
|
@ -25,6 +25,9 @@ clap = { version = "3.0", features = ["derive"] }
|
|||
console = { version = "0.15" }
|
||||
lazy_static = { version = "1.4" }
|
||||
|
||||
# cli add to playlist functionality
|
||||
m3u8-rs = { version = "^3.0.0" }
|
||||
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
muss-player = { version = "0.9.0", path = "./player", default-features = false, features = ["mpd"] }
|
||||
|
||||
|
|
|
@ -196,5 +196,8 @@ REPL-specific operations to help with writing Muss scripts: ?command
|
|||
pause
|
||||
Immediate song control actions to apply to the current item.
|
||||
|
||||
volume number
|
||||
Set playback volume to number, in percent (starts at 100% when the cli parameter is not used).
|
||||
|
||||
verbose
|
||||
Toggle verbose messages, like extra information on items in ?list and other goodies.";
|
||||
|
|
|
@ -57,6 +57,7 @@ mod channel_io;
|
|||
mod cli;
|
||||
mod debug_state;
|
||||
mod help;
|
||||
mod playlists;
|
||||
mod repl;
|
||||
|
||||
use std::io;
|
||||
|
|
89
src/playlists.rs
Normal file
89
src/playlists.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use m3u8_rs::{MediaSegment, MediaPlaylist};
|
||||
|
||||
use muss_interpreter::Item;
|
||||
|
||||
use crate::repl::{TERMINAL_WRITE_ERROR, ReplState};
|
||||
|
||||
/// Add the currently-playing item to a m3u8 playlist, printing any error to the terminal
|
||||
pub fn add_to_playlist_cmd(cmd_args: &[&str], state: &mut ReplState) {
|
||||
if let Some(&file_arg) = cmd_args.get(1) {
|
||||
let mut playlist = match open_media_playlist(file_arg) {
|
||||
Ok(playlist) => playlist,
|
||||
Err(e) => {
|
||||
writeln!(state.terminal, "Failed to open playlist {}: {}", file_arg, e).expect(TERMINAL_WRITE_ERROR);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let current_item = match get_current_item(state) {
|
||||
Some(item) => item,
|
||||
None => {
|
||||
writeln!(state.terminal, "Nothing playing").expect(TERMINAL_WRITE_ERROR);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match item_to_media_segment(¤t_item) {
|
||||
Some(segment) => playlist.segments.push(segment),
|
||||
None => {
|
||||
writeln!(state.terminal, "Failed to convert item into playlist media segment").expect(TERMINAL_WRITE_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
match save_media_playlist(file_arg, playlist) {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
writeln!(state.terminal, "Failed to save playlist {}: {}", file_arg, e).expect(TERMINAL_WRITE_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeln!(state.terminal, "Missing ?add-to parameter, usage: ?add-to <playlist file>").expect(TERMINAL_WRITE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
fn open_media_playlist(path: impl AsRef<std::path::Path>) -> Result<MediaPlaylist, String> {
|
||||
if path.as_ref().exists() {
|
||||
let playlist_bytes = std::fs::read(path).map_err(|e| e.to_string())?;
|
||||
m3u8_rs::parse_media_playlist_res(&playlist_bytes).map_err(|e| e.to_string())
|
||||
} else {
|
||||
Ok(MediaPlaylist::default())
|
||||
}
|
||||
}
|
||||
|
||||
fn save_media_playlist(path: impl AsRef<std::path::Path>, playlist: MediaPlaylist) -> std::io::Result<()> {
|
||||
let mut playlist_file = std::io::BufWriter::new(std::fs::File::create(path)?);
|
||||
playlist.write_to(&mut playlist_file)
|
||||
}
|
||||
|
||||
fn item_to_media_segment(item: &Item) -> Option<m3u8_rs::MediaSegment> {
|
||||
Some(MediaSegment {
|
||||
uri: music_filename(item)?,
|
||||
title: music_title(item),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn music_title(item: &Item) -> Option<String> {
|
||||
item.field("title").and_then(|x| x.to_owned().to_str())
|
||||
}
|
||||
|
||||
fn music_filename(item: &Item) -> Option<String> {
|
||||
if let Some(filename) = item.field("filename") {
|
||||
let relative_path = if let Ok(cwd) = std::env::current_dir() {
|
||||
let cwd: &Path = &cwd;
|
||||
filename.as_str().replace(cwd.to_str().unwrap_or(""), "./")
|
||||
} else {
|
||||
filename.to_string()
|
||||
};
|
||||
Some(relative_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_current_item(state: &mut ReplState) -> Option<muss_interpreter::Item> {
|
||||
let data = state.controller_debug.read().expect("Failed to get read lock for debug player data");
|
||||
data.now_playing.clone()
|
||||
}
|
27
src/repl.rs
27
src/repl.rs
|
@ -22,13 +22,13 @@ lazy_static! {
|
|||
});
|
||||
}
|
||||
|
||||
const TERMINAL_WRITE_ERROR: &str = "Failed to write to terminal output";
|
||||
pub const TERMINAL_WRITE_ERROR: &str = "Failed to write to terminal output";
|
||||
const INTERPRETER_WRITE_ERROR: &str = "Failed to write to interpreter";
|
||||
|
||||
type DebugItem = Result<Item, String>;
|
||||
|
||||
struct ReplState {
|
||||
terminal: Term,
|
||||
pub struct ReplState {
|
||||
pub terminal: Term,
|
||||
line_number: usize,
|
||||
statement_buf: Vec<char>,
|
||||
writer: ChannelWriter,
|
||||
|
@ -41,7 +41,7 @@ struct ReplState {
|
|||
cursor_rightward_position: usize,
|
||||
//debug: Arc<RwLock<DebugState>>,
|
||||
list_rx: Receiver<DebugItem>,
|
||||
controller_debug: std::sync::Arc<std::sync::RwLock<crate::debug_state::DebugState>>,
|
||||
pub controller_debug: std::sync::Arc<std::sync::RwLock<crate::debug_state::DebugState>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -753,13 +753,12 @@ fn repl_commands(command_str: &str, state: &mut ReplState, args: &CliArgs) {
|
|||
writeln!(state.terminal, "{}", super::help::REPL_COMMANDS).expect(TERMINAL_WRITE_ERROR)
|
||||
}
|
||||
"?now" => {
|
||||
let data = state.controller_debug.read().expect("Failed to get read lock for debug player data");
|
||||
if let Some(item) = &data.now_playing {
|
||||
if let Some(item) = crate::playlists::get_current_item(state) {
|
||||
let verbose = DEBUG_STATE
|
||||
.read()
|
||||
.expect("Failed to get read lock for debug state")
|
||||
.verbose;
|
||||
pretty_print_item(item, &mut state.terminal, args, verbose);
|
||||
pretty_print_item(&item, &mut state.terminal, args, verbose);
|
||||
} else {
|
||||
writeln!(state.terminal, "Nothing playing").expect(TERMINAL_WRITE_ERROR)
|
||||
}
|
||||
|
@ -809,19 +808,11 @@ fn repl_commands(command_str: &str, state: &mut ReplState, args: &CliArgs) {
|
|||
.expect("Failed to send control action");
|
||||
}
|
||||
"?volume" => volume_cmd(&words, state),
|
||||
"?add-to" => add_to_playlist_cmd(&words, state),
|
||||
"?add-to" => crate::playlists::add_to_playlist_cmd(&words, state),
|
||||
_ => writeln!(state.terminal, "Unknown command, try ?help").expect(TERMINAL_WRITE_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_to_playlist_cmd(cmd_args: &[&str], state: &mut ReplState) {
|
||||
if let Some(file_arg) = cmd_args.get(1) {
|
||||
writeln!(state.terminal, "Got ?add-to {}, doing nothing (this command is not implemented)", file_arg).expect(TERMINAL_WRITE_ERROR);
|
||||
} else {
|
||||
writeln!(state.terminal, "Missing ?add-to parameter, usage: ?add-to <playlist file>").expect(TERMINAL_WRITE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
fn volume_cmd(cmd_args: &[&str], state: &mut ReplState) {
|
||||
if let Some(volume_arg) = cmd_args.get(1) {
|
||||
let volume_result: Result<f32, _> = volume_arg.parse();
|
||||
|
@ -835,9 +826,9 @@ fn volume_cmd(cmd_args: &[&str], state: &mut ReplState) {
|
|||
.expect("Failed to get lock for control action sender")
|
||||
.send(muss_player::ControlAction::SetVolume { ack: false, volume: (vol * 100.0).round() as _ })
|
||||
.expect("Failed to send control action"),
|
||||
Err(e) => writeln!(state.terminal, "Error parsing ?volume <float> parameter: {}", e).expect(TERMINAL_WRITE_ERROR)
|
||||
Err(e) => writeln!(state.terminal, "Error parsing ?volume number parameter: {}", e).expect(TERMINAL_WRITE_ERROR)
|
||||
}
|
||||
} else {
|
||||
writeln!(state.terminal, "Missing ?volume parameter, usage: ?volume <float>").expect(TERMINAL_WRITE_ERROR);
|
||||
writeln!(state.terminal, "Missing ?volume parameter, usage: ?volume number").expect(TERMINAL_WRITE_ERROR);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue