mirror of
https://github.com/G2-Games/minidisc-cli.git
synced 2025-04-19 11:42:53 -05:00
Added more functions to interface.rs
This commit is contained in:
parent
4b1f7c5908
commit
02338a69a6
4 changed files with 212 additions and 10 deletions
|
@ -2,7 +2,7 @@ use crate::netmd::base;
|
||||||
use crate::netmd::query_utils::{format_query, scan_query, QueryValue};
|
use crate::netmd::query_utils::{format_query, scan_query, QueryValue};
|
||||||
use crate::netmd::utils::{
|
use crate::netmd::utils::{
|
||||||
half_width_to_full_width_range, length_after_encoding_to_jis,
|
half_width_to_full_width_range, length_after_encoding_to_jis,
|
||||||
sanitize_full_width_title, sanitize_half_width_title
|
sanitize_full_width_title, sanitize_half_width_title, time_to_frames
|
||||||
};
|
};
|
||||||
use encoding_rs::*;
|
use encoding_rs::*;
|
||||||
use rusb;
|
use rusb;
|
||||||
|
@ -48,7 +48,8 @@ impl WireFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Encoding {
|
#[derive(Debug)]
|
||||||
|
pub enum Encoding {
|
||||||
SP = 0x90,
|
SP = 0x90,
|
||||||
LP2 = 0x92,
|
LP2 = 0x92,
|
||||||
LP4 = 0x93,
|
LP4 = 0x93,
|
||||||
|
@ -318,6 +319,7 @@ impl NetMDInterface {
|
||||||
let _ = self.send_query(&mut query, false, false);
|
let _ = self.send_query(&mut query, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a query to the NetMD player
|
||||||
fn send_query(
|
fn send_query(
|
||||||
&self,
|
&self,
|
||||||
query: &mut Vec<u8>,
|
query: &mut Vec<u8>,
|
||||||
|
@ -664,14 +666,17 @@ impl NetMDInterface {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change to the next track (skip forward)
|
||||||
pub fn next_track(&self) -> Result<(), Box<dyn Error>> {
|
pub fn next_track(&self) -> Result<(), Box<dyn Error>> {
|
||||||
self._track_change(Track::Next)
|
self._track_change(Track::Next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change to the next track (skip back)
|
||||||
pub fn previous_track(&self) -> Result<(), Box<dyn Error>> {
|
pub fn previous_track(&self) -> Result<(), Box<dyn Error>> {
|
||||||
self._track_change(Track::Next)
|
self._track_change(Track::Next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change to the next track (skip to beginning of track)
|
||||||
pub fn restart_track(&self) -> Result<(), Box<dyn Error>> {
|
pub fn restart_track(&self) -> Result<(), Box<dyn Error>> {
|
||||||
self._track_change(Track::Next)
|
self._track_change(Track::Next)
|
||||||
}
|
}
|
||||||
|
@ -700,6 +705,7 @@ impl NetMDInterface {
|
||||||
Ok(res[0].to_i64().unwrap() as u8)
|
Ok(res[0].to_i64().unwrap() as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The number of tracks on the disc
|
||||||
pub fn track_count(&self) -> Result<u8, Box<dyn Error>> {
|
pub fn track_count(&self) -> Result<u8, Box<dyn Error>> {
|
||||||
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::OpenRead);
|
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::OpenRead);
|
||||||
|
|
||||||
|
@ -784,6 +790,7 @@ impl NetMDInterface {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the disc title
|
||||||
pub fn disc_title(&self, wchar: bool) -> Result<String, Box<dyn Error>> {
|
pub fn disc_title(&self, wchar: bool) -> Result<String, Box<dyn Error>> {
|
||||||
let mut title = self._disc_title(wchar)?;
|
let mut title = self._disc_title(wchar)?;
|
||||||
|
|
||||||
|
@ -809,6 +816,7 @@ impl NetMDInterface {
|
||||||
Ok(title)
|
Ok(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets all groups on the disc
|
||||||
pub fn track_group_list(
|
pub fn track_group_list(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Vec<(Option<String>, Option<String>, Vec<u16>)>, Box<dyn Error>> {
|
) -> Result<Vec<(Option<String>, Option<String>, Vec<u16>)>, Box<dyn Error>> {
|
||||||
|
@ -930,6 +938,7 @@ impl NetMDInterface {
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets the title of the disc
|
||||||
pub fn set_disc_title(&self, title: String, wchar: bool) -> Result<(), Box<dyn Error>> {
|
pub fn set_disc_title(&self, title: String, wchar: bool) -> Result<(), Box<dyn Error>> {
|
||||||
let current_title = self._disc_title(wchar)?;
|
let current_title = self._disc_title(wchar)?;
|
||||||
if current_title == title {
|
if current_title == title {
|
||||||
|
@ -981,4 +990,185 @@ impl NetMDInterface {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the title of a track
|
||||||
|
pub fn set_track_title(&self, track: u16, title: String, wchar: bool) -> Result<(), Box<dyn Error>> {
|
||||||
|
let new_title: Vec<u8>;
|
||||||
|
let (wchar_value, descriptor) = match wchar {
|
||||||
|
true => {
|
||||||
|
new_title = sanitize_full_width_title(&title, false);
|
||||||
|
(3, Descriptor::AudioUTOC4TD)
|
||||||
|
},
|
||||||
|
false => {
|
||||||
|
new_title = sanitize_half_width_title(title.clone());
|
||||||
|
(2, Descriptor::AudioUTOC1TD)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let old_len: u16;
|
||||||
|
let new_len = new_title.len();
|
||||||
|
|
||||||
|
match self.track_title(track, wchar) {
|
||||||
|
Ok(current_title) => {
|
||||||
|
if title == current_title {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
old_len = length_after_encoding_to_jis(¤t_title) as u16;
|
||||||
|
},
|
||||||
|
Err(error) if error.to_string() == "Rejected" => {
|
||||||
|
old_len = 0;
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.change_descriptor_state(&descriptor, &DescriptorAction::OpenWrite);
|
||||||
|
let mut query = format_query(
|
||||||
|
"1807 022018%b %w 3000 0a00 5000 %w 0000 %w %*".to_string(),
|
||||||
|
vec![
|
||||||
|
QueryValue::Number(wchar_value),
|
||||||
|
QueryValue::Number(track as i64),
|
||||||
|
QueryValue::Number(new_len as i64),
|
||||||
|
QueryValue::Number(old_len as i64),
|
||||||
|
QueryValue::Array(new_title),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
let reply = self.send_query(&mut query, false, false)?;
|
||||||
|
|
||||||
|
let _ = scan_query(reply, "1807 022018%? %?%? 3000 0a00 5000 %?%? 0000 %?%?".to_string());
|
||||||
|
self.change_descriptor_state(&descriptor, &DescriptorAction::Close);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a track from the UTOC
|
||||||
|
pub fn erase_track(&self, track: u16) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut query = format_query(
|
||||||
|
"1840 ff01 00 201001 %w".to_string(),
|
||||||
|
vec![
|
||||||
|
QueryValue::Number(track as i64),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let _ = self.send_query(&mut query, false, false);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves a track to another position on the disc
|
||||||
|
pub fn move_track(&self, source: u16, dest: u16) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut query = format_query(
|
||||||
|
"1843 ff00 00 201001 %w 201001 %w".to_string(),
|
||||||
|
vec![
|
||||||
|
QueryValue::Number(source as i64),
|
||||||
|
QueryValue::Number(dest as i64),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let _ = self.send_query(&mut query, false, false);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _get_track_info(&self, track: u16, p1: i32, p2: i32) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::OpenRead);
|
||||||
|
|
||||||
|
let mut query = format_query(
|
||||||
|
"1806 02201001 %w %w %w ff00 00000000".to_string(),
|
||||||
|
vec![
|
||||||
|
QueryValue::Number(track as i64),
|
||||||
|
QueryValue::Number(p1 as i64),
|
||||||
|
QueryValue::Number(p2 as i64),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let reply = self.send_query(&mut query, false, false)?;
|
||||||
|
let res = scan_query(reply, "1806 02201001 %?%? %?%? %?%? 1000 00%?0000 %x".to_string())?;
|
||||||
|
|
||||||
|
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::OpenRead);
|
||||||
|
|
||||||
|
return Ok(res[0].to_vec().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the length of a track as a `std::time::Duration`
|
||||||
|
pub fn track_length(&self, track_number: u16) -> Result<std::time::Duration, Box<dyn Error>> {
|
||||||
|
let raw_value = self._get_track_info(track_number, 0x3000, 0x0100)?;
|
||||||
|
let result = scan_query(raw_value, "01 0006 0000 %B %B %B %B".to_string())?;
|
||||||
|
|
||||||
|
let hours = result[0].to_i64().unwrap();
|
||||||
|
let minutes = result[1].to_i64().unwrap();
|
||||||
|
let seconds = result[2].to_i64().unwrap();
|
||||||
|
let frames = result[3].to_i64().unwrap();
|
||||||
|
|
||||||
|
let time_ms = (hours * 3600000000) + (minutes * 60000000) + (seconds * 1000000) + (frames * 11600);
|
||||||
|
|
||||||
|
Ok(std::time::Duration::from_micros(time_ms as u64))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the encoding of a track (SP, LP2, LP4)
|
||||||
|
pub fn track_encoding(&self, track_number: u16) -> Result<Encoding, Box<dyn Error>> {
|
||||||
|
let raw_value = self._get_track_info(track_number, 0x3080, 0x0700)?;
|
||||||
|
let result = scan_query(raw_value, "07 0004 0110 %b %b".to_string())?;
|
||||||
|
|
||||||
|
let final_encoding = match result[0].to_i64() {
|
||||||
|
Ok(0x90) => Encoding::SP,
|
||||||
|
Ok(0x92) => Encoding::LP2,
|
||||||
|
Ok(0x93) => Encoding::LP4,
|
||||||
|
Ok(e) => return Err(format!("Encoding value {e} out of range (0x90..0x92)").into()),
|
||||||
|
Err(error) => return Err(error)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(final_encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a track's flags
|
||||||
|
pub fn track_flags(&self, track: u16) -> Result<u8, Box<dyn Error>> {
|
||||||
|
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::OpenRead);
|
||||||
|
let mut query = format_query(
|
||||||
|
"1806 01201001 %w ff00 00010008".to_string(),
|
||||||
|
vec![
|
||||||
|
QueryValue::Number(track as i64),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
let reply = self.send_query(&mut query, false, false)?;
|
||||||
|
|
||||||
|
let res = scan_query(reply, "1806 01201001 %?%? 10 00 00010008 %b".to_string())?;
|
||||||
|
|
||||||
|
self.change_descriptor_state(&Descriptor::AudioContentsTD, &DescriptorAction::Close);
|
||||||
|
|
||||||
|
Ok(res[0].to_i64().unwrap() as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disc_capacity(&self) -> Result<[std::time::Duration; 3], Box<dyn Error>> {
|
||||||
|
self.change_descriptor_state(&Descriptor::RootTD, &DescriptorAction::OpenRead);
|
||||||
|
let mut query = format_query(
|
||||||
|
"1806 02101000 3080 0300 ff00 00000000".to_string(),
|
||||||
|
vec![],
|
||||||
|
)?;
|
||||||
|
let reply = self.send_query(&mut query, false, false)?;
|
||||||
|
let mut result: [std::time::Duration; 3] = [std::time::Duration::from_secs(0); 3];
|
||||||
|
|
||||||
|
println!("{:?}", reply);
|
||||||
|
|
||||||
|
// 8003 changed to %?03 - Panasonic returns 0803 instead. This byte's meaning is unknown
|
||||||
|
let res = scan_query(
|
||||||
|
reply,
|
||||||
|
"1806 02101000 3080 0300 1000 001d0000 001b %?03 0017 8000 0005 %W %B %B %B 0005 %W %B %B %B 0005 %W %B %B %B".to_string()
|
||||||
|
)?; //25^
|
||||||
|
let res_num: Vec<u64> = res.into_iter().map(|v| v.to_i64().unwrap() as u64).collect();
|
||||||
|
|
||||||
|
println!("{:?}", res_num);
|
||||||
|
// Create 3 values, `Frames Used`, `Frames Total`, and `Frames Left`
|
||||||
|
for i in 0..3 {
|
||||||
|
let tmp = &res_num[(4 * i)..=(4 * i) + 3];
|
||||||
|
println!("{:?}", tmp);
|
||||||
|
let time_micros = (tmp[0] * 3600000000) + (tmp[1] * 60000000) + (tmp[2] * 1000000) + (tmp[3] * 11600);
|
||||||
|
result[i] = std::time::Duration::from_micros(time_micros);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.change_descriptor_state(&Descriptor::RootTD, &DescriptorAction::Close);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ const FORMAT_TYPE_LEN_DICT: Lazy<HashMap<char, i32>> = Lazy::new(|| {
|
||||||
|
|
||||||
const DEBUG: bool = false;
|
const DEBUG: bool = false;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub enum QueryValue {
|
pub enum QueryValue {
|
||||||
Number(i64),
|
Number(i64),
|
||||||
Array(Vec<u8>),
|
Array(Vec<u8>),
|
||||||
|
@ -250,7 +251,7 @@ pub fn scan_query(
|
||||||
u8::from_str_radix(&String::from_iter([half.unwrap(), character]), 16).unwrap();
|
u8::from_str_radix(&String::from_iter([half.unwrap(), character]), 16).unwrap();
|
||||||
if format_value != input_value {
|
if format_value != input_value {
|
||||||
let i = initial_length - input_stack.len() - 1;
|
let i = initial_length - input_stack.len() - 1;
|
||||||
return Err(format!("Format and input mismatch at {i}: expected {format_value:#0x}, got {input_value:#0x} (format {format})").into());
|
return Err(format!("Format and input mismatch at {i}: expected {format_value:#04x}, got {input_value:#04x} (format {format})").into());
|
||||||
}
|
}
|
||||||
half = None;
|
half = None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,3 +175,8 @@ pub fn agressive_sanitize_title(title: &String) -> String {
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn time_to_frames(time: &[i64]) -> i64 {
|
||||||
|
assert_eq!(time.len(), 4);
|
||||||
|
return (((time[0] as f64 * 60.0 + time[1] as f64) * 60.0 + time[2] as f64) * 424.0 + time[3] as f64) as i64
|
||||||
|
}
|
||||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -21,6 +21,7 @@ fn main() {
|
||||||
new_device.read_product_string_ascii(&device_desc)
|
new_device.read_product_string_ascii(&device_desc)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Ensure the player is a minidisc player and not some other random device
|
||||||
let player_controller = match interface::NetMDInterface::new(new_device, device_desc) {
|
let player_controller = match interface::NetMDInterface::new(new_device, device_desc) {
|
||||||
Ok(player) => player,
|
Ok(player) => player,
|
||||||
Err(_) => continue
|
Err(_) => continue
|
||||||
|
@ -30,25 +31,30 @@ fn main() {
|
||||||
"Player Model: {}",
|
"Player Model: {}",
|
||||||
player_controller.net_md_device.device_name().clone().unwrap()
|
player_controller.net_md_device.device_name().clone().unwrap()
|
||||||
);
|
);
|
||||||
println!("Track Count: {:?}", player_controller.track_count().unwrap());
|
println!("Track Count: {:?}", player_controller.track_count().unwrap());
|
||||||
|
|
||||||
|
println!("TEST CASE: {:?}", player_controller.disc_capacity().unwrap());
|
||||||
|
|
||||||
//println!("TEST CASE: {:?}", player_controller);
|
|
||||||
sleep(std::time::Duration::from_secs(2));
|
|
||||||
println!(
|
println!(
|
||||||
"Disc Title: {: >18} | {}\n-----------------------------------------------------------------",
|
"Disc Title: {: >18} | {}\n-----------------------------------------------------------------",
|
||||||
player_controller.disc_title(false).unwrap().split_at(18).0,
|
player_controller.disc_title(false).unwrap(),
|
||||||
player_controller.disc_title(true).unwrap()
|
player_controller.disc_title(true).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = player_controller.play();
|
let mut total = 0;
|
||||||
|
|
||||||
for i in 0..player_controller.track_count().unwrap() {
|
for i in 0..player_controller.track_count().unwrap() {
|
||||||
println!(
|
println!(
|
||||||
"Track {: >2}: {: >21} | {}",
|
"{: >2} | {:0>2}:{:0>2}:{:0>2} | {:?} : {: >21} | {}",
|
||||||
i + 1,
|
i + 1,
|
||||||
|
(player_controller.track_length(i as u16).unwrap().as_secs() / 60) / 60,
|
||||||
|
(player_controller.track_length(i as u16).unwrap().as_secs() / 60) % 60,
|
||||||
|
player_controller.track_length(i as u16).unwrap().as_secs() % 60,
|
||||||
|
player_controller.track_encoding(i as u16).unwrap(),
|
||||||
player_controller.track_title(i as u16, false).unwrap(),
|
player_controller.track_title(i as u16, false).unwrap(),
|
||||||
player_controller.track_title(i as u16, true).unwrap()
|
player_controller.track_title(i as u16, true).unwrap()
|
||||||
);
|
);
|
||||||
|
total += player_controller.track_length(i as u16).unwrap().as_secs();
|
||||||
}
|
}
|
||||||
|
println!("{}", total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue