Skip to content

Commit 7d32dc9

Browse files
committed
[*] feat: video editor playlist
1 parent 564c474 commit 7d32dc9

File tree

3 files changed

+307
-0
lines changed

3 files changed

+307
-0
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use std::path::PathBuf;
2+
use video_editor::{
3+
media::MediaType,
4+
playlist::{Playlist, PlaylistItem},
5+
};
6+
7+
fn main() -> Result<(), Box<dyn std::error::Error>> {
8+
env_logger::init();
9+
10+
log::info!("=== Simple Playlist Demo ===");
11+
12+
// Step 1: Create a single playlist
13+
log::info!("Step 1: Creating a playlist...");
14+
let mut playlist =
15+
Playlist::new("My Music".to_string()).with_description("Favorite songs".to_string());
16+
17+
// Step 2: Add items to the playlist
18+
log::info!("Step 2: Adding items...");
19+
20+
let item1 = PlaylistItem::new(
21+
PathBuf::from("data/song1.mp3"),
22+
"Song 1".to_string(),
23+
MediaType::Audio,
24+
)
25+
.with_duration(std::time::Duration::from_secs(180));
26+
27+
playlist.add_item(item1);
28+
29+
let item2 = PlaylistItem::new(
30+
PathBuf::from("data/song2.mp3"),
31+
"Song 2".to_string(),
32+
MediaType::Audio,
33+
)
34+
.with_duration(std::time::Duration::from_secs(210));
35+
36+
playlist.add_item(item2);
37+
38+
log::info!(" Added {} items", playlist.item_count());
39+
40+
// Step 3: Serialize to JSON
41+
log::info!("Step 3: Serializing to JSON...");
42+
let json_string = playlist.to_json(true)?;
43+
log::info!(" JSON:\n{}", json_string);
44+
45+
// Step 4: Deserialize from JSON
46+
log::info!("Step 4: Deserializing from JSON...");
47+
let playlist2 = Playlist::from_json(&json_string)?;
48+
log::info!(
49+
" Deserialized playlist: {} with {} items",
50+
playlist2.name,
51+
playlist2.item_count()
52+
);
53+
54+
// Step 5: Create another playlist
55+
log::info!("Step 5: Creating another playlist...");
56+
let mut video_playlist = Playlist::new("Videos".to_string());
57+
58+
let video_item = PlaylistItem::new(
59+
PathBuf::from("data/video1.mp4"),
60+
"Video 1".to_string(),
61+
MediaType::Video,
62+
)
63+
.with_duration(std::time::Duration::from_secs(300));
64+
65+
video_playlist.add_item(video_item);
66+
67+
log::info!(
68+
" Created video playlist with {} items",
69+
video_playlist.item_count()
70+
);
71+
72+
// Step 6: Query and filter
73+
log::info!("Step 6: Query and filter...");
74+
75+
for (i, item) in playlist.items.iter().enumerate() {
76+
log::info!(" [{}] {} ({})", i, item.name, item.format_duration());
77+
}
78+
79+
// Get audio items
80+
let audio_items = playlist.items_by_type(MediaType::Audio);
81+
log::info!(" Audio items: {}", audio_items.len());
82+
83+
// Search items
84+
let results = playlist.search("song");
85+
log::info!(" Search results for 'song': {}", results.len());
86+
87+
// Step 7: Modify a playlist
88+
log::info!("Step 7: Modifying playlist...");
89+
90+
// Get item
91+
if let Some(item) = playlist.get_item(0) {
92+
log::info!(" First item: {}", item.name);
93+
}
94+
95+
// Move an item
96+
if playlist.item_count() > 1 {
97+
playlist.move_item(0, 1)?;
98+
log::info!(" Moved item 0 to position 1");
99+
}
100+
101+
// Remove an item
102+
if playlist.item_count() > 0 {
103+
let removed = playlist.remove_item(0)?;
104+
log::info!(" Removed item: {}", removed.name);
105+
}
106+
107+
log::info!(" Remaining items: {}", playlist.item_count());
108+
109+
// Step 8: Serialize modified playlist
110+
log::info!("Step 8: Serializing modified playlist...");
111+
let modified_json = playlist.to_json(true)?;
112+
log::info!(" Modified playlist JSON:\n{}", modified_json);
113+
114+
log::info!("=== Demo Complete ===");
115+
116+
Ok(())
117+
}

lib/video-editor/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod filters;
44
pub mod font;
55
pub mod media;
66
pub mod metadata;
7+
pub mod playlist;
78
pub mod preview;
89
pub mod project;
910
pub mod tracks;

