feat: streaming video download via ImageStorage::get_stream
Some checks failed
CI / Check / Test (push) Failing after 41s
Some checks failed
CI / Check / Test (push) Failing after 41s
This commit is contained in:
@@ -74,5 +74,7 @@ sqlite-federation = { workspace = true, optional = true }
|
||||
postgres-federation = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bytes = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tower = { version = "0.5", features = ["util"] }
|
||||
http-body-util = "0.1"
|
||||
|
||||
@@ -162,19 +162,40 @@ pub async fn get_video(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> impl IntoResponse {
|
||||
let record = match state.app_ctx.repos.wrapup_repo.get_by_id(&WrapUpId::from_uuid(id)).await {
|
||||
let record = match state
|
||||
.app_ctx
|
||||
.repos
|
||||
.wrapup_repo
|
||||
.get_by_id(&WrapUpId::from_uuid(id))
|
||||
.await
|
||||
{
|
||||
Ok(Some(r)) if r.status == WrapUpStatus::Ready => r,
|
||||
_ => return StatusCode::NOT_FOUND.into_response(),
|
||||
};
|
||||
let _ = record; // used only for status check
|
||||
let _ = record;
|
||||
let video_key = format!("wrapups/{}/video.mp4", id);
|
||||
match state.app_ctx.services.image_storage.get(&video_key).await {
|
||||
Ok(bytes) => (
|
||||
StatusCode::OK,
|
||||
[(axum::http::header::CONTENT_TYPE, "video/mp4"),
|
||||
(axum::http::header::CONTENT_DISPOSITION, "attachment; filename=\"wrapup.mp4\"")],
|
||||
bytes,
|
||||
).into_response(),
|
||||
match state
|
||||
.app_ctx
|
||||
.services
|
||||
.image_storage
|
||||
.get_stream(&video_key)
|
||||
.await
|
||||
{
|
||||
Ok(stream) => {
|
||||
let body = axum::body::Body::from_stream(stream);
|
||||
(
|
||||
StatusCode::OK,
|
||||
[
|
||||
(axum::http::header::CONTENT_TYPE, "video/mp4"),
|
||||
(
|
||||
axum::http::header::CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"wrapup.mp4\"",
|
||||
),
|
||||
],
|
||||
body,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +201,13 @@ impl ImageStorage for Panic {
|
||||
async fn get(&self, _: &str) -> Result<Vec<u8>, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn get_stream(
|
||||
&self,
|
||||
_: &str,
|
||||
) -> Result<futures::stream::BoxStream<'static, Result<bytes::Bytes, DomainError>>, DomainError>
|
||||
{
|
||||
panic!()
|
||||
}
|
||||
async fn delete(&self, _: &str) -> Result<(), DomainError> {
|
||||
panic!()
|
||||
}
|
||||
@@ -657,6 +664,12 @@ pub fn make_test_state(auth_service: Arc<dyn AuthService>) -> crate::state::AppS
|
||||
allow_registration: false,
|
||||
base_url: "http://localhost:3000".to_string(),
|
||||
rate_limit: 20,
|
||||
wrapup: application::config::WrapUpConfig {
|
||||
font_path: None,
|
||||
logo_path: None,
|
||||
ffmpeg_path: "ffmpeg".into(),
|
||||
max_concurrent_renders: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
rss_renderer: Arc::new(Panic),
|
||||
|
||||
@@ -70,6 +70,13 @@ impl ImageStorage for PanicImageStorage {
|
||||
async fn get(&self, _: &str) -> Result<Vec<u8>, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn get_stream(
|
||||
&self,
|
||||
_: &str,
|
||||
) -> Result<futures::stream::BoxStream<'static, Result<bytes::Bytes, DomainError>>, DomainError>
|
||||
{
|
||||
panic!()
|
||||
}
|
||||
async fn delete(&self, _: &str) -> Result<(), DomainError> {
|
||||
panic!()
|
||||
}
|
||||
@@ -433,6 +440,12 @@ async fn test_app() -> Router {
|
||||
allow_registration: false,
|
||||
base_url: "http://localhost:3000".to_string(),
|
||||
rate_limit: 20,
|
||||
wrapup: application::config::WrapUpConfig {
|
||||
font_path: None,
|
||||
logo_path: None,
|
||||
ffmpeg_path: "ffmpeg".into(),
|
||||
max_concurrent_renders: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
rss_renderer: Arc::new(RssAdapter::new("http://localhost:3000".into())),
|
||||
|
||||
Reference in New Issue
Block a user