feat: resize images

This commit is contained in:
kieran 2025-01-14 11:17:37 +00:00
parent 7e9e61d14c
commit 62a7933de0
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
15 changed files with 212 additions and 161 deletions

35
Cargo.lock generated
View File

@ -1362,11 +1362,11 @@ dependencies = [
[[package]] [[package]]
name = "directories" name = "directories"
version = "5.0.1" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
dependencies = [ dependencies = [
"dirs-sys", "dirs-sys 0.5.0",
] ]
[[package]] [[package]]
@ -1375,7 +1375,7 @@ version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [ dependencies = [
"dirs-sys", "dirs-sys 0.4.1",
] ]
[[package]] [[package]]
@ -1386,10 +1386,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users", "redox_users 0.4.6",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "dirs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users 0.5.0",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "dispatch" name = "dispatch"
version = "0.2.0" version = "0.2.0"
@ -1512,7 +1524,7 @@ dependencies = [
[[package]] [[package]]
name = "egui-video" name = "egui-video"
version = "0.8.0" version = "0.8.0"
source = "git+https://github.com/v0l/egui-video.git?rev=d2ea3b4db21eb870a207db19e4cd21c7d1d24836#d2ea3b4db21eb870a207db19e4cd21c7d1d24836" source = "git+https://github.com/v0l/egui-video.git?rev=11db7d0c30070529a36bfb050844cdb75c32902b#11db7d0c30070529a36bfb050844cdb75c32902b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"atomic", "atomic",
@ -4517,6 +4529,17 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]]
name = "redox_users"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [
"getrandom",
"libredox",
"thiserror 2.0.9",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.11.0" version = "1.11.0"

View File

@ -18,8 +18,8 @@ bech32 = "0.11.0"
anyhow = "^1.0.91" anyhow = "^1.0.91"
itertools = "0.14.0" itertools = "0.14.0"
serde = { version = "1.0.214", features = ["derive"] } serde = { version = "1.0.214", features = ["derive"] }
directories = "5.0.1" directories = "6.0.0"
egui-video = { git = "https://github.com/v0l/egui-video.git", rev = "d2ea3b4db21eb870a207db19e4cd21c7d1d24836" } egui-video = { git = "https://github.com/v0l/egui-video.git", rev = "11db7d0c30070529a36bfb050844cdb75c32902b" }
egui_qr = { git = "https://git.v0l.io/Kieran/egui_qr.git", rev = "f9cf52b7eae353fa9e59ed0358151211d48824d1" } egui_qr = { git = "https://git.v0l.io/Kieran/egui_qr.git", rev = "f9cf52b7eae353fa9e59ed0358151211d48824d1" }
# notedeck stuff # notedeck stuff

View File

@ -62,6 +62,12 @@ impl ZapStreamApp {
.insert(FontFamily::Proportional, vec!["Outfit".to_string()]); .insert(FontFamily::Proportional, vec!["Outfit".to_string()]);
cc.egui_ctx.set_fonts(fd); cc.egui_ctx.set_fonts(fd);
// ffmpeg log redirect
unsafe {
egui_video::ffmpeg_sys_the_third::av_log_set_callback(Some(
egui_video::ffmpeg_rs_raw::av_log_redirect,
));
}
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
Self { Self {
current: RouteType::HomePage, current: RouteType::HomePage,
@ -103,6 +109,8 @@ impl notedeck::App for ZapStreamApp {
}, },
); );
//ui.ctx().set_debug_on_hover(true);
let app_frame = egui::containers::Frame::default().outer_margin(self.frame_margin()); let app_frame = egui::containers::Frame::default().outer_margin(self.frame_margin());
// handle app state changes // handle app state changes

View File

@ -2,7 +2,7 @@ use anyhow::Result;
use directories::ProjectDirs; use directories::ProjectDirs;
use eframe::Renderer; use eframe::Renderer;
use egui::{Vec2, ViewportBuilder}; use egui::{Vec2, ViewportBuilder};
use log::error; use log::{error, info};
use zap_stream_app::app::ZapStreamApp; use zap_stream_app::app::ZapStreamApp;
#[tokio::main] #[tokio::main]
@ -13,11 +13,12 @@ async fn main() -> Result<()> {
options.viewport = ViewportBuilder::default().with_inner_size(Vec2::new(1300., 900.)); options.viewport = ViewportBuilder::default().with_inner_size(Vec2::new(1300., 900.));
options.renderer = Renderer::Glow; options.renderer = Renderer::Glow;
let data_path = ProjectDirs::from("stream", "zap", "app") let data_path = ProjectDirs::from("stream", "zap", "zap_stream_app")
.unwrap() .unwrap()
.config_dir() .data_dir()
.to_path_buf(); .to_path_buf();
info!("Data path: {}", data_path.display());
if let Err(e) = eframe::run_native( if let Err(e) = eframe::run_native(
"zap.stream", "zap.stream",
options, options,

View File

@ -1,13 +1,4 @@
<svg width="33" height="24" viewBox="0 0 33 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="33" height="24" viewBox="0 0 33 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4303_1419)"> <path d="M32.7877 2.41234C32.3558 1.36818 31.3439 0.691406 30.216 0.691406H10.6802C10.6738 0.691406 10.6673 0.691406 10.6609 0.691406C10.6545 0.691406 10.648 0.691406 10.6416 0.691406C6.54235 0.691406 3.21012 4.02369 3.21012 8.11654C3.21012 10.2693 4.13825 12.2738 5.70446 13.6661L0.812466 18.5581C0.0132476 19.3574 -0.225229 20.5498 0.206607 21.5875C0.638443 22.6316 1.65036 23.3084 2.77829 23.3084H22.314C22.3205 23.3084 22.3269 23.3084 22.3398 23.3084C22.3463 23.3084 22.3527 23.3084 22.3656 23.3084C26.4584 23.3084 29.7906 19.9761 29.7906 15.8833C29.7906 13.7305 28.8625 11.726 27.2963 10.3338L32.1882 5.44169C32.981 4.64245 33.2195 3.45005 32.7877 2.41234ZM2.71383 20.5498C2.6945 20.5047 2.70739 20.4918 2.73317 20.466L8.10856 15.0905L22.3914 20.1437C22.4043 20.1502 22.4236 20.1566 22.4365 20.1566C22.4558 20.163 22.4752 20.1695 22.4945 20.1824C22.5267 20.2017 22.5525 20.2339 22.5718 20.2726C22.5783 20.2791 22.5783 20.2855 22.5847 20.292C22.5912 20.3048 22.5912 20.3177 22.5912 20.3306C22.5912 20.3435 22.5976 20.3564 22.5976 20.3693C22.5976 20.4853 22.4687 20.5884 22.3269 20.5884H2.78473C2.75251 20.6013 2.73317 20.6013 2.71383 20.5498ZM25.208 19.6474C25.208 19.641 25.2015 19.6281 25.2015 19.6216C25.1757 19.5185 25.1435 19.4218 25.1048 19.3251C25.0984 19.3122 25.0984 19.2994 25.092 19.2865C25.0533 19.1833 25.0017 19.0867 24.9502 18.99C24.9437 18.9771 24.9308 18.9577 24.9244 18.9449C24.8148 18.7515 24.6859 18.571 24.5377 18.4034C24.5248 18.3905 24.5119 18.3777 24.499 18.3648C24.4216 18.2874 24.3443 18.2101 24.2605 18.1392C24.2476 18.1327 24.2347 18.1198 24.2283 18.1134C24.1509 18.0489 24.0672 17.9909 23.9769 17.9329C23.964 17.9265 23.9511 17.9136 23.9382 17.9071C23.848 17.8491 23.7513 17.7976 23.6547 17.746C23.6353 17.7331 23.6095 17.7267 23.5902 17.7138C23.4935 17.6687 23.3904 17.6235 23.2808 17.5913L16.6744 15.2516L9.03668 12.551L9.0109 12.5445C9.00446 12.5445 9.00446 12.5445 8.99801 12.5381C8.8111 12.4672 8.62418 12.3834 8.43727 12.2867C6.88395 11.4682 5.9236 9.86969 5.9236 8.11654C5.9236 6.58253 6.65836 5.2161 7.79918 4.35241C7.79918 4.35885 7.80563 4.37175 7.80563 4.37819C7.83141 4.47487 7.86364 4.57155 7.90231 4.66823C7.9152 4.70046 7.92809 4.73269 7.94098 4.76492C7.97321 4.83582 8.00543 4.90027 8.03766 4.97117C8.05055 5.0034 8.06989 5.03562 8.08278 5.06141C8.1279 5.1452 8.17946 5.22254 8.23747 5.29989C8.26325 5.33211 8.28258 5.36434 8.30836 5.39657C8.35348 5.45458 8.3986 5.51259 8.45016 5.56415C8.4695 5.58993 8.48883 5.61571 8.51462 5.63505C8.57907 5.70595 8.65641 5.7704 8.72731 5.83486C8.75309 5.86064 8.77887 5.87998 8.8111 5.89931C8.882 5.95732 8.9529 6.00888 9.03024 6.06045C9.04313 6.07334 9.05602 6.07978 9.07536 6.09267C9.16559 6.15068 9.26227 6.20225 9.35895 6.24736C9.38473 6.26026 9.41051 6.27315 9.43629 6.28604C9.53942 6.33116 9.64254 6.37627 9.74567 6.4085L24.0091 11.4553C24.0156 11.4553 24.022 11.4617 24.022 11.4617C24.209 11.5326 24.3894 11.61 24.5634 11.7066C26.1168 12.5252 27.0771 14.1237 27.0771 15.8768C27.0836 17.4173 26.3423 18.7837 25.208 19.6474ZM30.2675 3.52739L24.8922 8.90288C24.8793 8.89643 24.8599 8.88999 24.847 8.88354L10.6222 3.85611C10.6029 3.84967 10.5836 3.84322 10.5707 3.83677H10.5642C10.5449 3.83033 10.532 3.82388 10.5127 3.81099C10.5062 3.80455 10.4998 3.7981 10.4933 3.79166C10.4804 3.78521 10.474 3.77232 10.4675 3.76588C10.4611 3.75943 10.4547 3.74654 10.4547 3.74009C10.4482 3.7272 10.4418 3.71431 10.4353 3.70142C10.4353 3.69498 10.4289 3.68208 10.4289 3.66919C10.4289 3.6563 10.4224 3.63697 10.4224 3.61763C10.4224 3.60474 10.4289 3.59829 10.4289 3.5854C10.4482 3.48872 10.5642 3.40493 10.6867 3.40493H30.2289C30.2611 3.40493 30.2804 3.40493 30.2998 3.45005C30.3062 3.48872 30.2933 3.50806 30.2675 3.52739Z"
<path d="M4.45977 2.75527L4.44727 2.74902C4.44727 2.74902 4.45352 2.74902 4.45977 2.75527Z" fill="white"/> fill="white"/>
<path d="M8.97119 1.70361L9.00244 1.71611C8.98994 1.70986 8.98369 1.70361 8.97119 1.70361Z" fill="white"/>
<path d="M9.01006 1.71762C9.00381 1.71137 9.00381 1.71762 9.01006 1.71762V1.71762Z" fill="white"/>
<path d="M32.7877 2.41234C32.3558 1.36818 31.3439 0.691406 30.216 0.691406H10.6802C10.6738 0.691406 10.6673 0.691406 10.6609 0.691406C10.6545 0.691406 10.648 0.691406 10.6416 0.691406C6.54235 0.691406 3.21012 4.02369 3.21012 8.11654C3.21012 10.2693 4.13825 12.2738 5.70446 13.6661L0.812466 18.5581C0.0132476 19.3574 -0.225229 20.5498 0.206607 21.5875C0.638443 22.6316 1.65036 23.3084 2.77829 23.3084H22.314C22.3205 23.3084 22.3269 23.3084 22.3398 23.3084C22.3463 23.3084 22.3527 23.3084 22.3656 23.3084C26.4584 23.3084 29.7906 19.9761 29.7906 15.8833C29.7906 13.7305 28.8625 11.726 27.2963 10.3338L32.1882 5.44169C32.981 4.64245 33.2195 3.45005 32.7877 2.41234ZM2.71383 20.5498C2.6945 20.5047 2.70739 20.4918 2.73317 20.466L8.10856 15.0905L22.3914 20.1437C22.4043 20.1502 22.4236 20.1566 22.4365 20.1566C22.4558 20.163 22.4752 20.1695 22.4945 20.1824C22.5267 20.2017 22.5525 20.2339 22.5718 20.2726C22.5783 20.2791 22.5783 20.2855 22.5847 20.292C22.5912 20.3048 22.5912 20.3177 22.5912 20.3306C22.5912 20.3435 22.5976 20.3564 22.5976 20.3693C22.5976 20.4853 22.4687 20.5884 22.3269 20.5884H2.78473C2.75251 20.6013 2.73317 20.6013 2.71383 20.5498ZM25.208 19.6474C25.208 19.641 25.2015 19.6281 25.2015 19.6216C25.1757 19.5185 25.1435 19.4218 25.1048 19.3251C25.0984 19.3122 25.0984 19.2994 25.092 19.2865C25.0533 19.1833 25.0017 19.0867 24.9502 18.99C24.9437 18.9771 24.9308 18.9577 24.9244 18.9449C24.8148 18.7515 24.6859 18.571 24.5377 18.4034C24.5248 18.3905 24.5119 18.3777 24.499 18.3648C24.4216 18.2874 24.3443 18.2101 24.2605 18.1392C24.2476 18.1327 24.2347 18.1198 24.2283 18.1134C24.1509 18.0489 24.0672 17.9909 23.9769 17.9329C23.964 17.9265 23.9511 17.9136 23.9382 17.9071C23.848 17.8491 23.7513 17.7976 23.6547 17.746C23.6353 17.7331 23.6095 17.7267 23.5902 17.7138C23.4935 17.6687 23.3904 17.6235 23.2808 17.5913L16.6744 15.2516L9.03668 12.551L9.0109 12.5445C9.00446 12.5445 9.00446 12.5445 8.99801 12.5381C8.8111 12.4672 8.62418 12.3834 8.43727 12.2867C6.88395 11.4682 5.9236 9.86969 5.9236 8.11654C5.9236 6.58253 6.65836 5.2161 7.79918 4.35241C7.79918 4.35885 7.80563 4.37175 7.80563 4.37819C7.83141 4.47487 7.86364 4.57155 7.90231 4.66823C7.9152 4.70046 7.92809 4.73269 7.94098 4.76492C7.97321 4.83582 8.00543 4.90027 8.03766 4.97117C8.05055 5.0034 8.06989 5.03562 8.08278 5.06141C8.1279 5.1452 8.17946 5.22254 8.23747 5.29989C8.26325 5.33211 8.28258 5.36434 8.30836 5.39657C8.35348 5.45458 8.3986 5.51259 8.45016 5.56415C8.4695 5.58993 8.48883 5.61571 8.51462 5.63505C8.57907 5.70595 8.65641 5.7704 8.72731 5.83486C8.75309 5.86064 8.77887 5.87998 8.8111 5.89931C8.882 5.95732 8.9529 6.00888 9.03024 6.06045C9.04313 6.07334 9.05602 6.07978 9.07536 6.09267C9.16559 6.15068 9.26227 6.20225 9.35895 6.24736C9.38473 6.26026 9.41051 6.27315 9.43629 6.28604C9.53942 6.33116 9.64254 6.37627 9.74567 6.4085L24.0091 11.4553C24.0156 11.4553 24.022 11.4617 24.022 11.4617C24.209 11.5326 24.3894 11.61 24.5634 11.7066C26.1168 12.5252 27.0771 14.1237 27.0771 15.8768C27.0836 17.4173 26.3423 18.7837 25.208 19.6474ZM30.2675 3.52739L24.8922 8.90288C24.8793 8.89643 24.8599 8.88999 24.847 8.88354L10.6222 3.85611C10.6029 3.84967 10.5836 3.84322 10.5707 3.83677H10.5642C10.5449 3.83033 10.532 3.82388 10.5127 3.81099C10.5062 3.80455 10.4998 3.7981 10.4933 3.79166C10.4804 3.78521 10.474 3.77232 10.4675 3.76588C10.4611 3.75943 10.4547 3.74654 10.4547 3.74009C10.4482 3.7272 10.4418 3.71431 10.4353 3.70142C10.4353 3.69498 10.4289 3.68208 10.4289 3.66919C10.4289 3.6563 10.4224 3.63697 10.4224 3.61763C10.4224 3.60474 10.4289 3.59829 10.4289 3.5854C10.4482 3.48872 10.5642 3.40493 10.6867 3.40493H30.2289C30.2611 3.40493 30.2804 3.40493 30.2998 3.45005C30.3062 3.48872 30.2933 3.50806 30.2675 3.52739Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_4303_1419">
<rect width="33" height="22.617" fill="white" transform="translate(0 0.691406)"/>
</clipPath>
</defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -23,7 +23,7 @@ impl HomePage {
} }
fn get_filters() -> Vec<Filter> { fn get_filters() -> Vec<Filter> {
vec![Filter::new().kinds([30_311]).limit(100).build()] vec![Filter::new().kinds([30_311, 30_313]).limit(100).build()]
} }
} }

