diff --git a/src/lib.rs b/src/lib.rs
index 19bbac4..4ef2719 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -171,6 +171,13 @@
//!
//! You should only put the filename (aka basename) and not the full path in the field; `
` will *not* work. Media files should have unique filenames.
//!
+//! ### Media files from memory
+//! If you want to add `media_files` from memory use [`Package::new_from_memory`] instead of [`Package::new`]:
+//! ```rust
+//! // ...
+//! let mut my_package = Package::new_from_memory(vec![deck], vec![MediaFile::new_from_bytes(&mp3_bytes, "sound.mp3"), MediaFile::new_from_bytes(&jpg_bytes, "image.jpg")])?;
+//! ```
+//!
//! ### sort_field
//! Anki has a value for each `Note` called the `sort_field`. Anki uses this
//! value to sort the cards in the Browse interface. Anki also is happier if
@@ -203,6 +210,7 @@ pub use error::Error;
pub use model::{Model, ModelType};
pub use note::Note;
pub use package::Package;
+pub use package::MediaFile;
#[cfg(test)]
mod tests {
@@ -582,7 +590,7 @@ def check_media(col):
#[test]
#[serial]
- fn media_files() {
+ fn media_files_fs() {
let tmp_dir = TempDir::new().unwrap();
std::env::set_current_dir(tmp_dir.path()).unwrap();
@@ -620,6 +628,32 @@ def check_media(col):
assert!(missing.contains(&"missing.mp3".to_string()));
});
}
+ #[test]
+ #[serial]
+ fn media_files_mem() {
+ let mut deck = Deck::new(123456, "foodeck", "");
+ let note = Note::new(
+ model(),
+ vec![
+ "question [sound:present.mp3] [sound:missing.mp3]",
+ r#"answer
"#,
+ ],
+ )
+ .unwrap();
+ deck.add_note(note);
+ Python::with_gil(|py| {
+ let mut setup = TestSetup::new(&py);
+ setup.import_package(
+ Package::new_from_memory(vec![deck], vec![MediaFile::new_from_bytes(VALID_MP3, "present.mp3"), MediaFile::new_from_bytes(VALID_JPG, "present.jpg")]).unwrap(),
+ None,
+ );
+
+ let (missing, _, _) = setup.check_media();
+ assert_eq!(missing.len(), 2);
+ assert!(missing.contains(&"missing.jpg".to_string()));
+ assert!(missing.contains(&"missing.mp3".to_string()));
+ });
+ }
#[test]
#[serial]
diff --git a/src/package.rs b/src/package.rs
index 96a18cd..3c8ece7 100644
--- a/src/package.rs
+++ b/src/package.rs
@@ -17,7 +17,7 @@ use std::str::FromStr;
/// `Package` to pack `Deck`s and `media_files` and write them to a `.apkg` file
///
-/// Example:
+/// # Example (media files on the filesystem):
/// ```rust
/// use genanki_rs::{Package, Deck, Note, Model, Field, Template};
///
@@ -41,11 +41,70 @@ use std::str::FromStr;
/// let mut package = Package::new(vec![my_deck], vec!["sound.mp3", "images/image.jpg"])?;
/// package.write_to_file("output.apkg")?;
/// ```
+/// # Example (media files from memory):
+/// ```rust
+/// use genanki_rs::{Package, Deck, Note, Model, Field, Template, MediaFile};
+
+/// const VALID_MP3: &[u8] =
+/// b"\xff\xe3\x18\xc4\x00\x00\x00\x03H\x00\x00\x00\x00LAME3.98.2\x00\x00\x00\
+/// \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+/// \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
+/// \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
+///
+/// const VALID_JPG: &[u8] =
+/// b"\xff\xd8\xff\xdb\x00C\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03\x03\
+/// \x03\x03\x04\x06\x04\x04\x04\x04\x04\x08\x06\x06\x05\x06\t\x08\n\n\t\x08\t\
+/// \t\n\x0c\x0f\x0c\n\x0b\x0e\x0b\t\t\r\x11\r\x0e\x0f\x10\x10\x11\x10\n\x0c\
+/// \x12\x13\x12\x10\x13\x0f\x10\x10\x10\xff\xc9\x00\x0b\x08\x00\x01\x00\x01\
+/// \x01\x01\x11\x00\xff\xcc\x00\x06\x00\x10\x10\x05\xff\xda\x00\x08\x01\x01\
+/// \x00\x00?\x00\xd2\xcf \xff\xd9";
+///
+/// let model = Model::new(
+/// 1607392319,
+/// "Simple Model",
+/// vec![
+/// Field::new("Question"),
+/// Field::new("Answer"),
+/// Field::new("MyMedia"),
+/// ],
+/// vec![Template::new("Card 1")
+/// .qfmt("{{Question}}{{Question}}
{{MyMedia}}")
+/// .afmt(r#"{{FrontSide}}
"#])?);
+///
+/// let mut package = Package::new_from_memory(vec![deck], vec![MediaFile::new_from_bytes(VALID_MP3, "sound.mp3"), MediaFile::new_from_bytes(VALID_JPG, "image.jpg")])?;
+/// package.write_to_file("output.apkg")?;
+/// ```
pub struct Package {
decks: Vec