aviutl2 0.44.0

AviUtl2 SDKのRustラッパー
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
use crate::load_wide_string;

/// プロジェクトファイルにデータを保存・取得するための構造体。
pub struct ProjectFile<'a> {
    pub(crate) internal: *mut aviutl2_sys::plugin2::PROJECT_FILE,
    _marker: std::marker::PhantomData<&'a ()>,
}

/// プロジェクトファイルのデータ取得・保存に関するエラー。
#[derive(thiserror::Error, Debug)]
pub enum ProjectFileError {
    #[error("key contains null byte: {0}")]
    KeyContainsNull(std::ffi::NulError),
    #[error("data retrieval failed for key {0}")]
    RetrievalFailed(String),
    #[error("data length exceeds 4096 bytes, got {0} bytes")]
    DataTooLarge(usize),
    #[error("value contains null byte: {0}")]
    ValueContainsNull(std::ffi::NulError),
}

impl<'a> ProjectFile<'a> {
    /// 生ポインタから`ProjectFile`を作成します。
    ///
    /// # Safety
    ///
    /// - `raw`は有効な`PROJECT_FILE`ポインタである必要があります。
    pub unsafe fn from_raw(raw: *mut aviutl2_sys::plugin2::PROJECT_FILE) -> Self {
        Self {
            internal: raw,
            _marker: std::marker::PhantomData,
        }
    }

    /// プロジェクトに保存されている文字列を取得します。
    ///
    /// # Errors
    ///
    /// - `key`にヌル文字が含まれている場合、失敗します。
    /// - 文字列が見つからなかった場合は失敗します。
    pub fn get_param_string(&self, key: &str) -> Result<String, ProjectFileError> {
        let c_key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
        unsafe {
            let raw_str = ((*self.internal).get_param_string)(c_key.as_ptr() as _);
            if raw_str.is_null() {
                return Err(ProjectFileError::RetrievalFailed(key.to_string()));
            }
            Ok(std::ffi::CStr::from_ptr(raw_str)
                .to_string_lossy()
                .into_owned())
        }
    }

    /// プロジェクトに保存されているバイナリデータを取得します。
    ///
    /// # Errors
    ///
    /// - `key`にヌル文字が含まれている場合、失敗します。
    /// - `data` の長さが保存されているデータの長さと一致しない場合、失敗します。
    /// - 指定されたキーに対応するデータが存在しない場合、失敗します。
    pub fn get_param_binary(&self, key: &str, data: &mut [u8]) -> Result<(), ProjectFileError> {
        let success = unsafe {
            let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
            ((*self.internal).get_param_binary)(
                key.as_ptr() as _,
                data.as_mut_ptr() as _,
                data.len() as _,
            )
        };
        if !success {
            return Err(ProjectFileError::RetrievalFailed(key.to_string()));
        }
        Ok(())
    }

    /// プロジェクトに文字列を保存します。
    ///
    /// # Errors
    ///
    /// key、valueにヌル文字が含まれている場合、失敗します。
    pub fn set_param_string(&mut self, key: &str, value: &str) -> Result<(), ProjectFileError> {
        let key_cstr = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
        let value_cstr =
            std::ffi::CString::new(value).map_err(ProjectFileError::ValueContainsNull)?;
        unsafe {
            ((*self.internal).set_param_string)(key_cstr.as_ptr() as _, value_cstr.as_ptr() as _);
        }
        Ok(())
    }

    /// プロジェクトにバイナリデータを保存します。
    ///
    /// # Errors
    ///
    /// - `data` の長さが4096バイトを超える場合、失敗します。
    /// - `key`にヌル文字が含まれている場合、失敗します。
    pub fn set_param_binary(&mut self, key: &str, data: &[u8]) -> Result<(), ProjectFileError> {
        if data.len() > 4096 {
            return Err(ProjectFileError::DataTooLarge(data.len()));
        }
        unsafe {
            let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
            ((*self.internal).set_param_binary)(
                key.as_ptr() as _,
                data.as_ptr() as _,
                data.len() as _,
            );
        }
        Ok(())
    }