View File

@ -3,7 +3,8 @@ use crate::services::ffmpeg_loader::FfmpegLoader;
use crate::widgets::PlaceholderRect; use crate::widgets::PlaceholderRect;
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use egui::load::SizedTexture; use egui::load::SizedTexture;
use egui::{Context, Id, Image, ImageSource, TextureHandle, Ui}; use egui::{Context, Id, Image, ImageSource, TextureHandle, Ui, Vec2};
use egui_video::ffmpeg_rs_raw::Transcoder;
use ehttp::Response; use ehttp::Response;
use enostr::EventClientMessage; use enostr::EventClientMessage;
use lnurl::lightning_address::LightningAddress; use lnurl::lightning_address::LightningAddress;
@ -187,18 +188,31 @@ impl<'a, 'ctx> RouteServices<'a, 'ctx> {
} }
} }
const BLACK_PIXEL: [u8; 4] = [0, 0, 0, 0]; const LOGO_BYTES: &[u8] = include_bytes!("../resources/logo.svg");
pub fn image_from_cache<'a>(img_cache: &mut ImageCache, ui: &Ui, url: &str) -> Image<'a> { pub fn image_from_cache<'a>(
if let Some(promise) = img_cache.map().get(url) { img_cache: &mut ImageCache,
ui: &Ui,
url: &str,
size: Option<Vec2>,
) -> Image<'a> {
let cache_key = if let Some(s) = size {
format!("{}:{}", url, s)
} else {
url.to_string()
};
if url.len() == 0 {
return Image::from_bytes(cache_key, LOGO_BYTES);
}
if let Some(promise) = img_cache.map().get(&cache_key) {
match promise.poll() { match promise.poll() {
Poll::Ready(Ok(t)) => Image::new(SizedTexture::from_handle(t)), Poll::Ready(Ok(t)) => Image::new(SizedTexture::from_handle(t)),
_ => Image::from_bytes(url.to_string(), &BLACK_PIXEL), _ => Image::from_bytes(url.to_string(), LOGO_BYTES),
} }
} else { } else {
let fetch = fetch_img(img_cache, ui.ctx(), url); let fetch = fetch_img(img_cache, ui.ctx(), url, size);
img_cache.map_mut().insert(url.to_string(), fetch); img_cache.map_mut().insert(cache_key.clone(), fetch);
Image::from_bytes(url.to_string(), &BLACK_PIXEL) Image::from_bytes(cache_key, LOGO_BYTES)
} }
} }
@ -206,52 +220,48 @@ fn fetch_img(
img_cache: &ImageCache, img_cache: &ImageCache,
ctx: &Context, ctx: &Context,
url: &str, url: &str,
size: Option<Vec2>,
) -> Promise<notedeck::Result<TextureHandle>> { ) -> Promise<notedeck::Result<TextureHandle>> {
let k = ImageCache::key(url); let name = ImageCache::key(url);
let dst_path = img_cache.cache_dir.join(k); let dst_path = img_cache.cache_dir.join(&name);
if dst_path.exists() { if dst_path.exists() {
let ctx = ctx.clone(); let ctx = ctx.clone();
let url = url.to_owned(); Promise::spawn_thread("load_from_disk", move || {
let dst_path = dst_path.clone();
Promise::spawn_blocking(move || {
info!("Loading image from disk: {}", dst_path.display()); info!("Loading image from disk: {}", dst_path.display());
match FfmpegLoader::new().load_image(dst_path) { match FfmpegLoader::new().load_image(dst_path, size) {
Ok(img) => Ok(ctx.load_texture(&url, img, Default::default())), Ok(img) => Ok(ctx.load_texture(&name, img, Default::default())),
Err(e) => Err(notedeck::Error::Generic(e.to_string())), Err(e) => Err(notedeck::Error::Generic(e.to_string())),
} }
}) })
} else { } else {
fetch_img_from_net(&dst_path, ctx, url) let url = url.to_string();
let ctx = ctx.clone();
Promise::spawn_thread("load_from_net", move || {
let img = match fetch_img_from_net(&url).block_and_take() {
Ok(img) => img,
Err(e) => return Err(notedeck::Error::Generic(e.to_string())),
};
std::fs::create_dir_all(&dst_path.parent().unwrap()).unwrap();
std::fs::write(&dst_path, &img.bytes).unwrap();
info!("Loading image from net: {}", &url);
match FfmpegLoader::new().load_image(dst_path, size) {
Ok(img) => {
ctx.request_repaint();
Ok(ctx.load_texture(&name, img, Default::default()))
}
Err(e) => Err(notedeck::Error::Generic(e.to_string())),
}
})
} }
} }
fn fetch_img_from_net( fn fetch_img_from_net(url: &str) -> Promise<ehttp::Result<Response>> {
cache_path: &Path,
ctx: &Context,
url: &str,
) -> Promise<notedeck::Result<TextureHandle>> {
let (sender, promise) = Promise::new(); let (sender, promise) = Promise::new();
let request = ehttp::Request::get(url); let request = ehttp::Request::get(url);
let ctx = ctx.clone(); info!("Downloaded image: {}", url);
let cloned_url = url.to_owned();
let cache_path = cache_path.to_owned();
ehttp::fetch(request, move |response| { ehttp::fetch(request, move |response| {
let handle = response sender.send(response);
.and_then(|img| {
std::fs::create_dir_all(cache_path.parent().unwrap()).unwrap();
std::fs::write(&cache_path, &img.bytes).unwrap();
info!("Loading image from net: {}", cloned_url);
let img_loaded = FfmpegLoader::new()
.load_image(cache_path)
.map_err(|e| e.to_string())?;
Ok(ctx.load_texture(&cloned_url, img_loaded, Default::default()))
})
.map_err(notedeck::Error::Generic);
sender.send(handle);
ctx.request_repaint();
}); });
promise promise
} }

