WIP
Signed-off-by: Vasiliy Doylov <nekocwd@mainlining.org>
This commit is contained in:
parent
06406bb455
commit
ed2e965f9c
12 changed files with 298 additions and 204 deletions
|
@ -1,44 +0,0 @@
|
||||||
{
|
|
||||||
"id": "io.gitlab.nekocwd.eyeneko",
|
|
||||||
"runtime": "org.gnome.Platform",
|
|
||||||
"runtime-version": "master",
|
|
||||||
"sdk": "org.gnome.Sdk",
|
|
||||||
"sdk-extensions": ["org.freedesktop.Sdk.Extension.vala"],
|
|
||||||
"command": "eyeneko",
|
|
||||||
"finish-args": [
|
|
||||||
"--share=network",
|
|
||||||
"--share=ipc",
|
|
||||||
"--socket=fallback-x11",
|
|
||||||
"--device=dri",
|
|
||||||
"--socket=wayland"
|
|
||||||
],
|
|
||||||
"build-options": {
|
|
||||||
"append-path": "/usr/lib/sdk/vala/bin",
|
|
||||||
"prepend-ld-library-path": "/usr/lib/sdk/vala/lib"
|
|
||||||
},
|
|
||||||
"cleanup": [
|
|
||||||
"/include",
|
|
||||||
"/lib/pkgconfig",
|
|
||||||
"/man",
|
|
||||||
"/share/doc",
|
|
||||||
"/share/gtk-doc",
|
|
||||||
"/share/man",
|
|
||||||
"/share/pkgconfig",
|
|
||||||
"/share/vala",
|
|
||||||
"*.la",
|
|
||||||
"*.a"
|
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
{
|
|
||||||
"name": "eyeneko",
|
|
||||||
"builddir": true,
|
|
||||||
"buildsystem": "meson",
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "git",
|
|
||||||
"url": "file:///home/neko/Projects"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -29,7 +29,7 @@ public class AutoFocus : Object {
|
||||||
|
|
||||||
public void adjust_step (double prev_score) {
|
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);
|
// 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 ()) {
|
if (fc < EyeNeko.Env.get_variable_or ("FRAME_DELAY", "3").to_int ()) {
|
||||||
fc++;
|
fc++;
|
||||||
return;
|
return;
|
||||||
} else
|
} else
|
||||||
|
@ -51,10 +51,16 @@ public class AutoFocus : Object {
|
||||||
break;
|
break;
|
||||||
case 20:
|
case 20:
|
||||||
message ("meow on 2");
|
message ("meow on 2");
|
||||||
step = 0.1;
|
step = 0.5;
|
||||||
set_diff (2);
|
set_diff (2);
|
||||||
set_camfocus (prev_pos);
|
set_camfocus (prev_pos);
|
||||||
break;
|
break;
|
||||||
|
case 5:
|
||||||
|
message ("meow on 0.5");
|
||||||
|
step = 0.1;
|
||||||
|
set_diff (1);
|
||||||
|
set_camfocus (prev_pos);
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
message ("meow on 0.1");
|
message ("meow on 0.1");
|
||||||
step = 10;
|
step = 10;
|
||||||
|
@ -84,4 +90,13 @@ public class AutoFocus : Object {
|
||||||
prev_pos += step;
|
prev_pos += step;
|
||||||
set_camfocus (prev_pos);
|
set_camfocus (prev_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AutoFocus _instance = null;
|
||||||
|
public static AutoFocus instance {
|
||||||
|
get {
|
||||||
|
if (_instance == null)
|
||||||
|
_instance = new AutoFocus ();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,10 +39,14 @@ public class EyeNeko.Elements.BinBase : Gst.Bin {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void add_pads(Gst.Element sink, Gst.Element src) {
|
protected void add_pads(Gst.Element? sink, Gst.Element? src) {
|
||||||
sinkpad = new Gst.GhostPad("sink", sink.get_static_pad("sink"));
|
if (sink != null) {
|
||||||
add_pad(sinkpad);
|
sinkpad = new Gst.GhostPad("sink", sink.get_static_pad("sink"));
|
||||||
srcpad = new Gst.GhostPad("src", src.get_static_pad("src"));
|
add_pad(sinkpad);
|
||||||
add_pad(srcpad);
|
}
|
||||||
|
if (src != null) {
|
||||||
|
srcpad = new Gst.GhostPad("src", src.get_static_pad("src"));
|
||||||
|
add_pad(srcpad);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
46
src/elements/camerasrc.vala
Normal file
46
src/elements/camerasrc.vala
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/* downscale.vala
|
||||||
|
*
|
||||||
|
* Copyright 2025 Vasiliy Doylov <nekocwd@mainlining.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class EyeNeko.Elements.CameraSrc : BinBase {
|
||||||
|
public Gst.Element source = null;
|
||||||
|
public Gst.Element capsfilter = Gst.ElementFactory.make ("capsfilter", "capsfilter");
|
||||||
|
public Gst.Element decodebin = Gst.ElementFactory.make ("decodebin3", "decoder");
|
||||||
|
public Gst.Element identity = Gst.ElementFactory.make ("identity");
|
||||||
|
|
||||||
|
static construct {
|
||||||
|
set_static_metadata ("camerasrc",
|
||||||
|
"Source",
|
||||||
|
"Camera source",
|
||||||
|
"nekocwd@mainlining.org");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CameraSrc (Gst.Element src, Gst.Caps caps = new Gst.Caps.any ()) {
|
||||||
|
source = src;
|
||||||
|
add_many (source, capsfilter, decodebin, identity);
|
||||||
|
source.link_many (capsfilter, decodebin);
|
||||||
|
capsfilter.set_property ("caps", caps);
|
||||||
|
decodebin.pad_added.connect ((pad) => {
|
||||||
|
if (pad.get_stream () != null && pad.get_stream ().stream_type == Gst.StreamType.VIDEO) {
|
||||||
|
pad.link (identity.get_static_pad ("sink"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add_pads (null, identity);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
public class EyeNeko.Elements.FocusAnalyze : BinBase {
|
public class EyeNeko.Elements.FocusAnalyze : BinBase {
|
||||||
public Gst.Element tee = Gst.ElementFactory.make ("tee");
|
public Gst.Element tee = Gst.ElementFactory.make ("tee");
|
||||||
public Gst.Element queue_analyze = Gst.ElementFactory.make ("queue");
|
public Gst.Element queue_analyze = Gst.ElementFactory.make ("queue");
|
||||||
|
public Gst.Element videocrop = Gst.ElementFactory.make ("videocrop");
|
||||||
|
public Gst.Element glupload = Gst.ElementFactory.make ("glupload");
|
||||||
public Gst.Element laplacian = Gst.ElementFactory.make ("gleffects_laplacian");
|
public Gst.Element laplacian = Gst.ElementFactory.make ("gleffects_laplacian");
|
||||||
public Gst.Element gldownload = Gst.ElementFactory.make ("gldownload");
|
public Gst.Element gldownload = Gst.ElementFactory.make ("gldownload");
|
||||||
public Gst.Element videoconvert = Gst.ElementFactory.make ("videoconvert");
|
public Gst.Element videoconvert = Gst.ElementFactory.make ("videoconvert");
|
||||||
|
@ -10,10 +12,23 @@ public class EyeNeko.Elements.FocusAnalyze : BinBase {
|
||||||
|
|
||||||
public Gst.Element queue_passthrough = Gst.ElementFactory.make ("queue");
|
public Gst.Element queue_passthrough = Gst.ElementFactory.make ("queue");
|
||||||
public double focus_mark { get; private set; }
|
public double focus_mark { get; private set; }
|
||||||
|
public const int window_h = 256;
|
||||||
|
public const int window_w = 256;
|
||||||
|
public double window_wpos { get; set; default = 0.1; }
|
||||||
|
public double window_hpos { get; set; default = 0.1; }
|
||||||
|
public int width = 0;
|
||||||
|
public int height = 0;
|
||||||
|
public enum Orientation {
|
||||||
|
ROTATE0,
|
||||||
|
ROTATE90,
|
||||||
|
ROTATE180,
|
||||||
|
ROTATE270
|
||||||
|
}
|
||||||
|
Orientation orientation = Orientation.ROTATE0;
|
||||||
|
|
||||||
public Gdk.Paintable paintable {
|
public Gdk.Paintable paintable {
|
||||||
owned get {
|
owned get {
|
||||||
Gdk.Paintable paintable;
|
Gdk.Paintable paintable = null;
|
||||||
gtk4paintablesink.get ("paintable", out paintable);
|
gtk4paintablesink.get ("paintable", out paintable);
|
||||||
return paintable;
|
return paintable;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +41,7 @@ public class EyeNeko.Elements.FocusAnalyze : BinBase {
|
||||||
double val;
|
double val;
|
||||||
message.get_structure ().get_double ("luma-average", out val);
|
message.get_structure ().get_double ("luma-average", out val);
|
||||||
focus_mark = val;
|
focus_mark = val;
|
||||||
|
AutoFocus.instance.adjust_step (focus_mark);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -41,14 +57,105 @@ public class EyeNeko.Elements.FocusAnalyze : BinBase {
|
||||||
"Focus analyze video filter.",
|
"Focus analyze video filter.",
|
||||||
"nekocwd@mainlining.org");
|
"nekocwd@mainlining.org");
|
||||||
}
|
}
|
||||||
|
public void set_window () {
|
||||||
|
var wpos = window_wpos;
|
||||||
|
var hpos = window_hpos;
|
||||||
|
switch (orientation) {
|
||||||
|
case Orientation.ROTATE90:
|
||||||
|
wpos = window_hpos;
|
||||||
|
hpos = 1 - window_wpos;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Orientation.ROTATE180:
|
||||||
|
wpos = 1 - window_wpos;
|
||||||
|
hpos = 1 - window_hpos;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Orientation.ROTATE270:
|
||||||
|
wpos = 1 - window_hpos;
|
||||||
|
hpos = window_wpos;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int left = (int) (width * wpos - window_w / 2);
|
||||||
|
left = int.min (width - window_w, int.max (left, 0));
|
||||||
|
left = int.max (0, left);
|
||||||
|
|
||||||
|
int right = width - left - window_w;
|
||||||
|
right = int.max (0, right);
|
||||||
|
|
||||||
|
int top = (int) (height * hpos - window_h / 2);
|
||||||
|
top = int.min (height - window_h, int.max (top, 0));
|
||||||
|
top = int.max (0, top);
|
||||||
|
|
||||||
|
int bottom = height - top - window_h;
|
||||||
|
bottom = int.max (0, bottom);
|
||||||
|
|
||||||
|
|
||||||
|
videocrop.set_property ("left", left);
|
||||||
|
videocrop.set_property ("right", right);
|
||||||
|
videocrop.set_property ("top", top);
|
||||||
|
videocrop.set_property ("bottom", bottom);
|
||||||
|
message ("l=%d r=%d t=%d b=%d (%dx%d)", left, right, top, bottom, width - left - right, height - top - bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sink_event (Gst.Pad pad, Gst.Object? parent, owned Gst.Event event) {
|
||||||
|
var filter = (FocusAnalyze) parent;
|
||||||
|
if (event.type == Gst.EventType.TAG) {
|
||||||
|
Gst.TagList taglist;
|
||||||
|
event.parse_tag (out taglist);
|
||||||
|
string orientation;
|
||||||
|
taglist.get_string ("image-orientation", out orientation);
|
||||||
|
if (orientation != null) {
|
||||||
|
switch (orientation) {
|
||||||
|
case "rotate-0" :
|
||||||
|
filter.orientation = Orientation.ROTATE0;
|
||||||
|
break;
|
||||||
|
case "rotate-90":
|
||||||
|
filter.orientation = Orientation.ROTATE90;
|
||||||
|
break;
|
||||||
|
case "rotate-180":
|
||||||
|
filter.orientation = Orientation.ROTATE180;
|
||||||
|
break;
|
||||||
|
case "rotate-270":
|
||||||
|
filter.orientation = Orientation.ROTATE270;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
message ("%s", orientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.type == Gst.EventType.CAPS) {
|
||||||
|
message ("Caps");
|
||||||
|
Gst.Caps caps;
|
||||||
|
event.parse_caps (out caps);
|
||||||
|
var info = new Gst.Video.Info.with_caps (caps);
|
||||||
|
filter.width = info.width;
|
||||||
|
filter.height = info.height;
|
||||||
|
filter.set_window ();
|
||||||
|
}
|
||||||
|
if (event.type == Gst.EventType.STREAM_START) {
|
||||||
|
message ("Stream-Collection");
|
||||||
|
filter.width = 0;
|
||||||
|
filter.height = 0;
|
||||||
|
filter.set_window ();
|
||||||
|
}
|
||||||
|
|
||||||
|
return pad.event_default (parent, event);
|
||||||
|
}
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
add_many (tee, queue_analyze, laplacian, gldownload, videoconvert, videoanalyze, videoconvert2, gtk4paintablesink, queue_passthrough);
|
add_many (tee, queue_analyze, videocrop, glupload, laplacian, gldownload, videoconvert, videoanalyze, videoconvert2, gtk4paintablesink, queue_passthrough);
|
||||||
tee.link_many (queue_analyze, laplacian, gldownload, videoconvert, videoanalyze, videoconvert2, gtk4paintablesink);
|
|
||||||
tee.link_many (queue_passthrough);
|
tee.link_many (queue_passthrough);
|
||||||
add_pads (tee, queue_passthrough);
|
add_pads (tee, queue_passthrough);
|
||||||
|
tee.link_many (queue_analyze, videocrop, glupload, laplacian, gldownload, videoconvert, videoanalyze, videoconvert2, gtk4paintablesink);
|
||||||
|
sinkpad.set_event_function (sink_event, null, null);
|
||||||
var bus = new Gst.Bus ();
|
var bus = new Gst.Bus ();
|
||||||
videoanalyze.set_bus (bus);
|
videoanalyze.set_bus (bus);
|
||||||
bus.add_watch (0, bus_callback);
|
bus.add_watch (0, bus_callback);
|
||||||
|
notify["window-wpos"].connect (set_window);
|
||||||
|
notify["window-hpos"].connect (set_window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
141
src/gst.vala
141
src/gst.vala
|
@ -1,16 +1,41 @@
|
||||||
|
|
||||||
public class EyeNeko.Gstreamer : Object {
|
public class EyeNeko.Gstreamer : Object {
|
||||||
public Gst.Element viewfinder;
|
|
||||||
private Gst.Bin camerabin;
|
|
||||||
public enum CameraBinMode {
|
public enum CameraBinMode {
|
||||||
VIDEO = 2,
|
VIDEO = 2,
|
||||||
PHOTO = 1,
|
PHOTO = 1,
|
||||||
}
|
}
|
||||||
|
// Public properties
|
||||||
public bool ready { get; set; default = false; }
|
public bool ready { get; set; default = false; }
|
||||||
public CameraBinMode camerabin_mode { get; set; default = CameraBinMode.PHOTO; }
|
public CameraBinMode camerabin_mode { get; set; default = CameraBinMode.PHOTO; }
|
||||||
|
|
||||||
|
public Gdk.Paintable viewfinder_paintable {
|
||||||
|
owned get {
|
||||||
|
Gdk.Paintable paintable;
|
||||||
|
viewfinder.get ("paintable", out paintable);
|
||||||
|
return paintable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Gdk.Paintable focus_paintable {
|
||||||
|
owned get {
|
||||||
|
return focus_analyze.paintable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private elements
|
||||||
|
private Gst.Bin camerabin = (Gst.Bin) Gst.ElementFactory.make ("camerabin", "camerabin");
|
||||||
|
|
||||||
|
private Gst.Element viewfinder = Gst.ElementFactory.make ("gtk4paintablesink", "paintablesink");
|
||||||
|
|
||||||
|
private Gst.Bin camerasrc_wrapper = (Gst.Bin) Gst.ElementFactory.make ("wrappercamerabinsrc");
|
||||||
|
public Elements.FocusAnalyze focus_analyze = new Elements.FocusAnalyze ();
|
||||||
|
private Elements.ColorCorrectionMatrix color_correction_matrix = new Elements.ColorCorrectionMatrix ();
|
||||||
|
private Elements.Downscale downscale = new Elements.Downscale ();
|
||||||
|
|
||||||
|
|
||||||
public Camera current_camera = null;
|
public Camera current_camera = null;
|
||||||
public int downscale_video_to = int.parse (Env.get_variable_or ("DOWNSCALE_VIDEO", "640"));
|
public int downscale_video_to = int.parse (Env.get_variable_or ("DOWNSCALE_VIDEO", "640"));
|
||||||
public int downscale_photo_to = int.parse (Env.get_variable_or ("DOWNSCALE_PHOTO", "0"));
|
public int downscale_photo_to = int.parse (Env.get_variable_or ("DOWNSCALE_PHOTO", "0"));
|
||||||
|
public delegate int SelectStreamCB (Gst.Element decodebin, Gst.StreamCollection collection, Gst.Stream stream, void* userdata);
|
||||||
|
|
||||||
|
|
||||||
Gst.Caps get_best_caps (Gst.Device device) {
|
Gst.Caps get_best_caps (Gst.Device device) {
|
||||||
|
@ -23,7 +48,7 @@ public class EyeNeko.Gstreamer : Object {
|
||||||
var caps = device.get_caps ();
|
var caps = device.get_caps ();
|
||||||
for (uint i = 0; i < caps.get_size (); i++) {
|
for (uint i = 0; i < caps.get_size (); i++) {
|
||||||
var format = caps.get_structure (i).get_name ();
|
var format = caps.get_structure (i).get_name ();
|
||||||
int num, denom, width, height;
|
int num = 1, denom = 1, width, height;
|
||||||
if (caps.get_structure (i).get_field_type ("width") != typeof (int) || caps.get_structure (i).get_field_type ("height") != typeof (int)) {
|
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!");
|
warning ("Can't find best caps :(. I hate arrays!");
|
||||||
}
|
}
|
||||||
|
@ -86,13 +111,6 @@ public class EyeNeko.Gstreamer : Object {
|
||||||
message ("Capturing %s", location);
|
message ("Capturing %s", location);
|
||||||
camerabin.set_property ("location", location);
|
camerabin.set_property ("location", location);
|
||||||
Signal.emit_by_name (camerabin, "start-capture");
|
Signal.emit_by_name (camerabin, "start-capture");
|
||||||
var t = new TimeoutSource.seconds (5);
|
|
||||||
t.set_callback (() => {
|
|
||||||
Gst.Debug.bin_to_dot_file ((Gst.Bin) camerabin, Gst.DebugGraphDetails.ALL, "Capture");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
t.attach ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop_capture () {
|
public void stop_capture () {
|
||||||
|
@ -114,37 +132,13 @@ public class EyeNeko.Gstreamer : Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start_stream_from (Camera camera) {
|
public void start_stream_from (Camera camera) {
|
||||||
current_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",
|
|
||||||
pipe_elements ("Camera filter",
|
|
||||||
new Elements.Downscale.to (camerabin_mode == CameraBinMode.VIDEO ? downscale_video_to : downscale_photo_to),
|
|
||||||
new Logic.Filters.CCM ("CCM", true, true).element
|
|
||||||
));
|
|
||||||
|
|
||||||
camerabin.set_property ("camera-source", camerasrc_wrapper);
|
|
||||||
|
|
||||||
camerabin.set_state (Gst.State.NULL);
|
camerabin.set_state (Gst.State.NULL);
|
||||||
|
|
||||||
|
current_camera = camera;
|
||||||
|
camerasrc_wrapper.set_property
|
||||||
|
("video-source",
|
||||||
|
new Elements.CameraSrc (camera.device.create_element (null),
|
||||||
|
get_best_caps (camera.device)));
|
||||||
camerabin.set_state (Gst.State.PLAYING);
|
camerabin.set_state (Gst.State.PLAYING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,35 +153,53 @@ public class EyeNeko.Gstreamer : Object {
|
||||||
|
|
||||||
public void init (ref weak string[] args) {
|
public void init (ref weak string[] args) {
|
||||||
Gst.init (ref args);
|
Gst.init (ref args);
|
||||||
|
camerabin.set_property ("camera-source", camerasrc_wrapper);
|
||||||
|
camerabin.set_property ("viewfinder-sink", viewfinder);
|
||||||
|
color_correction_matrix.red_in_red = 1f;
|
||||||
|
color_correction_matrix.red_in_blue = 0.1f;
|
||||||
|
color_correction_matrix.red_in_green = 0.1f;
|
||||||
|
color_correction_matrix.blue_in_red = 0.1f;
|
||||||
|
color_correction_matrix.blue_in_blue = 1f;
|
||||||
|
color_correction_matrix.blue_in_green = 0.1f;
|
||||||
|
color_correction_matrix.green_in_green = 0.8f;
|
||||||
|
|
||||||
|
camerasrc_wrapper.set_property ("video-source-filter",
|
||||||
|
pipe_elements ("Camera Processing",
|
||||||
|
focus_analyze,
|
||||||
|
Gst.ElementFactory.make ("glupload"),
|
||||||
|
color_correction_matrix,
|
||||||
|
downscale
|
||||||
|
));
|
||||||
|
|
||||||
|
camerabin.set_property ("image-filter",
|
||||||
|
pipe_elements ("Image Processing",
|
||||||
|
Gst.ElementFactory.make ("gldownload")
|
||||||
|
));
|
||||||
|
|
||||||
camerabin = (Gst.Bin) Gst.ElementFactory.make ("camerabin", "camerabin");
|
|
||||||
camerabin.set_property ("video-filter",
|
camerabin.set_property ("video-filter",
|
||||||
pipe_elements ("Video Pipeline",
|
pipe_elements ("Video Processing",
|
||||||
|
Gst.ElementFactory.make ("gldownload"),
|
||||||
Gst.ElementFactory.make ("queue"),
|
Gst.ElementFactory.make ("queue"),
|
||||||
Gst.ElementFactory.make ("videoconvert"),
|
Gst.ElementFactory.make ("videoconvert"),
|
||||||
Gst.parse_launch (Env.get_variable_or
|
Gst.parse_launch (Env.get_variable_or
|
||||||
(
|
(
|
||||||
"VIDEO_ENCODE",
|
"VIDEO_ENCODE",
|
||||||
"x264enc tune=zerolatency speed-preset=ultrafast bitrate=8192"
|
"x264enc tune=zerolatency speed-preset=ultrafast bitrate=8192"
|
||||||
))));
|
)
|
||||||
|
)));
|
||||||
|
|
||||||
camerabin.bind_property ("idle", this, "ready", BindingFlags.SYNC_CREATE);
|
camerabin.bind_property ("idle", this, "ready", BindingFlags.SYNC_CREATE);
|
||||||
this.bind_property ("camerabin-mode", camerabin, "mode");
|
this.bind_property ("camerabin-mode", camerabin, "mode");
|
||||||
add_enc_profile ();
|
add_enc_profile ();
|
||||||
|
|
||||||
notify["camerabin-mode"].connect (() => {
|
notify["camerabin-mode"].connect (() => {
|
||||||
|
downscale.max_size = camerabin_mode == CameraBinMode.VIDEO ? downscale_video_to : downscale_photo_to;
|
||||||
start_stream_from (current_camera);
|
start_stream_from (current_camera);
|
||||||
});
|
});
|
||||||
|
|
||||||
camerabin.bus.add_watch (0, bus_callback);
|
camerabin.bus.add_watch (0, bus_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Gdk.Paintable init_viewfinder () {
|
|
||||||
Gdk.Paintable paintable;
|
|
||||||
viewfinder = Gst.ElementFactory.make ("gtk4paintablesink", "paintablesink");
|
|
||||||
viewfinder.get ("paintable", out paintable);
|
|
||||||
camerabin.set_property ("viewfinder-sink", viewfinder);
|
|
||||||
return paintable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void add_pads_to_bin (ref Gst.Bin bin, bool src = true, bool sink = true) {
|
public static void add_pads_to_bin (ref Gst.Bin bin, bool src = true, bool sink = true) {
|
||||||
if (src)
|
if (src)
|
||||||
bin.add_pad (new Gst.GhostPad ("src", ((Gst.Element) bin.get_child_by_index (0)).get_static_pad ("src")));
|
bin.add_pad (new Gst.GhostPad ("src", ((Gst.Element) bin.get_child_by_index (0)).get_static_pad ("src")));
|
||||||
|
@ -201,10 +213,10 @@ public class EyeNeko.Gstreamer : Object {
|
||||||
GLib.Error err;
|
GLib.Error err;
|
||||||
string debug;
|
string debug;
|
||||||
message.parse_error (out err, out debug);
|
message.parse_error (out err, out debug);
|
||||||
GLib.warning ("Error: %s", err.message);
|
GLib.warning ("Error: %s %s", err.message, err.domain.to_string ());
|
||||||
break;
|
break;
|
||||||
case Gst.MessageType.EOS:
|
case Gst.MessageType.EOS:
|
||||||
GLib.warning ("end of stream\n");
|
GLib.warning ("End of stream\n");
|
||||||
break;
|
break;
|
||||||
case Gst.MessageType.STATE_CHANGED:
|
case Gst.MessageType.STATE_CHANGED:
|
||||||
Gst.State oldstate;
|
Gst.State oldstate;
|
||||||
|
@ -212,21 +224,13 @@ public class EyeNeko.Gstreamer : Object {
|
||||||
Gst.State pending;
|
Gst.State pending;
|
||||||
message.parse_state_changed (out oldstate, out newstate,
|
message.parse_state_changed (out oldstate, out newstate,
|
||||||
out pending);
|
out pending);
|
||||||
// GLib.message ("state changed: %s->%s:%s\n",
|
/*
|
||||||
// oldstate.to_string (), newstate.to_string (),
|
GLib.message ("state changed: %s->%s:%s\n",
|
||||||
// pending.to_string ());
|
oldstate.to_string (), newstate.to_string (),
|
||||||
break;
|
pending.to_string ());
|
||||||
case Gst.MessageType.TAG:
|
*/
|
||||||
Gst.TagList tag_list;
|
|
||||||
stdout.printf ("taglist found\n");
|
|
||||||
message.parse_tag (out tag_list);
|
|
||||||
break;
|
break;
|
||||||
case Gst.MessageType.ELEMENT:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -246,4 +250,9 @@ public class EyeNeko.Gstreamer : Object {
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static construct {
|
||||||
|
unowned string[] ? gst_args = null;
|
||||||
|
Gst.init (ref gst_args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
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.1f; }
|
|
||||||
public float red_in_blue { get; set; default = 0.1f; }
|
|
||||||
|
|
||||||
public float green_in_red { get; set; default = 0.0f; }
|
|
||||||
public float green_in_green { get; set; default = 0.8f; }
|
|
||||||
public float green_in_blue { get; set; default = 0.0f; }
|
|
||||||
|
|
||||||
public float blue_in_red { get; set; default = 0.1f; }
|
|
||||||
public float blue_in_green { get; set; default = 0.1f; }
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,14 +6,14 @@ eyeneko_sources = [
|
||||||
'gst.vala',
|
'gst.vala',
|
||||||
'pipetap_proxy.vala',
|
'pipetap_proxy.vala',
|
||||||
'auto_focus.vala',
|
'auto_focus.vala',
|
||||||
'logic/color_correction_filter.vala',
|
|
||||||
'logic/filter.vala',
|
|
||||||
'logic/helpers.vala',
|
'logic/helpers.vala',
|
||||||
'elements/bin_base.vala',
|
'elements/bin_base.vala',
|
||||||
|
'elements/camerasrc.vala',
|
||||||
'elements/downscale.vala',
|
'elements/downscale.vala',
|
||||||
'elements/focus_analyze.vala',
|
'elements/focus_analyze.vala',
|
||||||
'elements/gl_filter.vala',
|
'elements/gl_filter.vala',
|
||||||
'elements/color_correction_matrix.vala',
|
'elements/color_correction_matrix.vala',
|
||||||
|
'ui/preferences.vala',
|
||||||
]
|
]
|
||||||
|
|
||||||
vapi_dir = meson.current_source_dir() / 'vapi'
|
vapi_dir = meson.current_source_dir() / 'vapi'
|
||||||
|
|
6
src/ui/preferences.vala
Normal file
6
src/ui/preferences.vala
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
public class EyeNeko.Ui.Settings : Adw.PreferencesDialog {
|
||||||
|
private Adw.PreferencesPage cameras = new Adw.PreferencesPage ();
|
||||||
|
construct {
|
||||||
|
cameras.title = _("Cameras");
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,13 +13,19 @@ template $EyeNekoWindow: Adw.ApplicationWindow {
|
||||||
content: Gtk.Overlay overlay {
|
content: Gtk.Overlay overlay {
|
||||||
child: Gtk.Picture viewfinder {
|
child: Gtk.Picture viewfinder {
|
||||||
halign: fill;
|
halign: fill;
|
||||||
valign: fill;
|
valign: start;
|
||||||
hexpand: true;
|
hexpand: true;
|
||||||
vexpand: true;
|
vexpand: true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Gtk.Picture focus_overlay {
|
||||||
|
opacity: 0.5;
|
||||||
|
halign: start;
|
||||||
|
valign: start;
|
||||||
|
}
|
||||||
|
|
||||||
Adw.ToolbarView toolbar {
|
Adw.ToolbarView toolbar {
|
||||||
styles [
|
styles [
|
||||||
"flat",
|
"flat",
|
||||||
|
@ -64,7 +70,7 @@ Adw.ToolbarView toolbar {
|
||||||
valign: center;
|
valign: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button focus_btn {
|
||||||
valign: center;
|
valign: center;
|
||||||
halign: end;
|
halign: end;
|
||||||
width-request: 60;
|
width-request: 60;
|
||||||
|
|
|
@ -27,12 +27,19 @@ public class EyeNeko.Window : Adw.ApplicationWindow {
|
||||||
[GtkChild]
|
[GtkChild]
|
||||||
private unowned Gtk.Picture viewfinder;
|
private unowned Gtk.Picture viewfinder;
|
||||||
[GtkChild]
|
[GtkChild]
|
||||||
|
private unowned Gtk.Picture focus_overlay;
|
||||||
|
[GtkChild]
|
||||||
private unowned Gtk.MenuButton video_source_btn;
|
private unowned Gtk.MenuButton video_source_btn;
|
||||||
[GtkChild]
|
[GtkChild]
|
||||||
private unowned Gtk.Button capture_btn;
|
private unowned Gtk.Button capture_btn;
|
||||||
[GtkChild]
|
[GtkChild]
|
||||||
private unowned Adw.ToggleGroup camera_mode;
|
private unowned Adw.ToggleGroup camera_mode;
|
||||||
|
|
||||||
|
[GtkChild]
|
||||||
|
private unowned Gtk.Button focus_btn;
|
||||||
|
[GtkChild]
|
||||||
|
private unowned Gtk.Label focus_label;
|
||||||
|
|
||||||
public string camera_path { get; set; default = "Unknown"; }
|
public string camera_path { get; set; default = "Unknown"; }
|
||||||
private void setup_video_source_changer () {
|
private void setup_video_source_changer () {
|
||||||
notify["camera-path"].connect (() => {
|
notify["camera-path"].connect (() => {
|
||||||
|
@ -74,16 +81,31 @@ public class EyeNeko.Window : Adw.ApplicationWindow {
|
||||||
public Window (Gtk.Application app) {
|
public Window (Gtk.Application app) {
|
||||||
Object (application: app);
|
Object (application: app);
|
||||||
overlay.add_overlay (toolbar);
|
overlay.add_overlay (toolbar);
|
||||||
|
overlay.add_overlay (focus_overlay);
|
||||||
|
|
||||||
|
|
||||||
setup_video_source_changer ();
|
setup_video_source_changer ();
|
||||||
|
|
||||||
|
Gstreamer.instance.bind_property ("focus-paintable", focus_overlay, "paintable", BindingFlags.SYNC_CREATE);
|
||||||
|
|
||||||
viewfinder.set_paintable (Gstreamer.instance.init_viewfinder ());
|
viewfinder.set_paintable (Gstreamer.instance.viewfinder_paintable);
|
||||||
Gstreamer.instance.start_stream_from (Gstreamer.Camera.get_all ()[0]);
|
Gstreamer.instance.start_stream_from (Gstreamer.Camera.get_all ()[0]);
|
||||||
|
var ec = new Gtk.GestureClick ();
|
||||||
|
overlay.add_controller (ec);
|
||||||
|
ec.pressed.connect ((n, x, y) => {
|
||||||
|
if (!viewfinder.contains (x, y))
|
||||||
|
return;
|
||||||
|
var width = x / viewfinder.get_width ();
|
||||||
|
var height = y / viewfinder.get_height ();
|
||||||
|
|
||||||
|
Gstreamer.instance.focus_analyze.window_hpos = height;
|
||||||
|
Gstreamer.instance.focus_analyze.window_wpos = width;
|
||||||
|
});
|
||||||
|
ec.button = 0;
|
||||||
|
|
||||||
|
|
||||||
// focus.clicked.connect (Gstreamer.af.reset_focus);
|
focus_btn.clicked.connect (AutoFocus.instance.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; });
|
AutoFocus.instance.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 (() => {
|
capture_btn.clicked.connect (() => {
|
||||||
message ("%d %d", Gstreamer.instance.camerabin_mode, (int) Gstreamer.instance.ready);
|
message ("%d %d", Gstreamer.instance.camerabin_mode, (int) Gstreamer.instance.ready);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue