use std::path::Path;
use crate::error::{RoxError, RoxResult};
use crate::model::RoxChart;
use super::formats::{
FnfDecoder, FnfEncoder, OsuDecoder, OsuEncoder, QuaDecoder, QuaEncoder, SmDecoder, SmEncoder,
TaikoDecoder,
};
use super::rox::RoxCodec;
use super::{Decoder, Encoder};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputFormat {
Rox,
Osu,
Taiko,
Sm,
Qua,
Fnf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Rox,
Osu,
Sm,
Qua,
Fnf,
}
impl InputFormat {
pub const EXTENSIONS: &'static [(&'static str, Self)] = &[
("rox", Self::Rox),
("osu", Self::Osu),
("sm", Self::Sm),
("qua", Self::Qua),
("json", Self::Fnf),
];
pub fn from_extension(ext: &str) -> RoxResult<Self> {
let ext_lower = ext.to_lowercase();
for (e, format) in Self::EXTENSIONS {
if *e == ext_lower {
return Ok(*format);
}
}
Err(RoxError::UnsupportedFormat(format!(
"Unknown input extension: .{ext}"
)))
}
pub fn from_path(path: impl AsRef<Path>) -> RoxResult<Self> {
let path = path.as_ref();
let ext = path
.extension()
.and_then(|e| e.to_str())
.ok_or_else(|| RoxError::InvalidFormat("No file extension".into()))?;
Self::from_extension(ext)
}
}
impl OutputFormat {
pub const EXTENSIONS: &'static [(&'static str, Self)] = &[
("rox", Self::Rox),
("osu", Self::Osu),
("sm", Self::Sm),
("qua", Self::Qua),
("json", Self::Fnf),
];
pub fn from_extension(ext: &str) -> RoxResult<Self> {
let ext_lower = ext.to_lowercase();
for (e, format) in Self::EXTENSIONS {
if *e == ext_lower {
return Ok(*format);
}
}
Err(RoxError::UnsupportedFormat(format!(
"Unknown output extension: .{ext}"
)))
}
pub fn from_path(path: impl AsRef<Path>) -> RoxResult<Self> {
let path = path.as_ref();
let ext = path
.extension()
.and_then(|e| e.to_str())
.ok_or_else(|| RoxError::InvalidFormat("No file extension".into()))?;
Self::from_extension(ext)
}
}
pub fn auto_decode(path: impl AsRef<Path>) -> RoxResult<RoxChart> {
let path = path.as_ref();
let format = InputFormat::from_path(path)?;
let data = std::fs::read(path)?;
match format {
InputFormat::Rox => RoxCodec::decode(&data),
InputFormat::Osu | InputFormat::Taiko => decode_osu_by_mode(&data),
InputFormat::Sm => SmDecoder::decode(&data),
InputFormat::Qua => QuaDecoder::decode(&data),
InputFormat::Fnf => FnfDecoder::decode(&data),
}
}
fn decode_osu_by_mode(data: &[u8]) -> RoxResult<RoxChart> {
match detect_osu_mode(data) {
1 => TaikoDecoder::decode(data),
3 => OsuDecoder::decode(data),
mode => Err(RoxError::UnsupportedFormat(format!(
"osu! mode {mode} is not supported (only taiko=1 and mania=3)"
))),
}
}
fn detect_osu_mode(data: &[u8]) -> u8 {
let Ok(content) = std::str::from_utf8(data) else {
return 3; };
for line in content.lines() {
let line = line.trim();
if let Some(value) = line.strip_prefix("Mode:")
&& let Ok(mode) = value.trim().parse::<u8>()
{
return mode;
}
if line == "[Metadata]" {
break;
}
}
3 }
pub fn decode_with_format(data: &[u8], format: InputFormat) -> RoxResult<RoxChart> {
match format {
InputFormat::Rox => RoxCodec::decode(data),
InputFormat::Osu => OsuDecoder::decode(data),
InputFormat::Taiko => TaikoDecoder::decode(data),
InputFormat::Sm => SmDecoder::decode(data),
InputFormat::Qua => QuaDecoder::decode(data),
InputFormat::Fnf => FnfDecoder::decode(data),
}
}
pub fn auto_encode(chart: &RoxChart, path: impl AsRef<Path>) -> RoxResult<()> {
let path = path.as_ref();
let format = OutputFormat::from_path(path)?;
let data = match format {
OutputFormat::Rox => RoxCodec::encode(chart)?,
OutputFormat::Osu => OsuEncoder::encode(chart)?,
OutputFormat::Sm => SmEncoder::encode(chart)?,
OutputFormat::Qua => QuaEncoder::encode(chart)?,
OutputFormat::Fnf => FnfEncoder::encode(chart)?,
};
std::fs::write(path, data)?;
Ok(())
}
pub fn encode_with_format(chart: &RoxChart, format: OutputFormat) -> RoxResult<Vec<u8>> {
match format {
OutputFormat::Rox => RoxCodec::encode(chart),
OutputFormat::Osu => OsuEncoder::encode(chart),
OutputFormat::Sm => SmEncoder::encode(chart),
OutputFormat::Qua => QuaEncoder::encode(chart),
OutputFormat::Fnf => FnfEncoder::encode(chart),
}
}
pub fn auto_convert(input: impl AsRef<Path>, output: impl AsRef<Path>) -> RoxResult<()> {
let chart = auto_decode(input)?;
auto_encode(&chart, output)
}