GST&UI: add QR code scan logic

Signed-off-by: Vasiliy Doylov <nekocwd@mainlining.org>
This commit is contained in:
Vasiliy Doylov 2025-06-24 16:36:09 +03:00
parent 482e2188ff
commit 269a72967b
Signed by: NekoCWD
GPG key ID: B7BE22D44474A582
5 changed files with 73 additions and 25 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"><path d="m 2 0 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 9 0 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 -8 3 v 2 h 2 v -2 z m 2 2 v 2 h 2 v -2 z m 3 -2 v 5 h 5 v -5 z m 0 5 h -5 v 5 h 5 z m 1 -4 h 3 v 3 h -3 z m 1 1 v 1 h 1 v -1 z m -6 4 h 3 v 3 h -3 z m 5 0 v 3 h 1 v -1 h 1 v -1 h 1 v -1 z m 3 1 v 1 h 1 v -1 z m 0 1 h -1 v 1 h 1 z m 0 1 v 1 h 1 v -1 z m -1 0 h -1 v 1 h 1 z m -6 -2 v 1 h 1 v -1 z m -5 1 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 14 0 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" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 996 B

View file

@ -8,5 +8,6 @@
<file preprocess="xml-stripblanks">actions/camera-photo-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/pick-camera-alt2-symbolic.svg</file>
<file preprocess="xml-stripblanks">actions/encoder-knob-symbolic.svg</file> <file preprocess="xml-stripblanks">actions/encoder-knob-symbolic.svg</file>
<file preprocess="xml-stripblanks">actions/qr-code-scanner-symbolic.svg</file>
</gresource> </gresource>
</gresources> </gresources>

View file

