use fframes::error::FFramesError;
use fframes::{
AudioTimelineFrames, Duration, ResolvedAudioMap, ResolvedRenderingTimeline,
ResolvedScenesTimeline, Scenes, ScenesWithAudio, StaticMediaProvider, TimeBase, Video,
};
use futures::future::join_all;
use std::collections::HashMap;
use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
#[wasm_bindgen(module = "@fframes/editor")]
extern "C" {
#[wasm_bindgen(catch)]
async fn load_audio_wasm_callback(path: &str) -> Result<JsValue, JsValue>;
}
#[wasm_bindgen]
pub struct AudioData {
pub sample_rate: u32,
name: &'static str,
fltp_data: js_sys::Float32Array,
}
impl AudioData {
pub fn new(audio: &fframes::AudioData, name: &'static str) -> Self {
let preloaded_data = match audio {
fframes::AudioData::Preloaded(data) => data,
fframes::AudioData::Lazy => panic!("Lazy audio is not supported in wasm"),
};
Self {
name,
fltp_data: js_sys::Float32Array::from(preloaded_data.samples.as_ref()),
sample_rate: preloaded_data.sample_rate,
}
}
}
#[wasm_bindgen]
impl AudioData {
#[wasm_bindgen(getter)]
pub fn fltp_data(&self) -> js_sys::Float32Array {
self.fltp_data.clone()
}
#[wasm_bindgen(getter)]
pub fn name(&self) -> String {
self.name.to_string()
}
}
async fn resolve_used_media_durations<'a, 'media: 'a, TStaticMedia: StaticMediaProvider<'media>>(
duration: &'a Duration<'a>,
scene_audios: &'a ScenesWithAudio<'media>,
global_audio_map: &'a fframes::AudioMap<'a>,
static_media: &'media TStaticMedia,
resolve_video_duration: impl Fn(&str) -> Option<f32>,
) -> fframes::error::Result<HashMap<String, f64>> {
let mut duration_map: HashMap<String, f64> = HashMap::new();
let mut all_used_files = global_audio_map
.used_audio_files::<Vec<&str>>()
.unwrap_or_default();
if let Some(duration_audios) = duration.used_audio_files() {
all_used_files.extend(duration_audios.into_iter());
}
if let Some(duration_audios) = scene_audios.used_audio_files() {
all_used_files.extend(duration_audios.into_iter());
}
let mut video_durations: HashMap<String, f64> = HashMap::new();
for file in duration
.used_video_files()
.into_iter()
.chain(scene_audios.used_video_files())
.flatten()
{
match resolve_video_duration(file) {
Some(seconds) => {
video_durations.insert(file.to_string(), seconds as f64);
}
None => all_used_files.push(file),
}
}
if all_used_files.is_empty() {
return Ok(video_durations);
}
all_used_files.sort_unstable();
all_used_files.dedup();
let all_used_files = all_used_files
.into_iter()
.filter(|file| {
let static_audio = static_media.resolve_audio(file);
static_audio
.map(|audio| {
duration_map.insert(file.to_string(), audio.duration_in_seconds() as f64);
})
.is_none()
})
.collect::<Vec<_>>();
if all_used_files.is_empty() {
duration_map.extend(video_durations);
return Ok(duration_map);
}
join_all(
all_used_files
.iter()
.map(|file| load_audio_wasm_callback(file)),
)
.await
.into_iter()
.zip(all_used_files.into_iter())
.try_for_each(|(js_value, file)| match js_value {
Ok(val) => val
.as_f64()
.map(|seconds| {
duration_map.insert(file.to_string(), seconds);
})
.ok_or_else(|| FFramesError::CanNotProcessAudioDuration(file.to_string())),
Err(_) => Err(FFramesError::CanNotProcessAudioDuration(file.to_string())),
})?;
duration_map.extend(video_durations);
Ok(duration_map)
}
pub async fn prepare_video_with_audio<
'a,
TVideo: Video,
TStaticMedia: StaticMediaProvider<'static>,
>(
video: &TVideo,
tb: &TimeBase,
static_media: &'static TStaticMedia,
scenes: &Scenes<'static>,
resolve_video_duration: impl Fn(&str) -> Option<f32>,
) -> (
usize,
Option<ResolvedScenesTimeline<'static>>,
Option<ResolvedAudioMap<AudioTimelineFrames>>,
) {
let video_duration = video.duration();
let audio_map = video.audio();
let scene_audios = ScenesWithAudio::new(scenes);
let audio_durations = resolve_used_media_durations(
&video_duration,
&scene_audios,
&audio_map,
static_media,
resolve_video_duration,
)
.await
.unwrap();
let resolve_audio_duration_in_frames = |val: &str| {
audio_durations
.get(val)
.copied()
.ok_or_else(|| FFramesError::CanNotProcessAudioDuration(val.to_string()))
};
let ResolvedRenderingTimeline {
duration_in_frames: duration,
scenes,
audio_map,
} = fframes::resolve_timeline::<AudioTimelineFrames, _>(
&video_duration,
&scene_audios,
tb,
&audio_map,
resolve_audio_duration_in_frames,
)
.unwrap();
(duration, scenes, audio_map)
}