@@ -18,18 +18,22 @@ use std::{
18
18
thread,
19
19
} ;
20
20
21
- use eframe:: egui:: { self , pos2, vec2 , Rect , util:: undoer:: Undoer , Response } ;
21
+ use eframe:: egui:: { self , pos2, util:: undoer:: Undoer , vec2 , ColorImage , Rect , Response } ;
22
22
use futures_lite:: { Future , FutureExt } ;
23
23
use glib:: clone:: Downgrade ;
24
- use gstreamer:: { glib, prelude:: * , ClockTime } ;
24
+ use gstreamer:: {
25
+ glib:: { self , subclass:: types:: ObjectSubclassExt } ,
26
+ prelude:: * ,
27
+ ClockTime ,
28
+ } ;
25
29
use gstreamer_video:: { VideoCapsBuilder , VideoFormat , VideoInterlaceMode } ;
26
30
27
31
use gui:: {
28
32
expression_parser:: eval_expression_string,
29
33
gst_utils:: {
30
34
clock_format:: { clock_time_format, clock_time_parser} ,
31
- egui_sink:: { EffectPreviewSetting , EguiCtx , SinkTexture } ,
32
- elements:: { EguiSink , NtscFilter , VideoPadFilter } ,
35
+ egui_sink:: { EffectPreviewSetting , EguiCtx , EguiSink , SinkTexture } ,
36
+ elements,
33
37
gstreamer_error:: GstreamerError ,
34
38
ntscrs_filter:: NtscFilterSettings ,
35
39
pipeline_utils:: { create_pipeline, PipelineError } ,
@@ -44,7 +48,7 @@ use ntscrs::settings::{
44
48
NtscEffect , NtscEffectFullSettings , ParseSettingsError , SettingDescriptor , SettingID ,
45
49
SettingKind , SettingsList , UseField ,
46
50
} ;
47
- use snafu:: prelude:: * ;
51
+ use snafu:: { prelude:: * , ResultExt } ;
48
52
49
53
use log:: debug;
50
54
@@ -76,21 +80,21 @@ fn initialize_gstreamer() -> Result<(), GstreamerError> {
76
80
None ,
77
81
"eguisink" ,
78
82
gstreamer:: Rank :: None ,
79
- EguiSink :: static_type ( ) ,
83
+ elements :: EguiSink :: static_type ( ) ,
80
84
) ?;
81
85
82
86
gstreamer:: Element :: register (
83
87
None ,
84
88
"ntscfilter" ,
85
89
gstreamer:: Rank :: None ,
86
- NtscFilter :: static_type ( ) ,
90
+ elements :: NtscFilter :: static_type ( ) ,
87
91
) ?;
88
92
89
93
gstreamer:: Element :: register (
90
94
None ,
91
95
"videopadfilter" ,
92
96
gstreamer:: Rank :: None ,
93
- VideoPadFilter :: static_type ( ) ,
97
+ elements :: VideoPadFilter :: static_type ( ) ,
94
98
) ?;
95
99
96
100
// PulseAudio has a severe bug that will greatly delay initial playback to the point of unusability:
@@ -2218,8 +2222,9 @@ impl NtscApp {
2218
2222
egui:: TopBottomPanel :: top ( "video_info" ) . show_inside ( ui, |ui| {
2219
2223
ui. with_layout ( egui:: Layout :: right_to_left ( egui:: Align :: Center ) , |ui| {
2220
2224
let mut remove_pipeline = false ;
2221
- let mut res = None ;
2225
+ let mut change_framerate_res = None ;
2222
2226
let mut save_image_to: Option < ( PathBuf , PathBuf ) > = None ;
2227
+ let mut copy_image_res: Option < Result < ColorImage , GstreamerError > > = None ;
2223
2228
if let Some ( info) = & mut self . pipeline {
2224
2229
let mut metadata = info. metadata . lock ( ) . unwrap ( ) ;
2225
2230
if ui. button ( "🗙" ) . clicked ( ) {
@@ -2228,13 +2233,21 @@ impl NtscApp {
2228
2233
2229
2234
ui. separator ( ) ;
2230
2235
2231
- if ui. button ( "Save image " ) . clicked ( ) {
2236
+ if ui. button ( "Save frame " ) . clicked ( ) {
2232
2237
let src_path = info. path . clone ( ) ;
2233
2238
2234
2239
let dst_path = src_path. with_extension ( "" ) ;
2235
2240
save_image_to = Some ( ( src_path, dst_path) ) ;
2236
2241
}
2237
2242
2243
+ if ui. button ( "Copy frame" ) . clicked ( ) {
2244
+ let egui_sink =
2245
+ info. egui_sink . downcast_ref :: < elements:: EguiSink > ( ) . unwrap ( ) ;
2246
+
2247
+ let egui_sink = EguiSink :: from_obj ( egui_sink) ;
2248
+ copy_image_res = Some ( egui_sink. get_image ( ) . map_err ( |e| e. into ( ) ) ) ;
2249
+ }
2250
+
2238
2251
if let Some ( current_framerate) = metadata. framerate {
2239
2252
ui. separator ( ) ;
2240
2253
match metadata. is_still_image {
@@ -2258,7 +2271,7 @@ impl NtscApp {
2258
2271
metadata. framerate = Some ( new_framerate) ;
2259
2272
}
2260
2273
2261
- res = Some ( changed_framerate) ;
2274
+ change_framerate_res = Some ( changed_framerate) ;
2262
2275
}
2263
2276
}
2264
2277
}
@@ -2292,10 +2305,30 @@ impl NtscApp {
2292
2305
} ) ;
2293
2306
}
2294
2307
2295
- if let Some ( res) = res {
2308
+ if let Some ( res) = change_framerate_res {
2296
2309
self . handle_result ( res) ;
2297
2310
}
2298
2311
2312
+ if let Some ( res) = copy_image_res {
2313
+ match res {
2314
+ Ok ( image) => {
2315
+ let res = arboard:: Clipboard :: new ( ) . and_then ( |mut cb| {
2316
+ let data = arboard:: ImageData {
2317
+ width : image. width ( ) ,
2318
+ height : image. height ( ) ,
2319
+ bytes : Cow :: from ( image. as_raw ( ) ) ,
2320
+ } ;
2321
+ cb. set_image ( data) ?;
2322
+ Ok ( ( ) )
2323
+ } ) ;
2324
+ self . handle_result ( res) ;
2325
+ }
2326
+ Err ( e) => {
2327
+ self . handle_error ( & e) ;
2328
+ }
2329
+ }
2330
+ }
2331
+
2299
2332
if remove_pipeline {
2300
2333
self . handle_result_with ( |app| app. remove_pipeline ( ) ) ;
2301
2334
}
0 commit comments