Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 80 additions & 0 deletions crates/claudius-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
//! | `GET /api/audio/{version}.wav` / `.mp3` | the render of that state version (`RenderView.url` points here) |
//! | `GET /api/stems/{version}/{track}.wav` | one track of that render, when it was rendered with stems |
//! | `GET /api/export/{version}/{name}.{ext}?format=SPEC` | that render in another format (`ExportFormat.spec`), as a download named `{name}.{ext}` |
//! | `POST /api/release/artwork` | body: a JPEG or PNG (`Content-Type: image/jpeg`, `image/png` or `application/octet-stream`), the release's artwork; answer: `Reply` |
//! | `GET /api/release/artwork` | the release's artwork, as it was given |
//! | `GET /api/release/preview.png` | the artwork at most 512 pixels on a side, as PNG |
//! | `POST /mcp` | the MCP endpoint (Streamable HTTP) |
//! | `GET /` | the web UI |

Expand Down Expand Up @@ -83,6 +86,70 @@ pub struct State {
/// the formats a render can be exported to (`/api/export`, `Command::Export`)
#[serde(default)]
pub exports: Vec<ExportFormat>,
/// the release the session is preparing for the open song (`Command::SetRelease`, `Command::WriteKit`)
#[serde(default)]
pub release: Option<ReleaseView>,
}

/// What stores and distributors ask about a release, as a song's release.json keeps it.
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[serde(default)]
pub struct ReleaseDetails {
/// the song's title when empty
pub title: String,
/// a version of the song, such as "Extended Mix"
pub version: String,
pub artist: String,
pub featuring: Vec<String>,
/// the album or EP the song is on; a single when empty
pub album: String,
pub genre: String,
pub subgenre: String,
/// YYYY-MM-DD
pub date: String,
/// the record label: the artist when empty
pub label: String,
/// the songwriters' full legal names
pub writers: Vec<String>,
pub producers: Vec<String>,
/// who owns the recording and the composition, for the ℗ and © lines: the artist when empty
pub owner: String,
pub isrc: String,
pub upc: String,
pub explicit: bool,
/// how the song was made, which distributors ask: `written`, `assisted` or `generated`; None until it is said
pub made: Option<String>,
}

