use super::{range_is_gap_backed, track_is_empty_boundary};
use crate::{InsertPolicy, OverlapPolicy, Seconds, Stack, TrackKind};
use std::collections::HashSet;
pub(super) struct PartnerSlotRequest<'a> {
pub primary_track_index: usize,
pub owner_track_index: usize,
pub kind: TrackKind,
pub start: Seconds,
pub end: Seconds,
pub used: &'a [usize],
pub preferred: Option<usize>,
pub horizontal: bool,
pub exclude_fallback: &'a [usize],
}
impl Stack {
pub(super) fn partner_slot_order(
&self,
primary_track_index: usize,
partner_track_index: usize,
) -> (usize, usize) {
let slot = self
.associated_track_indices(primary_track_index)
.iter()
.position(|&index| index == partner_track_index)
.unwrap_or(usize::MAX);
(slot, partner_track_index)
}
pub(super) fn partner_track_usable(
&self,
primary_track_index: usize,
track_index: usize,
start: Seconds,
end: Seconds,
overlap_policy: OverlapPolicy,
overridden_sync_ids: &HashSet<i64>,
) -> bool {
let Some(track) = self.children.get(track_index) else {
return false;
};
if track_is_empty_boundary(track) || range_is_gap_backed(track, start, end) {
return true;
}
match overlap_policy {
OverlapPolicy::Override => {
self.range_only_holds_sync_clips(track_index, start, end, overridden_sync_ids)
}
OverlapPolicy::Push => self.tracks_share_sync_clips(primary_track_index, track_index),
}
}
pub(super) fn partner_create_index(
&self,
primary_track_index: usize,
kind: TrackKind,
also_past: &[usize],
) -> usize {
let associated_of_kind: Vec<usize> = self
.associated_track_indices(primary_track_index)
.into_iter()
.chain(also_past.iter().copied())
.filter(|&index| index != primary_track_index && self.children[index].kind == kind)
.collect();
if let (Some(&lowest), Some(&highest)) = (
associated_of_kind.iter().min(),
associated_of_kind.iter().max(),
) {
return if lowest > primary_track_index {
highest + 1
} else {
lowest
};
}
match kind {
TrackKind::Video => {
let mut index = primary_track_index + 1;
while index < self.children.len() && self.children[index].kind == TrackKind::Audio {
index += 1;
}
index
}
TrackKind::Audio | TrackKind::Other => primary_track_index,
}
}
pub(super) fn create_partner_track(
&mut self,
primary_track_index: usize,
kind: TrackKind,
also_past: &[usize],
created_track_indices: &mut Vec<usize>,
) -> usize {
let insert_at = self.partner_create_index(primary_track_index, kind.clone(), also_past);
let track = self.new_numbered_track(kind);
self.children.insert(insert_at, track);
for index in created_track_indices.iter_mut() {
if *index >= insert_at {
*index += 1;
}
}
created_track_indices.push(insert_at);
insert_at
}
pub(super) fn pick_partner_track(
&mut self,
request: &PartnerSlotRequest<'_>,
usable: &dyn Fn(&Stack, usize) -> bool,
created_track_indices: &mut Vec<usize>,
) -> usize {
let primary = request.primary_track_index;
let owner = request.owner_track_index;
let associated: Vec<usize> = std::iter::once(owner)
.chain(self.associated_track_indices(owner))
.filter(|&index| index != primary && self.children[index].kind == request.kind)
.collect();
let available = |stack: &Stack, index: usize| {
index != primary && !request.used.contains(&index) && usable(stack, index)
};
if let Some(preferred) = request.preferred {
let kind_matches = self
.children
.get(preferred)
.is_some_and(|track| track.kind == request.kind);
if kind_matches
&& (request.horizontal || associated.contains(&preferred))
&& available(self, preferred)
{
return preferred;
}
}
if let Some(index) = associated
.iter()
.copied()
.find(|&index| available(self, index))
{
return index;
}
let exclude: Vec<usize> = request
.used
.iter()
.chain(request.exclude_fallback.iter())
.copied()
.collect();
if let Some(index) = self
.nearest_free_tracks_of_kind(
request.kind.clone(),
primary,
request.start,
request.end,
&exclude,
)
.into_iter()
.find(|&index| self.track_is_free(index))
{
return index;
}
if let Some(preferred) = request.preferred {
let kind_matches = self
.children
.get(preferred)
.is_some_and(|track| track.kind == request.kind);
if kind_matches
&& associated.is_empty()
&& request.used.is_empty()
&& available(self, preferred)
{
return preferred;
}
}
self.create_partner_track(owner, request.kind.clone(), request.used, created_track_indices)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn assign_audio_partner_slots(
&mut self,
dest_track_index: &mut usize,
owner_track_index: &mut usize,
cluster: &mut Vec<usize>,
created_track_indices: &mut Vec<usize>,
start: Seconds,
durations: &[Seconds],
preferred: Option<&[usize]>,
exclude_fallback: &[usize],
overlap_policy: OverlapPolicy,
overridden_sync_ids: &HashSet<i64>,
) -> Vec<usize> {
let mut slots: Vec<usize> = Vec::with_capacity(durations.len());
let mut preferred: Vec<Option<usize>> = (0..durations.len())
.map(|i| preferred.and_then(|indices| indices.get(i).copied()))
.collect();
let mut exclude_fallback: Vec<usize> = exclude_fallback.to_vec();
let horizontal = preferred.iter().any(|p| p.is_some())
&& (exclude_fallback.is_empty() || exclude_fallback.contains(dest_track_index));
for (slot, &duration) in durations.iter().enumerate() {
let end = start + duration;
let primary = *dest_track_index;
let owner = *owner_track_index;
let usable = move |stack: &Stack, index: usize| {
stack.partner_track_usable(
primary,
index,
start,
end,
overlap_policy,
overridden_sync_ids,
) || (owner != primary
&& stack.partner_track_usable(
owner,
index,
start,
end,
overlap_policy,
overridden_sync_ids,
))
};
let track_count_before = self.children.len();
let track_index = self.pick_partner_track(
&PartnerSlotRequest {
primary_track_index: primary,
owner_track_index: *owner_track_index,
kind: TrackKind::Audio,
start,
end,
used: &slots,
preferred: preferred[slot],
horizontal,
exclude_fallback: &exclude_fallback,
},
&usable,
created_track_indices,
);
if self.children.len() > track_count_before {
Self::shift_insert_track_indices_after_create(
track_index,
dest_track_index,
cluster,
&mut slots,
&mut [],
);
if track_index <= *owner_track_index {
*owner_track_index += 1;
}
for index in preferred.iter_mut().flatten() {
if *index >= track_index {
*index += 1;
}
}
for index in exclude_fallback.iter_mut() {
if *index >= track_index {
*index += 1;
}
}
}
slots.push(track_index);
}
for &slot in &slots {
if !cluster.contains(&slot) {
cluster.push(slot);
}
}
cluster.sort_unstable();
slots
}
#[allow(clippy::too_many_arguments)]
pub(super) fn pick_video_partner_track(
&mut self,
audio_track_index: usize,
start: Seconds,
end: Seconds,
preferred_video_track_id: Option<&str>,
used_video: &[usize],
sync_clips_id: Option<i64>,
overlap_policy: OverlapPolicy,
insert_policy: InsertPolicy,
created_track_indices: &mut Vec<usize>,
) -> Option<usize> {
let reusable = |stack: &Stack, index: usize| {
!used_video.contains(&index)
&& stack
.try_reuse_video_track_for_audio_move(
index,
audio_track_index,
start,
end,
sync_clips_id,
true,
overlap_policy,
insert_policy,
)
.is_some()
};
let mut candidates: Vec<usize> = self.tracks_associating(audio_track_index);
candidates.sort_by_key(|&index| (index.abs_diff(audio_track_index), index));
candidates.extend(self.associated_track_indices(audio_track_index));
let mut associated: Vec<usize> = Vec::new();
for index in candidates {
if self.children[index].kind == TrackKind::Video && !associated.contains(&index) {
associated.push(index);
}
}
let mut chosen = preferred_video_track_id
.and_then(|id| self.get_track_by_id(id))
.map(|(index, _)| index)
.filter(|&index| {
(overlap_policy == OverlapPolicy::Push
&& self.children[index].kind == TrackKind::Video
&& !used_video.contains(&index))
|| reusable(self, index)
});
if chosen.is_none() {
chosen = associated
.iter()
.copied()
.find(|&index| reusable(self, index));
}
if chosen.is_none() && !associated.is_empty() {
chosen = Some(self.create_partner_track(
audio_track_index,
TrackKind::Video,
&[],
created_track_indices,
));
}
let track_count_before = self.children.len();
let created_before = created_track_indices.len();
let track_index = match chosen {
Some(index) => index,
None => {
let index = self.find_or_create_video_track_for_audio(
audio_track_index,
start,
end - start,
created_track_indices,
sync_clips_id,
true,
overlap_policy,
insert_policy,
)?;
if self.children.len() > track_count_before {
for created in created_track_indices[..created_before].iter_mut() {
if *created >= index {
*created += 1;
}
}
}
index
}
};
Some(track_index)
}
}