This commit is contained in:
Vasiliy Doylov 2025-05-23 00:12:47 +03:00
parent 6e48e1f3cd
commit 7bf6b78fb4
Signed by: NekoCWD
GPG key ID: B7BE22D44474A582
26 changed files with 819 additions and 16 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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>

View file

@ -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',
)

View file

@ -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'

View file

@ -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
View 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);
}
}

View file

@ -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
View 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;
}
}
}

View 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

View 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
View 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
View 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;
}
}
}

View file

@ -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);
}

View file

@ -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
View 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
View 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
View file

@ -0,0 +1,6 @@
.capture-btn {
border: 5px solid white;
}
.focus-btn {
color: green;
}

View 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);
}

View file

@ -2,29 +2,246 @@ using Gtk 4.0;
using Adw 1;
template $EyeNekoWindow: Adw.ApplicationWindow {
styles [
"osd",
]
title: _("EyeNeko");
default-width: 800;
default-height: 600;
content: Adw.ToolbarView {
[top]
Adw.HeaderBar {
[end]
MenuButton {
primary: true;
icon-name: "open-menu-symbolic";
tooltip-text: _("Main Menu");
menu-model: primary_menu;
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;
icon-name: "open-menu-symbolic";
tooltip-text: _("Main Menu");
menu-model: primary_menu;
}
}
[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";
}
}
}
}
content: Label label {
label: _("Hello, World!");
/*CenterBox {
halign: fill;
hexpand: true;
margin-bottom: 0;
margin-end: 0;
margin-start: 0;
height-request: 120;
styles [
"title-1",
]
};
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: _("EyeNeko");
styles [
"title-1",
]
};
}

View file

@ -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;
}
});
}
}