    /// プロジェクトに保存されているデータをすべて削除します。
    pub fn clear_params(&mut self) {
        unsafe { ((*self.internal).clear_params)() }
    }

    /// プロジェクトファイルのパスを取得します。
    pub fn get_path(&self) -> Option<std::path::PathBuf> {
        unsafe {
            let raw_str = ((*self.internal).get_project_file_path)();
            if raw_str.is_null() {
                return None;
            }
            let path_str = load_wide_string(raw_str);
            if path_str.is_empty() {
                return None;
            }
            Some(std::path::PathBuf::from(path_str))
        }
    }
}

#[cfg(feature = "serde")]
const _: () = {
    use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
    use std::io::Read;

    static NAMESPACE: &str = "--aviutl2-rs";
    const BINARY_CHUNK_SIZE: usize = 4096;
    // 現状制限はないが、一応4096バイトで分割しておく
    const BASE64_CHUNK_RAW_SIZE: usize = 3072;

    /// プロジェクトのシリアライズ・デシリアライズ関連のエラー。
    #[derive(thiserror::Error, Debug)]
    pub enum ProjectFileSerdeError {
        #[error("serialization error: {0}")]
        Serialization(#[from] rmp_serde::encode::Error),
        #[error("deserialization error: {0}")]
        Deserialization(#[from] rmp_serde::decode::Error),
        #[error("base64 decode error: {0}")]
        Base64Decode(#[from] base64::DecodeError),
        #[error("zstd dompression error: {0}")]
        Decompression(#[from] std::io::Error),
        #[error("zlib error: {0:?}")]
        Zlib(zlib_rs::ReturnCode),
        #[error("project file error: {0}")]
        ProjectFile(#[from] ProjectFileError),
        #[error("unsupported serialization format")]
        UnsupportedFormat,
        #[error("invalid header format: {0}")]
        InvalidHeaderFormat(String),
        #[error("incomplete data retrieved for key")]
        IncompleteData,
    }

    impl<'a> ProjectFile<'a> {
        /// プロジェクトにデータをシリアライズして保存します。
        ///
        /// # Note
        ///
        /// 今現在の実装ではデータはMessagePackにシリアライズされ、必要に応じてzlibで圧縮された後にbase64エンコードされて保存されます。
        ///
        /// # Errors
        ///
        /// - シリアライズに失敗した場合。
        pub fn serialize<T: serde::Serialize>(
            &mut self,
            key: &str,
            value: &T,
        ) -> Result<(), ProjectFileSerdeError> {
            self.delete_old_chunks(key)?;
            let base_bytes = rmp_serde::to_vec_named(value)?;
            let compressed_bound = zlib_rs::compress_bound(base_bytes.len());
            let mut compressed_bytes = vec![0u8; compressed_bound];
            let (compressed_bytes, result) = zlib_rs::compress_slice(
                &mut compressed_bytes,
                &base_bytes,
                zlib_rs::DeflateConfig::default(),
            );
            if result != zlib_rs::ReturnCode::Ok {
                return Err(ProjectFileSerdeError::Zlib(result));
            }
            let num_bytes = compressed_bytes.len();
            if num_bytes > base_bytes.len() {
                // 圧縮後のサイズが元のサイズより大きい場合は、圧縮せずに保存する
                self.set_param_string(
                    key,
                    &format!("{NAMESPACE}:serde-rmp-base64-v1:{}", base_bytes.len()),
                )?;
                for (i, chunk) in base_bytes.chunks(BASE64_CHUNK_RAW_SIZE).enumerate() {
                    let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
                    self.set_param_string(&chunk_key, &BASE64.encode(chunk))?;
                }
                return Ok(());
            }
            self.set_param_string(
                key,
                &format!(
                    "{NAMESPACE}:serde-rmp-base64-zlib-v1:{},{}",
                    num_bytes,
                    base_bytes.len()
                ),
            )?;
            for (i, chunk) in compressed_bytes.chunks(BASE64_CHUNK_RAW_SIZE).enumerate() {
                let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
                self.set_param_string(&chunk_key, &BASE64.encode(chunk))?;
            }
            Ok(())
        }

        /// プロジェクトからデータをデシリアライズして取得します。
        pub fn deserialize<T: serde::de::DeserializeOwned>(
            &self,
            key: &str,
        ) -> Result<T, ProjectFileSerdeError> {
            let header = self.get_param_string(key)?;
            if let Ok(value) = self.decode_serde_rmp_base64_v1(key, &header) {
                return Ok(value);
            }
            if let Ok(value) = self.decode_serde_zstd_v1(key, &header) {
                return Ok(value);
            }
            if let Ok(value) = self.decode_serde_rmp_v1(key, &header) {
                return Ok(value);
            }
            if let Ok(value) = self.decode_serde_rmp_base64_zlib_v1(key, &header) {
                return Ok(value);
            }
            Err(ProjectFileSerdeError::UnsupportedFormat)
        }

        fn decode_serde_rmp_base64_v1<T: serde::de::DeserializeOwned>(
            &self,
            key: &str,
            header: &str,
        ) -> Result<T, ProjectFileSerdeError> {
            let header_prefix = format!("{NAMESPACE}:serde-rmp-base64-v1:");
            let num_bytes = header
                .strip_prefix(&header_prefix)
                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
            let num_bytes: usize = num_bytes
                .parse()
                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
            if num_bytes == 0 {
                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
                    header.to_string(),
                ));
            }
            let chunks = self.collect_base64_chunks(num_bytes, key)?;
            let value: T = rmp_serde::from_slice(&chunks)?;
            Ok(value)
        }

        fn decode_serde_rmp_v1<T: serde::de::DeserializeOwned>(
            &self,
            key: &str,
            header: &str,
        ) -> Result<T, ProjectFileSerdeError> {
            let header_prefix = format!("{NAMESPACE}:serde-rmp-v1:");
            let num_bytes = header
                .strip_prefix(&header_prefix)
                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
            let num_bytes: usize = num_bytes
                .parse()
                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
            if num_bytes == 0 {
                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
                    header.to_string(),
                ));
            }
            let chunks = self.collect_chunks(num_bytes, key)?;
            let value: T = rmp_serde::from_slice(&chunks)?;
            Ok(value)
        }

        fn decode_serde_rmp_base64_zlib_v1<T: serde::de::DeserializeOwned>(
            &self,
            key: &str,
            header: &str,
        ) -> Result<T, ProjectFileSerdeError> {
            let header_prefix = format!("{NAMESPACE}:serde-rmp-base64-zlib-v1:");
            let num_bytes = header
                .strip_prefix(&header_prefix)
                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
            let (compressed_size, uncompressed_size) =
                num_bytes
                    .split_once(',')
                    .ok_or(ProjectFileSerdeError::InvalidHeaderFormat(
                        header.to_string(),
                    ))?;
            let compressed_size: usize = compressed_size
                .parse()
                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
            let uncompressed_size: usize = uncompressed_size
                .parse()
                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
            if compressed_size == 0 || uncompressed_size == 0 {
                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
                    header.to_string(),
                ));
            }
            let compressed_chunks = self.collect_base64_chunks(compressed_size, key)?;
            let mut decompressed_bytes = vec![0u8; uncompressed_size];
            let (decompressed_bytes, result) = zlib_rs::decompress_slice(
                &mut decompressed_bytes,
                &compressed_chunks,
                zlib_rs::InflateConfig::default(),
            );
            if result != zlib_rs::ReturnCode::Ok {
                return Err(ProjectFileSerdeError::Zlib(result));
            }
            let value: T = rmp_serde::from_slice(decompressed_bytes)?;
            Ok(value)
        }

        fn collect_chunks(
            &self,
            num_bytes: usize,
            key: &str,
        ) -> Result<Vec<u8>, ProjectFileSerdeError> {
            let mut bytes = Vec::with_capacity(num_bytes);
            let mut read_bytes = 0;
            let mut chunk = vec![0u8; BINARY_CHUNK_SIZE];
            for i in 0.. {
                let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
                let to_read = std::cmp::min(BINARY_CHUNK_SIZE, num_bytes - read_bytes);
                chunk.resize(to_read, 0);
                match self.get_param_binary(&chunk_key, &mut chunk) {
                    Ok(()) => {
                        bytes.extend_from_slice(&chunk);
                        read_bytes += to_read;
                        if read_bytes >= num_bytes {
                            break;
                        }
                    }
                    Err(_) => break,
                }
            }
            if read_bytes != num_bytes {
                return Err(ProjectFileSerdeError::IncompleteData);
            }
            Ok(bytes)
        }

        fn collect_base64_chunks(
            &self,
            num_bytes: usize,
            key: &str,
        ) -> Result<Vec<u8>, ProjectFileSerdeError> {
            let mut bytes = Vec::with_capacity(num_bytes);
            for i in 0.. {
                let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
                let chunk = match self.get_param_string(&chunk_key) {
                    Ok(chunk) => chunk,
                    Err(_) => break,
                };
                let chunk = BASE64.decode(chunk)?;
                bytes.extend_from_slice(&chunk);
                if bytes.len() >= num_bytes {
                    break;
                }
            }
            if bytes.len() != num_bytes {
                return Err(ProjectFileSerdeError::IncompleteData);
            }
            Ok(bytes)
        }

        fn decode_serde_zstd_v1<T: serde::de::DeserializeOwned>(
            &self,
            key: &str,
            header: &str,
        ) -> Result<T, ProjectFileSerdeError> {
            let header_prefix = format!("{NAMESPACE}:serde-zstd-v1:");
            let num_bytes = header
                .strip_prefix(&header_prefix)
                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
            let num_bytes: usize = num_bytes
                .parse()
                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
            if num_bytes == 0 {
                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
                    header.to_string(),
                ));
            }
            let mut bytes = Vec::with_capacity(num_bytes);
            let mut read_bytes = 0;
            let mut chunk = vec![0u8; BINARY_CHUNK_SIZE];
            for i in 0.. {
                let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
                let to_read = std::cmp::min(BINARY_CHUNK_SIZE, num_bytes - read_bytes);
                chunk.resize(to_read, 0);
                match self.get_param_binary(&chunk_key, &mut chunk) {
                    Ok(()) => {
                        bytes.extend_from_slice(&chunk);
                        read_bytes += to_read;
                        if read_bytes >= num_bytes {
                            break;
                        }
                    }
                    Err(_) => break,
                }
            }
            if read_bytes != num_bytes {
                return Err(ProjectFileSerdeError::IncompleteData);
            }
            let mut decoder = ruzstd::decoding::StreamingDecoder::new(&bytes[..])
                .map_err(|e| ProjectFileSerdeError::Decompression(std::io::Error::other(e)))?;
            let mut decompressed_bytes = vec![];
            decoder.read_to_end(&mut decompressed_bytes)?;
            let value: T = rmp_serde::from_slice(&decompressed_bytes)?;
            Ok(value)
        }

        fn delete_old_chunks(&mut self, key: &str) -> Result<(), ProjectFileSerdeError> {
            for i in 0.. {
                let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
                match self.get_param_string(&chunk_key) {
                    Ok(_) => {
                        self.set_param_string(&chunk_key, "")?;
                    }
                    Err(_) => break,
                }
            }
            for i in 0.. {
                let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
                match self.get_param_binary(&chunk_key, &mut [0u8; 1]) {
                    Ok(_) => {
                        self.set_param_string(&chunk_key, "")?;
                    }
                    Err(_) => break,
                }
            }
            for i in 0.. {
                let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
                match self.get_param_binary(&chunk_key, &mut [0u8; 1]) {
                    Ok(_) => {
                        self.set_param_string(&chunk_key, "")?;
                    }
                    Err(_) => break,
                }
            }
            for i in 0.. {
                let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
                match self.get_param_string(&chunk_key) {
                    Ok(_) => {
                        self.set_param_string(&chunk_key, "")?;
                    }
                    Err(_) => break,
                }
            }
            Ok(())
        }
    }
};