Add musicdb support to ddr2osu

This commit is contained in:
Simon Bruder 2020-06-30 09:37:27 +02:00
parent dc02995496
commit b49425751e
No known key found for this signature in database
GPG key ID: 6F03E0000CC5B62F
5 changed files with 100 additions and 19 deletions

View file

@ -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`

View file

@ -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![],
}, },

View file

@ -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
}
} }

View file

@ -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 {

View file

@ -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(|| {