use crate::{IdMetadataExt, Item, Seconds, Track};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OverlapPolicy {
Override,
Push,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InsertPolicy {
SplitAndInsert,
InsertBefore,
InsertAfter,
InsertBeforeOrAfter,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SplitClipInfo {
pub old_clip_id: String,
pub left_clip_id: Option<String>,
pub right_clip_id: Option<String>,
pub sync_clips_id: Option<i64>,
pub split_time: Seconds,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeletedClipInfo {
pub clip_id: String,
pub sync_clips_id: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct TrackInsertResult {
pub success: bool,
pub deleted_clips: Vec<DeletedClipInfo>,
pub split_clips: Vec<SplitClipInfo>,
}
impl TrackInsertResult {
pub(crate) fn merge(&mut self, other: TrackInsertResult) {
self.success = self.success && other.success;
self.deleted_clips.extend(other.deleted_clips);
self.split_clips.extend(other.split_clips);
}
}
impl Track {
pub(crate) fn insert_at_index(
&mut self,
index: usize,
mut item: Item,
overlap_policy: OverlapPolicy,
) -> TrackInsertResult {
item.clamp_to_active_available_range();
if overlap_policy == OverlapPolicy::Push {
self.insert_and_push(index, item);
return TrackInsertResult {
success: true,
..Default::default()
};
}
self.insert_and_override(index, item)
}
pub(crate) fn insert_and_push(&mut self, index: usize, mut item: Item) {
item.clamp_to_active_available_range();
self.items.insert(index.min(self.items.len()), item);
}
pub(crate) fn insert_and_override(&mut self, index: usize, mut item: Item) -> TrackInsertResult {
const EPS: Seconds = 1e-9;
item.clamp_to_active_available_range();
let mut result = TrackInsertResult {
success: true,
..Default::default()
};
let mut insert_index = index.min(self.items.len());
let insert_start = self.start_time_of_item(insert_index);
let insert_end = insert_start + item.duration().max(0.0);
if item.duration() <= EPS {
self.items.insert(insert_index, item);
self.sanitize_preserving_all_gap_track();
return result;
}
if let Some(containing_idx) = self.get_item_at_time(insert_start) {
let containing_start = self.start_time_of_item(containing_idx);
if insert_start > containing_start + EPS && containing_idx <= insert_index {
if let Some(split) = self.split_at_time(insert_start) {
result.split_clips.push(split);
}
if insert_index <= containing_idx {
insert_index = containing_idx + 1;
} else {
insert_index += 1;
}
}
}
let mut end_split: Option<SplitClipInfo> = None;
if self.get_item_at_time(insert_end).is_some() {
end_split = self.split_at_time(insert_end);
}
let end_index = self
.get_item_at_time(insert_end)
.unwrap_or_else(|| self.items.len());
if end_index > insert_index {
let remove_count = end_index - insert_index;
for _ in 0..remove_count {
if let Item::Clip(clip) = &self.items[insert_index] {
let clip_id = clip.get_id().unwrap_or_default();
let is_trimmed_head = end_split
.as_ref()
.is_some_and(|split| split.old_clip_id == clip_id);
if !is_trimmed_head {
result.deleted_clips.push(DeletedClipInfo {
clip_id,
sync_clips_id: clip.sync_clips_id(),
});
}
}
self.items.remove(insert_index);
}
}
self.items.insert(insert_index, item);
if let Some(mut split) = end_split {
self.restore_trimmed_head_id(&mut split);
result.split_clips.push(split);
}
self.sanitize_preserving_all_gap_track();
result
}
pub(crate) fn restore_trimmed_head_id(&mut self, split: &mut SplitClipInfo) {
let left_alive = self
.items
.iter()
.any(|item| item.get_id().as_deref() == Some(split.old_clip_id.as_str()));
if left_alive {
return;
}
let Some(right_id) = split.right_clip_id.clone() else {
return;
};
if let Some(right) = self
.items
.iter_mut()
.find(|item| item.get_id().as_deref() == Some(right_id.as_str()))
{
right.set_id(Some(split.old_clip_id.clone()));
split.left_clip_id = None;
split.right_clip_id = Some(split.old_clip_id.clone());
}
}
pub(crate) fn insert_at_time(
&mut self,
insert_time: Seconds,
mut item: Item,
overlap_policy: OverlapPolicy,
insert_policy: InsertPolicy,
) -> TrackInsertResult {
item.clamp_to_active_available_range();
let mut effective_insert_time = insert_time;
let total_track_duration = self.total_duration();
if effective_insert_time < 0.0 {
effective_insert_time = total_track_duration - effective_insert_time;
}
if effective_insert_time < 0.0 {
panic!(
"Negative insert start time ({}) is bigger than track duration ({})",
insert_time, total_track_duration
);
}
if effective_insert_time > total_track_duration {
let gap_duration: Seconds = (effective_insert_time - total_track_duration).max(0.0);
self.items
.push(Item::Gap(crate::types::Gap::make_gap(gap_duration)));
self.items.push(item);
self.sanitize_preserving_all_gap_track();
return TrackInsertResult {
success: true,
..Default::default()
};
}
let containing_index = self.get_item_at_time(effective_insert_time);
let insert_index = self.get_insertion_index(effective_insert_time, insert_policy);
let mut result = TrackInsertResult {
success: true,
..Default::default()
};
if let (InsertPolicy::SplitAndInsert, Some(_i)) = (insert_policy, containing_index) {
if let Some(split) = self.split_at_time(effective_insert_time) {
result.split_clips.push(split);
}
}
result.merge(self.insert_at_index(insert_index, item, overlap_policy));
result
}
fn get_insertion_index(&self, t: Seconds, policy: InsertPolicy) -> usize {
let i = self.get_item_at_time(t).unwrap_or(self.items.len());
if i == self.items.len() {
return self.items.len();
}
match policy {
InsertPolicy::InsertBefore => i,
InsertPolicy::InsertAfter => i + 1,
InsertPolicy::InsertBeforeOrAfter => {
let start = self.start_time_of_item(i);
let end = start + self.items[i].duration().max(0.0);
let d_start = (t - start).abs();
let d_end = (end - t).abs();
if d_start <= d_end {
i
} else {
i + 1
}
}
InsertPolicy::SplitAndInsert => {
const EPS: Seconds = 1e-9;
let start = self.start_time_of_item(i);
if (t - start).abs() <= EPS {
i
} else {
i + 1
}
}
}
}
}