WIP
2
data/icons/actions/camera-focus-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 8 5 c -1.644531 0 -3 1.355469 -3 3 s 1.355469 3 3 3 s 3 -1.355469 3 -3 s -1.355469 -3 -3 -3 z m 0 2 c 0.5625 0 1 0.4375 1 1 s -0.4375 1 -1 1 s -1 -0.4375 -1 -1 s 0.4375 -1 1 -1 z m 0 0"/><path d="m 3 1 s -0.457031 -0.015625 -0.949219 0.230469 c -0.488281 0.246093 -1.050781 0.9375 -1.050781 1.769531 v 3 h 2 v -3 h 3 v -2 z m 0 0"/><path d="m 10 1 v 2 h 3 v 3 h 2 v -3 c 0 -0.832031 -0.5625 -1.523438 -1.054688 -1.769531 c -0.488281 -0.246094 -0.945312 -0.230469 -0.945312 -0.230469 z m 0 0"/><path d="m 1 10 v 3 c 0 0.832031 0.5625 1.523438 1.050781 1.769531 c 0.492188 0.246094 0.949219 0.230469 0.949219 0.230469 h 3 v -2 h -3 v -3 z m 0 0"/><path d="m 13 10 v 3 h -3 v 2 h 3 s 0.457031 0.015625 0.945312 -0.230469 c 0.492188 -0.246093 1.054688 -0.9375 1.054688 -1.769531 v -3 z m 0 0"/></g></svg>
|
After Width: | Height: | Size: 956 B |
7
data/icons/actions/camera-photo-symbolic.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2e3434">
|
||||
<path d="m 6.5 0 c -0.265625 0 -0.519531 0.105469 -0.707031 0.292969 l -1.707031 1.707031 h -1.085938 c -1.644531 0 -3 1.355469 -3 3 v 7 c 0 1.644531 1.355469 3 3 3 h 10 c 1.644531 0 3 -1.355469 3 -3 v -7 c 0 -1.644531 -1.355469 -3 -3 -3 h -1.085938 l -1.707031 -1.707031 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m 0.414062 2 h 2.171876 l 1.707031 1.707031 c 0.1875 0.1875 0.441406 0.292969 0.707031 0.292969 h 1.5 c 0.570312 0 1 0.429688 1 1 v 7 c 0 0.570312 -0.429688 1 -1 1 h -10 c -0.570312 0 -1 -0.429688 -1 -1 v -7 c 0 -0.570312 0.429688 -1 1 -1 h 1.5 c 0.265625 0 0.519531 -0.105469 0.707031 -0.292969 z m 0 0"/>
|
||||
<path d="m 8 4 c -2.199219 0 -4 1.800781 -4 4 s 1.800781 4 4 4 s 4 -1.800781 4 -4 s -1.800781 -4 -4 -4 z m 0 2 c 1.117188 0 2 0.882812 2 2 s -0.882812 2 -2 2 s -2 -0.882812 -2 -2 s 0.882812 -2 2 -2 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
2
data/icons/actions/cameras-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 3.734375 5.476562 l -2.371094 -2.699218 h -0.363281 v 5.445312 h 0.339844 z m 1.265625 -3.476562 c -0.554688 0 -1 0.445312 -1 1 v 4.777344 c 0 -0.550782 0.449219 -1 1 -1 h 0.363281 c 0.289063 0 0.5625 0.125 0.75 0.339844 l 0.886719 1.007812 v -0.125 c 0 -1.089844 0.910156 -2 2 -2 h 2 v -3 c 0 -0.554688 -0.445312 -1 -1 -1 z m 0 0" fill-opacity="0.34902"/><path d="m 7.734375 10.476562 l -2.371094 -2.699218 h -0.363281 v 5.445312 h 0.339844 z m 6.265625 -3.476562 h -5 c -0.554688 0 -1 0.445312 -1 1 v 5 c 0 0.554688 0.445312 1 1 1 h 5 c 0.554688 0 1 -0.445312 1 -1 v -5 c 0 -0.554688 -0.445312 -1 -1 -1 z m 0 0"/></g></svg>
|
After Width: | Height: | Size: 781 B |
2
data/icons/actions/encoder-knob-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 8 14 c -2.222656 0 -4 -1.777344 -4 -4 s 1.777344 -4 4 -4 c 2.21875 0 4 1.777344 4 4 s -1.78125 4 -4 4 z m 0 -10 c -3.316406 0 -6 2.683594 -6 6 s 2.683594 6 6 6 c 3.3125 0 6 -2.683594 6 -6 s -2.6875 -6 -6 -6 z m 0 0"/><path d="m 5.253906 7.039062 l 2.226563 3.292969 c 0.152343 0.226563 0.464843 0.289063 0.695312 0.132813 c 0.226563 -0.152344 0.289063 -0.464844 0.132813 -0.695313 l -2.226563 -3.292969 c -0.152343 -0.226562 -0.464843 -0.289062 -0.691406 -0.132812 c -0.230469 0.15625 -0.289063 0.464844 -0.136719 0.695312 z m 0 0"/><path d="m 2 4.984375 c 0 0.542969 -0.441406 0.984375 -0.984375 0.984375 c -0.546875 0 -0.984375 -0.441406 -0.984375 -0.984375 s 0.4375 -0.984375 0.984375 -0.984375 c 0.542969 0 0.984375 0.441406 0.984375 0.984375 z m 0 0"/><path d="m 6 1.015625 c 0 0.542969 -0.441406 0.984375 -0.984375 0.984375 c -0.546875 0 -0.984375 -0.441406 -0.984375 -0.984375 s 0.4375 -0.984375 0.984375 -0.984375 c 0.542969 0 0.984375 0.441406 0.984375 0.984375 z m 0 0"/><path d="m 12 1.015625 c 0 0.542969 -0.441406 0.984375 -0.984375 0.984375 c -0.546875 0 -0.984375 -0.441406 -0.984375 -0.984375 s 0.4375 -0.984375 0.984375 -0.984375 c 0.542969 0 0.984375 0.441406 0.984375 0.984375 z m 0 0"/><path d="m 16 4.984375 c 0 0.542969 -0.441406 0.984375 -0.984375 0.984375 c -0.546875 0 -0.984375 -0.441406 -0.984375 -0.984375 s 0.4375 -0.984375 0.984375 -0.984375 c 0.542969 0 0.984375 0.441406 0.984375 0.984375 z m 0 0"/></g></svg>
|
After Width: | Height: | Size: 1.6 KiB |
2
data/icons/actions/pick-camera-alt2-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 12.546875 6.035156 c -0.277344 -0.097656 -0.589844 -0.039062 -0.796875 0.144532 l -1.542969 1.359374 c -0.28125 0.25 -0.277343 0.648438 0.003907 0.894532 l 1.539062 1.339844 c 0.210938 0.183593 0.523438 0.234374 0.796875 0.136718 s 0.449219 -0.328125 0.449219 -0.585937 v -2.699219 c 0 -0.257812 -0.175782 -0.492188 -0.449219 -0.589844 z m -7.550781 -1.015625 h 3 c 1.109375 0 2 0.890625 2 2 v 2 c 0 1.109375 -0.890625 2 -2 2 h -3 c -1.105469 0 -2 -0.890625 -2 -2 v -2 c 0 -1.109375 0.894531 -2 2 -2 z m 0 0"/><g fill-rule="evenodd"><path d="m -0.00390625 4.988281 v 1 h 1.00000025 v -1 c 0 -0.550781 0.445312 -1 1 -1 h 1 v -1 h -1 c -1.105469 0 -2.00000025 0.894531 -2.00000025 2 z m 0 0"/><path d="m -0.00390625 10.988281 v -1 h 1.00000025 v 1 c 0 0.554688 0.445312 1 1 1 h 1 v 1 h -1 c -1.105469 0 -2.00000025 -0.894531 -2.00000025 -2 z m 0 0"/><path d="m 16 4.988281 v 1 h -1 v -1 c 0 -0.550781 -0.445312 -1 -1 -1 h -1 v -1 h 1 c 1.105469 0 2 0.894531 2 2 z m 0 0"/><path d="m 16 10.988281 v -1 h -1 v 1 c 0 0.554688 -0.445312 1 -1 1 h -1 v 1 h 1 c 1.105469 0 2 -0.894531 2 -2 z m 0 0"/></g><path d="m 5 13 c -1.109375 0 -2 0.890625 -2 2 v 1 h 7 v -1 c 0 -1.109375 -0.890625 -2 -2 -2 z m 7.335938 0.972656 c -0.214844 -0.015625 -0.429688 0.050782 -0.585938 0.1875 l -1.539062 1.359375 c -0.140626 0.128907 -0.214844 0.304688 -0.203126 0.480469 h 2.992188 v -1.394531 c 0 -0.257813 -0.179688 -0.488281 -0.453125 -0.585938 c -0.066406 -0.027343 -0.136719 -0.039062 -0.210937 -0.046875 z m -9.335938 -13.972656 v 1 c 0 1.109375 0.890625 2 2 2 h 3 c 1.109375 0 2 -0.890625 2 -2 v -1 z m 7.007812 0 c 0.007813 0.15625 0.082032 0.300781 0.203126 0.414062 l 1.542968 1.339844 c 0.207032 0.183594 0.523438 0.234375 0.796875 0.136719 c 0.269531 -0.097656 0.449219 -0.328125 0.449219 -0.585937 v -1.304688 z m 0 0" fill-opacity="0.329412"/></g></svg>
|
After Width: | Height: | Size: 2 KiB |
2
data/icons/actions/video-camera-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7 2 c -1.644531 0 -3 1.355469 -3 3 v 1.425781 l -3.316406 -3.433593 h -0.683594 v 9 h 0.644531 l 3.355469 -3.492188 v 1.5 c 0 1.644531 1.355469 3 3 3 h 6 c 1.644531 0 3 -1.355469 3 -3 v -5 c 0 -1.644531 -1.355469 -3 -3 -3 z m 0 2 h 6 c 0.570312 0 1 0.429688 1 1 v 5 c 0 0.570312 -0.429688 1 -1 1 h -6 c -0.570312 0 -1 -0.429688 -1 -1 v -5 c 0 -0.570312 0.429688 -1 1 -1 z m 0 0" fill="#222222"/></svg>
|
After Width: | Height: | Size: 539 B |
2
data/icons/actions/video-decode-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 6 3 v 2 h 2 v -2 z m 2 2 v 2 h 2 v -2 z m 2 0 h 1 c 0 -0.742188 -0.402344 -1.386719 -1 -1.730469 z m 0 2 v 2 h -2 v -2 h -2 v 2 h -2 v -2 h -2 v -2 h -1 v 6 c 0 1.109375 0.890625 2 2 2 h 6 c 1.105469 0 2 -0.890625 2 -2 v -4 z m 0 0"/><path d="m 11 8 l 3 -3 h 1 v 6 h -1 z m 0 0"/></g></svg>
|
After Width: | Height: | Size: 446 B |
12
data/icons/icons.gresource.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<gresources>
|
||||
<gresource prefix="/io/gitlab/nekocwd/eyeneko/icons/scalable">
|
||||
<file preprocess="xml-stripblanks">actions/camera-focus-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">actions/cameras-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">actions/video-decode-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">actions/video-camera-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">actions/camera-photo-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">actions/pick-camera-alt2-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">actions/encoder-knob-symbolic.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
|
@ -11,3 +11,9 @@ install_data(
|
|||
symbolic_dir / ('@0@-symbolic.svg').format(application_id),
|
||||
install_dir: get_option('datadir') / 'icons' / symbolic_dir,
|
||||
)
|
||||
|
||||
icons = gnome.compile_resources(
|
||||
'icons-resources',
|
||||
'icons.gresource.xml',
|
||||
c_name: 'icons',
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ project(
|
|||
i18n = import('i18n')
|
||||
gnome = import('gnome')
|
||||
valac = meson.get_compiler('vala')
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
srcdir = meson.project_source_root() / 'src'
|
||||
|
||||
|
|
|
@ -41,6 +41,11 @@ public class EyeNeko.Application : Adw.Application {
|
|||
base.activate ();
|
||||
var win = this.active_window ?? new EyeNeko.Window (this);
|
||||
win.present ();
|
||||
var styling = new Gtk.CssProvider ();
|
||||
styling.load_from_resource ("/io/gitlab/nekocwd/eyeneko/style.css");
|
||||
Gtk.StyleContext.add_provider_for_display (Gdk.Display.get_default (),
|
||||
styling,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
}
|
||||
|
||||
private void on_about_action () {
|
||||
|
|
87
src/auto_focus.vala
Normal file
|
@ -0,0 +1,87 @@
|
|||
public class AutoFocus : Object {
|
||||
void set_camfocus (double pos) {
|
||||
PipeTap.instance.focus = (int) (pos * 10);
|
||||
}
|
||||
|
||||
private double best_score { get; set; default = 0; }
|
||||
private double best_lens_pos { get; set; default = 0; }
|
||||
public double prev_pos { get; private set; default = 0; }
|
||||
private double step { get; set; default = 10; }
|
||||
private double max_pos { get; set; default = 100; }
|
||||
private bool focus_in_process { get; set; default = false; }
|
||||
private int fc { get; set; default = 0; }
|
||||
|
||||
public void reset_focus () {
|
||||
best_score = 0;
|
||||
best_lens_pos = 0;
|
||||
prev_pos = 0;
|
||||
step = 10;
|
||||
max_pos = 100;
|
||||
focus_in_process = true;
|
||||
fc = 0;
|
||||
set_camfocus (0);
|
||||
}
|
||||
|
||||
void set_diff (double diff) {
|
||||
max_pos = double.min (best_lens_pos + diff, 100);
|
||||
prev_pos = double.max (best_lens_pos - diff, 0);
|
||||
}
|
||||
|
||||
public void adjust_step (double prev_score) {
|
||||
// message ("%lf < %lf < %lf : %lf %lf / %lf", best_lens_pos, prev_pos, max_pos, step, prev_score, best_score);
|
||||
if (fc < Environment.get_variable ("FRAME_DELAY").to_int ()) {
|
||||
fc++;
|
||||
return;
|
||||
} else
|
||||
fc = 0;
|
||||
if (prev_pos >= max_pos) {
|
||||
message ("Best focus %lf", best_lens_pos);
|
||||
switch ((int) (step * 10)) {
|
||||
case 100:
|
||||
message ("meow on 10");
|
||||
step = 5;
|
||||
set_diff (10);
|
||||
set_camfocus (prev_pos);
|
||||
break;
|
||||
case 50:
|
||||
message ("meow on 5");
|
||||
step = 2;
|
||||
set_diff (5);
|
||||
set_camfocus (prev_pos);
|
||||
break;
|
||||
case 20:
|
||||
message ("meow on 2");
|
||||
step = 0.1;
|
||||
set_diff (2);
|
||||
set_camfocus (prev_pos);
|
||||
break;
|
||||
case 1:
|
||||
message ("meow on 0.1");
|
||||
step = 10;
|
||||
max_pos = 100;
|
||||
focus_in_process = false;
|
||||
if (prev_pos != best_lens_pos) {
|
||||
prev_pos = best_lens_pos;
|
||||
set_camfocus (best_lens_pos);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
message ("meow on def?");
|
||||
step = 10;
|
||||
break;
|
||||
}
|
||||
message ("min %lf max %lf step %lf", prev_pos, max_pos, step);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!focus_in_process)
|
||||
return;
|
||||
if (prev_score > best_score) {
|
||||
best_score = prev_score;
|
||||
best_lens_pos = prev_pos;
|
||||
message ("%lf better than %lf", prev_pos, best_lens_pos);
|
||||
}
|
||||
prev_pos += step;
|
||||
set_camfocus (prev_pos);
|
||||
}
|
||||
}
|
|
@ -3,5 +3,7 @@
|
|||
<gresource prefix="/io/gitlab/nekocwd/eyeneko">
|
||||
<file preprocess="xml-stripblanks">window.ui</file>
|
||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||
<file>style.css</file>
|
||||
<file>shaders/ccm.glsl</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
|
222
src/gst.vala
Normal file
|
@ -0,0 +1,222 @@
|
|||
|
||||
public class EyeNeko.Gstreamer : Object {
|
||||
public Gst.Element viewfinder;
|
||||
private Gst.Element camerabin;
|
||||
public enum CameraBinMode {
|
||||
VIDEO = 2,
|
||||
PHOTO = 1,
|
||||
}
|
||||
public bool ready { get; set; default = false; }
|
||||
public CameraBinMode camerabin_mode { get; set; default = CameraBinMode.PHOTO; }
|
||||
|
||||
Gst.Caps get_best_caps (Gst.Device device) {
|
||||
int max_w = 0;
|
||||
int max_h = 0;
|
||||
float max_fps = 0;
|
||||
string max_format = "";
|
||||
|
||||
Gst.Structure max_caps = null;
|
||||
var caps = device.get_caps ();
|
||||
for (uint i = 0; i < caps.get_size (); i++) {
|
||||
var format = caps.get_structure (i).get_name ();
|
||||
int num, denom, width, height;
|
||||
if (caps.get_structure (i).get_field_type ("width") != typeof (int) || caps.get_structure (i).get_field_type ("height") != typeof (int)) {
|
||||
warning ("Can't find best caps :(. I hate arrays!");
|
||||
}
|
||||
caps.get_structure (i).get_int ("width", out width);
|
||||
caps.get_structure (i).get_int ("height", out height);
|
||||
caps.get_structure (i).get_fraction ("framerate", out num, out denom);
|
||||
if (denom == 0)
|
||||
denom = 1;
|
||||
var fps = num / denom;
|
||||
if (max_caps == null || (width > max_w && height > max_h) || (width == max_w && height == max_h && fps > max_fps)) {
|
||||
max_w = width;
|
||||
max_h = height;
|
||||
max_caps = caps.get_structure (i).copy ();
|
||||
max_fps = fps;
|
||||
max_format = format;
|
||||
}
|
||||
}
|
||||
var best_cap = new Gst.Caps.empty ();
|
||||
best_cap.append_structure (max_caps.copy ());
|
||||
message ("%s %dx%d %f (%s)", max_format, max_w, max_h, max_fps, best_cap.to_string ());
|
||||
return best_cap;
|
||||
}
|
||||
|
||||
Gst.Caps get_downscale_caps (Gst.Device device, int max_size) {
|
||||
int w = 0, h = 0;
|
||||
var caps = get_best_caps (device);
|
||||
var struct = caps.get_structure (0).copy ();
|
||||
struct.get_int ("width", out w);
|
||||
struct.get_int ("height", out h);
|
||||
struct = new Gst.Structure.empty ("video/x-raw");
|
||||
var max_dim = int.max (w, h);
|
||||
double mult = 1;
|
||||
if (max_dim > max_size) {
|
||||
mult = max_size / (double) max_dim;
|
||||
}
|
||||
struct.set ("width", typeof (int), (int) (w * mult));
|
||||
struct.set ("height", typeof (int), (int) (h * mult));
|
||||
|
||||
caps = new Gst.Caps.empty ();
|
||||
caps.append_structure (struct.copy ());
|
||||
return caps;
|
||||
}
|
||||
|
||||
public class Camera : Object {
|
||||
public string name { owned get { return device.display_name; } }
|
||||
public Gst.Device device { get; }
|
||||
|
||||
private Camera (Gst.Device device) {
|
||||
_device = device;
|
||||
}
|
||||
|
||||
private static Camera[] cameras = null;
|
||||
public static Camera[] get_all () {
|
||||
if (cameras == null) {
|
||||
cameras = new Camera[0];
|
||||
var pwprovider = Gst.DeviceProviderFactory.get_by_name ("pipewiredeviceprovider");
|
||||
pwprovider.start ();
|
||||
foreach (var device in pwprovider.get_devices ()) {
|
||||
if (device.has_classes ("Video/Source")) {
|
||||
cameras += new Camera (device);
|
||||
message ("Camera appended %s:", device.display_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cameras;
|
||||
}
|
||||
}
|
||||
|
||||
public void start_capture () {
|
||||
Signal.emit_by_name (camerabin, "start-capture");
|
||||
}
|
||||
|
||||
public void stop_capture () {
|
||||
Signal.emit_by_name (camerabin, "stop-capture");
|
||||
}
|
||||
|
||||
public void set_video_filter (Gst.Caps caps) {
|
||||
var filter = new Gst.Bin ("VideoFilter");
|
||||
var videoscale = Gst.ElementFactory.make ("videoconvertscale");
|
||||
var capsfilt = Gst.ElementFactory.make ("capsfilter");
|
||||
capsfilt.set_property ("caps", caps);
|
||||
filter.add_many (videoscale, capsfilt);
|
||||
videoscale.link_many (capsfilt);
|
||||
add_pads_to_bin (ref filter);
|
||||
camerabin.set_property ("video-filter", filter);
|
||||
}
|
||||
|
||||
public void start_stream_from (Camera camera) {
|
||||
var camerasrc = new Gst.Bin ("CameraSRC");
|
||||
|
||||
var src = camera.device.create_element ("camerasrc");
|
||||
var caps = get_best_caps (camera.device);
|
||||
var capsfilt = Gst.ElementFactory.make ("capsfilter");
|
||||
capsfilt.set_property ("caps", caps);
|
||||
|
||||
var db = Gst.ElementFactory.make ("decodebin3");
|
||||
var identity = Gst.ElementFactory.make ("identity");
|
||||
|
||||
camerasrc.add_many (src, capsfilt, db, identity);
|
||||
src.link_many (capsfilt, db);
|
||||
db.pad_added.connect ((pad) => {
|
||||
if (pad.get_stream () != null && pad.get_stream ().stream_type == Gst.StreamType.VIDEO) {
|
||||
pad.link (identity.get_static_pad ("sink"));
|
||||
}
|
||||
});
|
||||
camerasrc.add_pad (new Gst.GhostPad ("src", identity.get_static_pad ("src")));
|
||||
|
||||
var camerasrc_wrapper = Gst.ElementFactory.make ("wrappercamerabinsrc");
|
||||
camerasrc_wrapper.set_property ("video-source", camerasrc);
|
||||
camerasrc_wrapper.set_property ("video-source-filter", new Logic.Filters.CCM ("CCM", true, true).element);
|
||||
|
||||
camerabin.set_property ("camera-source", camerasrc_wrapper);
|
||||
set_video_filter (get_downscale_caps (camera.device, 600));
|
||||
|
||||
camerabin.set_state (Gst.State.NULL);
|
||||
|
||||
camerabin.set_state (Gst.State.PLAYING);
|
||||
Gst.Debug.bin_to_dot_file ((Gst.Bin) camerabin, Gst.DebugGraphDetails.ALL, camera.name);
|
||||
}
|
||||
|
||||
private void add_enc_profile () {
|
||||
var cp = new Gst.PbUtils.EncodingContainerProfile (null, null, Gst.Caps.from_string ("video/quicktime"), null);
|
||||
var vp = new Gst.PbUtils.EncodingVideoProfile (Gst.Caps.from_string ("video/x-h264"), "Zero Latency", null, 0);
|
||||
var ap = new Gst.PbUtils.EncodingAudioProfile (Gst.Caps.from_string ("audio/x-ac3"), null, null, 0);
|
||||
cp.add_profile (vp);
|
||||
cp.add_profile (ap);
|
||||
camerabin.set_property ("video-profile", cp);
|
||||
}
|
||||
|
||||
public void init (ref weak string[] args) {
|
||||
Gst.init (ref args);
|
||||
viewfinder = Gst.ElementFactory.make ("gtk4paintablesink", "paintablesink");
|
||||
|
||||
camerabin = Gst.ElementFactory.make ("camerabin", "camerabin");
|
||||
camerabin.set_property ("viewfinder-sink", viewfinder);
|
||||
camerabin.bind_property ("idle", this, "ready", BindingFlags.SYNC_CREATE);
|
||||
this.bind_property ("camerabin-mode", camerabin, "mode");
|
||||
add_enc_profile ();
|
||||
camerabin.bus.add_watch (0, bus_callback);
|
||||
}
|
||||
|
||||
public static void add_pads_to_bin (ref Gst.Bin bin, bool src = true, bool sink = true) {
|
||||
if (src)
|
||||
bin.add_pad (new Gst.GhostPad ("src", ((Gst.Element) bin.get_child_by_index (0)).get_static_pad ("src")));
|
||||
if (sink)
|
||||
bin.add_pad (new Gst.GhostPad ("sink", ((Gst.Element) bin.get_child_by_index (bin.get_children_count () - 1)).get_static_pad ("sink")));
|
||||
}
|
||||
|
||||
private bool bus_callback (Gst.Bus bus, Gst.Message message) {
|
||||
switch (message.type) {
|
||||
case Gst.MessageType.ERROR:
|
||||
GLib.Error err;
|
||||
string debug;
|
||||
message.parse_error (out err, out debug);
|
||||
GLib.warning ("Error: %s", err.message);
|
||||
break;
|
||||
case Gst.MessageType.EOS:
|
||||
GLib.warning ("end of stream\n");
|
||||
break;
|
||||
case Gst.MessageType.STATE_CHANGED:
|
||||
Gst.State oldstate;
|
||||
Gst.State newstate;
|
||||
Gst.State pending;
|
||||
message.parse_state_changed (out oldstate, out newstate,
|
||||
out pending);
|
||||
// GLib.message ("state changed: %s->%s:%s\n",
|
||||
// oldstate.to_string (), newstate.to_string (),
|
||||
// pending.to_string ());
|
||||
break;
|
||||
case Gst.MessageType.TAG:
|
||||
Gst.TagList tag_list;
|
||||
stdout.printf ("taglist found\n");
|
||||
message.parse_tag (out tag_list);
|
||||
break;
|
||||
case Gst.MessageType.ELEMENT:
|
||||
if (message.get_structure ().get_name () == "GstVideoAnalyse") {
|
||||
double val;
|
||||
message.get_structure ().get_double ("luma-average", out val);
|
||||
// af.adjust_step (val);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Gstreamer () {
|
||||
}
|
||||
|
||||
private static Gstreamer _instance = null;
|
||||
public static Gstreamer instance {
|
||||
get {
|
||||
if (_instance == null)
|
||||
_instance = new Gstreamer ();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
2
src/icons/camera-focus-symbolic.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 8 5 c -1.644531 0 -3 1.355469 -3 3 s 1.355469 3 3 3 s 3 -1.355469 3 -3 s -1.355469 -3 -3 -3 z m 0 2 c 0.5625 0 1 0.4375 1 1 s -0.4375 1 -1 1 s -1 -0.4375 -1 -1 s 0.4375 -1 1 -1 z m 0 0"/><path d="m 3 1 s -0.457031 -0.015625 -0.949219 0.230469 c -0.488281 0.246093 -1.050781 0.9375 -1.050781 1.769531 v 3 h 2 v -3 h 3 v -2 z m 0 0"/><path d="m 10 1 v 2 h 3 v 3 h 2 v -3 c 0 -0.832031 -0.5625 -1.523438 -1.054688 -1.769531 c -0.488281 -0.246094 -0.945312 -0.230469 -0.945312 -0.230469 z m 0 0"/><path d="m 1 10 v 3 c 0 0.832031 0.5625 1.523438 1.050781 1.769531 c 0.492188 0.246094 0.949219 0.230469 0.949219 0.230469 h 3 v -2 h -3 v -3 z m 0 0"/><path d="m 13 10 v 3 h -3 v 2 h 3 s 0.457031 0.015625 0.945312 -0.230469 c 0.492188 -0.246093 1.054688 -0.9375 1.054688 -1.769531 v -3 z m 0 0"/></g></svg>
|
After Width: | Height: | Size: 956 B |
34
src/logic/color_correction_filter.vala
Normal file
|
@ -0,0 +1,34 @@
|
|||
public class EyeNeko.Logic.Filters.CCM : GLFilter {
|
||||
public float red_in_red { get; set; default = 1.0f; }
|
||||
public float red_in_green { get; set; default = 0.0f; }
|
||||
public float red_in_blue { get; set; default = 0.0f; }
|
||||
|
||||
public float green_in_red { get; set; default = 0.0f; }
|
||||
public float green_in_green { get; set; default = 1.0f; }
|
||||
public float green_in_blue { get; set; default = 0.0f; }
|
||||
|
||||
public float blue_in_red { get; set; default = 0.0f; }
|
||||
public float blue_in_green { get; set; default = 0.0f; }
|
||||
public float blue_in_blue { get; set; default = 1.0f; }
|
||||
|
||||
public CCM (string name = "CCM", bool glupload = false, bool gldownload = false) {
|
||||
base ("ccm", name, glupload, gldownload);
|
||||
update_props ();
|
||||
notify.connect (update_props);
|
||||
}
|
||||
|
||||
private void update_props () {
|
||||
Gst.Structure uniforms = new Gst.Structure.empty ("uniforms");
|
||||
uniforms.set ("rr", typeof (float), red_in_red);
|
||||
uniforms.set ("rg", typeof (float), red_in_green);
|
||||
uniforms.set ("rb", typeof (float), red_in_blue);
|
||||
uniforms.set ("gr", typeof (float), green_in_red);
|
||||
uniforms.set ("gg", typeof (float), green_in_green);
|
||||
uniforms.set ("gb", typeof (float), green_in_blue);
|
||||
uniforms.set ("br", typeof (float), blue_in_red);
|
||||
uniforms.set ("bg", typeof (float), blue_in_green);
|
||||
uniforms.set ("bb", typeof (float), blue_in_blue);
|
||||
|
||||
gl_shader.set_property ("uniforms", uniforms);
|
||||
}
|
||||
}
|
43
src/logic/filter.vala
Normal file
|
@ -0,0 +1,43 @@
|
|||
namespace EyeNeko.Logic.Filters {
|
||||
public interface Filter : Object {
|
||||
public abstract Gst.Element element { get; }
|
||||
}
|
||||
public class GLFilter : Filter, Object {
|
||||
public static string get_shader_source (string name) {
|
||||
try {
|
||||
return (string) resources_lookup_data ("/io/gitlab/nekocwd/eyeneko/shaders/" + name + ".glsl", GLib.ResourceLookupFlags.NONE).get_data ();
|
||||
} catch (Error err) {
|
||||
warning ("Error during shader lookup. %s", err.message);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public Gst.Element element { get; }
|
||||
public Gst.Element gl_shader { get; }
|
||||
|
||||
public GLFilter (string shader_name, string elem_name = "GLShader", bool glupload = false, bool gldownload = false) {
|
||||
var bin = new Gst.Bin (elem_name);
|
||||
var gl_upload = Gst.ElementFactory.make ("glupload", @"$(elem_name)-upload");
|
||||
var gl_download = Gst.ElementFactory.make ("gldownload", @"$(elem_name)-download");
|
||||
_gl_shader = Gst.ElementFactory.make ("glshader", @"$(elem_name)-shader");
|
||||
gl_shader.set_property ("fragment", GLFilter.get_shader_source (shader_name));
|
||||
if (glupload) {
|
||||
bin.add (gl_upload);
|
||||
}
|
||||
|
||||
bin.add (gl_shader);
|
||||
|
||||
if (gldownload) {
|
||||
bin.add (gl_download);
|
||||
gl_shader.link (gl_download);
|
||||
}
|
||||
|
||||
if (glupload) {
|
||||
gl_upload.link (gl_shader);
|
||||
}
|
||||
|
||||
EyeNeko.Gstreamer.add_pads_to_bin (ref bin);
|
||||
_element = bin;
|
||||
}
|
||||
}
|
||||
}
|
11
src/logic/helpers.vala
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace EyeNeko {
|
||||
namespace Enum {
|
||||
public int value (string enum, string name) {
|
||||
return ((EnumClass) Type.from_name (enum).class_peek ()).get_value_by_name ((string) name).value;
|
||||
}
|
||||
|
||||
public string alias (string enum, int value) {
|
||||
return ((EnumClass) Type.from_name (enum).class_peek ()).get_value (value).value_name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ int main (string[] args) {
|
|||
Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
|
||||
Intl.textdomain (Config.GETTEXT_PACKAGE);
|
||||
|
||||
EyeNeko.Gstreamer.instance.init (ref args);
|
||||
var app = new EyeNeko.Application ();
|
||||
return app.run (args);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,26 @@
|
|||
eyeneko_sources = [
|
||||
icons,
|
||||
'main.vala',
|
||||
'application.vala',
|
||||
'window.vala',
|
||||
'gst.vala',
|
||||
'pipetap_proxy.vala',
|
||||
'auto_focus.vala',
|
||||
'logic/color_correction_filter.vala',
|
||||
'logic/filter.vala',
|
||||
'logic/helpers.vala',
|
||||
]
|
||||
|
||||
vapi_dir = meson.current_source_dir() / 'vapi'
|
||||
add_project_arguments(['--vapidir', vapi_dir], language: 'vala')
|
||||
eyeneko_deps = [
|
||||
config_dep,
|
||||
dependency('gtk4'),
|
||||
dependency('libadwaita-1', version: '>= 1.4'),
|
||||
dependency('gstreamer-1.0'),
|
||||
# dependency('gstreamer-pbutils-1.0'),
|
||||
dependency('gstreamer-pbutils-1.0'),
|
||||
valac.find_library('encoding-profile-helper', dirs: vapi_dir),
|
||||
]
|
||||
|
||||
blueprints = custom_target(
|
||||
|
|
13
src/pipetap_proxy.vala
Normal file
|
@ -0,0 +1,13 @@
|
|||
[DBus (name = "io.gitlab.nekocwd.pipetap1")]
|
||||
public interface PipeTap : Object {
|
||||
public abstract int focus { get; set; }
|
||||
|
||||
private static PipeTap _instance = null;
|
||||
public static PipeTap instance {
|
||||
get {
|
||||
if (_instance == null)
|
||||
_instance = Bus.get_proxy_sync<PipeTap> (BusType.SESSION, "io.gitlab.nekocwd.pipetap", "/io/gitlab/nekocwd/pipetap", DBusProxyFlags.NONE);
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
29
src/shaders/ccm.glsl
Normal file
|
@ -0,0 +1,29 @@
|
|||
#version 100
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_texcoord;
|
||||
uniform sampler2D image;
|
||||
|
||||
uniform float rr;
|
||||
uniform float rg;
|
||||
uniform float rb;
|
||||
|
||||
uniform float gr;
|
||||
uniform float gg;
|
||||
uniform float gb;
|
||||
|
||||
uniform float br;
|
||||
uniform float bg;
|
||||
uniform float bb;
|
||||
|
||||
void main() {
|
||||
vec3 color = texture2D(image, v_texcoord).rgb;
|
||||
mat4 matrix = mat4(
|
||||
vec4(rr, rg, rb, 0.0),
|
||||
vec4(gr, gg, gb, 0.0),
|
||||
vec4(br, bg, bb, 0.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0)
|
||||
);
|
||||
gl_FragColor = matrix * vec4(color, 1.0);
|
||||
}
|
6
src/style.css
Normal file
|
@ -0,0 +1,6 @@
|
|||
.capture-btn {
|
||||
border: 5px solid white;
|
||||
}
|
||||
.focus-btn {
|
||||
color: green;
|
||||
}
|
4
src/vapi/encoding-profile-helper.vapi
Normal file
|
@ -0,0 +1,4 @@
|
|||
[CCode (cheader_filename = "gstreamer-1.0/gst/pbutils/encoding-profile.h", lower_case_cprefix = "gst_")]
|
||||
namespace Gst.EyeNeko {
|
||||
public Gst.PbUtils.EncodingProfile encoding_profile_from_string (string string);
|
||||
}
|
223
src/window.blp
|
@ -2,13 +2,33 @@ using Gtk 4.0;
|
|||
using Adw 1;
|
||||
|
||||
template $EyeNekoWindow: Adw.ApplicationWindow {
|
||||
styles [
|
||||
"osd",
|
||||
]
|
||||
|
||||
title: _("EyeNeko");
|
||||
default-width: 800;
|
||||
default-height: 600;
|
||||
|
||||
content: Adw.ToolbarView {
|
||||
content: Gtk.Overlay overlay {
|
||||
child: Gtk.Picture viewfinder {
|
||||
halign: fill;
|
||||
valign: fill;
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Adw.ToolbarView toolbar {
|
||||
styles [
|
||||
"flat",
|
||||
]
|
||||
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
show-title: false;
|
||||
|
||||
[end]
|
||||
MenuButton {
|
||||
primary: true;
|
||||
|
@ -18,14 +38,211 @@ template $EyeNekoWindow: Adw.ApplicationWindow {
|
|||
}
|
||||
}
|
||||
|
||||
[bottom]
|
||||
ActionBar {
|
||||
styles [
|
||||
"osd",
|
||||
]
|
||||
|
||||
[center]
|
||||
Box {
|
||||
spacing: 12;
|
||||
|
||||
MenuButton profile_btn {
|
||||
styles [
|
||||
"circular",
|
||||
"menu",
|
||||
]
|
||||
|
||||
Image {
|
||||
icon-name: "encoder-knob-symbolic";
|
||||
pixel-size: 24;
|
||||
}
|
||||
|
||||
width-request: 40;
|
||||
height-request: 40;
|
||||
valign: center;
|
||||
}
|
||||
|
||||
Button {
|
||||
valign: center;
|
||||
halign: end;
|
||||
width-request: 60;
|
||||
height-request: 60;
|
||||
|
||||
Box {
|
||||
halign: center;
|
||||
valign: center;
|
||||
orientation: vertical;
|
||||
|
||||
Image {
|
||||
icon-name: "camera-focus-symbolic";
|
||||
pixel-size: 24;
|
||||
}
|
||||
|
||||
Label focus_label {
|
||||
label: _("100.0");
|
||||
}
|
||||
}
|
||||
|
||||
styles [
|
||||
"circular",
|
||||
"focus-btn",
|
||||
]
|
||||
}
|
||||
|
||||
Button capture_btn {
|
||||
width-request: 80;
|
||||
height-request: 80;
|
||||
|
||||
Image {
|
||||
icon-name: "emote-love-symbolic";
|
||||
pixel-size: 32;
|
||||
}
|
||||
|
||||
styles [
|
||||
"circular",
|
||||
"capture-btn",
|
||||
]
|
||||
}
|
||||
|
||||
MenuButton video_source_btn {
|
||||
styles [
|
||||
"circular",
|
||||
"menu",
|
||||
]
|
||||
|
||||
Image {
|
||||
icon-name: "pick-camera-alt2-symbolic";
|
||||
pixel-size: 24;
|
||||
}
|
||||
|
||||
width-request: 40;
|
||||
height-request: 40;
|
||||
valign: center;
|
||||
}
|
||||
|
||||
Adw.ToggleGroup camera_mode {
|
||||
styles [
|
||||
"round",
|
||||
]
|
||||
|
||||
valign: center;
|
||||
orientation: vertical;
|
||||
active-name: "photo";
|
||||
|
||||
Adw.Toggle {
|
||||
icon-name: "camera-photo-symbolic";
|
||||
name: "photo";
|
||||
}
|
||||
|
||||
Adw.Toggle {
|
||||
icon-name: "video-camera-symbolic";
|
||||
name: "video";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*CenterBox {
|
||||
halign: fill;
|
||||
hexpand: true;
|
||||
margin-bottom: 0;
|
||||
margin-end: 0;
|
||||
margin-start: 0;
|
||||
height-request: 120;
|
||||
|
||||
styles [
|
||||
"toolbar",
|
||||
"osd",
|
||||
]
|
||||
|
||||
[center]
|
||||
CenterBox {
|
||||
[start]
|
||||
Box {
|
||||
valign: center;
|
||||
|
||||
MenuButton mode_switch {
|
||||
width-request: 80;
|
||||
height-request: 80;
|
||||
margin-end: 24;
|
||||
icon-name: "video-decode-symbolic";
|
||||
|
||||
styles [
|
||||
"circular",
|
||||
"cam-switch-btn",
|
||||
]
|
||||
}
|
||||
|
||||
MenuButton cam_switch {
|
||||
width-request: 80;
|
||||
height-request: 80;
|
||||
margin-end: 24;
|
||||
icon-name: "cameras-symbolic";
|
||||
|
||||
styles [
|
||||
"circular",
|
||||
"cam-switch-btn",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
[center]
|
||||
Button capture {
|
||||
valign: center;
|
||||
halign: center;
|
||||
width-request: 120;
|
||||
height-request: 120;
|
||||
|
||||
Image {
|
||||
icon-name: "emote-love-symbolic";
|
||||
pixel-size: 48;
|
||||
}
|
||||
|
||||
styles [
|
||||
"circular",
|
||||
"capture-btn",
|
||||
]
|
||||
}
|
||||
|
||||
[end]
|
||||
Button focus {
|
||||
valign: center;
|
||||
halign: end;
|
||||
width-request: 80;
|
||||
height-request: 80;
|
||||
margin-start: 24;
|
||||
|
||||
Box {
|
||||
halign: center;
|
||||
valign: center;
|
||||
orientation: vertical;
|
||||
|
||||
Image {
|
||||
icon-name: "camera-focus-symbolic";
|
||||
pixel-size: 32;
|
||||
}
|
||||
|
||||
Label focus_label {
|
||||
label: _("100.0");
|
||||
}
|
||||
}
|
||||
|
||||
styles [
|
||||
"circular",
|
||||
"focus-btn",
|
||||
]
|
||||
}
|
||||
}
|
||||
}*/
|
||||
content: Label label {
|
||||
label: _("Hello, World!");
|
||||
label: _("EyeNeko");
|
||||
|
||||
styles [
|
||||
"title-1",
|
||||
]
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
menu primary_menu {
|
||||
|
|
|
@ -21,9 +21,85 @@
|
|||
[GtkTemplate (ui = "/io/gitlab/nekocwd/eyeneko/window.ui")]
|
||||
public class EyeNeko.Window : Adw.ApplicationWindow {
|
||||
[GtkChild]
|
||||
private unowned Gtk.Label label;
|
||||
private unowned Gtk.Overlay overlay;
|
||||
[GtkChild]
|
||||
private unowned Adw.ToolbarView toolbar;
|
||||
[GtkChild]
|
||||
private unowned Gtk.Picture viewfinder;
|
||||
[GtkChild]
|
||||
private unowned Gtk.MenuButton video_source_btn;
|
||||
[GtkChild]
|
||||
private unowned Gtk.Button capture_btn;
|
||||
[GtkChild]
|
||||
private unowned Adw.ToggleGroup camera_mode;
|
||||
|
||||
public string camera_path { get; set; default = "Unknown"; }
|
||||
private void setup_video_source_changer () {
|
||||
notify["camera-path"].connect (() => {
|
||||
message ("Camera switching P0: retrived camera path: %s", camera_path);
|
||||
foreach (var camera in Gstreamer.Camera.get_all ()) {
|
||||
if (camera.device.get_path_string () == camera_path) {
|
||||
message ("Camera switching P1: camera found by path: %s", camera.name);
|
||||
Gstreamer.instance.start_stream_from (camera);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Gstreamer.Camera.get_all ().length > 0)
|
||||
camera_path = Gstreamer.Camera.get_all ()[0].device.get_path_string ();
|
||||
|
||||
var change_video_source_action = new PropertyAction ("change-video-source", this, "camera-path");
|
||||
add_action (change_video_source_action);
|
||||
|
||||
|
||||
|
||||
|
||||
var sources_menu = new Menu ();
|
||||
foreach (var camera in Gstreamer.Camera.get_all ()) {
|
||||
var item = new MenuItem (camera.name, null);
|
||||
item.set_action_and_target_value ("win.change-video-source", camera.device.get_path_string ());
|
||||
sources_menu.append_item (item);
|
||||
}
|
||||
|
||||
video_source_btn.set_menu_model (sources_menu);
|
||||
}
|
||||
|
||||
public Window (Gtk.Application app) {
|
||||
Object (application: app);
|
||||
overlay.add_overlay (toolbar);
|
||||
|
||||
setup_video_source_changer ();
|
||||
|
||||
Gdk.Paintable paintable;
|
||||
Gstreamer.instance.viewfinder.get ("paintable", out paintable);
|
||||
viewfinder.set_paintable (paintable);
|
||||
Gstreamer.instance.start_stream_from (Gstreamer.Camera.get_all ()[0]);
|
||||
|
||||
|
||||
// focus.clicked.connect (Gstreamer.af.reset_focus);
|
||||
// Gstreamer.af.bind_property ("prev_pos", focus_label, "label", GLib.BindingFlags.SYNC_CREATE, (b, src, ref tgt) => { tgt = "%.1f".printf (src.get_double ()); return true; });
|
||||
|
||||
capture_btn.clicked.connect (() => {
|
||||
message ("%d %d", Gstreamer.instance.camerabin_mode, (int) Gstreamer.instance.ready);
|
||||
if (Gstreamer.instance.camerabin_mode == Gstreamer.CameraBinMode.VIDEO && !Gstreamer.instance.ready) {
|
||||
Gstreamer.instance.stop_capture ();
|
||||
} else {
|
||||
Gstreamer.instance.start_capture ();
|
||||
}
|
||||
});
|
||||
|
||||
camera_mode.notify["active"].connect (() => {
|
||||
switch (camera_mode.active_name) {
|
||||
case "photo":
|
||||
Gstreamer.instance.camerabin_mode = Gstreamer.CameraBinMode.PHOTO;
|
||||
break;
|
||||
case "video":
|
||||
Gstreamer.instance.camerabin_mode = Gstreamer.CameraBinMode.VIDEO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|