162 lines
4.7 KiB
Rust
162 lines
4.7 KiB
Rust
// {{{ Imports
|
|
use std::fs;
|
|
use std::io::{stdout, Write};
|
|
|
|
use anyhow::{anyhow, bail, Context};
|
|
use image::imageops::FilterType;
|
|
|
|
use shimmeringmoon::arcaea::chart::{Difficulty, SongCache};
|
|
use shimmeringmoon::arcaea::jacket::{ImageVec, BITMAP_IMAGE_SIZE};
|
|
use shimmeringmoon::assets::{get_asset_dir, get_data_dir};
|
|
use shimmeringmoon::context::{connect_db, Error};
|
|
use shimmeringmoon::recognition::fuzzy_song_name::guess_chart_name;
|
|
// }}}
|
|
|
|
/// Hacky function which clears the current line of the standard output.
|
|
#[inline]
|
|
fn clear_line() {
|
|
print!("\r \r");
|
|
}
|
|
|
|
pub fn run() -> Result<(), Error> {
|
|
let db = connect_db(&get_data_dir());
|
|
let song_cache = SongCache::new(&db)?;
|
|
|
|
let songs_dir = get_asset_dir().join("songs");
|
|
let raw_songs_dir = songs_dir.join("raw");
|
|
|
|
let by_id_dir = songs_dir.join("by_id");
|
|
if by_id_dir.exists() {
|
|
fs::remove_dir_all(&by_id_dir).with_context(|| "Could not remove `by_id` dir")?;
|
|
}
|
|
fs::create_dir_all(&by_id_dir).with_context(|| "Could not create `by_id` dir")?;
|
|
|
|
let mut jacket_vectors = vec![];
|
|
|
|
let entries = fs::read_dir(&raw_songs_dir)
|
|
.with_context(|| "Couldn't read songs directory")?
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.with_context(|| format!("Could not read member of `songs/raw`"))?;
|
|
|
|
for (i, dir) in entries.iter().enumerate() {
|
|
let raw_dir_name = dir.file_name();
|
|
let dir_name = raw_dir_name.to_str().unwrap();
|
|
|
|
// {{{ Update progress live
|
|
if i != 0 {
|
|
clear_line();
|
|
}
|
|
|
|
print!("{}/{}: {dir_name}", i, entries.len());
|
|
|
|
if i % 5 == 0 {
|
|
stdout().flush()?;
|
|
}
|
|
// }}}
|
|
|
|
let entries = fs::read_dir(dir.path())
|
|
.with_context(|| "Couldn't read song directory")?
|
|
.map(|f| f.unwrap())
|
|
.filter(|f| f.file_name().to_str().unwrap().ends_with("_256.jpg"))
|
|
.collect::<Vec<_>>();
|
|
|
|
for file in &entries {
|
|
let raw_name = file.file_name();
|
|
let name = raw_name
|
|
.to_str()
|
|
.unwrap()
|
|
.strip_suffix("_256.jpg")
|
|
.ok_or_else(|| {
|
|
anyhow!("No '_256.jpg' suffix to remove from filename {raw_name:?}")
|
|
})?;
|
|
|
|
let difficulty = match name {
|
|
"0" => Some(Difficulty::PST),
|
|
"1" => Some(Difficulty::PRS),
|
|
"2" => Some(Difficulty::FTR),
|
|
"3" => Some(Difficulty::BYD),
|
|
"4" => Some(Difficulty::ETR),
|
|
"base" => None,
|
|
"base_night" => None,
|
|
"base_ja" => None,
|
|
_ => bail!("Unknown jacket suffix {}", name),
|
|
};
|
|
|
|
// Sometimes it's useful to distinguish between separate (but related)
|
|
// charts like "Vicious Heroism" and "Vicious [ANTi] Heroism" being in
|
|
// the same directory. To do this, we only allow the base jacket to refer
|
|
// to the FUTURE difficulty, unless it's the only jacket present
|
|
// (or unless we are parsing the tutorial)
|
|
let search_difficulty =
|
|
if entries.len() > 1 && difficulty.is_none() && dir_name != "tutorial" {
|
|
Some(Difficulty::FTR)
|
|
} else {
|
|
difficulty
|
|
};
|
|
|
|
let (song, _) = guess_chart_name(dir_name, &song_cache, search_difficulty, true)
|
|
.with_context(|| format!("Could not recognise chart name from '{dir_name}'"))?;
|
|
|
|
// {{{ Set up `out_dir` paths
|
|
let out_dir = {
|
|
let out = by_id_dir.join(song.id.to_string());
|
|
if !out.exists() {
|
|
fs::create_dir_all(&out).with_context(|| {
|
|
format!(
|
|
"Could not create parent dir for song '{}' inside `by_id`",
|
|
song.title
|
|
)
|
|
})?;
|
|
}
|
|
|
|
out
|
|
};
|
|
// }}}
|
|
|
|
let difficulty_string = if let Some(difficulty) = difficulty {
|
|
&Difficulty::DIFFICULTY_SHORTHANDS[difficulty.to_index()].to_lowercase()
|
|
} else {
|
|
"def"
|
|
};
|
|
|
|
let contents: &'static _ = fs::read(file.path())
|
|
.with_context(|| format!("Could not read image for file {:?}", file.path()))?
|
|
.leak();
|
|
let image = image::load_from_memory(contents)?;
|
|
|
|
jacket_vectors.push((song.id, ImageVec::from_image(&image)));
|
|
|
|
let image = image.resize(BITMAP_IMAGE_SIZE, BITMAP_IMAGE_SIZE, FilterType::Gaussian);
|
|
let image_out_path =
|
|
out_dir.join(format!("{difficulty_string}_{BITMAP_IMAGE_SIZE}.jpg"));
|
|
image
|
|
// .blur(27.5)
|
|
.save(&image_out_path)
|
|
.with_context(|| format!("Could not save image to {image_out_path:?}"))?;
|
|
}
|
|
}
|
|
|
|
clear_line();
|
|
|
|
// NOTE: this is N^2, but it's a one-off warning thing, so it's fine
|
|
for chart in song_cache.charts() {
|
|
if jacket_vectors.iter().all(|(i, _)| chart.song_id != *i) {
|
|
println!(
|
|
"No jacket found for '{} [{:?}]'",
|
|
song_cache.lookup_song(chart.song_id)?.song.title,
|
|
chart.difficulty
|
|
)
|
|
}
|
|
}
|
|
|
|
{
|
|
println!("Encoded {} images", jacket_vectors.len());
|
|
let bytes = postcard::to_allocvec(&jacket_vectors)
|
|
.with_context(|| format!("Coult not encode jacket matrix"))?;
|
|
fs::write(songs_dir.join("recognition_matrix"), bytes)
|
|
.with_context(|| format!("Could not write jacket matrix"))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|