| Crates.io | fusion-media-provider |
| lib.rs | fusion-media-provider |
| version | 1.0.2 |
| created_at | 2026-01-09 14:15:27.167759+00 |
| updated_at | 2026-01-09 14:46:15.63751+00 |
| description | Unified media provider supporting multiple sources (Pixabay, Pexels) |
| homepage | https://github.com/tornado-product |
| repository | https://github.com/tornado-product/FusionMediaProvider.git |
| max_upload_size | |
| id | 2032156 |
| size | 164,174 |
统一的媒体下载库,支持多个免费媒体源 (Pixabay, Pexels 等)。
[dependencies]
fusion-media-provider = "xxxx"
dotenvy = "0.15"
tokio = { version = "1", features = ["full"] }
use media_downloader::{
MediaDownloader, DownloadConfig, SearchParams, MediaType,
PixabayProvider, ImageQuality,
};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建下载器
let downloader = MediaDownloader::new()
.add_provider(Arc::new(PixabayProvider::new(
std::env::var("PIXABAY_API_KEY")?
)));
// 搜索图片
let results = downloader.search(
SearchParams::new("nature", MediaType::Image).limit(10)
).await?;
println!("Found {} images", results.len());
// 下载前 5 张
let paths = downloader.download_items(&results[..5]).await;
for (i, result) in paths.iter().enumerate() {
match result {
Ok(path) => println!("Downloaded {}: {}", i + 1, path),
Err(e) => eprintln!("Failed {}: {}", i + 1, e),
}
}
Ok(())
}
所有媒体源都实现这个 trait:
#[async_trait]
pub trait MediaProvider: Send + Sync {
fn name(&self) -> &str;
async fn search_images(&self, query: &str, limit: u32, page: u32) -> Result<Vec<MediaItem>>;
async fn search_videos(&self, query: &str, limit: u32, page: u32) -> Result<Vec<MediaItem>>;
async fn get_media(&self, id: &str, media_type: MediaType) -> Result<MediaItem>;
}
pub struct MediaItem {
pub id: String,
pub media_type: MediaType, // Image or Video
pub title: String,
pub description: String,
pub tags: Vec<String>,
pub author: String,
pub provider: String, // "Pixabay", "Pexels", etc.
pub urls: MediaUrls, // 各种质量的 URL
pub metadata: MediaMetadata, // 尺寸、统计信息等
}
pub struct MediaUrls {
pub thumbnail: String, // 缩略图 (总是可用)
pub medium: Option<String>, // 中等质量
pub large: Option<String>, // 大尺寸
pub original: Option<String>, // 原始质量
pub video_files: Option<Vec<VideoFile>>, // 视频的多个分辨率
}
let config = DownloadConfig {
// 图片质量: Thumbnail, Medium, Large, Original
image_quality: ImageQuality::Large,
// 视频质量: Tiny(360p), Small(540p), Medium(720p), Large(1080p)
video_quality: VideoQuality::Large,
// 下载目录
output_dir: "./downloads".to_string(),
// 文件命名方式
use_original_names: false,
// 最大并发下载数
max_concurrent: 5,
};
let downloader = MediaDownloader::new().with_config(config);
let params = SearchParams::new("sunset", MediaType::Image)
.limit(50) // 每页结果数
.page(1); // 页码
let downloader = MediaDownloader::new()
.add_provider(Arc::new(PixabayProvider::new(pixabay_key)))
.add_provider(Arc::new(PexelsProvider::new(pexels_key)));
// 从所有源搜索
let results = downloader.search(
SearchParams::new("mountains", MediaType::Image).limit(20)
).await?;
// 结果会包含来自两个源的图片
for item in results {
println!("{}: {} (from {})", item.id, item.title, item.provider);
}
// 只从 Pixabay 搜索
let pixabay_results = downloader
.search_from_provider("Pixabay", params)
.await?;
// 只从 Pexels 搜索
let pexels_results = downloader
.search_from_provider("Pexels", params)
.await?;
let params = SearchParams::new("ocean waves", MediaType::Video)
.limit(5);
let videos = downloader.search(params).await?;
for video in &videos {
println!("Video: {}", video.title);
println!(" Duration: {}s", video.metadata.duration.unwrap_or(0));
// 查看可用的质量选项
if let Some(files) = &video.urls.video_files {
for file in files {
println!(" - {}: {}x{} ({} MB)",
file.quality, file.width, file.height,
file.size / 1_000_000);
}
}
}
// 下载视频
let paths = downloader.download_items(&videos).await;
let config = DownloadConfig {
image_quality: ImageQuality::Original,
output_dir: "./high_res_images".to_string(),
use_original_names: true,
max_concurrent: 10,
};
let downloader = MediaDownloader::new()
.with_config(config)
.add_provider(Arc::new(PixabayProvider::new(api_key)));
let results = downloader.search(
SearchParams::new("wallpaper 4k", MediaType::Image).limit(100)
).await?;
// 批量下载,最多 10 个并发
let paths = downloader.download_items(&results).await;
let item = &results[0];
match downloader.download_item(item).await {
Ok(path) => println!("Downloaded to: {}", path),
Err(e) => eprintln!("Download failed: {}", e),
}
use media_downloader::{MediaError, Result};
match downloader.search(params).await {
Ok(results) => {
println!("Success: {} items", results.len());
}
Err(MediaError::NoProviders) => {
eprintln!("No providers configured!");
}
Err(MediaError::AllProvidersFailed) => {
eprintln!("All providers failed to return results");
}
Err(MediaError::PixabayError(e)) => {
eprintln!("Pixabay error: {}", e);
}
Err(e) => {
eprintln!("Error: {}", e);
}
}
use async_trait::async_trait;
use media_downloader::{MediaProvider, MediaItem, MediaType, Result};
pub struct MyCustomProvider {
api_key: String,
}
impl MyCustomProvider {
pub fn new(api_key: String) -> Self {
Self { api_key }
}
}
#[async_trait]
impl MediaProvider for MyCustomProvider {
fn name(&self) -> &str {
"MyCustom"
}
async fn search_images(&self, query: &str, limit: u32, page: u32)
-> Result<Vec<MediaItem>>
{
// 调用你的 API
// 将响应转换为 MediaItem
todo!()
}
async fn search_videos(&self, query: &str, limit: u32, page: u32)
-> Result<Vec<MediaItem>>
{
todo!()
}
async fn get_media(&self, id: &str, media_type: MediaType)
-> Result<MediaItem>
{
todo!()
}
}
let downloader = MediaDownloader::new()
.add_provider(Arc::new(MyCustomProvider::new(api_key)));
use dotenvy::dotenv;
dotenv().ok();
let pixabay_key = std::env::var("PIXABAY_API_KEY")?;
let pexels_key = std::env::var("PEXELS_API_KEY").ok();
// 根据网络状况调整
let config = DownloadConfig {
max_concurrent: 5, // 快速网络可以更高
..Default::default()
};
let results = downloader.download_items(&items).await;
let successful: Vec<_> = results.iter()
.filter_map(|r| r.as_ref().ok())
.collect();
let failed: Vec<_> = results.iter()
.filter_map(|r| r.as_ref().err())
.collect();
println!("Downloaded: {}, Failed: {}", successful.len(), failed.len());
for (i, result) in paths.iter().enumerate() {
match result {
Ok(path) => {
println!("[{}/{}] ✓ {}", i + 1, paths.len(), path);
}
Err(e) => {
eprintln!("[{}/{}] ✗ {}", i + 1, paths.len(), e);
}
}
}
// ✅ 好 - 批量下载利用并发
let paths = downloader.download_items(&items).await;
// ❌ 不好 - 串行下载
for item in items {
downloader.download_item(item).await?;
}
// 只获取需要的数量
let params = SearchParams::new("nature", MediaType::Image)
.limit(10); // 而不是 limit(1000)
// 如果需要多次使用相同查询,缓存结果
let results = downloader.search(params).await?;
// 存储到变量中重复使用,而不是重复搜索
A: 只添加 Pixabay provider:
let downloader = MediaDownloader::new()
.add_provider(Arc::new(PixabayProvider::new(key)));
A: 设置 image_quality:
let config = DownloadConfig {
image_quality: ImageQuality::Original,
..Default::default()
};
注意: 原始质量可能需要完整 API 访问权限。
A: 默认在 ./downloads,可以通过配置修改:
let config = DownloadConfig {
output_dir: "/path/to/your/dir".to_string(),
..Default::default()
};
A: 库会返回相应错误,你需要处理:
match downloader.search(params).await {
Err(MediaError::PixabayError(e)) if e.to_string().contains("rate limit") => {
eprintln!("Rate limited, waiting...");
tokio::time::sleep(Duration::from_secs(60)).await;
}
result => result?,
}
# 设置 API keys
export PIXABAY_API_KEY=your_key
export PEXELS_API_KEY=your_key
# 运行示例
cargo run --example download
cargo test
MIT OR Apache-2.0