lib/video-editor/src/playlist.rs

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
use crate::{Result, media::MediaType};
2+
use serde::{Deserialize, Serialize};
3+
use std::{fs, path::PathBuf, time::Duration, time::SystemTime};
4+
5+
#[derive(Debug, Clone, Serialize, Deserialize)]
6+
pub struct PlaylistItem {
7+
pub file_path: PathBuf,
8+
pub name: String,
9+
pub media_type: MediaType,
10+
pub duration: Option<Duration>,
11+
pub file_size: u64,
12+
}
13+
14+
impl PlaylistItem {
15+
pub fn new(file_path: PathBuf, name: String, media_type: MediaType) -> Self {
16+
// Convert to absolute path
17+
let absolute_path = file_path.canonicalize().unwrap_or_else(|_| {
18+
// If canonicalize fails (file doesn't exist), use absolute path
19+
std::path::absolute(&file_path).unwrap_or(file_path.clone())
20+
});
21+
22+
let file_size = fs::metadata(&absolute_path).map(|m| m.len()).unwrap_or(0);
23+
24+
Self {
25+
file_path: absolute_path,
26+
name,
27+
media_type,
28+
duration: None,
29+
file_size,
30+
}
31+
}
32+
33+
pub fn with_duration(mut self, duration: Duration) -> Self {
34+
self.duration = Some(duration);
35+
self
36+
}
37+
38+
pub fn format_duration(&self) -> String {
39+
if let Some(duration) = self.duration {
40+
let secs = duration.as_secs();
41+
let minutes = secs / 60;
42+
let seconds = secs % 60;
43+
format!("{:02}:{:02}", minutes, seconds)
44+
} else {
45+
"--:--".to_string()
46+
}
47+
}
48+
49+
pub fn format_file_size(&self) -> String {
50+
cutil::str::pretty_size_string(self.file_size)
51+
}
52+
}
53+
54+
#[derive(Debug, Clone, Serialize, Deserialize)]
55+
pub struct Playlist {
56+
pub name: String,
57+
pub description: Option<String>,
58+
59+
#[serde(default)]
60+
pub items: Vec<PlaylistItem>,
61+
62+
#[serde(default = "SystemTime::now")]
63+
pub created_at: SystemTime,
64+
65+
#[serde(default = "SystemTime::now")]
66+
pub modified_at: SystemTime,
67+
68+
pub thumbnail_path: Option<PathBuf>,
69+
}
70+
71+
impl Playlist {
72+
pub fn new(name: String) -> Self {
73+
let now = SystemTime::now();
74+
Self {
75+
name,
76+
description: None,
77+
items: Vec::new(),
78+
created_at: now,
79+
modified_at: now,
80+
thumbnail_path: None,
81+
}
82+
}
83+
84+
pub fn with_description(mut self, description: String) -> Self {
85+
self.description = Some(description);
86+
self
87+
}
88+
89+
pub fn add_item(&mut self, item: PlaylistItem) {
90+
self.items.push(item);
91+
self.modified_at = SystemTime::now();
92+
}
93+
94+
pub fn remove_item(&mut self, index: usize) -> Result<PlaylistItem> {
95+
if index >= self.items.len() {
96+
return Err(crate::Error::IndexOutOfBounds(index, self.items.len()));
97+
}
98+
99+
self.modified_at = SystemTime::now();
100+
let item = self.items.remove(index);
101+
Ok(item)
102+
}
103+
104+
pub fn get_item(&self, index: usize) -> Option<&PlaylistItem> {
105+
self.items.get(index)
106+
}
107+
108+
pub fn get_item_mut(&mut self, index: usize) -> Option<&mut PlaylistItem> {
109+
self.modified_at = SystemTime::now();
110+
self.items.get_mut(index)
111+
}
112+
113+
pub fn update_item(&mut self, index: usize, item: PlaylistItem) -> Result<()> {
114+
if index >= self.items.len() {
115+
return Err(crate::Error::IndexOutOfBounds(index, self.items.len()));
116+
}
117+
118+
self.items[index] = item;
119+
self.modified_at = SystemTime::now();
120+
121+
Ok(())
122+
}
123+
124+
pub fn move_item(&mut self, from_index: usize, to_index: usize) -> Result<()> {
125+
if from_index >= self.items.len() || to_index >= self.items.len() {
126+
return Err(crate::Error::IndexOutOfBounds(
127+
from_index.max(to_index),
128+
self.items.len(),
129+
));
130+
}
131+
132+
if from_index == to_index {
133+
return Ok(());
134+
}
135+
136+
let item = self.items.remove(from_index);
137+
self.items.insert(to_index, item);
138+
self.modified_at = SystemTime::now();
139+
140+
Ok(())
141+
}
142+
143+
pub fn clear(&mut self) {
144+
self.items.clear();
145+
self.modified_at = SystemTime::now();
146+
}
147+
148+
pub fn item_count(&self) -> usize {
149+
self.items.len()
150+
}
151+
152+
pub fn is_empty(&self) -> bool {
153+
self.items.is_empty()
154+
}
155+
156+
pub fn items_by_type(&self, media_type: MediaType) -> Vec<&PlaylistItem> {
157+
self.items
158+
.iter()
159+
.filter(|item| item.media_type == media_type)
160+
.collect()
161+
}
162+
163+
pub fn search(&self, query: &str) -> Vec<&PlaylistItem> {
164+
let query_lower = query.to_lowercase();
165+
self.items
166+
.iter()
167+
.filter(|item| {
168+
item.name.to_lowercase().contains(&query_lower)
169+
|| item
170+
.file_path
171+
.to_string_lossy()
172+
.to_lowercase()
173+
.contains(&query_lower)
174+
})
175+
.collect()
176+
}
177+
178+
pub fn to_json(&self, pretty: bool) -> Result<String> {
179+
if pretty {
180+
serde_json::to_string_pretty(self).map_err(|e| crate::Error::Json(e))
181+
} else {
182+
serde_json::to_string(self).map_err(|e| crate::Error::Json(e))
183+
}
184+
}
185+
186+
pub fn from_json(json: &str) -> Result<Self> {
187+
serde_json::from_str(json).map_err(|e| crate::Error::Json(e))
188+
}
189+
}

0 commit comments

Comments
 (0)