diff options
-rw-r--r-- | Cargo.lock | 174 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | import/Cargo.toml | 1 | ||||
-rw-r--r-- | import/fallback_generator/Cargo.toml | 10 | ||||
-rw-r--r-- | import/fallback_generator/src/lib.rs | 103 | ||||
-rw-r--r-- | import/src/lib.rs | 21 |
6 files changed, 305 insertions, 5 deletions
@@ -3,6 +3,22 @@ version = 4 [[package]] +name = "ab_glyph" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -155,9 +171,18 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] [[package]] name = "arbitrary" @@ -1567,9 +1592,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", @@ -1599,6 +1624,24 @@ dependencies = [ ] [[package]] +name = "imageproc" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2393fb7808960751a52e8a154f67e7dd3f8a2ef9bd80d1553078a7b4e8ed3f0d" +dependencies = [ + "ab_glyph", + "approx", + "getrandom 0.2.15", + "image", + "itertools", + "nalgebra", + "num", + "rand 0.8.5", + "rand_distr", + "rayon", +] + +[[package]] name = "imgref" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1764,6 +1807,7 @@ dependencies = [ "futures", "jellybase", "jellyclient", + "jellyimport-fallback-generator", "jellyremuxer", "log", "rayon", @@ -1777,6 +1821,16 @@ dependencies = [ ] [[package]] +name = "jellyimport-fallback-generator" +version = "0.1.0" +dependencies = [ + "ab_glyph", + "anyhow", + "image", + "imageproc", +] + +[[package]] name = "jellymatroska" version = "0.1.0" dependencies = [ @@ -2093,6 +2147,16 @@ dependencies = [ ] [[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] name = "maybe-rayon" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2192,6 +2256,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" [[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] name = "nasm-rs" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2233,6 +2312,20 @@ dependencies = [ ] [[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2243,6 +2336,15 @@ dependencies = [ ] [[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2269,6 +2371,17 @@ dependencies = [ ] [[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2339,6 +2452,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser", +] + +[[package]] name = "ownedbytes" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2780,6 +2902,12 @@ dependencies = [ ] [[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3147,6 +3275,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3277,6 +3414,19 @@ dependencies = [ ] [[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3902,6 +4052,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] name = "tungstenite" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4218,6 +4374,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] +name = "wide" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -10,6 +10,7 @@ members = [ "transcoder", "base", "import", + "import/fallback_generator", ] resolver = "2" diff --git a/import/Cargo.toml b/import/Cargo.toml index 37b5a77..ad9d529 100644 --- a/import/Cargo.toml +++ b/import/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" jellybase = { path = "../base" } jellyclient = { path = "../client" } jellyremuxer = { path = "../remuxer" } +jellyimport-fallback-generator = { path = "fallback_generator" } rayon = "1.10.0" crossbeam-channel = "0.5.14" diff --git a/import/fallback_generator/Cargo.toml b/import/fallback_generator/Cargo.toml new file mode 100644 index 0000000..13823ba --- /dev/null +++ b/import/fallback_generator/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "jellyimport-fallback-generator" +version = "0.1.0" +edition = "2024" + +[dependencies] +ab_glyph = "0.2.29" +imageproc = "0.25.0" +image = { version = "0.25.6", default-features = false, features = ["qoi"] } +anyhow = "1.0.98" diff --git a/import/fallback_generator/src/lib.rs b/import/fallback_generator/src/lib.rs new file mode 100644 index 0000000..efb6579 --- /dev/null +++ b/import/fallback_generator/src/lib.rs @@ -0,0 +1,103 @@ +#![feature(random, string_remove_matches)] +use ab_glyph::{FontRef, PxScale}; +use anyhow::Result; +use image::{DynamicImage, ImageBuffer, ImageEncoder, Rgba, codecs::qoi::QoiEncoder}; +use imageproc::drawing::{draw_text_mut, text_size}; +use std::{ + hash::{Hash, Hasher}, + io::Write, +}; + +pub fn generate_fallback(name: &str, output: &mut dyn Write) -> Result<()> { + let width = 1024; + let height = (width * 1000) / 707; + + let mut image = ImageBuffer::<Rgba<f32>, Vec<f32>>::new(width, height); + + let text = name; + let bg = random_accent(text, 0.4); + image.pixels_mut().for_each(|p| *p = bg); + + let font = FontRef::try_from_slice(include_bytes!( + "/usr/share/fonts/cantarell/Cantarell-VF.otf" + )) + .unwrap(); + let font_bold = FontRef::try_from_slice(include_bytes!( + "/usr/share/fonts/TTF/OpenSans-CondensedExtraBold.ttf" + )) + .unwrap(); + + let mut bgtext = text.to_string(); + bgtext.remove_matches(&[',', ' ']); + let bgtext = bgtext.repeat(3); + let scale = PxScale { x: 1000., y: 1000. }; + let (w, h) = text_size(scale, &font, &bgtext); + for i in -1..4 { + draw_text_mut( + &mut image, + random_accent(&text, 0.6), + width as i32 / 2 - w as i32 / 2 + i * h as i32, + i * h as i32 * 2 / 3, + scale, + &font_bold, + &bgtext, + ); + } + + image.enumerate_pixels_mut().for_each(|(_x, y, p)| { + let f = 1. - (y as f32 / height as f32) * 0.5; + p.0[0] *= f; + p.0[1] *= f; + p.0[2] *= f; + }); + + let scale = PxScale { x: 200., y: 200. }; + let (w, _h) = text_size(scale, &font, text); + draw_text_mut( + &mut image, + Rgba([1., 1., 1., 1.]), + width as i32 / 2 - w as i32 / 2, + height as i32 * 3 / 4, + scale, + &font, + text, + ); + + let image = DynamicImage::from(image).to_rgb8(); + + QoiEncoder::new(output).write_image( + image.as_raw(), + image.width(), + image.height(), + image::ExtendedColorType::Rgb8, + )?; + Ok(()) +} + +struct XorshiftHasher(u64); +impl Hasher for XorshiftHasher { + fn finish(&self) -> u64 { + self.0 + } + fn write(&mut self, bytes: &[u8]) { + for b in bytes { + self.0 = self + .0 + .wrapping_add((*b as u64).wrapping_shl(24) + (*b as u64).wrapping_shl(56)); + self.0 ^= self.0.wrapping_shl(13); + self.0 ^= self.0.wrapping_shr(7); + self.0 ^= self.0.wrapping_shl(17); + } + } +} + +fn random_accent(text: &str, y: f32) -> Rgba<f32> { + let mut hasher = XorshiftHasher(0); + text.hash(&mut hasher); + let h = hasher.finish(); + let mut u = (h >> 32) as u32 as f32 / u32::MAX as f32; + let mut v = (h) as u32 as f32 / u32::MAX as f32; + u *= 0.2; + v *= 0.2; + Rgba([y - u * 0.5 - v * 0.5, y + v, y + u, 1.]) +} diff --git a/import/src/lib.rs b/import/src/lib.rs index b93dbec..0d72da2 100644 --- a/import/src/lib.rs +++ b/import/src/lib.rs @@ -9,6 +9,7 @@ use anyhow::{anyhow, bail, Context, Result}; use infojson::YVideo; use jellybase::{ assetfed::AssetInner, + cache::cache_file, common::{Chapter, MediaInfo, Node, NodeID, NodeKind, Rating, SourceTrack, SourceTrackKind}, database::Database, CONF, SECRETS, @@ -17,6 +18,7 @@ use jellyclient::{ Appearance, LocalTrack, ObjectIds, PeopleGroup, Person, TmdbKind, TrackSource, TraktKind, Visibility, }; +use jellyimport_fallback_generator::generate_fallback; use jellyremuxer::metadata::checked_matroska_metadata; use log::info; use musicbrainz::MusicBrainz; @@ -702,6 +704,9 @@ fn apply_musicbrainz_recording( .insert("musicbrainz.artist".to_string(), a.artist.id.to_string()); } + // // TODO proper dedup + // node.people.clear(); + for rel in &rec.relations { use musicbrainz::reltypes::*; let a = match rel.type_id.as_str() { @@ -757,6 +762,20 @@ fn apply_musicbrainz_recording( jobs.push(note.to_string()); } jobs.extend(rel.attributes.clone()); + + let headshot = match image_1.or(image_2) { + Some(x) => x, + None => AssetInner::Cache(cache_file( + "person-headshot-fallback", + &artist.sort_name, + |mut file| { + generate_fallback(&artist.sort_name, &mut file)?; + Ok(()) + }, + )?) + .ser(), + }; + node.people.entry(group).or_default().push(Appearance { jobs, characters: vec![], @@ -766,7 +785,7 @@ fn apply_musicbrainz_recording( } else { rel.target_credit.clone() }, - headshot: image_1.or(image_2), + headshot: Some(headshot), ids: ObjectIds::default(), }, }); |