/// one check of a release, as a store or a distributor would make it
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct ReleaseCheck {
/// `problem` (a store would turn the release down), `warning` or `note`
pub level: String,
/// `artwork`, `audio`, `sounds` or `details`
pub about: String,
pub message: String,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct ReleaseView {
pub details: ReleaseDetails,
/// the song's own title, which a title in the details replaces
pub song_title: String,
/// what the artwork is ("3000 × 3000 JPEG"), or why it cannot be read; None without artwork
pub artwork: Option<String>,
/// changes with every new artwork, for `/api/release/preview.png?v=`
pub artwork_version: u64,
/// what stores would say of the release with the render `checked` (problems first)
pub checks: Vec<ReleaseCheck>,
/// the render the checks are of; 0 until one is checked
pub checked: u64,
/// nothing a store would turn it down for
pub ready: bool,
/// the folder the last release kit went to
pub kit: Option<String>,
/// what `made` may be, with what each means
pub made_options: Vec<(String, String)>,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -300,6 +367,19 @@ pub enum Command {
#[serde(default)]
path: Option<String>,
},
/// what stores ask about the release, all of it (`ReleaseDetails`), and optionally a JPEG or PNG file for the
/// artwork; the release is checked again
SetRelease {
details: Box<ReleaseDetails>,
#[serde(default)]
artwork: Option<String>,
},
/// write the release kit from the latest render, when it is of the song as it is now and nothing stops the
/// release: into `path` (default: release/ in the song's folder)
WriteKit {
#[serde(default)]
path: Option<String>,
},
SetTempo {
bpm: f64,
},
Expand Down
4 changes: 2 additions & 2 deletions crates/claudius-cli/src/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ pub fn run(r: Request) -> Result<()> {
let mut release = publish::load(dir, &bp);
apply(&mut release, r.details, r.artwork)?;
publish::save(dir, &release)?;
let cover = match &release.artwork {
Some(p) => Some(std::fs::read(p).with_context(|| format!("cannot read the artwork {}", p.display()))?),
let cover = match publish::artwork_file(dir, &release) {
Some(p) => Some(std::fs::read(&p).with_context(|| format!("cannot read the artwork {}", p.display()))?),
None => None,
};
let master = audio::load_once(&wav).with_context(|| format!("cannot read {}", wav.display()))?;
Expand Down
1 change: 1 addition & 0 deletions crates/claudius-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ureq = { version = "3", features = ["json"] }

[dev-dependencies]
claudius-decode = { version = "0.2.0", path = "../claudius-decode" }
image = { version = "0.25", default-features = false, features = ["png"] }
tokio = { version = "1.53.3", features = ["rt-multi-thread", "macros"] }
tower = { version = "0.5", features = ["util"] }

Expand Down
20 changes: 18 additions & 2 deletions crates/claudius-server/src/app/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,14 @@ impl App {
bp.bpm,
bp.key.name()
);
self.lock().folder = None;
self.commit(Some(bp), msg)
}
Command::Load { path } => self.load(Path::new(&path)),
Command::Save { path } => self.save(path.map(PathBuf::from)),
Command::Export { formats, path } => self.export(&formats, path.map(PathBuf::from)),
Command::SetRelease { details, artwork } => self.set_release(*details, artwork.map(PathBuf::from)),
Command::WriteKit { path } => self.write_kit(path.map(PathBuf::from)),
Command::SetTempo { bpm } => self.run_script(&format!("tempo({bpm})"), Some(format!("tempo {bpm:.0}"))),
Command::SetKey { key } => self.run_script(&format!("key({})", quote(&key)), Some(format!("key {key}"))),
Command::SetSwing { swing } => {
Expand Down Expand Up @@ -385,6 +388,7 @@ impl App {
} else {
String::new()
};
self.lock().folder = None;
self.commit(Some(bp), format!("remade {path} as {genre}{moved}:\n{}", analysis_text(&a)))
}
Command::Mutate { count, keep_best } => {
Expand Down Expand Up @@ -468,11 +472,20 @@ impl App {
None => Ok(()),
});
match checked {
Ok(()) => self.commit(Some(bp), format!("loaded {}", path.display())),
Ok(()) => {
self.lock().folder = path.parent().map(Path::to_path_buf);
self.commit(Some(bp), format!("loaded {}", path.display()))
}
Err(e) => no(format!("cannot open {}: {e}", path.display()), v),
}
} else {
self.run_script(&text, Some(format!("loaded {}", path.display())))
let r = self.run_script(&text, Some(format!("loaded {}", path.display())));
if r.ok {
// a song folder's song.rhai opens that song; a script elsewhere makes a new one
let folder = path.parent().filter(|d| d.join("blueprint.json").is_file()).map(Path::to_path_buf);
self.update(|s| s.folder = folder);
}
r
}
}

Expand Down Expand Up @@ -551,6 +564,9 @@ impl App {
return no(format!("cannot write {}: {e}", dir.join(file).display()), v);
}
}
// the release in the making, too, and the song lives in this folder from now on
self.keep_release(&dir);
self.update(|s| super::release::moved(s, dir.clone()));
loop {
let dir2 = dir.clone();
if self.start_job("saving", move |app| app.render_job(true, vec![dir2])).ok {
Expand Down
1 change: 1 addition & 0 deletions crates/claudius-server/src/app/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ impl App {
}
}
});
self.check_render(&bp, version, &project, &r.master);
self.prune(version);
}

Expand Down
10 changes: 9 additions & 1 deletion crates/claudius-server/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ use crate::views;

mod commands;
mod jobs;
mod release;

pub use commands::analysis_text;
pub use release::{MAX_ARTWORK, report_lines};

pub struct Settings {
/// read the Apple Loops / user library for recorded sounds
Expand Down Expand Up @@ -64,6 +66,10 @@ struct Session {
rerender: Option<bool>,
/// saves asked for while a job ran: one render into all these folders when the job is done
pending_saves: Vec<PathBuf>,
/// the folder the song was opened from or last saved to
folder: Option<PathBuf>,
/// the release in the making for the song
release: Option<release::Prepared>,
}

impl Session {
Expand Down Expand Up @@ -266,7 +272,8 @@ impl App {
}

pub fn state(&self) -> State {
let s = self.lock();
let mut s = self.lock();
let release = self.release_view(&mut s);
State {
version: s.version,
song: s.view.clone(),
Expand All @@ -283,6 +290,7 @@ impl App {
log: s.log.iter().cloned().collect(),
genres: self.genres.clone(),
exports: self.exports.clone(),
release,
}
}

Expand Down
Loading
Loading