Add musicdb support to ddr2osu
This commit is contained in:
parent
dc02995496
commit
b49425751e
|
@ -19,7 +19,11 @@ This converts DDR step charts (.ssq files) and the corresponding audio (from
|
||||||
|
|
||||||
Basic usage:
|
Basic usage:
|
||||||
|
|
||||||
brd ddr2osu -s file.ssq -x file.xwb -o file.osz --title "Song Title" --artist "Song Artist"
|
```shell
|
||||||
|
brd ddr2osu -s file.ssq -x file.xwb -o file.osz --title "Song Title" --artist "Song Artist"
|
||||||
|
# use musicdb from DDR A
|
||||||
|
brd ddr2osu -s file.ssq -x file.xwb -o file.osz -m startup.arc
|
||||||
|
```
|
||||||
|
|
||||||
To learn more about supported options run `brd ddr2osu --help`
|
To learn more about supported options run `brd ddr2osu --help`
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ use log::{debug, info, trace, warn};
|
||||||
use crate::ddr::ssq;
|
use crate::ddr::ssq;
|
||||||
use crate::osu::beatmap;
|
use crate::osu::beatmap;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ConfigRange(f32, f32);
|
pub struct ConfigRange(f32, f32);
|
||||||
|
|
||||||
impl ConfigRange {
|
impl ConfigRange {
|
||||||
|
@ -35,7 +35,7 @@ impl FromStr for ConfigRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clap)]
|
#[derive(Debug, Clap, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[clap(skip = "audio.wav")]
|
#[clap(skip = "audio.wav")]
|
||||||
pub audio_filename: String,
|
pub audio_filename: String,
|
||||||
|
@ -43,7 +43,7 @@ pub struct Config {
|
||||||
long = "no-stops",
|
long = "no-stops",
|
||||||
about = "Disable stops",
|
about = "Disable stops",
|
||||||
parse(from_flag = std::ops::Not::not),
|
parse(from_flag = std::ops::Not::not),
|
||||||
display_order = 5
|
display_order = 3
|
||||||
)]
|
)]
|
||||||
pub stops: bool,
|
pub stops: bool,
|
||||||
#[clap(
|
#[clap(
|
||||||
|
@ -51,7 +51,7 @@ pub struct Config {
|
||||||
long,
|
long,
|
||||||
default_value = "step",
|
default_value = "step",
|
||||||
about = "What to do with shocks",
|
about = "What to do with shocks",
|
||||||
display_order = 5
|
display_order = 3
|
||||||
)]
|
)]
|
||||||
pub shock_action: ShockAction,
|
pub shock_action: ShockAction,
|
||||||
#[clap(
|
#[clap(
|
||||||
|
@ -70,19 +70,21 @@ pub struct Config {
|
||||||
pub metadata: ConfigMetadata,
|
pub metadata: ConfigMetadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clap, Debug)]
|
#[derive(Clap, Debug, Clone)]
|
||||||
pub struct ConfigMetadata {
|
pub struct ConfigMetadata {
|
||||||
#[clap(long, about = "Song title to use in beatmap", display_order = 6)]
|
#[clap(long, about = "Song title to use in beatmap", display_order = 4)]
|
||||||
pub title: String,
|
pub title: Option<String>,
|
||||||
#[clap(long, about = "Artist name to use in beatmap", display_order = 6)]
|
#[clap(long, about = "Artist name to use in beatmap", display_order = 4)]
|
||||||
pub artist: String,
|
pub artist: Option<String>,
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
default_value = "Dance Dance Revolution",
|
default_value = "Dance Dance Revolution",
|
||||||
about = "Source to use in beatmap",
|
about = "Source to use in beatmap",
|
||||||
display_order = 6
|
display_order = 4
|
||||||
)]
|
)]
|
||||||
pub source: String,
|
pub source: String,
|
||||||
|
#[clap(skip)]
|
||||||
|
pub levels: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Config {
|
impl fmt::Display for Config {
|
||||||
|
@ -336,10 +338,26 @@ impl ConvertedChart {
|
||||||
},
|
},
|
||||||
editor: beatmap::Editor {},
|
editor: beatmap::Editor {},
|
||||||
metadata: beatmap::Metadata {
|
metadata: beatmap::Metadata {
|
||||||
title: config.metadata.title.clone(),
|
title: config
|
||||||
artist: config.metadata.artist.clone(),
|
.metadata
|
||||||
|
.title
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&"unknown title".to_string())
|
||||||
|
.clone(),
|
||||||
|
artist: config
|
||||||
|
.metadata
|
||||||
|
.artist
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&"unknown artist".to_string())
|
||||||
|
.clone(),
|
||||||
creator: format!("{}", config),
|
creator: format!("{}", config),
|
||||||
version: format!("{}", self.difficulty),
|
version: match &config.metadata.levels {
|
||||||
|
Some(levels) => {
|
||||||
|
let level = self.difficulty.to_level(levels);
|
||||||
|
format!("{} (Lv. {})", self.difficulty, level)
|
||||||
|
}
|
||||||
|
None => format!("{}", self.difficulty),
|
||||||
|
},
|
||||||
source: config.metadata.source.clone(),
|
source: config.metadata.source.clone(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
},
|
},
|
||||||
|
|
|
@ -92,4 +92,14 @@ impl MusicDB {
|
||||||
|
|
||||||
Self::parse(&String::from_utf8(musicdb_data.to_vec())?).map_err(|err| err.into())
|
Self::parse(&String::from_utf8(musicdb_data.to_vec())?).map_err(|err| err.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_entry_from_basename(&self, basename: &str) -> Option<&Entry> {
|
||||||
|
for entry in &self.music {
|
||||||
|
if entry.basename == basename {
|
||||||
|
return Some(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -351,6 +351,26 @@ impl Into<f32> for Difficulty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets level for difficulty from [`ddr::musicdb::Entry.diff_lv`].
|
||||||
|
///
|
||||||
|
/// [`ddr::musicdb::Entry.diff_lv`]: ../musicdb/struct.Entry.html#structfield.diff_lv
|
||||||
|
impl Difficulty {
|
||||||
|
pub fn to_level(&self, levels: &[u8]) -> u8 {
|
||||||
|
let base = match self.difficulty {
|
||||||
|
1 => 1,
|
||||||
|
2 => 2,
|
||||||
|
3 => 4,
|
||||||
|
4 => 0,
|
||||||
|
6 => 3,
|
||||||
|
_ => 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
let index: usize = (base + (self.players - 1) * 5).into();
|
||||||
|
|
||||||
|
levels[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Difficulty {
|
impl fmt::Display for Difficulty {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let players = match self.players {
|
let players = match self.players {
|
||||||
|
|
39
src/main.rs
39
src/main.rs
|
@ -45,7 +45,7 @@ enum SubCommand {
|
||||||
about = "Converts DDR step charts to osu!mania beatmaps",
|
about = "Converts DDR step charts to osu!mania beatmaps",
|
||||||
display_order = 1
|
display_order = 1
|
||||||
)]
|
)]
|
||||||
DDR2osu(DDR2osu),
|
DDR2osu(Box<DDR2osu>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clap)]
|
#[derive(Clap)]
|
||||||
|
@ -89,7 +89,7 @@ struct DDR2osu {
|
||||||
long = "xwb",
|
long = "xwb",
|
||||||
name = "file.xwb",
|
name = "file.xwb",
|
||||||
about = "XAC3 wave bank file",
|
about = "XAC3 wave bank file",
|
||||||
display_order = 2
|
display_order = 1
|
||||||
)]
|
)]
|
||||||
xwb_file: PathBuf,
|
xwb_file: PathBuf,
|
||||||
#[clap(
|
#[clap(
|
||||||
|
@ -97,14 +97,22 @@ struct DDR2osu {
|
||||||
long = "out",
|
long = "out",
|
||||||
name = "file.osz",
|
name = "file.osz",
|
||||||
about = "osu! beatmap archive",
|
about = "osu! beatmap archive",
|
||||||
display_order = 3
|
display_order = 1
|
||||||
)]
|
)]
|
||||||
out_file: PathBuf,
|
out_file: PathBuf,
|
||||||
|
#[clap(
|
||||||
|
short = "m",
|
||||||
|
long = "musicdb",
|
||||||
|
name = "musicdb.xml|startup.arc",
|
||||||
|
about = "musicdb.xml or startup.arc for metadata",
|
||||||
|
display_order = 1
|
||||||
|
)]
|
||||||
|
musicdb_file: Option<PathBuf>,
|
||||||
#[clap(
|
#[clap(
|
||||||
short = "n",
|
short = "n",
|
||||||
name = "sound name",
|
name = "sound name",
|
||||||
about = "Sound in wave bank, otherwise inferred from SSQ filename",
|
about = "Sound in wave bank, otherwise inferred from SSQ filename",
|
||||||
display_order = 4
|
display_order = 2
|
||||||
)]
|
)]
|
||||||
sound_name: Option<String>,
|
sound_name: Option<String>,
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
|
@ -271,8 +279,29 @@ fn main() -> Result<()> {
|
||||||
.with_context(|| format!("failed to read SSQ file {}", &opts.ssq_file.display()))?;
|
.with_context(|| format!("failed to read SSQ file {}", &opts.ssq_file.display()))?;
|
||||||
let ssq = SSQ::parse(&ssq_data).context("failed to parse SSQ file")?;
|
let ssq = SSQ::parse(&ssq_data).context("failed to parse SSQ file")?;
|
||||||
|
|
||||||
|
let mut convert_options = opts.convert.clone();
|
||||||
|
|
||||||
|
if let Some(musicdb_file) = &opts.musicdb_file {
|
||||||
|
debug!("Reading metadata from {}", musicdb_file.display());
|
||||||
|
let musicdb = read_musicdb(&musicdb_file)?;
|
||||||
|
let musicdb_entry = musicdb
|
||||||
|
.get_entry_from_basename(sound_name)
|
||||||
|
.ok_or_else(|| anyhow!("Entry not found in musicdb"))?;
|
||||||
|
if convert_options.metadata.title.is_none() {
|
||||||
|
info!("Using title from musicdb: “{}”", musicdb_entry.title);
|
||||||
|
convert_options.metadata.title = Some(musicdb_entry.title.clone());
|
||||||
|
}
|
||||||
|
if convert_options.metadata.artist.is_none() {
|
||||||
|
info!("Using artist from musicdb: “{}”", musicdb_entry.artist);
|
||||||
|
convert_options.metadata.artist = Some(musicdb_entry.artist.clone());
|
||||||
|
}
|
||||||
|
convert_options.metadata.levels = Some(musicdb_entry.diff_lv.clone());
|
||||||
|
} else if convert_options.metadata.title.is_none() {
|
||||||
|
convert_options.metadata.title = Some(sound_name.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
let beatmaps = ssq
|
let beatmaps = ssq
|
||||||
.to_beatmaps(&opts.convert)
|
.to_beatmaps(&convert_options)
|
||||||
.context("failed to convert DDR step chart to osu!mania beatmap")?;
|
.context("failed to convert DDR step chart to osu!mania beatmap")?;
|
||||||
|
|
||||||
let xwb_data = fs::read(&opts.xwb_file).with_context(|| {
|
let xwb_data = fs::read(&opts.xwb_file).with_context(|| {
|
||||||
|
|
Reference in a new issue