View File

@ -35,8 +35,12 @@ impl NostrWidget for ProfilePage {
ui.spacing_mut().item_spacing.y = 8.0; ui.spacing_mut().item_spacing.y = 8.0;
if let Some(banner) = profile.map(|p| p.banner()).flatten() { if let Some(banner) = profile.map(|p| p.banner()).flatten() {
image_from_cache(&mut services.ctx.img_cache, ui, banner) image_from_cache(
.fit_to_exact_size(vec2(ui.available_width(), 360.0)) &mut services.ctx.img_cache,
ui,
banner,
Some(vec2(ui.available_width(), 360.0)),
)
.rounding(ROUNDING_DEFAULT) .rounding(ROUNDING_DEFAULT)
.ui(ui); .ui(ui);
} else { } else {

View File

@ -1,14 +1,14 @@
use crate::link::NostrLink; use crate::link::NostrLink;
use crate::route::RouteServices; use crate::route::RouteServices;
use crate::stream_info::StreamInfo;
use crate::theme::{MARGIN_DEFAULT, NEUTRAL_800, ROUNDING_DEFAULT}; use crate::theme::{MARGIN_DEFAULT, NEUTRAL_800, ROUNDING_DEFAULT};
use crate::widgets::{ use crate::widgets::{
sub_or_poll, Chat, NostrWidget, PlaceholderRect, StreamPlayer, StreamTitle, WriteChat, sub_or_poll, Chat, NostrWidget, PlaceholderRect, StreamPlayer, StreamTitle, WriteChat,
}; };
use egui::{vec2, Align, Frame, Layout, Response, Stroke, Ui, Vec2, Widget}; use egui::{vec2, Align, Frame, Layout, Response, ScrollArea, Stroke, Ui, Vec2, Widget};
use nostrdb::{Filter, Note}; use nostrdb::{Filter, Note};
use crate::note_ref::NoteRef; use crate::note_ref::NoteRef;
use crate::stream_info::StreamInfo;
use crate::sub::SubRef; use crate::sub::SubRef;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashSet; use std::collections::HashSet;
@ -95,20 +95,23 @@ impl StreamPage {
let video_width = ui.available_width() - chat_w; let video_width = ui.available_width() - chat_w;
let video_height = max_h.min((video_width / 16.0) * 9.0); let video_height = max_h.min((video_width / 16.0) * 9.0);
ui.with_layout( ui.horizontal(|ui| {
Layout::left_to_right(Align::TOP).with_main_justify(true), ui.allocate_ui_with_layout(
vec2(video_width, max_h),
Layout::top_down_justified(Align::Min),
|ui| { |ui| {
ui.vertical(|ui| { ScrollArea::vertical().show(ui, |ui| {
ui.allocate_ui(vec2(video_width, video_height), |ui| {
if let Some(player) = &mut self.player { if let Some(player) = &mut self.player {
player.ui(ui) ui.add_sized(vec2(video_width, video_height), player);
} else { } else {
ui.add(PlaceholderRect) ui.add_sized(vec2(video_width, video_height), PlaceholderRect);
} }
});
ui.add_space(10.); ui.add_space(10.);
StreamTitle::new(event).render(ui, services); StreamTitle::new(event).render(ui, services);
}); });
},
);
ui.allocate_ui_with_layout( ui.allocate_ui_with_layout(
vec2(chat_w, max_h), vec2(chat_w, max_h),
Layout::top_down_justified(Align::Min), Layout::top_down_justified(Align::Min),
@ -136,8 +139,7 @@ impl StreamPage {
}); });
}, },
); );
}, });
);
ui.response() ui.response()
} }
@ -152,12 +154,17 @@ impl NostrWidget for StreamPage {
.collect(); .collect();
if let Some(event) = events.first() { if let Some(event) = events.first() {
if let Some(stream) = event.streaming() {
if self.player.is_none() { if self.player.is_none() {
let p = StreamPlayer::new(ui.ctx(), &stream.to_string()); match event.kind() {
30_311 => {
if let Some(u) = event.streaming().or(event.recording()) {
let p = StreamPlayer::new(ui.ctx(), &u.to_string());
self.player = Some(p); self.player = Some(p);
} }
} }
_ => {}
};
}
if self.chat.is_none() { if self.chat.is_none() {
let ok = event.key().unwrap(); let ok = event.key().unwrap();

View File

@ -1,5 +1,5 @@
use anyhow::Error; use anyhow::Error;
use egui::ColorImage; use egui::{ColorImage, Vec2};
use egui_video::ffmpeg_rs_raw::{get_frame_from_hw, Decoder, Demuxer, Scaler}; use egui_video::ffmpeg_rs_raw::{get_frame_from_hw, Decoder, Demuxer, Scaler};
use egui_video::ffmpeg_sys_the_third::{av_frame_free, av_packet_free, AVPixelFormat}; use egui_video::ffmpeg_sys_the_third::{av_frame_free, av_packet_free, AVPixelFormat};
use egui_video::media_player::video_frame_to_image; use egui_video::media_player::video_frame_to_image;
@ -12,17 +12,25 @@ impl FfmpegLoader {
Self {} Self {}
} }
pub fn load_image(&self, path: PathBuf) -> Result<ColorImage, Error> { pub fn load_image(&self, path: PathBuf, size: Option<Vec2>) -> Result<ColorImage, Error> {
let demux = Demuxer::new(path.to_str().unwrap())?; let demux = Demuxer::new(path.to_str().unwrap())?;
Self::load_image_from_demuxer(demux) Self::load_image_from_demuxer(demux, size)
} }
pub fn load_image_bytes(&self, key: &str, data: &'static [u8]) -> Result<ColorImage, Error> { pub fn load_image_bytes(
&self,
key: &str,
data: &'static [u8],
size: Option<Vec2>,
) -> Result<ColorImage, Error> {
let demux = Demuxer::new_custom_io(data, Some(key.to_string()))?; let demux = Demuxer::new_custom_io(data, Some(key.to_string()))?;
Self::load_image_from_demuxer(demux) Self::load_image_from_demuxer(demux, size)
} }
fn load_image_from_demuxer(mut demuxer: Demuxer) -> Result<ColorImage, Error> { fn load_image_from_demuxer(
mut demuxer: Demuxer,
size: Option<Vec2>,
) -> Result<ColorImage, Error> {
unsafe { unsafe {
let info = demuxer.probe_input()?; let info = demuxer.probe_input()?;
@ -46,8 +54,8 @@ impl FfmpegLoader {
let mut frame = get_frame_from_hw(*frame)?; let mut frame = get_frame_from_hw(*frame)?;
let frame_rgb = scaler.process_frame( let frame_rgb = scaler.process_frame(
frame, frame,
(*frame).width as u16, size.map(|s| s.x as u16).unwrap_or((*frame).width as u16),
(*frame).height as u16, size.map(|s| s.y as u16).unwrap_or((*frame).height as u16),
rgb, rgb,
)?; )?;
av_frame_free(&mut frame); av_frame_free(&mut frame);

View File

@ -89,6 +89,9 @@ impl StreamInfo for Note<'_> {
/// Is the stream playable by this app /// Is the stream playable by this app
fn can_play(&self) -> bool { fn can_play(&self) -> bool {
if self.kind() == 30_313 {
return true; // n94-stream can always be played
}
if let Some(stream) = self.streaming() { if let Some(stream) = self.streaming() {
stream.contains(".m3u8") stream.contains(".m3u8")
} else { } else {

View File

@ -1,5 +1,6 @@
use crate::route::image_from_cache; use crate::route::image_from_cache;
use egui::{vec2, Color32, Pos2, Response, Rounding, Sense, Ui, Vec2, Widget}; use crate::theme::NEUTRAL_800;
use egui::{vec2, Response, Rounding, Sense, Ui, Vec2, Widget};
use nostrdb::{Ndb, NdbProfile, Transaction}; use nostrdb::{Ndb, NdbProfile, Transaction};
use notedeck::ImageCache; use notedeck::ImageCache;
@ -42,11 +43,8 @@ impl Avatar {
fn placeholder(ui: &mut Ui, size: f32) -> Response { fn placeholder(ui: &mut Ui, size: f32) -> Response {
let (response, painter) = ui.allocate_painter(vec2(size, size), Sense::click()); let (response, painter) = ui.allocate_painter(vec2(size, size), Sense::click());
painter.circle_filled( let pos = response.rect.min + vec2(size / 2., size / 2.);
Pos2::new(size / 2., size / 2.), painter.circle_filled(pos, size / 2., NEUTRAL_800);
size / 2.,
Color32::from_rgb(200, 200, 200),
);
response response
} }
@ -57,9 +55,7 @@ impl Avatar {
return Self::placeholder(ui, size_v); return Self::placeholder(ui, size_v);
} }
match &self.image { match &self.image {
Some(img) => image_from_cache(img_cache, ui, img) Some(img) => image_from_cache(img_cache, ui, img, Some(size))
.max_size(size)
.fit_to_exact_size(size)
.rounding(Rounding::same(size_v)) .rounding(Rounding::same(size_v))
.sense(Sense::click()) .sense(Sense::click())
.ui(ui), .ui(ui),

View File

@ -64,7 +64,7 @@ impl NostrWidget for Chat {
1311 => { 1311 => {
let profile = services.profile(ev.pubkey()); let profile = services.profile(ev.pubkey());
ChatMessage::new(&stream, &ev, &profile) ChatMessage::new(&stream, &ev, &profile)
.render(ui, services.ctx.img_cache); .render(ui, services);
} }
9735 => { 9735 => {
if let Ok(zap) = Zap::from_receipt(ev) { if let Ok(zap) = Zap::from_receipt(ev) {

View File

@ -1,3 +1,5 @@
use crate::link::NostrLink;
use crate::route::{RouteServices, RouteType};
use crate::stream_info::StreamInfo; use crate::stream_info::StreamInfo;
use crate::theme::{NEUTRAL_500, PRIMARY}; use crate::theme::{NEUTRAL_500, PRIMARY};
use crate::widgets::Avatar; use crate::widgets::Avatar;
@ -5,7 +7,6 @@ use eframe::epaint::text::TextWrapMode;
use egui::text::LayoutJob; use egui::text::LayoutJob;
use egui::{Align, Color32, Label, Response, TextFormat, Ui}; use egui::{Align, Color32, Label, Response, TextFormat, Ui};
use nostrdb::{NdbProfile, Note}; use nostrdb::{NdbProfile, Note};
use notedeck::ImageCache;
pub struct ChatMessage<'a> { pub struct ChatMessage<'a> {
stream: &'a Note<'a>, stream: &'a Note<'a>,
@ -26,7 +27,7 @@ impl<'a> ChatMessage<'a> {
} }
} }
pub fn render(self, ui: &mut Ui, img_cache: &mut ImageCache) -> Response { pub fn render(self, ui: &mut Ui, services: &mut RouteServices) -> Response {
ui.horizontal_wrapped(|ui| { ui.horizontal_wrapped(|ui| {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
// TODO: avoid this somehow // TODO: avoid this somehow
@ -48,9 +49,15 @@ impl<'a> ChatMessage<'a> {
format.color = Color32::WHITE; format.color = Color32::WHITE;
job.append(self.ev.content(), 5.0, format.clone()); job.append(self.ev.content(), 5.0, format.clone());
Avatar::from_profile(self.profile) if Avatar::from_profile(self.profile)
.size(24.) .size(24.)
.render(ui, img_cache); .render(ui, services.ctx.img_cache)
.clicked()
{
services.navigate(RouteType::ProfilePage {
link: NostrLink::profile(self.ev.pubkey()),
})
}
ui.add(Label::new(job).wrap_mode(TextWrapMode::Wrap)); ui.add(Label::new(job).wrap_mode(TextWrapMode::Wrap));
// consume reset of space // consume reset of space

View File

@ -5,10 +5,9 @@ use crate::theme::{NEUTRAL_800, NEUTRAL_900, PRIMARY, ROUNDING_DEFAULT};
use crate::widgets::avatar::Avatar; use crate::widgets::avatar::Avatar;
use eframe::epaint::{Rounding, Vec2}; use eframe::epaint::{Rounding, Vec2};
use egui::epaint::RectShape; use egui::epaint::RectShape;
use egui::load::TexturePoll;
use egui::{ use egui::{
vec2, Color32, CursorIcon, FontId, Label, Pos2, Rect, Response, RichText, Sense, TextWrapMode, vec2, Color32, CursorIcon, FontId, ImageSource, Label, Pos2, Rect, Response, RichText, Sense,
Ui, TextWrapMode, Ui,
}; };
use nostrdb::Note; use nostrdb::Note;
@ -34,33 +33,27 @@ impl<'a> StreamEvent<'a> {
let (response, painter) = ui.allocate_painter(Vec2::new(w, h), Sense::click()); let (response, painter) = ui.allocate_painter(Vec2::new(w, h), Sense::click());
let cover = if ui.is_rect_visible(response.rect) { let cover = if ui.is_rect_visible(response.rect) {
self.event self.event.image().map(|p| {
.image() image_from_cache(services.ctx.img_cache, ui, p, Some(Vec2::new(w, h)))
.map(|p| image_from_cache(services.ctx.img_cache, ui, p)) .rounding(ROUNDING_DEFAULT)
})
} else { } else {
None None
}; };
if let Some(cover) = cover.map(|c| { if let Some(cover) = cover {
c.rounding(Rounding::same(12.))
.load_for_size(painter.ctx(), Vec2::new(w, h))
}) {
match cover {
Ok(TexturePoll::Ready { texture }) => {
painter.add(RectShape { painter.add(RectShape {
rect: response.rect, rect: response.rect,
rounding: Rounding::same(ROUNDING_DEFAULT), rounding: Rounding::same(ROUNDING_DEFAULT),
fill: Color32::WHITE, fill: Color32::WHITE,
stroke: Default::default(), stroke: Default::default(),
blur_width: 0.0, blur_width: 0.0,
fill_texture_id: texture.id, fill_texture_id: match cover.source(ui.ctx()) {
ImageSource::Texture(t) => t.id,
_ => Default::default(),
},
uv: Rect::from_min_max(Pos2::new(0.0, 0.0), Pos2::new(1.0, 1.0)), uv: Rect::from_min_max(Pos2::new(0.0, 0.0), Pos2::new(1.0, 1.0)),
}); });
}
_ => {
painter.rect_filled(response.rect, ROUNDING_DEFAULT, NEUTRAL_800);
}
}
} else { } else {
painter.rect_filled(response.rect, ROUNDING_DEFAULT, NEUTRAL_800); painter.rect_filled(response.rect, ROUNDING_DEFAULT, NEUTRAL_800);
} }