Skip to content

Commit 3f50c98

Browse files
committed
[*] refactor: video-editor
1 parent 1cc0c24 commit 3f50c98

File tree

8 files changed

+1134
-469
lines changed

8 files changed

+1134
-469
lines changed

lib/video-editor/src/commands/segment.rs

Lines changed: 564 additions & 0 deletions
Large diffs are not rendered by default.

lib/video-editor/src/commands/track.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,3 +787,221 @@ impl Command for UpdateTrackMetadataCommand {
787787
format!("Update track {} metadata", self.track_index)
788788
}
789789
}
790+
791+
pub struct DetachAudioTracksCommand {
792+
track_index: usize,
793+
old_metadata: Option<Arc<Metadata>>,
794+
detached_track_indices: Option<Vec<usize>>,
795+
}
796+
797+
impl DetachAudioTracksCommand {
798+
pub fn new(track_index: usize) -> Self {
799+
Self {
800+
track_index,
801+
old_metadata: None,
802+
detached_track_indices: None,
803+
}
804+
}
805+
}
806+
807+
impl Command for DetachAudioTracksCommand {
808+
fn execute(&mut self, manager: &mut Manager) -> Result<()> {
809+
execute_detach(
810+
manager,
811+
self.track_index,
812+
DetachType::Audio,
813+
&mut self.old_metadata,
814+
&mut self.detached_track_indices,
815+
)
816+
}
817+
818+
fn undo(&mut self, manager: &mut Manager) -> Result<()> {
819+
undo_detach(
820+
manager,
821+
self.track_index,
822+
&mut self.old_metadata,
823+
&mut self.detached_track_indices,
824+
)
825+
}
826+
827+
fn describe(&self) -> String {
828+
format!("Detach audio tracks from track {}", self.track_index)
829+
}
830+
}
831+
832+
pub struct DetachSubtitleTracksCommand {
833+
track_index: usize,
834+
old_metadata: Option<Arc<Metadata>>,
835+
detached_track_indices: Option<Vec<usize>>,
836+
}
837+
838+
impl DetachSubtitleTracksCommand {
839+
pub fn new(track_index: usize) -> Self {
840+
Self {
841+
track_index,
842+
old_metadata: None,
843+
detached_track_indices: None,
844+
}
845+
}
846+
}
847+
848+
impl Command for DetachSubtitleTracksCommand {
849+
fn execute(&mut self, manager: &mut Manager) -> Result<()> {
850+
execute_detach(
851+
manager,
852+
self.track_index,
853+
DetachType::Subtitle,
854+
&mut self.old_metadata,
855+
&mut self.detached_track_indices,
856+
)
857+
}
858+
859+
fn undo(&mut self, manager: &mut Manager) -> Result<()> {
860+
undo_detach(
861+
manager,
862+
self.track_index,
863+
&mut self.old_metadata,
864+
&mut self.detached_track_indices,
865+
)
866+
}
867+
868+
fn describe(&self) -> String {
869+
format!("Detach subtitle tracks from track {}", self.track_index)
870+
}
871+
}
872+
873+
#[derive(Clone, Copy)]
874+
enum DetachType {
875+
Audio,
876+
Subtitle,
877+
}
878+
879+
fn execute_detach(
880+
manager: &mut Manager,
881+
track_index: usize,
882+
detach_type: DetachType,
883+
old_metadata: &mut Option<Arc<Metadata>>,
884+
detached_track_indices: &mut Option<Vec<usize>>,
885+
) -> Result<()> {
886+
let manager_len = manager.len();
887+
if track_index >= manager_len {
888+
return Err(Error::IndexOutOfBounds(track_index, manager_len));
889+
}
890+
891+
let track = manager
892+
.get(track_index)
893+
.ok_or_else(|| Error::IndexOutOfBounds(track_index, manager_len))?;
894+
895+
let video_track = match track {
896+
Track::Video(vt) => vt,
897+
_ => {
898+
return Err(Error::InvalidConfig(format!(
899+
"Only video tracks can have detached {}",
900+
match detach_type {
901+
DetachType::Audio => "audio",
902+
DetachType::Subtitle => "subtitles",
903+
}
904+
)));
905+
}
906+
};
907+
908+
let has_streams = match detach_type {
909+
DetachType::Audio => !video_track.track.metadata.audios.is_empty(),
910+
DetachType::Subtitle => !video_track.track.metadata.subtitles.is_empty(),
911+
};
912+
913+
if !has_streams {
914+
return Err(Error::InvalidConfig(format!(
915+
"Video track has no {} streams to detach",
916+
match detach_type {
917+
DetachType::Audio => "audio",
918+
DetachType::Subtitle => "subtitle",
919+
}
920+
)));
921+
}
922+
923+
let mut video_track_mut = video_track.as_ref().clone();
924+
*old_metadata = Some(video_track.track.metadata.clone());
925+
926+
let detached_tracks: Vec<Track> = match detach_type {
927+
DetachType::Audio => video_track_mut
928+
.detach_audio_tracks()
929+
.into_iter()
930+
.map(|at| Track::Audio(Arc::new(at)))
931+
.collect(),
932+
DetachType::Subtitle => video_track_mut
933+
.detach_subtitle_tracks()
934+
.into_iter()
935+
.map(|st| Track::Subtitle(Arc::new(st)))
936+
.collect(),
937+
};
938+
939+
if detached_tracks.is_empty() {
940+
return Err(Error::InvalidConfig(format!(
941+
"No {} tracks were created{}",
942+
match detach_type {
943+
DetachType::Audio => "audio",
944+
DetachType::Subtitle => "subtitle",
945+
},
946+
match detach_type {
947+
DetachType::Audio => "",
948+
DetachType::Subtitle => " (extraction may have failed)",
949+
}
950+
)));
951+
}
952+
953+
// Update video track metadata
954+
let new_metadata = video_track_mut.track.metadata.clone();
955+
let track = manager
956+
.get_mut(track_index)
957+
.ok_or_else(|| Error::IndexOutOfBounds(track_index, manager_len))?;
958+
959+
if let Track::Video(vt) = track {
960+
let vt = Arc::make_mut(vt);
961+
vt.track.metadata = new_metadata;
962+
}
963+
964+
// Insert detached tracks
965+
let mut inserted_indices = Vec::new();
966+
for (i, track) in detached_tracks.into_iter().enumerate() {
967+
let insert_idx = track_index + 1 + i;
968+
manager.insert_track(insert_idx, track)?;
969+
inserted_indices.push(insert_idx);
970+
}
971+
972+
*detached_track_indices = Some(inserted_indices);
973+
Ok(())
974+
}
975+
976+
fn undo_detach(
977+
manager: &mut Manager,
978+
track_index: usize,
979+
old_metadata: &mut Option<Arc<Metadata>>,
980+
detached_track_indices: &mut Option<Vec<usize>>,
981+
) -> Result<()> {
982+
let old_meta = old_metadata
983+
.take()
984+
.ok_or_else(|| Error::InvalidConfig("Command not yet executed".into()))?;
985+
986+
let detached_indices = detached_track_indices
987+
.take()
988+
.ok_or_else(|| Error::InvalidConfig("Command not yet executed".into()))?;
989+
990+
// Remove detached tracks in reverse order
991+
for idx in detached_indices.into_iter().rev() {
992+
manager.remove_track(idx)?;
993+
}
994+
995+
// Restore original metadata
996+
let manager_len = manager.len();
997+
let track = manager
998+
.get_mut(track_index)
999+
.ok_or_else(|| Error::IndexOutOfBounds(track_index, manager_len))?;
1000+
1001+
if let Track::Video(vt) = track {
1002+
let vt = Arc::make_mut(vt);
1003+
vt.track.metadata = old_meta;
1004+
}
1005+
1006+
Ok(())
1007+
}

