msg_tool\scripts\bgi\audio/
audio.rs

1//! Buriko General Interpreter/Ethornell Audio File (Ogg/Vorbis)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use anyhow::Result;
6use std::io::{Read, Seek, SeekFrom, Write};
7
8#[derive(Debug)]
9/// Builder for BGI Audio scripts.
10pub struct BgiAudioBuilder {}
11
12impl BgiAudioBuilder {
13    /// Creates a new instance of `BgiAudioBuilder`.
14    pub fn new() -> Self {
15        Self {}
16    }
17}
18
19impl ScriptBuilder for BgiAudioBuilder {
20    fn default_encoding(&self) -> Encoding {
21        Encoding::Utf8
22    }
23
24    fn build_script(
25        &self,
26        buf: Vec<u8>,
27        _filename: &str,
28        _encoding: Encoding,
29        _archive_encoding: Encoding,
30        config: &ExtraConfig,
31        _archive: Option<&Box<dyn Script>>,
32    ) -> Result<Box<dyn Script + Send + Sync>> {
33        Ok(Box::new(BgiAudio::new(MemReader::new(buf), config)?))
34    }
35
36    fn build_script_from_file(
37        &self,
38        filename: &str,
39        _encoding: Encoding,
40        _archive_encoding: Encoding,
41        config: &ExtraConfig,
42        _archive: Option<&Box<dyn Script>>,
43    ) -> Result<Box<dyn Script + Send + Sync>> {
44        let file = std::fs::File::open(filename)?;
45        let f = std::io::BufReader::new(file);
46        Ok(Box::new(BgiAudio::new(f, config)?))
47    }
48
49    fn build_script_from_reader<'a>(
50        &self,
51        reader: Box<dyn ReadSeek + Send + Sync + 'a>,
52        _filename: &str,
53        _encoding: Encoding,
54        _archive_encoding: Encoding,
55        config: &ExtraConfig,
56        _archive: Option<&Box<dyn Script>>,
57    ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
58        Ok(Box::new(BgiAudio::new(reader, config)?))
59    }
60
61    fn extensions(&self) -> &'static [&'static str] {
62        &[]
63    }
64
65    fn script_type(&self) -> &'static ScriptType {
66        &ScriptType::BGIAudio
67    }
68
69    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
70        if buf_len >= 8 && buf[4..].starts_with(b"bw  ") {
71            Some(10)
72        } else {
73            None
74        }
75    }
76
77    fn is_audio(&self) -> bool {
78        true
79    }
80}
81
82#[derive(Debug)]
83/// BGI Audio script.
84pub struct BgiAudio {
85    data: MemReader,
86}
87
88impl BgiAudio {
89    /// Creates a new instance of `BgiAudio` from a reader.
90    ///
91    /// * `reader` - The reader to read the audio data from.
92    /// * `config` - Extra configuration options.
93    pub fn new<R: Read + Seek>(mut reader: R, _config: &ExtraConfig) -> Result<Self> {
94        let offset = reader.read_u32()?;
95        let len = reader.stream_length()?;
96        if (offset as u64) > len {
97            return Err(anyhow::anyhow!("Invalid offset in BGI audio file"));
98        }
99        let mut magic = [0; 4];
100        reader.read_exact(&mut magic)?;
101        if magic != *b"bw  " {
102            return Err(anyhow::anyhow!(
103                "Invalid magic in BGI audio file: {:?}",
104                magic
105            ));
106        }
107        reader.seek(SeekFrom::Start(offset as u64))?;
108        let mut data = Vec::new();
109        reader.read_to_end(&mut data)?;
110        Ok(Self {
111            data: MemReader::new(data),
112        })
113    }
114}
115
116impl Script for BgiAudio {
117    fn default_output_script_type(&self) -> OutputScriptType {
118        OutputScriptType::Custom
119    }
120
121    fn default_format_type(&self) -> FormatOptions {
122        FormatOptions::None
123    }
124
125    fn is_output_supported(&self, output: OutputScriptType) -> bool {
126        matches!(output, OutputScriptType::Custom)
127    }
128
129    fn custom_output_extension<'a>(&'a self) -> &'a str {
130        "ogg"
131    }
132
133    fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> {
134        let mut writer = std::fs::File::create(filename)?;
135        writer.write_all(&self.data.data)?;
136        writer.flush()?;
137        Ok(())
138    }
139}