Skip to main content

aviutl2\generic\binding/
edit_section.rs

1use std::num::NonZero;
2
3use crate::common::Rational32;
4
5/// オブジェクトへのハンドル。
6#[derive(Clone, Copy, Hash, PartialEq, Eq)]
7pub struct ObjectHandle {
8    pub(crate) internal: aviutl2_sys::plugin2::OBJECT_HANDLE,
9}
10impl std::fmt::Debug for ObjectHandle {
11    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12        f.debug_tuple("ObjectHandle").field(&self.internal).finish()
13    }
14}
15impl From<aviutl2_sys::plugin2::OBJECT_HANDLE> for ObjectHandle {
16    fn from(value: aviutl2_sys::plugin2::OBJECT_HANDLE) -> Self {
17        Self { internal: value }
18    }
19}
20impl From<ObjectHandle> for aviutl2_sys::plugin2::OBJECT_HANDLE {
21    fn from(value: ObjectHandle) -> Self {
22        value.internal
23    }
24}
25
26// 動いたし、このObjectHandleをグローバルに持っておくことも想定されてそうなので多分大丈夫なはず
27unsafe impl Send for ObjectHandle {}
28unsafe impl Sync for ObjectHandle {}
29
30/// 編集情報構造体。
31///
32/// # Note
33///
34/// UI表示と異なり、フレーム番号・レイヤー番号は0始まりです。
35#[derive(Debug, Clone, Copy)]
36pub struct EditInfo {
37    /// シーンの幅。
38    pub width: usize,
39    /// シーンの高さ。
40    pub height: usize,
41    /// フレームレート。
42    pub fps: Rational32,
43    /// サンプルレート。
44    pub sample_rate: usize,
45    /// 現在のカーソルのフレーム番号。
46    pub frame: usize,
47    /// 現在の選択レイヤー番号。
48    pub layer: usize,
49    /// オブジェクトが存在する最大のフレーム番号。
50    pub frame_max: usize,
51    /// オブジェクトが存在する最大のレイヤー番号。
52    pub layer_max: usize,
53    /// レイヤー編集で表示されているフレームの開始番号。
54    pub display_frame_start: usize,
55    /// レイヤー編集で表示されているレイヤーの開始番号。
56    pub display_layer_start: usize,
57    /// レイヤー編集で表示されているフレーム数。
58    ///
59    /// # Note
60    ///
61    /// この値は厳密な値ではありません。
62    pub display_frame_num: usize,
63    /// レイヤー編集で表示されているレイヤー数
64    ///
65    /// # Note
66    ///
67    /// この値は厳密な値ではありません。
68    pub display_layer_num: usize,
69    /// フレーム範囲選択の開始フレーム番号
70    /// 未選択の場合は`None`になります。
71    pub select_range_start: Option<usize>,
72    /// フレーム範囲選択の終了フレーム番号。
73    /// 未選択の場合は`None`になります。
74    pub select_range_end: Option<usize>,
75    /// グリッド(BPM)のテンポ。
76    pub grid_bpm_tempo: f32,
77    /// グリッド(BPM)の拍数。
78    pub grid_bpm_beat: usize,
79    /// グリッド(BPM)の基準時間。
80    pub grid_bpm_offset: f32,
81    /// シーンのID
82    pub scene_id: i32,
83}
84
85impl EditInfo {
86    /// # Safety
87    ///
88    /// `ptr`は有効な`EDIT_INFO`ポインタである必要があります。
89    pub unsafe fn from_raw(ptr: *const aviutl2_sys::plugin2::EDIT_INFO) -> Self {
90        let raw = unsafe { &*ptr };
91        Self {
92            width: raw.width as usize,
93            height: raw.height as usize,
94            fps: Rational32::new(raw.rate, raw.scale),
95            sample_rate: raw.sample_rate as usize,
96            frame: raw.frame as usize,
97            layer: raw.layer as usize,
98            frame_max: raw.frame_max as usize,
99            layer_max: raw.layer_max as usize,
100            display_frame_start: raw.display_frame_start as usize,
101            display_layer_start: raw.display_layer_start as usize,
102            display_frame_num: raw.display_frame_num as usize,
103            display_layer_num: raw.display_layer_num as usize,
104
105            select_range_start: (raw.select_range_start >= 0)
106                .then_some(raw.select_range_start as usize),
107            select_range_end: (raw.select_range_end >= 0).then_some(raw.select_range_end as usize),
108
109            grid_bpm_tempo: raw.grid_bpm_tempo,
110            grid_bpm_beat: raw.grid_bpm_beat as usize,
111            grid_bpm_offset: raw.grid_bpm_offset,
112            scene_id: raw.scene_id,
113        }
114    }
115}
116
117/// オブジェクトのレイヤーとフレーム情報。
118#[derive(Debug, Clone, Copy)]
119pub struct ObjectLayerFrame {
120    pub layer: usize,
121    pub start: usize,
122    pub end: usize,
123}
124
125impl ObjectLayerFrame {
126    /// フレームの範囲をRangeで取得する。
127    pub fn frame_range(&self) -> std::ops::Range<usize> {
128        self.start..(self.end + 1)
129    }
130
131    /// フレームの範囲をRangeInclusiveで取得する。
132    pub fn frame_range_inclusive(&self) -> std::ops::RangeInclusive<usize> {
133        self.start..=self.end
134    }
135}
136
137/// レイヤーとフレーム情報。
138#[derive(Debug, Clone, Copy)]
139pub struct LayerFrameData {
140    pub layer: usize,
141    pub frame: usize,
142}
143
144#[derive(Debug, Clone, Copy)]
145pub struct MediaInfo {
146    /// Videoトラック数。
147    pub video_track_num: Option<NonZero<usize>>,
148    /// Audioトラック数。
149    pub audio_track_num: Option<NonZero<usize>>,
150    /// 総時間(秒)。
151    pub total_time: f64,
152    /// 解像度の幅。
153    pub width: usize,
154    /// 解像度の高さ。
155    pub height: usize,
156}
157
158/// [`ReadSection::is_support_media_file`] のモード。
159#[derive(Debug, Clone, Copy)]
160pub enum MediaFileSupportMode {
161    /// 拡張子が対応しているかどうかのみを確認します。
162    ExtensionOnly,
163    /// 実際にファイルを開いて対応しているかどうかを確認します。
164    Strict,
165}
166
167/// [`EditSection`] 関連のエラー。
168#[derive(thiserror::Error, Debug)]
169pub enum EditSectionError {
170    #[error("api call failed")]
171    ApiCallFailed,
172    #[error("object does not exist")]
173    ObjectDoesNotExist,
174    #[error("input utf-8 string contains null byte")]
175    InputCstrContainsNull(#[from] std::ffi::NulError),
176    #[error("input utf-16 string contains null byte")]
177    InputCwstrContainsNull(#[from] crate::common::NullByteError),
178    #[error("value is out of range")]
179    ValueOutOfRange(#[from] std::num::TryFromIntError),
180    #[error("api returned non-utf8 data")]
181    NonUtf8Data(#[from] std::str::Utf8Error),
182
183    #[cfg(feature = "aviutl2-alias")]
184    #[error("alias parse error: {0}")]
185    ParseFailed(#[from] aviutl2_alias::TableParseError),
186}
187
188/// [`ReadSection::get_object_effect_item_parsed`] などのエラー。
189#[derive(thiserror::Error, Debug)]
190pub enum EditSectionParsedError<E: std::error::Error + Send + Sync + 'static> {
191    #[error(transparent)]
192    EditSectionError(#[from] EditSectionError),
193    #[error("value parse error: {0}")]
194    ParseError(E),
195}
196
197pub type EditSectionResult<T> = Result<T, EditSectionError>;
198
199/// 読み取り専用の編集セクションハンドル。
200#[derive(Debug)]
201pub struct ReadSection {
202    pub(crate) internal: *mut aviutl2_sys::plugin2::EDIT_SECTION,
203}
204
205/// 編集セクションのハンドル。
206#[derive(Debug)]
207pub struct EditSection {
208    /// 編集情報。
209    pub info: EditInfo,
210
211    pub(crate) internal: *mut aviutl2_sys::plugin2::EDIT_SECTION,
212    read_section: ReadSection,
213}
214
215impl std::ops::Deref for EditSection {
216    type Target = ReadSection;
217
218    fn deref(&self) -> &Self::Target {
219        &self.read_section
220    }
221}
222
223impl ReadSection {
224    /// 生ポインタから `ReadSection` を作成する。
225    ///
226    /// # Safety
227    ///
228    /// 有効な `EDIT_SECTION` ポインタである必要があります。
229    pub unsafe fn from_raw(ptr: *mut aviutl2_sys::plugin2::EDIT_SECTION) -> Self {
230        Self { internal: ptr }
231    }
232
233    /// 指定のフレーム番号以降にあるオブジェクトを検索する。
234    ///
235    /// # Arguments
236    ///
237    /// - `layer`:検索するレイヤー番号(0始まり)。
238    /// - `frame`:検索を開始するフレーム番号(0始まり)。
239    pub fn find_object_after(
240        &self,
241        layer: usize,
242        frame: usize,
243    ) -> EditSectionResult<Option<ObjectHandle>> {
244        let object_handle =
245            unsafe { ((*self.internal).find_object)(layer.try_into()?, frame.try_into()?) };
246        if object_handle.is_null() {
247            Ok(None)
248        } else {
249            Ok(Some(ObjectHandle {
250                internal: object_handle,
251            }))
252        }
253    }
254
255    /// オブジェクトに対象エフェクトが何個存在するかを取得する。
256    pub fn count_object_effect(
257        &self,
258        object: ObjectHandle,
259        effect: &str,
260    ) -> EditSectionResult<usize> {
261        self.ensure_object_exists(object)?;
262        let c_effect = crate::common::CWString::new(effect)?;
263        let count =
264            unsafe { ((*self.internal).count_object_effect)(object.internal, c_effect.as_ptr()) };
265        Ok(count.try_into()?)
266    }
267
268    /// 指定のオブジェクトのレイヤーとフレーム情報を取得する。
269    pub fn get_object_layer_frame(
270        &self,
271        object: ObjectHandle,
272    ) -> EditSectionResult<ObjectLayerFrame> {
273        self.ensure_object_exists(object)?;
274        let object = unsafe { ((*self.internal).get_object_layer_frame)(object.internal) };
275        Ok(ObjectLayerFrame {
276            layer: object.layer.try_into()?,
277            start: object.start.try_into()?,
278            end: object.end.try_into()?,
279        })
280    }
281
282    /// オブジェクトの情報をエイリアスデータとして取得する。
283    pub fn get_object_alias(&self, object: ObjectHandle) -> EditSectionResult<String> {
284        self.ensure_object_exists(object)?;
285        let alias_ptr = unsafe { ((*self.internal).get_object_alias)(object.internal) };
286        if alias_ptr.is_null() {
287            return Err(EditSectionError::ApiCallFailed);
288        }
289        let c_str = unsafe { std::ffi::CStr::from_ptr(alias_ptr) };
290        let alias = c_str.to_str()?.to_owned();
291        Ok(alias)
292    }
293
294    /// オブジェクトの情報をエイリアスデータとして取得し、パースする。
295    #[cfg(feature = "aviutl2-alias")]
296    pub fn get_object_alias_parsed(
297        &self,
298        object: ObjectHandle,
299    ) -> Result<aviutl2_alias::Table, EditSectionParsedError<aviutl2_alias::TableParseError>> {
300        let alias_str = self.get_object_alias(object)?;
301        let alias_data = alias_str
302            .parse()
303            .map_err(EditSectionParsedError::ParseError)?;
304        Ok(alias_data)
305    }
306
307    /// オブジェクト名を取得する。
308    pub fn get_object_name(&self, object: ObjectHandle) -> EditSectionResult<Option<String>> {
309        self.ensure_object_exists(object)?;
310        let name_ptr = unsafe { ((*self.internal).get_object_name)(object.internal) };
311        if name_ptr.is_null() {
312            return Ok(None);
313        }
314        Ok(Some(unsafe { crate::common::load_wide_string(name_ptr) }))
315    }
316
317    /// オブジェクトの設定項目の値を文字列で取得する。
318    pub fn get_object_effect_item(
319        &self,
320        object: ObjectHandle,
321        effect_name: &str,
322        effect_index: usize,
323        item: &str,
324    ) -> EditSectionResult<String> {
325        self.ensure_object_exists(object)?;
326        let c_effect_name = crate::common::CWString::new(&effect_key(effect_name, effect_index))?;
327        let c_item = crate::common::CWString::new(item)?;
328        let value_ptr = unsafe {
329            ((*self.internal).get_object_item_value)(
330                object.internal,
331                c_effect_name.as_ptr(),
332                c_item.as_ptr(),
333            )
334        };
335        if value_ptr.is_null() {
336            return Err(EditSectionError::ApiCallFailed);
337        }
338        let c_str = unsafe { std::ffi::CStr::from_ptr(value_ptr) };
339        let value = c_str.to_str()?.to_owned();
340        Ok(value)
341    }
342
343    /// オブジェクトの設定項目の値を取得し、パースする。
344    #[cfg(feature = "aviutl2-alias")]
345    pub fn get_object_effect_item_parsed<T: aviutl2_alias::FromTableValue>(
346        &self,
347        object: ObjectHandle,
348        effect_name: &str,
349        effect_index: usize,
350        item: &str,
351    ) -> Result<T, EditSectionParsedError<<T as aviutl2_alias::FromTableValue>::Err>>
352    where
353        <T as aviutl2_alias::FromTableValue>::Err: std::error::Error + Sync + Send + 'static,
354    {
355        let value_str = self.get_object_effect_item(object, effect_name, effect_index, item)?;
356        T::from_table_value(&value_str).map_err(EditSectionParsedError::ParseError)
357    }
358
359    /// 現在、オブジェクト設定ウィンドウで選択されているオブジェクトを取得する。
360    pub fn get_focused_object(&self) -> EditSectionResult<Option<ObjectHandle>> {
361        let object_handle = unsafe { ((*self.internal).get_focus_object)() };
362        if object_handle.is_null() {
363            Ok(None)
364        } else {
365            Ok(Some(ObjectHandle {
366                internal: object_handle,
367            }))
368        }
369    }
370
371    /// 現在選択されているオブジェクトの一覧を取得する。
372    pub fn get_selected_objects(&self) -> EditSectionResult<Vec<ObjectHandle>> {
373        let mut handles = Vec::new();
374        let num_objects = unsafe { ((*self.internal).get_selected_object_num)() };
375        for i in 0..num_objects {
376            let object_handle = unsafe { ((*self.internal).get_selected_object)(i) };
377            if object_handle.is_null() {
378                return Err(EditSectionError::ApiCallFailed);
379            }
380            handles.push(ObjectHandle {
381                internal: object_handle,
382            });
383        }
384        Ok(handles)
385    }
386
387    /// 指定のメディアファイルがサポートされているかどうか調べる。
388    pub fn is_support_media_file<P: AsRef<std::path::Path>>(
389        &self,
390        file_path: P,
391        mode: MediaFileSupportMode,
392    ) -> EditSectionResult<bool> {
393        let c_file_path = crate::common::CWString::new(&file_path.as_ref().to_string_lossy())?;
394        let is_supported = unsafe {
395            match mode {
396                MediaFileSupportMode::ExtensionOnly => {
397                    ((*self.internal).is_support_media_file)(c_file_path.as_ptr(), false)
398                }
399                MediaFileSupportMode::Strict => {
400                    ((*self.internal).is_support_media_file)(c_file_path.as_ptr(), true)
401                }
402            }
403        };
404        Ok(is_supported)
405    }
406
407    /// 指定のメディアファイルの情報を取得する。
408    pub fn get_media_info<P: AsRef<std::path::Path>>(
409        &self,
410        file_path: P,
411    ) -> EditSectionResult<MediaInfo> {
412        let c_file_path = crate::common::CWString::new(&file_path.as_ref().to_string_lossy())?;
413        let mut media_info = std::mem::MaybeUninit::<aviutl2_sys::plugin2::MEDIA_INFO>::uninit();
414        let success = unsafe {
415            ((*self.internal).get_media_info)(
416                c_file_path.as_ptr(),
417                media_info.as_mut_ptr(),
418                std::mem::size_of::<aviutl2_sys::plugin2::MEDIA_INFO>() as i32,
419            )
420        };
421        if !success {
422            return Err(EditSectionError::ApiCallFailed);
423        }
424        let media_info = unsafe { media_info.assume_init() };
425        Ok(MediaInfo {
426            video_track_num: NonZero::new(media_info.video_track_num.try_into()?),
427            audio_track_num: NonZero::new(media_info.audio_track_num.try_into()?),
428            total_time: media_info.total_time,
429            width: media_info.width.try_into()?,
430            height: media_info.height.try_into()?,
431        })
432    }
433
434    /// レイヤーの名前を取得する。
435    pub fn get_layer_name(&self, layer: usize) -> EditSectionResult<Option<String>> {
436        let name_ptr = unsafe { ((*self.internal).get_layer_name)(layer.try_into()?) };
437        if name_ptr.is_null() {
438            return Ok(None);
439        }
440        Ok(Some(unsafe { crate::common::load_wide_string(name_ptr) }))
441    }
442
443    /// シーン名を取得する。
444    pub fn get_scene_name(&self) -> EditSectionResult<String> {
445        let name_ptr = unsafe { ((*self.internal).get_scene_name)() };
446        if name_ptr.is_null() {
447            return Err(EditSectionError::ApiCallFailed);
448        }
449        Ok(unsafe { crate::common::load_wide_string(name_ptr) })
450    }
451
452    /// レイヤーの表示・非表示を取得する。
453    pub fn get_layer_enable(&self, layer: usize) -> EditSectionResult<bool> {
454        let visible = unsafe { ((*self.internal).get_layer_enable)(layer.try_into()?) };
455        Ok(visible)
456    }
457
458    /// レイヤーのロック状態を取得する。
459    pub fn get_layer_lock(&self, layer: usize) -> EditSectionResult<bool> {
460        let locked = unsafe { ((*self.internal).get_layer_lock)(layer.try_into()?) };
461        Ok(locked)
462    }
463
464    /// オブジェクトの区間の数を取得する。
465    pub fn get_object_section_num(&self, object: ObjectHandle) -> EditSectionResult<usize> {
466        self.ensure_object_exists(object)?;
467        let count = unsafe { ((*self.internal).get_object_section_num)(object.internal) };
468        Ok(count.try_into()?)
469    }
470
471    /// 選択中オブジェクトの区間の位置を取得する。
472    pub fn get_focus_object_section(&self) -> EditSectionResult<Option<usize>> {
473        let section = unsafe { ((*self.internal).get_focus_object_section)() };
474        if section == -1 {
475            Ok(None)
476        } else {
477            Ok(Some(section.try_into()?))
478        }
479    }
480
481    /// オブジェクトが存在するかどうか調べる。
482    pub fn object_exists(&self, object: ObjectHandle) -> bool {
483        let object = unsafe { ((*self.internal).get_object_layer_frame)(object.internal) };
484        object.layer != -1
485    }
486
487    fn ensure_object_exists(&self, object: ObjectHandle) -> EditSectionResult<()> {
488        if !self.object_exists(object) {
489            return Err(EditSectionError::ObjectDoesNotExist);
490        }
491        Ok(())
492    }
493
494    /// 特定のレイヤー内のオブジェクトをイテレータで取得する。
495    pub fn objects_in_layer(
496        &self,
497        layer: usize,
498    ) -> EditSectionLayerObjectsIterator<'_, ReadSection> {
499        EditSectionLayerObjectsIterator::new(self, layer)
500    }
501
502    /// [EditSectionLayerCaller] を作成する。
503    pub fn layer<'a>(&'a self, layer: usize) -> EditSectionLayerCaller<'a, ReadSection> {
504        EditSectionLayerCaller::new(self, layer)
505    }
506
507    /// [EditSectionObjectCaller] を作成する。
508    pub fn object<'a>(&'a self, object: ObjectHandle) -> EditSectionObjectCaller<'a, ReadSection> {
509        EditSectionObjectCaller::new(self, object)
510    }
511}
512
513impl EditSection {
514    /// 生ポインタから `EditSection` を作成する。
515    ///
516    /// # Safety
517    ///
518    /// call_edit_section から取得した、有効な `EDIT_SECTION` ポインタである必要があります。
519    pub unsafe fn from_raw(ptr: *mut aviutl2_sys::plugin2::EDIT_SECTION) -> Self {
520        let info = unsafe { EditInfo::from_raw((*ptr).info) };
521        Self {
522            internal: ptr,
523            info,
524            read_section: ReadSection { internal: ptr },
525        }
526    }
527
528    /// オブジェクトエイリアスから指定の位置にオブジェクトを作成する。
529    ///
530    /// # Arguments
531    ///
532    /// - `alias`:オブジェクトエイリアスのデータ。オブジェクトエイリアスと同じフォーマットで指定します。
533    /// - `layer`:作成するオブジェクトのレイヤー番号(0始まり)。
534    /// - `frame`:作成するオブジェクトのフレーム番号(0始まり)。
535    /// - `length`:作成するオブジェクトの長さ(フレーム数)。
536    ///
537    /// lengthはエイリアスデータにフレーム情報が無い場合に利用されます。
538    ///
539    /// # Errors
540    ///
541    /// エイリアスの変換に失敗した場合、またはオブジェクトが既存のオブジェクトに重なる場合にエラー
542    ///
543    pub fn create_object_from_alias(
544        &self,
545        alias: &str,
546        layer: usize,
547        frame: usize,
548        length: usize,
549    ) -> EditSectionResult<ObjectHandle> {
550        let c_alias = std::ffi::CString::new(alias)?;
551        let object_handle = unsafe {
552            ((*self.internal).create_object_from_alias)(
553                c_alias.as_ptr(),
554                layer.try_into()?,
555                frame.try_into()?,
556                length.try_into()?,
557            )
558        };
559        if object_handle.is_null() {
560            return Err(EditSectionError::ApiCallFailed);
561        }
562        Ok(ObjectHandle {
563            internal: object_handle,
564        })
565    }
566
567    /// オブジェクト名を設定する。
568    ///
569    /// # Note
570    ///
571    /// `name`に`None`や空文字を指定すると、標準の名前になります。
572    pub fn set_object_name(
573        &self,
574        object: ObjectHandle,
575        name: Option<&str>,
576    ) -> EditSectionResult<()> {
577        self.read_section.ensure_object_exists(object)?;
578        match name {
579            None => {
580                unsafe { ((*self.internal).set_object_name)(object.internal, std::ptr::null()) };
581                Ok(())
582            }
583            Some(name) => {
584                let c_name = crate::common::CWString::new(name)?;
585                unsafe {
586                    ((*self.internal).set_object_name)(object.internal, c_name.as_ptr());
587                }
588                Ok(())
589            }
590        }
591    }
592
593    /// オブジェクトの設定項目の値を文字列で設定する。
594    ///
595    /// # Arguments
596    ///
597    /// - `object`:対象のオブジェクトハンドル。
598    /// - `effect_name`:設定項目の名前。
599    /// - `effect_index`:同じ名前の設定項目が複数ある場合のインデックス(0始まり)。
600    /// - `item`:設定項目の名前。(エイリアスファイルのキーの名前)
601    /// - `value`:設定する値。
602    pub fn set_object_effect_item(
603        &self,
604        object: ObjectHandle,
605        effect_name: &str,
606        effect_index: usize,
607        item: &str,
608        value: &str,
609    ) -> EditSectionResult<()> {
610        self.read_section.ensure_object_exists(object)?;
611        let c_effect_name = crate::common::CWString::new(&effect_key(effect_name, effect_index))?;
612        let c_item = crate::common::CWString::new(item)?;
613        let c_value = std::ffi::CString::new(value)?;
614        let success = unsafe {
615            ((*self.internal).set_object_item_value)(
616                object.internal,
617                c_effect_name.as_ptr(),
618                c_item.as_ptr(),
619                c_value.as_ptr(),
620            )
621        };
622        if !success {
623            return Err(EditSectionError::ApiCallFailed);
624        }
625        Ok(())
626    }
627
628    /// オブジェクトを移動する。
629    pub fn move_object(
630        &self,
631        object: ObjectHandle,
632        new_layer: usize,
633        new_start_frame: usize,
634    ) -> EditSectionResult<()> {
635        self.read_section.ensure_object_exists(object)?;
636        let success = unsafe {
637            ((*self.internal).move_object)(
638                object.internal,
639                new_layer.try_into()?,
640                new_start_frame.try_into()?,
641            )
642        };
643        if !success {
644            return Err(EditSectionError::ApiCallFailed);
645        }
646        Ok(())
647    }
648
649    /// オブジェクトを削除する。
650    pub fn delete_object(&self, object: ObjectHandle) -> EditSectionResult<()> {
651        self.read_section.ensure_object_exists(object)?;
652        unsafe { ((*self.internal).delete_object)(object.internal) };
653        Ok(())
654    }
655
656    /// オブジェクト設定ウィンドウで指定のオブジェクトを選択状態にする。
657    ///
658    /// # Note
659    ///
660    /// コールバック処理の終了時に設定されます。
661    pub fn focus_object(&self, object: ObjectHandle) -> EditSectionResult<()> {
662        self.read_section.ensure_object_exists(object)?;
663        unsafe { ((*self.internal).set_focus_object)(object.internal) };
664        Ok(())
665    }
666
667    /// プロジェクトファイルのポインタを取得する。
668    pub fn get_project_file<'handle>(
669        &'handle self,
670        edit_handle: &crate::generic::EditHandle,
671    ) -> crate::generic::ProjectFile<'handle> {
672        let pf_ptr = unsafe { ((*self.internal).get_project_file)(edit_handle.internal) };
673        unsafe { crate::generic::ProjectFile::from_raw(pf_ptr) }
674    }
675
676    /// マウス座標のレイヤー・フレーム位置を取得する。
677    ///
678    /// # Returns
679    ///
680    /// マウスがレイヤー編集エリア内にある場合は `Some` を返し、
681    /// そうでない場合は `None` を返します。
682    pub fn get_mouse_layer_frame(&self) -> EditSectionResult<Option<LayerFrameData>> {
683        let mut layer = 0;
684        let mut frame = 0;
685        let on_layer_edit =
686            unsafe { ((*self.internal).get_mouse_layer_frame)(&mut layer, &mut frame) };
687        if on_layer_edit {
688            Ok(Some(LayerFrameData {
689                layer: layer.try_into()?,
690                frame: frame.try_into()?,
691            }))
692        } else {
693            Ok(None)
694        }
695    }
696
697    /// 指定のスクリーン座標のレイヤー・フレーム位置を取得する。
698    pub fn pos_to_layer_frame(&self, x: i32, y: i32) -> EditSectionResult<Option<LayerFrameData>> {
699        let mut layer = 0;
700        let mut frame = 0;
701        let on_layer_edit =
702            unsafe { ((*self.internal).pos_to_layer_frame)(x, y, &mut layer, &mut frame) };
703        if on_layer_edit {
704            Ok(Some(LayerFrameData {
705                layer: layer.try_into()?,
706                frame: frame.try_into()?,
707            }))
708        } else {
709            Ok(None)
710        }
711    }
712
713    /// 指定の位置にメディアファイルからオブジェクトを作成する。
714    ///
715    /// # Arguments
716    ///
717    /// - `file_path`:メディアファイルのパス。
718    /// - `layer`:作成するオブジェクトのレイヤー番号(0始まり)。
719    /// - `frame`:作成するオブジェクトのフレーム番号(0始まり)。
720    /// - `length`:作成するオブジェクトの長さ(フレーム数)。`None`を指定した場合、長さや追加位置は自動的に調整されます。
721    pub fn create_object_from_media_file<P: AsRef<std::path::Path>>(
722        &self,
723        file_path: P,
724        layer: usize,
725        frame: usize,
726        length: Option<usize>,
727    ) -> EditSectionResult<ObjectHandle> {
728        let c_file_path = crate::common::CWString::new(&file_path.as_ref().to_string_lossy())?;
729        let object_handle = unsafe {
730            ((*self.internal).create_object_from_media_file)(
731                c_file_path.as_ptr(),
732                layer.try_into()?,
733                frame.try_into()?,
734                length.unwrap_or(0).try_into()?,
735            )
736        };
737        if object_handle.is_null() {
738            return Err(EditSectionError::ApiCallFailed);
739        }
740        Ok(ObjectHandle {
741            internal: object_handle,
742        })
743    }
744
745    /// 指定の位置にオブジェクトを作成する。
746    ///
747    /// # Arguments
748    ///
749    /// - `effect`:エフェクト名。(エイリアスファイルの effect.name の値)
750    /// - `layer`:作成するオブジェクトのレイヤー番号(0始まり)。
751    /// - `frame`:作成するオブジェクトのフレーム番号(0始まり)。
752    /// - `length`:作成するオブジェクトの長さ(フレーム数)。`None`を指定した場合、長さや追加位置は自動的に調整されます。
753    pub fn create_object(
754        &self,
755        effect: &str,
756        layer: usize,
757        frame: usize,
758        length: Option<usize>,
759    ) -> EditSectionResult<ObjectHandle> {
760        let c_effect = crate::common::CWString::new(effect)?;
761        let object_handle = unsafe {
762            ((*self.internal).create_object)(
763                c_effect.as_ptr(),
764                layer.try_into()?,
765                frame.try_into()?,
766                length.unwrap_or(0).try_into()?,
767            )
768        };
769        if object_handle.is_null() {
770            return Err(EditSectionError::ApiCallFailed);
771        }
772        Ok(ObjectHandle {
773            internal: object_handle,
774        })
775    }
776
777    /// 現在のレイヤー・フレーム位置を設定する。
778    ///
779    /// # Note
780    ///
781    /// 設定出来る範囲に調整されます。
782    pub fn set_cursor_layer_frame(&self, layer: usize, frame: usize) -> EditSectionResult<()> {
783        unsafe {
784            ((*self.internal).set_cursor_layer_frame)(layer.try_into()?, frame.try_into()?);
785        }
786        Ok(())
787    }
788
789    /// レイヤー編集のレイヤー・フレームの表示開始位置を設定する。
790    ///
791    /// # Note
792    ///
793    /// 設定出来る範囲に調整されます。
794    pub fn set_display_layer_frame(&self, layer: usize, frame: usize) -> EditSectionResult<()> {
795        unsafe {
796            ((*self.internal).set_display_layer_frame)(layer.try_into()?, frame.try_into()?);
797        }
798        Ok(())
799    }
800
801    /// フレーム範囲選択を設定する。
802    ///
803    /// # Note
804    ///
805    /// 設定出来る範囲に調整されます。
806    pub fn set_select_range(&self, start: usize, end: usize) -> EditSectionResult<()> {
807        unsafe {
808            ((*self.internal).set_select_range)(start.try_into()?, end.try_into()?);
809        }
810        Ok(())
811    }
812
813    /// 選択されているフレーム範囲選択を解除する。
814    pub fn clear_select_range(&self) -> EditSectionResult<()> {
815        unsafe {
816            ((*self.internal).set_select_range)(-1, -1);
817        }
818        Ok(())
819    }
820
821    /// グリッド(BPM)を設定する。
822    pub fn set_grid_bpm(&self, tempo: f32, beat: usize, offset: f32) -> EditSectionResult<()> {
823        unsafe {
824            ((*self.internal).set_grid_bpm)(tempo, beat.try_into()?, offset);
825        }
826        Ok(())
827    }
828
829    /// レイヤーの名前を設定する。
830    /// `name`に`None`や空文字を指定すると、標準の名前になります。
831    pub fn set_layer_name(&self, layer: usize, name: Option<&str>) -> EditSectionResult<()> {
832        match name {
833            None => {
834                unsafe { ((*self.internal).set_layer_name)(layer.try_into()?, std::ptr::null()) };
835                Ok(())
836            }
837            Some(name) => {
838                let c_name = crate::common::CWString::new(name)?;
839                unsafe {
840                    ((*self.internal).set_layer_name)(layer.try_into()?, c_name.as_ptr());
841                }
842                Ok(())
843            }
844        }
845    }
846
847    /// シーン名を設定する。
848    ///
849    /// <div class="warning">
850    /// シーンの操作は現状Undoに対応していません。
851    /// </div>
852    pub fn set_scene_name(&self, name: &str) -> EditSectionResult<()> {
853        let c_name = crate::common::CWString::new(name)?;
854        unsafe { ((*self.internal).set_scene_name)(c_name.as_ptr()) };
855        Ok(())
856    }
857
858    /// シーンの解像度を設定する。
859    ///
860    /// <div class="warning">
861    /// シーンの操作は現状Undoに対応していません。
862    /// </div>
863    pub fn set_scene_size(&self, width: usize, height: usize) -> EditSectionResult<()> {
864        unsafe {
865            ((*self.internal).set_scene_size)(width.try_into()?, height.try_into()?);
866        }
867        Ok(())
868    }
869
870    /// シーンのフレームレートを設定する。
871    ///
872    /// <div class="warning">
873    /// シーンの操作は現状Undoに対応していません。
874    /// </div>
875    pub fn set_scene_fps(&self, fps: Rational32) -> EditSectionResult<()> {
876        unsafe {
877            ((*self.internal).set_scene_frame_rate)(*fps.numer(), *fps.denom());
878        }
879        Ok(())
880    }
881
882    /// シーンのサンプルレートを設定する。
883    ///
884    /// <div class="warning">
885    /// シーンの操作は現状Undoに対応していません。
886    /// </div>
887    pub fn set_scene_sample_rate(&self, sample_rate: usize) -> EditSectionResult<()> {
888        unsafe {
889            ((*self.internal).set_scene_sample_rate)(sample_rate.try_into()?);
890        }
891        Ok(())
892    }
893
894    /// レイヤーの表示・非表示を設定する。
895    pub fn set_layer_enable(&self, layer: usize, enable: bool) -> EditSectionResult<()> {
896        unsafe {
897            ((*self.internal).set_layer_enable)(layer.try_into()?, enable);
898        }
899        Ok(())
900    }
901
902    /// レイヤーのロック状態を設定する。
903    pub fn set_layer_lock(&self, layer: usize, lock: bool) -> EditSectionResult<()> {
904        unsafe {
905            ((*self.internal).set_layer_lock)(layer.try_into()?, lock);
906        }
907        Ok(())
908    }
909
910    /// すべてのレイヤーをイテレータで取得する。
911    pub fn layers(&self) -> EditSectionLayersIterator<'_> {
912        EditSectionLayersIterator::new(self)
913    }
914    /// 特定のレイヤー内のオブジェクトをイテレータで取得する。
915    pub fn objects_in_layer(
916        &self,
917        layer: usize,
918    ) -> EditSectionLayerObjectsIterator<'_, EditSection> {
919        EditSectionLayerObjectsIterator::new(self, layer)
920    }
921
922    /// [EditSectionLayerCaller] を作成する。
923    pub fn layer<'a>(&'a self, layer: usize) -> EditSectionLayerCaller<'a, EditSection> {
924        EditSectionLayerCaller::new(self, layer)
925    }
926    /// [EditSectionObjectCaller] を作成する。
927    pub fn object<'a>(&'a self, object: ObjectHandle) -> EditSectionObjectCaller<'a, EditSection> {
928        EditSectionObjectCaller::new(self, object)
929    }
930}
931
932trait ReadSectionProvider {
933    fn as_read_section(&self) -> &ReadSection;
934}
935
936impl ReadSectionProvider for ReadSection {
937    fn as_read_section(&self) -> &ReadSection {
938        self
939    }
940}
941
942impl ReadSectionProvider for EditSection {
943    fn as_read_section(&self) -> &ReadSection {
944        &self.read_section
945    }
946}
947
948/// オブジェクト主体で関数を呼び出すための構造体。
949/// EditSection と ObjectHandle の組をまとめ、対象オブジェクトに対する
950/// 操作を簡潔に呼び出せるようにします。
951pub struct EditSectionObjectCaller<'a, S> {
952    edit_section: &'a S,
953    pub handle: ObjectHandle,
954}
955impl<'a, S> EditSectionObjectCaller<'a, S> {
956    pub fn new(edit_section: &'a S, object: ObjectHandle) -> Self {
957        Self {
958            edit_section,
959            handle: object,
960        }
961    }
962}
963
964impl<S> std::ops::Deref for EditSectionObjectCaller<'_, S> {
965    type Target = ObjectHandle;
966
967    fn deref(&self) -> &Self::Target {
968        &self.handle
969    }
970}
971
972#[expect(private_bounds)]
973impl<S> EditSectionObjectCaller<'_, S>
974where
975    S: ReadSectionProvider,
976{
977    fn read_section(&self) -> &ReadSection {
978        self.edit_section.as_read_section()
979    }
980
981    /// オブジェクトのレイヤーとフレーム情報を取得する。
982    pub fn get_layer_frame(&self) -> EditSectionResult<ObjectLayerFrame> {
983        self.read_section().get_object_layer_frame(self.handle)
984    }
985
986    /// オブジェクトの情報をエイリアスデータとして取得する。
987    pub fn get_alias(&self) -> EditSectionResult<String> {
988        self.read_section().get_object_alias(self.handle)
989    }
990
991    /// オブジェクトの情報をエイリアスデータとして取得し、パースする。
992    #[cfg(feature = "aviutl2-alias")]
993    pub fn get_alias_parsed(&self) -> EditSectionResult<aviutl2_alias::Table> {
994        self.read_section()
995            .get_object_alias(self.handle)?
996            .parse()
997            .map_err(Into::into)
998    }
999
1000    /// オブジェクトに対象エフェクトが何個存在するかを取得する。
1001    ///
1002    /// # Arguments
1003    ///
1004    /// - `effect`:対象のエフェクト名。(エイリアスファイルの effect.name の値)
1005    ///
1006    /// # Returns
1007    ///
1008    /// 対象エフェクトの数。存在しない場合は0を返します。
1009    pub fn count_effect(&self, effect: &str) -> EditSectionResult<usize> {
1010        self.read_section().count_object_effect(self.handle, effect)
1011    }
1012
1013    /// オブジェクトの設定項目の値を文字列で取得する。
1014    ///
1015    /// # Arguments
1016    ///
1017    /// - `effect_name`:設定項目の名前。
1018    /// - `effect_index`:同じ名前の設定項目が複数ある場合のインデックス(0始まり)。
1019    /// - `item`:設定項目の名前。(エイリアスファイルのキーの名前)
1020    pub fn get_effect_item(
1021        &self,
1022        effect_name: &str,
1023        effect_index: usize,
1024        item: &str,
1025    ) -> EditSectionResult<String> {
1026        self.read_section()
1027            .get_object_effect_item(self.handle, effect_name, effect_index, item)
1028    }
1029
1030    /// オブジェクトの設定項目の値を取得し、パースする。
1031    ///
1032    /// # See Also
1033    ///
1034    /// [`EditSectionObjectCaller::get_effect_item`]
1035    #[cfg(feature = "aviutl2-alias")]
1036    pub fn get_effect_item_parsed<T: aviutl2_alias::FromTableValue>(
1037        &self,
1038        effect_name: &str,
1039        effect_index: usize,
1040        item: &str,
1041    ) -> Result<T, EditSectionParsedError<<T as aviutl2_alias::FromTableValue>::Err>>
1042    where
1043        <T as aviutl2_alias::FromTableValue>::Err: std::error::Error + Sync + Send + 'static,
1044    {
1045        let value_str = self.get_effect_item(effect_name, effect_index, item)?;
1046        T::from_table_value(&value_str).map_err(EditSectionParsedError::ParseError)
1047    }
1048
1049    /// このオブジェクトが存在するかどうか調べる。
1050    pub fn exists(&self) -> bool {
1051        self.read_section().object_exists(self.handle)
1052    }
1053
1054    /// オブジェクトの名前を取得する。
1055    ///
1056    /// # Returns
1057    ///
1058    /// 標準の名前の場合は`None`を返します。
1059    pub fn get_name(&self) -> EditSectionResult<Option<String>> {
1060        self.read_section().get_object_name(self.handle)
1061    }
1062
1063    /// オブジェクトの区間の数を取得する。
1064    pub fn get_section_num(&self) -> EditSectionResult<usize> {
1065        self.read_section().get_object_section_num(self.handle)
1066    }
1067}
1068
1069impl EditSectionObjectCaller<'_, EditSection> {
1070    /// オブジェクトの設定項目の値を文字列で設定する。
1071    ///
1072    /// # Arguments
1073    ///
1074    /// - `effect_name`:設定項目の名前。
1075    /// - `effect_index`:同じ名前の設定項目が複数ある場合のインデックス(0始まり)。
1076    /// - `item`:設定項目の名前。(エイリアスファイルのキーの名前)
1077    /// - `value`:設定する値。
1078    pub fn set_effect_item(
1079        &self,
1080        effect_name: &str,
1081        effect_index: usize,
1082        item: &str,
1083        value: &str,
1084    ) -> EditSectionResult<()> {
1085        self.edit_section.set_object_effect_item(
1086            self.handle,
1087            effect_name,
1088            effect_index,
1089            item,
1090            value,
1091        )
1092    }
1093
1094    /// オブジェクトを移動する。
1095    ///
1096    /// # Arguments
1097    ///
1098    /// - `new_layer`:移動先のレイヤー番号(0始まり)。
1099    /// - `new_start_frame`:移動先の開始フレーム番号(0始まり)。
1100    pub fn move_object(&self, new_layer: usize, new_start_frame: usize) -> EditSectionResult<()> {
1101        self.edit_section
1102            .move_object(self.handle, new_layer, new_start_frame)
1103    }
1104
1105    /// オブジェクトを削除する。
1106    pub fn delete_object(&self) -> EditSectionResult<()> {
1107        self.edit_section.delete_object(self.handle)
1108    }
1109
1110    /// オブジェクト設定ウィンドウでこのオブジェクトを選択状態にする。
1111    ///
1112    /// # Note
1113    ///
1114    /// コールバック処理の終了時に設定されます。
1115    pub fn focus_object(&self) -> EditSectionResult<()> {
1116        self.edit_section.focus_object(self.handle)
1117    }
1118
1119    /// オブジェクトの名前を設定する。
1120    /// `name`に`None`や空文字を指定すると、標準の名前になります。
1121    pub fn set_name(&self, name: Option<&str>) -> EditSectionResult<()> {
1122        self.edit_section.set_object_name(self.handle, name)
1123    }
1124}
1125
1126/// レイヤー主体で関数を呼び出すための構造体。
1127/// EditSection と レイヤー番号 の組をまとめ、対象レイヤーに対する
1128/// 操作を簡潔に呼び出せるようにします。
1129pub struct EditSectionLayerCaller<'a, S> {
1130    edit_section: &'a S,
1131    pub index: usize,
1132}
1133impl<'a, S> EditSectionLayerCaller<'a, S> {
1134    pub fn new(edit_section: &'a S, layer: usize) -> Self {
1135        Self {
1136            edit_section,
1137            index: layer,
1138        }
1139    }
1140}
1141
1142#[expect(private_bounds)]
1143impl<S> EditSectionLayerCaller<'_, S>
1144where
1145    S: ReadSectionProvider,
1146{
1147    fn read_section(&self) -> &ReadSection {
1148        self.edit_section.as_read_section()
1149    }
1150
1151    /// 指定のフレーム番号以降にあるオブジェクトを検索する。
1152    ///
1153    /// # Arguments
1154    ///
1155    /// - `frame`:検索を開始するフレーム番号(0始まり)。
1156    pub fn find_object_after(&self, frame: usize) -> EditSectionResult<Option<ObjectHandle>> {
1157        self.read_section().find_object_after(self.index, frame)
1158    }
1159
1160    /// レイヤーの名前を取得する。
1161    pub fn get_name(&self) -> EditSectionResult<Option<String>> {
1162        self.read_section().get_layer_name(self.index)
1163    }
1164
1165    /// レイヤーの表示・非表示を取得する。
1166    pub fn get_enable(&self) -> EditSectionResult<bool> {
1167        self.read_section().get_layer_enable(self.index)
1168    }
1169
1170    /// レイヤーのロック状態を取得する。
1171    pub fn get_lock(&self) -> EditSectionResult<bool> {
1172        self.read_section().get_layer_lock(self.index)
1173    }
1174
1175    /// このレイヤーに存在するすべてのオブジェクトを、
1176    /// 開始フレームの昇順で走査するイテレータを返す。
1177    pub fn objects(&self) -> EditSectionLayerObjectsIterator<'_, S> {
1178        EditSectionLayerObjectsIterator::new(self.edit_section, self.index)
1179    }
1180}
1181
1182impl EditSectionLayerCaller<'_, EditSection> {
1183    /// オブジェクトエイリアスから指定の位置にオブジェクトを作成する。
1184    ///
1185    /// # See Also
1186    ///
1187    /// [`EditSection::create_object_from_alias`]
1188    pub fn create_object_from_alias<P: AsRef<std::path::Path>>(
1189        &self,
1190        alias: &str,
1191        frame: usize,
1192        length: usize,
1193    ) -> EditSectionResult<ObjectHandle> {
1194        self.edit_section
1195            .create_object_from_alias(alias, self.index, frame, length)
1196    }
1197
1198    /// 指定の位置にメディアファイルからオブジェクトを作成する。
1199    ///
1200    /// # See Also
1201    ///
1202    /// [`EditSection::create_object_from_media_file`]
1203    pub fn create_object_from_media_file<P: AsRef<std::path::Path>>(
1204        &self,
1205        file_path: P,
1206        frame: usize,
1207        length: Option<usize>,
1208    ) -> EditSectionResult<ObjectHandle> {
1209        self.edit_section
1210            .create_object_from_media_file(file_path, self.index, frame, length)
1211    }
1212
1213    /// 指定の位置にオブジェクトを作成する。
1214    ///
1215    /// # See Also
1216    ///
1217    /// [`EditSection::create_object`]
1218    pub fn create_object(
1219        &self,
1220        effect: &str,
1221        frame: usize,
1222        length: Option<usize>,
1223    ) -> EditSectionResult<ObjectHandle> {
1224        self.edit_section
1225            .create_object(effect, self.index, frame, length)
1226    }
1227
1228    /// レイヤーの名前を設定する。
1229    /// `name`に`None`や空文字を指定すると、標準の名前になります。
1230    pub fn set_name(&self, name: Option<&str>) -> EditSectionResult<()> {
1231        self.edit_section.set_layer_name(self.index, name)
1232    }
1233
1234    /// レイヤーの表示・非表示を設定する。
1235    pub fn set_enable(&self, enable: bool) -> EditSectionResult<()> {
1236        self.edit_section.set_layer_enable(self.index, enable)
1237    }
1238
1239    /// レイヤーのロック状態を設定する。
1240    pub fn set_lock(&self, lock: bool) -> EditSectionResult<()> {
1241        self.edit_section.set_layer_lock(self.index, lock)
1242    }
1243}
1244
1245/// レイヤーのイテレータ。
1246#[derive(Debug, Clone)]
1247pub struct EditSectionLayersIterator<'a> {
1248    edit_section: &'a EditSection,
1249    current: usize,
1250    end: usize,
1251}
1252
1253impl<'a> EditSectionLayersIterator<'a> {
1254    fn new(edit_section: &'a EditSection) -> Self {
1255        Self {
1256            edit_section,
1257            current: 0,
1258            end: edit_section.info.layer_max.saturating_add(1),
1259        }
1260    }
1261}
1262
1263impl<'a> Iterator for EditSectionLayersIterator<'a> {
1264    type Item = EditSectionLayerCaller<'a, EditSection>;
1265
1266    fn next(&mut self) -> Option<Self::Item> {
1267        if self.current >= self.end {
1268            return None;
1269        }
1270        let layer = self.current;
1271        self.current += 1;
1272        Some(EditSectionLayerCaller::new(self.edit_section, layer))
1273    }
1274
1275    fn size_hint(&self) -> (usize, Option<usize>) {
1276        let len = self.end.saturating_sub(self.current);
1277        (len, Some(len))
1278    }
1279}
1280
1281impl<'a> DoubleEndedIterator for EditSectionLayersIterator<'a> {
1282    fn next_back(&mut self) -> Option<Self::Item> {
1283        if self.current >= self.end {
1284            return None;
1285        }
1286        self.end -= 1;
1287        Some(EditSectionLayerCaller::new(self.edit_section, self.end))
1288    }
1289}
1290
1291impl ExactSizeIterator for EditSectionLayersIterator<'_> {}
1292
1293/// レイヤー内のオブジェクトを走査するイテレータ。
1294/// アイテムは `(オブジェクトのレイヤー・フレーム情報, ハンドル)` の組です。
1295#[derive(Debug, Clone)]
1296pub struct EditSectionLayerObjectsIterator<'a, S> {
1297    edit_section: &'a S,
1298    layer: usize,
1299    next_frame: usize,
1300}
1301
1302impl<'a, S> EditSectionLayerObjectsIterator<'a, S> {
1303    fn new(edit_section: &'a S, layer: usize) -> Self {
1304        Self {
1305            edit_section,
1306            layer,
1307            next_frame: 0,
1308        }
1309    }
1310}
1311
1312impl<S> Iterator for EditSectionLayerObjectsIterator<'_, S>
1313where
1314    S: ReadSectionProvider,
1315{
1316    type Item = (ObjectLayerFrame, ObjectHandle);
1317
1318    fn next(&mut self) -> Option<Self::Item> {
1319        let read_section = self.edit_section.as_read_section();
1320        // 検索・取得でエラーが出た場合は None を返して終了する。
1321        let Ok(Some(handle)) = read_section.find_object_after(self.layer, self.next_frame) else {
1322            return None;
1323        };
1324
1325        let lf = match read_section.get_object_layer_frame(handle) {
1326            Ok(lf) => lf,
1327            Err(_) => return None,
1328        };
1329
1330        // 次の検索開始位置を、いま見つかったオブジェクトの末尾+1 に進める。
1331        self.next_frame = lf.end.saturating_add(1);
1332
1333        Some((lf, handle))
1334    }
1335}
1336
1337fn effect_key(effect_name: &str, effect_index: usize) -> String {
1338    format!("{effect_name}:{effect_index}")
1339}