lib/video-editor/src/tracks/video_track.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,97 @@ impl VideoTrack {
165165
audio_tracks
166166
}
167167

168+
/// Detach subtitle tracks from a specific segment within the video track.
169+
/// This creates new subtitle tracks from the segment's subtitle streams.
170+
pub fn detach_segment_subtitle_tracks(&mut self, segment_index: usize) -> Vec<SubtitleTrack> {
171+
let segment = match self.track.segments.get(segment_index) {
172+
Some(seg) => seg.clone(),
173+
None => return Vec::new(),
174+
};
175+
176+
let metadata = &segment.metadata;
177+
if metadata.subtitles.is_empty() {
178+
return Vec::new();
179+
}
180+
181+
let mut subtitle_tracks = Vec::new();
182+
183+
for subtitle_meta in &metadata.subtitles {
184+
let entries = match extract_subtitles(&metadata.path, subtitle_meta.index) {
185+
Ok(entries) => entries,
186+
Err(e) => {
187+
log::warn!(
188+
"Failed to extract subtitles from stream {}: {}",
189+
subtitle_meta.index,
190+
e
191+
);
192+
continue;
193+
}
194+
};
195+
196+
if entries.is_empty() {
197+
continue;
198+
}
199+
200+
let subtitle_metadata = Arc::new(crate::metadata::Metadata {
201+
path: metadata.path.clone(),
202+
size: metadata.size,
203+
bitrate: metadata.bitrate,
204+
duration: metadata.duration,
205+
format: metadata.format.clone(),
206+
videos: Vec::new(),
207+
audios: Vec::new(),
208+
subtitles: vec![subtitle_meta.clone()],
209+
});
210+
211+
// Filter entries to only include those within the segment's time range
212+
let segment_start = segment.source_offset;
213+
let segment_end = segment_start + segment.duration;
214+
let filtered_entries: Vec<crate::filters::SubtitleEntry> = entries
215+
.into_iter()
216+
.filter(|entry| {
217+
entry.start >= segment_start && entry.end <= segment_end
218+
})
219+
.collect();
220+
221+
if filtered_entries.is_empty() {
222+
continue;
223+
}
224+
225+
let segments: Vec<Arc<Segment>> = filtered_entries
226+
.iter()
227+
.map(|entry| {
228+
let segment_duration = entry.end.saturating_sub(entry.start);
229+
Arc::new(Segment::new_with_source_offset(
230+
entry.start, // timeline_offset
231+
entry.start, // source_offset
232+
segment_duration, // duration
233+
subtitle_metadata.clone(),
234+
))
235+
})
236+
.collect();
237+
238+
let subtitle_track = SubtitleTrack {
239+
name: String::default(),
240+
hiding: false,
241+
subtitles: filtered_entries,
242+
track: InnerTrack {
243+
metadata: subtitle_metadata,
244+
duration: segment.duration,
245+
segments,
246+
},
247+
};
248+
subtitle_tracks.push(subtitle_track);
249+
}
250+
251+
// Remove subtitle metadata from segment
252+
let segment = Arc::make_mut(&mut self.track.segments[segment_index]);
253+
let segment_metadata = Arc::make_mut(&mut segment.metadata);
254+
segment_metadata.subtitles.clear();
255+
256+
subtitle_tracks
257+
}
258+
168259
// 将视频轨道中的所有字幕轨道分离出来,并移除视频轨道中字幕轨道的信息
169260
pub fn detach_subtitle_tracks(&mut self) -> Vec<SubtitleTrack> {
170261
let metadata = &self.track.metadata;

0 commit comments

Comments
 (0)