1use std::collections::BTreeMap;
2use std::path::{Path, PathBuf};
3
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use crate::ProcessorCapabilities;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum ContentFormatKind {
11 HlsSegments,
12 DashSegments,
13 FlvSegments,
14 SingleFile,
15 Unknown,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub enum OutputFormat {
20 Mp4,
21 Mkv,
22 Original,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub enum StreamKind {
27 Combined,
28 Video,
29 Audio,
30 SecondaryAudio,
31 Subtitle,
32 Auxiliary,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
36pub enum AssemblyMode {
37 #[default]
38 Single,
39 SeparateAudioVideo,
40 MultiAudio,
41 WithSubtitles,
42 Generic,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
46pub struct DownloadMetadata {
47 pub source_uri: Option<String>,
48 pub manifest_uri: Option<String>,
49 pub total_bytes: Option<u64>,
50 pub version: Option<String>,
51 pub etag: Option<String>,
52 pub checksum: Option<String>,
53 pub mime_type: Option<String>,
54 pub custom: BTreeMap<String, String>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub struct CompletedDownloadInfo {
59 pub asset_id: String,
60 pub task_id: Option<String>,
61 pub content_format: CompletedContentFormat,
62 pub metadata: DownloadMetadata,
63 #[serde(default)]
64 pub streams: Vec<CompletedStream>,
65 #[serde(default)]
66 pub assembly_mode: AssemblyMode,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub struct CompletedStream {
71 pub stream_id: Option<String>,
72 pub kind: StreamKind,
73 pub content_format: CompletedContentFormat,
74 pub language: Option<String>,
75 pub codec: Option<String>,
76 pub label: Option<String>,
77 pub metadata: DownloadMetadata,
78 pub quality_rank: Option<u32>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82pub enum CompletedContentFormat {
83 HlsSegments {
84 manifest_path: PathBuf,
85 segment_paths: Vec<PathBuf>,
86 },
87 DashSegments {
88 manifest_path: PathBuf,
89 segment_paths: Vec<PathBuf>,
90 },
91 FlvSegments {
92 manifest_path: PathBuf,
93 segment_paths: Vec<PathBuf>,
94 },
95 SingleFile {
96 path: PathBuf,
97 },
98}
99
100impl CompletedContentFormat {
101 pub fn kind(&self) -> ContentFormatKind {
102 match self {
103 Self::HlsSegments { .. } => ContentFormatKind::HlsSegments,
104 Self::DashSegments { .. } => ContentFormatKind::DashSegments,
105 Self::FlvSegments { .. } => ContentFormatKind::FlvSegments,
106 Self::SingleFile { .. } => ContentFormatKind::SingleFile,
107 }
108 }
109}
110
111pub trait ProcessorProgress: Send + Sync {
112 fn on_progress(&self, ratio: f32);
113
114 fn is_cancelled(&self) -> bool {
115 false
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub enum ProcessorOutput {
121 MuxedFile { path: PathBuf, format: OutputFormat },
122 Skipped,
123}
124
125#[derive(Debug, Error, Clone, PartialEq, Eq, Serialize, Deserialize)]
126pub enum ProcessorError {
127 #[error("unsupported input format: {0:?}")]
128 UnsupportedFormat(ContentFormatKind),
129 #[error("dynamic input stream {stream_index} appeared after the output header")]
130 UnsupportedDynamicStream { stream_index: u32 },
131 #[error("payload codec error: {0}")]
132 PayloadCodec(String),
133 #[error("plugin ABI violation: {0}")]
134 AbiViolation(String),
135 #[error("mux failed: {0}")]
136 MuxFailed(String),
137 #[error("output path error: {0}")]
138 OutputPath(String),
139 #[error("cancelled")]
140 Cancelled,
141}
142
143pub trait PostDownloadProcessor: Send + Sync {
144 fn name(&self) -> &str;
145
146 fn supported_input_formats(&self) -> &[ContentFormatKind];
147
148 fn capabilities(&self) -> ProcessorCapabilities {
149 ProcessorCapabilities {
150 supported_input_formats: self.supported_input_formats().to_vec(),
151 output_formats: Vec::new(),
152 supports_cancellation: true,
153 supports_assembly: false,
154 supported_assembly_modes: Vec::new(),
155 }
156 }
157
158 fn process(
159 &self,
160 input: &CompletedDownloadInfo,
161 output_path: &Path,
162 progress: &dyn ProcessorProgress,
163 ) -> Result<ProcessorOutput, ProcessorError>;
164
165 fn supports_assembly(&self) -> bool {
166 self.capabilities().supports_assembly
167 }
168
169 fn assemble(
170 &self,
171 input: &CompletedDownloadInfo,
172 _output_path: &Path,
173 _progress: &dyn ProcessorProgress,
174 ) -> Result<ProcessorOutput, ProcessorError> {
175 Err(ProcessorError::UnsupportedFormat(
176 input.content_format.kind(),
177 ))
178 }
179}