mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-04 15:05:22 +00:00
fix(youtube): correct speed and media format options
This commit is contained in:
+106
-19
@@ -281,6 +281,18 @@ fn build_media_format_options(formats_arr: &[serde_json::Value]) -> Vec<MediaFor
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for height in available_heights {
|
for height in available_heights {
|
||||||
|
if let Some(video) = best_matching_format(&clean_formats, |format| has_video_stream(format) && matches_media_height(format, height)) {
|
||||||
|
let audio = best_audio_format(&clean_formats, None);
|
||||||
|
options.push(MediaFormat {
|
||||||
|
format_id: format!("bestvideo[height<={height}]+bestaudio/best[height<={height}]"),
|
||||||
|
resolution: format!("{height}p"),
|
||||||
|
ext: "mkv".to_string(),
|
||||||
|
format_label: joined_format_label("mkv", json_str(video, "vcodec"), audio.and_then(|format| json_str(format, "acodec"))),
|
||||||
|
fps: json_f64(video, "fps"),
|
||||||
|
filesize: estimated_merged_size(Some(video), audio),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(video) = best_matching_format(&clean_formats, |format| has_video_stream(format) && matches_media_height(format, height) && json_lower(format, "ext") == "mp4") {
|
if let Some(video) = best_matching_format(&clean_formats, |format| has_video_stream(format) && matches_media_height(format, height) && json_lower(format, "ext") == "mp4") {
|
||||||
let audio = best_audio_format(&clean_formats, Some("m4a")).or_else(|| best_audio_format(&clean_formats, None));
|
let audio = best_audio_format(&clean_formats, Some("m4a")).or_else(|| best_audio_format(&clean_formats, None));
|
||||||
options.push(MediaFormat {
|
options.push(MediaFormat {
|
||||||
@@ -357,6 +369,7 @@ struct MediaProgress {
|
|||||||
speed: String,
|
speed: String,
|
||||||
eta: String,
|
eta: String,
|
||||||
size: Option<String>,
|
size: Option<String>,
|
||||||
|
downloaded_bytes: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn progress_json_number(progress: &serde_json::Value, key: &str) -> Option<f64> {
|
fn progress_json_number(progress: &serde_json::Value, key: &str) -> Option<f64> {
|
||||||
@@ -422,6 +435,7 @@ fn parse_media_progress_line(line: &str) -> Option<MediaProgress> {
|
|||||||
speed,
|
speed,
|
||||||
eta,
|
eta,
|
||||||
size,
|
size,
|
||||||
|
downloaded_bytes: (downloaded > 0.0).then_some(downloaded),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,6 +466,7 @@ fn parse_media_progress_line(line: &str) -> Option<MediaProgress> {
|
|||||||
speed,
|
speed,
|
||||||
eta,
|
eta,
|
||||||
size,
|
size,
|
||||||
|
downloaded_bytes: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,9 +492,39 @@ fn parse_media_progress_line(line: &str) -> Option<MediaProgress> {
|
|||||||
.map(|value| value.as_str().to_string())
|
.map(|value| value.as_str().to_string())
|
||||||
.unwrap_or_else(|| "-".to_string()),
|
.unwrap_or_else(|| "-".to_string()),
|
||||||
size: None,
|
size: None,
|
||||||
|
downloaded_bytes: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn media_progress_speed(progress: &MediaProgress, now: Instant, last_sample: &mut Option<(Instant, f64)>) -> String {
|
||||||
|
let Some(downloaded_bytes) = progress.downloaded_bytes else {
|
||||||
|
return progress.speed.clone();
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((last_at, last_bytes)) = *last_sample else {
|
||||||
|
*last_sample = Some((now, downloaded_bytes));
|
||||||
|
return progress.speed.clone();
|
||||||
|
};
|
||||||
|
|
||||||
|
*last_sample = Some((now, downloaded_bytes));
|
||||||
|
|
||||||
|
if downloaded_bytes < last_bytes {
|
||||||
|
return progress.speed.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let elapsed = now.duration_since(last_at).as_secs_f64();
|
||||||
|
if elapsed <= 0.05 {
|
||||||
|
return progress.speed.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes_per_second = (downloaded_bytes - last_bytes) / elapsed;
|
||||||
|
if bytes_per_second > 0.0 {
|
||||||
|
crate::download::format_speed(bytes_per_second)
|
||||||
|
} else {
|
||||||
|
progress.speed.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn cleanup_media_processing_artifacts(out_path: &std::path::Path) {
|
async fn cleanup_media_processing_artifacts(out_path: &std::path::Path) {
|
||||||
let Some(parent) = out_path.parent() else {
|
let Some(parent) = out_path.parent() else {
|
||||||
return;
|
return;
|
||||||
@@ -1845,6 +1890,7 @@ pub(crate) async fn start_media_download_internal(
|
|||||||
let _keep_alive = config_path;
|
let _keep_alive = config_path;
|
||||||
let mut current_track: f64 = 0.0;
|
let mut current_track: f64 = 0.0;
|
||||||
let mut last_fraction: f64 = 0.0;
|
let mut last_fraction: f64 = 0.0;
|
||||||
|
let mut last_speed_sample: Option<(Instant, f64)> = None;
|
||||||
let mut last_progress_at = std::time::Instant::now()
|
let mut last_progress_at = std::time::Instant::now()
|
||||||
.checked_sub(std::time::Duration::from_millis(200))
|
.checked_sub(std::time::Duration::from_millis(200))
|
||||||
.unwrap_or_else(std::time::Instant::now);
|
.unwrap_or_else(std::time::Instant::now);
|
||||||
@@ -1969,19 +2015,21 @@ pub(crate) async fn start_media_download_internal(
|
|||||||
Some(tauri_plugin_shell::process::CommandEvent::Stdout(line_bytes)) => {
|
Some(tauri_plugin_shell::process::CommandEvent::Stdout(line_bytes)) => {
|
||||||
let line = String::from_utf8_lossy(&line_bytes);
|
let line = String::from_utf8_lossy(&line_bytes);
|
||||||
if let Some(progress) = parse_media_progress_line(&line) {
|
if let Some(progress) = parse_media_progress_line(&line) {
|
||||||
if progress.fraction < last_fraction && (last_fraction - progress.fraction) > 0.5 {
|
if progress.fraction < last_fraction && (last_fraction - progress.fraction) > 0.5 {
|
||||||
current_track += 1.0;
|
current_track += 1.0;
|
||||||
}
|
last_speed_sample = None;
|
||||||
last_fraction = progress.fraction;
|
}
|
||||||
|
last_fraction = progress.fraction;
|
||||||
|
let speed = media_progress_speed(&progress, std::time::Instant::now(), &mut last_speed_sample);
|
||||||
|
|
||||||
let overall_fraction = ((current_track + progress.fraction) / total_tracks).min(1.0);
|
let overall_fraction = ((current_track + progress.fraction) / total_tracks).min(1.0);
|
||||||
|
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
if now.duration_since(last_progress_at) >= std::time::Duration::from_millis(200) {
|
if now.duration_since(last_progress_at) >= std::time::Duration::from_millis(200) {
|
||||||
let _ = app_handle.emit("download-progress", DownloadProgressEvent {
|
let _ = app_handle.emit("download-progress", DownloadProgressEvent {
|
||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
fraction: overall_fraction,
|
fraction: overall_fraction,
|
||||||
speed: progress.speed,
|
speed,
|
||||||
eta: progress.eta,
|
eta: progress.eta,
|
||||||
size: progress.size,
|
size: progress.size,
|
||||||
});
|
});
|
||||||
@@ -1994,19 +2042,21 @@ pub(crate) async fn start_media_download_internal(
|
|||||||
if let Some(progress) = parse_media_progress_line(&line) {
|
if let Some(progress) = parse_media_progress_line(&line) {
|
||||||
if progress.fraction < last_fraction && (last_fraction - progress.fraction) > 0.5 {
|
if progress.fraction < last_fraction && (last_fraction - progress.fraction) > 0.5 {
|
||||||
current_track += 1.0;
|
current_track += 1.0;
|
||||||
|
last_speed_sample = None;
|
||||||
}
|
}
|
||||||
last_fraction = progress.fraction;
|
last_fraction = progress.fraction;
|
||||||
|
let speed = media_progress_speed(&progress, std::time::Instant::now(), &mut last_speed_sample);
|
||||||
|
|
||||||
let overall_fraction = ((current_track + progress.fraction) / total_tracks).min(1.0);
|
let overall_fraction = ((current_track + progress.fraction) / total_tracks).min(1.0);
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
if now.duration_since(last_progress_at) >= std::time::Duration::from_millis(200) {
|
if now.duration_since(last_progress_at) >= std::time::Duration::from_millis(200) {
|
||||||
let _ = app_handle.emit("download-progress", DownloadProgressEvent {
|
let _ = app_handle.emit("download-progress", DownloadProgressEvent {
|
||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
fraction: overall_fraction,
|
fraction: overall_fraction,
|
||||||
speed: progress.speed,
|
speed,
|
||||||
eta: progress.eta,
|
eta: progress.eta,
|
||||||
size: progress.size,
|
size: progress.size,
|
||||||
});
|
});
|
||||||
last_progress_at = now;
|
last_progress_at = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2740,9 +2790,11 @@ fn set_extension_frontend_ready(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
build_media_format_options, collect_download_uris, is_excluded_yt_dlp_format, json_lower,
|
build_media_format_options, collect_download_uris, is_excluded_yt_dlp_format, json_lower,
|
||||||
parse_firelink_urls, parse_media_progress_line, MediaProgress, MEDIA_PROGRESS_PREFIX,
|
media_progress_speed, parse_firelink_urls, parse_media_progress_line, MediaProgress,
|
||||||
|
MEDIA_PROGRESS_PREFIX,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn collects_primary_url_and_unique_mirrors_in_order() {
|
fn collects_primary_url_and_unique_mirrors_in_order() {
|
||||||
@@ -2837,12 +2889,18 @@ mod tests {
|
|||||||
|
|
||||||
let options = build_media_format_options(&formats);
|
let options = build_media_format_options(&formats);
|
||||||
|
|
||||||
assert!(!options.iter().any(|format| format.ext == "mhtml"));
|
assert!(!options.iter().any(|format| format.ext == "mhtml"));
|
||||||
assert!(options.iter().any(|format| {
|
assert!(options.iter().any(|format| {
|
||||||
format.resolution == "1080p"
|
format.resolution == "1080p"
|
||||||
&& format.ext == "mp4"
|
&& format.ext == "mkv"
|
||||||
&& format.format_label == "MP4 • H.264 + AAC"
|
&& format.format_label == "MKV • H.264 + AAC"
|
||||||
&& format.filesize == Some(110_000_000)
|
&& format.filesize == Some(110_000_000)
|
||||||
|
}));
|
||||||
|
assert!(options.iter().any(|format| {
|
||||||
|
format.resolution == "1080p"
|
||||||
|
&& format.ext == "mp4"
|
||||||
|
&& format.format_label == "MP4 • H.264 + AAC"
|
||||||
|
&& format.filesize == Some(110_000_000)
|
||||||
}));
|
}));
|
||||||
assert!(options.iter().any(|format| {
|
assert!(options.iter().any(|format| {
|
||||||
format.resolution == "Audio only" && format.format_label == "M4A • AAC"
|
format.resolution == "Audio only" && format.format_label == "M4A • AAC"
|
||||||
@@ -2917,10 +2975,37 @@ mod tests {
|
|||||||
speed: "1.00MiB/s".to_string(),
|
speed: "1.00MiB/s".to_string(),
|
||||||
eta: "00:05".to_string(),
|
eta: "00:05".to_string(),
|
||||||
size: Some("10.00MiB".to_string()),
|
size: Some("10.00MiB".to_string()),
|
||||||
|
downloaded_bytes: Some(5242880.0),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derives_main_window_speed_from_downloaded_byte_delta() {
|
||||||
|
let first = MediaProgress {
|
||||||
|
fraction: 0.25,
|
||||||
|
speed: "fallback".to_string(),
|
||||||
|
eta: "-".to_string(),
|
||||||
|
size: None,
|
||||||
|
downloaded_bytes: Some(1_000_000.0),
|
||||||
|
};
|
||||||
|
let second = MediaProgress {
|
||||||
|
fraction: 0.5,
|
||||||
|
speed: "fallback".to_string(),
|
||||||
|
eta: "-".to_string(),
|
||||||
|
size: None,
|
||||||
|
downloaded_bytes: Some(3_097_152.0),
|
||||||
|
};
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut sample = None;
|
||||||
|
|
||||||
|
assert_eq!(media_progress_speed(&first, start, &mut sample), "fallback");
|
||||||
|
assert_eq!(
|
||||||
|
media_progress_speed(&second, start + Duration::from_secs(1), &mut sample),
|
||||||
|
"2.0 MB/s"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_aria2_external_downloader_progress() {
|
fn parses_aria2_external_downloader_progress() {
|
||||||
let line = "[#2d2636 12MiB/34MiB(34%) CN:1 DL:910KiB ETA:25s]";
|
let line = "[#2d2636 12MiB/34MiB(34%) CN:1 DL:910KiB ETA:25s]";
|
||||||
@@ -2932,6 +3017,7 @@ mod tests {
|
|||||||
speed: "910KiB/s".to_string(),
|
speed: "910KiB/s".to_string(),
|
||||||
eta: "25s".to_string(),
|
eta: "25s".to_string(),
|
||||||
size: Some("34MiB".to_string()),
|
size: Some("34MiB".to_string()),
|
||||||
|
downloaded_bytes: None,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2947,6 +3033,7 @@ mod tests {
|
|||||||
speed: "2.00MiB/s".to_string(),
|
speed: "2.00MiB/s".to_string(),
|
||||||
eta: "00:03".to_string(),
|
eta: "00:03".to_string(),
|
||||||
size: None,
|
size: None,
|
||||||
|
downloaded_bytes: None,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,13 +379,14 @@ export const AddDownloadsModal = () => {
|
|||||||
password: keychainPassword
|
password: keychainPassword
|
||||||
});
|
});
|
||||||
if (mediaData && mediaData.formats.length > 0) {
|
if (mediaData && mediaData.formats.length > 0) {
|
||||||
const mappedFormats = mediaData.formats.map(f => {
|
const mappedFormats = mediaData.formats.map(f => {
|
||||||
const quality = f.resolution || 'Best';
|
const quality = f.resolution || 'Best';
|
||||||
const bytes = f.filesize || 0;
|
const container = f.ext.toUpperCase();
|
||||||
return {
|
const bytes = f.filesize || 0;
|
||||||
name: quality,
|
return {
|
||||||
ext: f.ext,
|
name: `${quality} ${container}`,
|
||||||
bytes,
|
ext: f.ext,
|
||||||
|
bytes,
|
||||||
formatLabel: f.format_label || f.ext.toUpperCase(),
|
formatLabel: f.format_label || f.ext.toUpperCase(),
|
||||||
detail: bytes ? formatBytes(bytes) : 'Unknown size',
|
detail: bytes ? formatBytes(bytes) : 'Unknown size',
|
||||||
selector: f.format_id,
|
selector: f.format_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user