@ -5,9 +5,39 @@ public class EyeNeko.Gstreamer : Object {
PHOTO = 1, PHOTO = 1,
} }
public enum CameraMode {
VIDEO,
PHOTO,
QR;
public string to_string () {
return ((EnumClass) typeof (CameraMode).class_ref ()).get_value (this).value_nick;
}
public static CameraMode parse (string data) {
return ((EnumClass) typeof (CameraMode).class_ref ()).get_value_by_nick (data) ? .value ?? 0;
}
public CameraBinMode to_camerabin_mode () {
return this == VIDEO ? CameraBinMode.VIDEO : CameraBinMode.PHOTO;
}
}
private static Gee.HashMap<CameraMode, Gst.Caps> mode_caps = new Gee.HashMap<CameraMode, Gst.Caps> ();
private static Gee.HashMap<CameraMode, int> mode_downscale = new Gee.HashMap<CameraMode, int> ();
static construct {
mode_caps[CameraMode.PHOTO] = new Gst.Caps.any ();
mode_caps[CameraMode.VIDEO] = new Gst.Caps.any ();
mode_caps[CameraMode.QR] = Gst.Caps.from_string ("video/x-raw, format=GRAY8");
mode_downscale[CameraMode.VIDEO] = int.parse (Env.get_variable_or ("DOWNSCALE_VIDEO", "640"));
mode_downscale[CameraMode.PHOTO] = int.parse (Env.get_variable_or ("DOWNSCALE_PHOTO", "0"));
mode_downscale[CameraMode.QR] = int.parse (Env.get_variable_or ("DOWNSCALE_QR", "0"));
}
// Public properties // 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 CameraMode camera_mode { get; set; default = CameraMode.PHOTO; }
public Gdk.Paintable viewfinder_paintable { get; set; } public Gdk.Paintable viewfinder_paintable { get; set; }
public ListStore available_caps = new ListStore (typeof (FriendlyCaps)); public ListStore available_caps = new ListStore (typeof (FriendlyCaps));
public Gtk.SingleSelection caps_selecton_model = new Gtk.SingleSelection (null); public Gtk.SingleSelection caps_selecton_model = new Gtk.SingleSelection (null);
@ -19,11 +49,11 @@ public class EyeNeko.Gstreamer : Object {
private Gst.Bin camerasrc_wrapper = (Gst.Bin) Gst.ElementFactory.make ("wrappercamerabinsrc"); private Gst.Bin camerasrc_wrapper = (Gst.Bin) Gst.ElementFactory.make ("wrappercamerabinsrc");
private Elements.ColorCorrectionMatrix color_correction_matrix = new Elements.ColorCorrectionMatrix (); private Elements.ColorCorrectionMatrix color_correction_matrix = new Elements.ColorCorrectionMatrix ();
private Elements.Downscale downscale = new Elements.Downscale (); private Elements.Downscale downscale = new Elements.Downscale ();
private Gst.Element camera_convert_caps = Gst.ElementFactory.make ("capsfilter");
public Elements.QrScaner qr_scaner = new Elements.QrScaner ();
// Other Fields // Other Fields
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_photo_to = int.parse (Env.get_variable_or ("DOWNSCALE_PHOTO", "0"));
private bool can_start_stream = true; private bool can_start_stream = true;
private void set_up_caps (Gst.Device device) { private void set_up_caps (Gst.Device device) {
@ -66,7 +96,7 @@ public class EyeNeko.Gstreamer : Object {
} }
public void start_capture () { public void start_capture () {
var file_dir = Environment.get_user_special_dir (camerabin_mode == CameraBinMode.VIDEO ? UserDirectory.VIDEOS : UserDirectory.PICTURES); var file_dir = Environment.get_user_special_dir (camera_mode == CameraMode.VIDEO ? UserDirectory.VIDEOS : UserDirectory.PICTURES);
var dir = File.new_build_filename (file_dir, "EyeNeko"); var dir = File.new_build_filename (file_dir, "EyeNeko");
try { try {
@ -79,7 +109,7 @@ public class EyeNeko.Gstreamer : Object {
var time = new DateTime.now (); var time = new DateTime.now ();
var formated = time.format ("%Y-%m-%d-%H-%M-%S"); var formated = time.format ("%Y-%m-%d-%H-%M-%S");
var filename = "%s.%s".printf (formated, camerabin_mode == CameraBinMode.VIDEO ? "mp4" : "jpeg"); var filename = "%s.%s".printf (formated, camera_mode == CameraMode.VIDEO ? "mp4" : "jpeg");
var location = Path.build_filename (dir.get_path (), filename); var location = Path.build_filename (dir.get_path (), filename);
message ("Capturing %s", location); message ("Capturing %s", location);
camerabin.set_property ("location", location); camerabin.set_property ("location", location);
@ -163,6 +193,10 @@ public class EyeNeko.Gstreamer : Object {
camerasrc_wrapper.set_property ("video-source-filter", camerasrc_wrapper.set_property ("video-source-filter",
pipe_elements ("Camera Processing", pipe_elements ("Camera Processing",
downscale, downscale,
Gst.ElementFactory.make ("videoconvert"),
camera_convert_caps,
qr_scaner,
Gst.ElementFactory.make ("videoconvert"),
Gst.ElementFactory.make ("glupload"), Gst.ElementFactory.make ("glupload"),
color_correction_matrix, color_correction_matrix,
Gst.ElementFactory.make ("gldownload") Gst.ElementFactory.make ("gldownload")
@ -181,15 +215,26 @@ public class EyeNeko.Gstreamer : Object {
))); )));
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 ("camera-mode", camerabin, "mode", BindingFlags.SYNC_CREATE, (b, s, ref d) => {
add_enc_profile (); d.set_enum (((CameraMode) s).to_camerabin_mode ());
return true;
notify["camerabin-mode"].connect (() => {
downscale.max_size = camerabin_mode == CameraBinMode.VIDEO ? downscale_video_to : downscale_photo_to;
if (current_camera != null) {
start_stream_from (current_camera);
}
}); });
this.bind_property ("camera-mode", camera_convert_caps, "caps", BindingFlags.SYNC_CREATE, (b, s, ref d) => {
d = mode_caps[((CameraMode) s)];
return true;
});
this.bind_property ("camera-mode", downscale, "max-size", BindingFlags.SYNC_CREATE, (b, s, ref d) => {
d = mode_downscale[((CameraMode) s)];
return true;
});
this.bind_property ("camera-mode", qr_scaner, "enabled", BindingFlags.SYNC_CREATE, (b, s, ref d) => {
d = (CameraMode) s == CameraMode.QR;
return true;
});
notify["camera-mode"].connect (() => start_stream_from (current_camera));
add_enc_profile ();
Gdk.GLContext context; Gdk.GLContext context;
viewfinder_paintable.get ("gl-context", out context); viewfinder_paintable.get ("gl-context", out context);

View file

@ -74,6 +74,12 @@ Adw.ToolbarView toolbar {
label: "Video"; label: "Video";
name: "video"; name: "video";
} }
Adw.Toggle {
icon-name: "qr-code-scanner-symbolic";
label: "QR";
name: "qr";
}
} }
Box { Box {

View file

@ -109,24 +109,16 @@ public class EyeNeko.Window : Adw.ApplicationWindow {
Gstreamer.instance.bind_property ("viewfinder-paintable", viewfinder, "paintable", BindingFlags.SYNC_CREATE); Gstreamer.instance.bind_property ("viewfinder-paintable", viewfinder, "paintable", BindingFlags.SYNC_CREATE);
capture_btn.clicked.connect (() => { capture_btn.clicked.connect (() => {
if (Gstreamer.instance.camerabin_mode == Gstreamer.CameraBinMode.VIDEO && !Gstreamer.instance.ready) { if (Gstreamer.instance.camera_mode == Gstreamer.CameraMode.VIDEO && !Gstreamer.instance.ready) {
Gstreamer.instance.stop_capture (); Gstreamer.instance.stop_capture ();
} else { } else {
Gstreamer.instance.start_capture (); Gstreamer.instance.start_capture ();
} }
}); });
camera_mode.notify["active"].connect (() => { camera_mode.bind_property ("active-name", Gstreamer.instance, "camera-mode", BindingFlags.SYNC_CREATE, (b, s, ref d) => {
switch (camera_mode.active_name) { d = Gstreamer.CameraMode.parse (s.get_string ());
case "photo": return true;
Gstreamer.instance.camerabin_mode = Gstreamer.CameraBinMode.PHOTO;
break;
case "video":
Gstreamer.instance.camerabin_mode = Gstreamer.CameraBinMode.VIDEO;
break;
default:
break;
}
}); });
camera_mode.active_name = Env.get_variable_or ("CAM_M", "photo"); camera_mode.active_name = Env.get_variable_or ("CAM_M", "photo");
@ -136,5 +128,7 @@ public class EyeNeko.Window : Adw.ApplicationWindow {
Gstreamer.instance.start_stream_from (null); Gstreamer.instance.start_stream_from (null);
return false; return false;
}); });
Gstreamer.instance.qr_scaner.new_qr.connect ((qr) => message ("QR scaned %s", qr));
} }
} }