2014-01-25 23:13:56 +00:00
|
|
|
#!/usr/bin/python3
|
2013-07-15 23:12:55 +00:00
|
|
|
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
2014-01-25 23:13:56 +00:00
|
|
|
# Mugshot - Lightweight user configuration utility
|
2020-12-29 03:55:14 +00:00
|
|
|
# Copyright (C) 2013-2020 Sean Davis <sean@bluesabre.org>
|
2014-01-25 22:21:53 +00:00
|
|
|
#
|
2014-01-25 23:13:56 +00:00
|
|
|
# Portions of this file are adapted from web_cam_box,
|
|
|
|
# Copyright (C) 2010 Rick Spencer <rick.spencer@canonical.com>
|
2014-01-25 22:21:53 +00:00
|
|
|
#
|
2014-01-25 23:13:56 +00:00
|
|
|
# This program is free software: you can redistribute it and/or modify it
|
2014-07-28 00:07:47 +00:00
|
|
|
# under the terms of the GNU General Public License as published by
|
2014-08-24 16:14:29 +00:00
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
2014-07-28 00:07:47 +00:00
|
|
|
# (at your option) any later version.
|
2014-01-25 23:13:56 +00:00
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful, but
|
|
|
|
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
|
|
|
# MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.
|
2013-07-15 23:12:55 +00:00
|
|
|
|
2016-10-18 10:34:57 +00:00
|
|
|
import os
|
2013-07-15 23:12:55 +00:00
|
|
|
import logging
|
2016-10-18 10:34:57 +00:00
|
|
|
|
|
|
|
from locale import gettext as _
|
2013-07-15 23:12:55 +00:00
|
|
|
|
2016-03-27 02:05:17 +00:00
|
|
|
import gi
|
|
|
|
gi.require_version('Gst', '1.0')
|
|
|
|
gi.require_version('Cheese', '3.0')
|
|
|
|
gi.require_version('GtkClutter', '1.0')
|
|
|
|
|
2018-08-07 10:14:29 +00:00
|
|
|
from gi.repository import Gtk, GObject, Gst # nopep8
|
2016-10-18 10:34:57 +00:00
|
|
|
from gi.repository import Cheese, Clutter, GtkClutter # nopep8
|
2013-07-16 00:48:29 +00:00
|
|
|
|
2016-10-18 10:34:57 +00:00
|
|
|
from mugshot_lib import helpers # nopep8
|
|
|
|
from mugshot_lib.CameraDialog import CameraDialog # nopep8
|
2013-07-15 23:12:55 +00:00
|
|
|
|
2016-10-18 10:34:57 +00:00
|
|
|
logger = logging.getLogger('mugshot')
|
2013-07-15 23:12:55 +00:00
|
|
|
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
class CameraBox(GtkClutter.Embed):
|
|
|
|
__gsignals__ = {
|
|
|
|
'photo-saved': (GObject.SIGNAL_RUN_LAST,
|
|
|
|
GObject.TYPE_NONE,
|
|
|
|
(GObject.TYPE_STRING,)),
|
|
|
|
'gst-state-changed': (GObject.SIGNAL_RUN_LAST,
|
|
|
|
GObject.TYPE_NONE,
|
|
|
|
(GObject.TYPE_INT,))
|
|
|
|
}
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def __init__(self, parent):
|
|
|
|
GtkClutter.Embed.__init__(self)
|
|
|
|
self.state = Gst.State.NULL
|
|
|
|
self.parent = parent
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2016-10-18 04:03:13 +00:00
|
|
|
video_texture = self.setup_ui()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2016-10-18 04:03:13 +00:00
|
|
|
self.camera = Cheese.Camera.new(video_texture,
|
|
|
|
"Mugshot", 1280, 720)
|
2015-08-30 02:24:42 +00:00
|
|
|
Cheese.Camera.setup(self.camera, None)
|
|
|
|
Cheese.Camera.play(self.camera)
|
|
|
|
self.state = Gst.State.PLAYING
|
|
|
|
|
|
|
|
def added(signal, data):
|
2018-08-07 10:14:29 +00:00
|
|
|
if "get_device_node" in dir(data):
|
2016-02-13 12:53:59 +00:00
|
|
|
node = data.get_device_node()
|
|
|
|
self.camera.set_device_by_device_node(node)
|
|
|
|
else:
|
|
|
|
self.camera.set_device(data)
|
2015-08-30 02:24:42 +00:00
|
|
|
self.camera.switch_camera_device()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
|
|
|
device_monitor = Cheese.CameraDeviceMonitor.new()
|
2015-08-30 02:24:42 +00:00
|
|
|
device_monitor.connect("added", added)
|
|
|
|
device_monitor.coldplug()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
self.camera.connect("photo-taken", self.on_photo_taken)
|
|
|
|
self.camera.connect("state-flags-changed", self.on_state_flags_changed)
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
self._save_filename = ""
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2016-10-18 04:03:13 +00:00
|
|
|
def setup_ui(self):
|
|
|
|
viewport = self.get_stage()
|
|
|
|
|
|
|
|
video_preview = Clutter.Actor.new()
|
|
|
|
video_preview.set_content_gravity(Clutter.ContentGravity.RESIZE_ASPECT)
|
|
|
|
video_preview.set_x_expand(True)
|
|
|
|
video_preview.set_y_expand(True)
|
|
|
|
video_preview.props.min_height = 100.0
|
|
|
|
video_preview.props.min_width = 100.0
|
|
|
|
video_texture = video_preview
|
|
|
|
|
|
|
|
viewport_layout = Clutter.Actor.new()
|
|
|
|
viewport_layout.add_child(video_preview)
|
|
|
|
|
|
|
|
viewport_layout_manager = Clutter.BinLayout()
|
|
|
|
|
|
|
|
background_layer = Clutter.Actor.new()
|
2018-08-06 09:47:21 +00:00
|
|
|
background_layer.props.background_color = \
|
2018-08-06 10:38:21 +00:00
|
|
|
Clutter.Color.from_string("Black")[1]
|
2016-10-18 04:03:13 +00:00
|
|
|
background_layer.props.x = 0
|
|
|
|
background_layer.props.y = 0
|
|
|
|
background_layer.props.width = 100
|
|
|
|
background_layer.props.height = 100
|
|
|
|
|
|
|
|
video_preview.props.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH
|
|
|
|
|
|
|
|
viewport.add_child(background_layer)
|
|
|
|
|
|
|
|
viewport_layout.set_layout_manager(viewport_layout_manager)
|
|
|
|
|
|
|
|
viewport.add_child(viewport_layout)
|
|
|
|
|
2016-10-18 10:34:57 +00:00
|
|
|
viewport.connect("allocation_changed", self.on_stage_resize,
|
|
|
|
viewport_layout, background_layer)
|
2016-10-18 04:03:13 +00:00
|
|
|
|
|
|
|
return video_texture
|
|
|
|
|
|
|
|
def on_stage_resize(self, actor, box, flags, layout, background):
|
|
|
|
s_width, s_height = self.get_stage().get_size()
|
|
|
|
|
|
|
|
v_width = self.camera.props.format.width
|
|
|
|
v_height = self.camera.props.format.height
|
|
|
|
|
|
|
|
square = min(s_width, s_height)
|
|
|
|
if v_width > v_height:
|
|
|
|
scale = square / v_height
|
|
|
|
v_height = square
|
|
|
|
v_width = v_width * scale
|
|
|
|
else:
|
|
|
|
scale = square / v_width
|
|
|
|
v_height = v_height * scale
|
|
|
|
v_width = square
|
|
|
|
|
|
|
|
x_adj, y_adj = (s_width - v_width) / 2.0, (s_height - v_height) / 2.0
|
|
|
|
|
|
|
|
layout.set_size(v_width, v_height)
|
|
|
|
layout.set_x(x_adj)
|
|
|
|
layout.set_y(y_adj)
|
|
|
|
|
|
|
|
background.set_size(s_width, s_height)
|
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def on_state_flags_changed(self, camera, state):
|
|
|
|
self.state = state
|
|
|
|
self.emit("gst-state-changed", self.state)
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def play(self):
|
|
|
|
if self.state != Gst.State.PLAYING:
|
|
|
|
Cheese.Camera.play(self.camera)
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def pause(self):
|
|
|
|
if self.state == Gst.State.PLAYING:
|
|
|
|
Cheese.Camera.play(self.camera)
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def stop(self):
|
|
|
|
Cheese.Camera.stop(self.camera)
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def take_photo(self, target_filename):
|
|
|
|
self._save_filename = target_filename
|
|
|
|
return self.camera.take_photo_pixbuf()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def on_photo_taken(self, camera, pixbuf):
|
|
|
|
# Get the image dimensions.
|
|
|
|
height = pixbuf.get_height()
|
|
|
|
width = pixbuf.get_width()
|
|
|
|
start_x = 0
|
|
|
|
start_y = 0
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
# Calculate a balanced center.
|
|
|
|
if width > height:
|
|
|
|
start_x = (width - height) / 2
|
|
|
|
width = height
|
|
|
|
else:
|
|
|
|
start_y = (height - width) / 2
|
|
|
|
height = width
|
2013-07-17 23:48:43 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
# Create a new cropped pixbuf.
|
|
|
|
new_pixbuf = pixbuf.new_subpixbuf(start_x, start_y, width, height)
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
# Overwrite the temporary file with our new cropped image.
|
|
|
|
new_pixbuf.savev(self._save_filename, "png", [], [])
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
self.emit("photo-saved", self._save_filename)
|
2015-09-01 02:31:02 +00:00
|
|
|
|
|
|
|
|
2013-07-15 23:12:55 +00:00
|
|
|
class CameraMugshotDialog(CameraDialog):
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2014-01-25 22:21:53 +00:00
|
|
|
"""Camera Capturing Dialog"""
|
2013-07-15 23:12:55 +00:00
|
|
|
__gtype_name__ = "CameraMugshotDialog"
|
2015-08-30 02:24:42 +00:00
|
|
|
__gsignals__ = {'apply': (GObject.SIGNAL_RUN_LAST,
|
|
|
|
GObject.TYPE_NONE,
|
|
|
|
(GObject.TYPE_STRING,))
|
2018-08-07 10:14:29 +00:00
|
|
|
}
|
2013-07-15 23:12:55 +00:00
|
|
|
|
2014-01-25 22:21:53 +00:00
|
|
|
def finish_initializing(self, builder): # pylint: disable=E1002
|
2013-07-16 02:57:35 +00:00
|
|
|
"""Set up the camera dialog"""
|
2013-07-15 23:12:55 +00:00
|
|
|
super(CameraMugshotDialog, self).finish_initializing(builder)
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 02:57:35 +00:00
|
|
|
# Initialize Gst or nothing will work.
|
2013-07-15 23:12:55 +00:00
|
|
|
Gst.init(None)
|
2015-09-06 20:24:59 +00:00
|
|
|
Clutter.init(None)
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
self.camera = CameraBox(self)
|
|
|
|
self.camera.show()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
self.camera.connect("gst-state-changed", self.on_camera_state_changed)
|
|
|
|
self.camera.connect("photo-saved", self.on_camera_photo_saved)
|
2013-07-15 23:12:55 +00:00
|
|
|
|
2013-07-16 02:57:35 +00:00
|
|
|
# Pack the video widget into the dialog.
|
2013-07-15 23:12:55 +00:00
|
|
|
vbox = builder.get_object('camera_box')
|
2015-08-30 02:24:42 +00:00
|
|
|
vbox.pack_start(self.camera, True, True, 0)
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 02:57:35 +00:00
|
|
|
# Essential widgets
|
2013-07-16 00:48:29 +00:00
|
|
|
self.record_button = builder.get_object('camera_record')
|
|
|
|
self.apply_button = builder.get_object('camera_apply')
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 02:57:35 +00:00
|
|
|
# Store the temporary filename to be used.
|
2013-07-16 00:48:29 +00:00
|
|
|
self.filename = None
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 02:57:35 +00:00
|
|
|
self.show_all()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def on_camera_state_changed(self, widget, state):
|
|
|
|
if state == Gst.State.PLAYING or self.apply_button.get_sensitive():
|
|
|
|
self.record_button.set_sensitive(True)
|
2013-07-15 23:12:55 +00:00
|
|
|
else:
|
2015-08-30 02:24:42 +00:00
|
|
|
self.record_button.set_sensitive(False)
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def on_camera_photo_saved(self, widget, filename):
|
|
|
|
self.filename = filename
|
|
|
|
self.apply_button.set_sensitive(True)
|
|
|
|
self.camera.pause()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def play(self):
|
|
|
|
self.camera.play()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2013-07-15 23:12:55 +00:00
|
|
|
def pause(self):
|
2015-08-30 02:24:42 +00:00
|
|
|
self.camera.pause()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2013-07-15 23:12:55 +00:00
|
|
|
def stop(self):
|
2015-08-30 02:24:42 +00:00
|
|
|
self.camera.stop()
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2015-08-30 02:24:42 +00:00
|
|
|
def take_picture(self, filename):
|
|
|
|
self.camera.take_photo(filename)
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 00:48:29 +00:00
|
|
|
def on_camera_record_clicked(self, widget):
|
2013-07-16 02:57:35 +00:00
|
|
|
"""When the camera record/retry button is clicked:
|
|
|
|
Record: Pause the video, start the capture, enable apply and retry.
|
|
|
|
Retry: Restart the video stream."""
|
|
|
|
# Remove any previous temporary file.
|
2014-01-25 22:21:53 +00:00
|
|
|
if self.filename and os.path.isfile(self.filename):
|
2013-07-16 02:57:35 +00:00
|
|
|
os.remove(self.filename)
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 02:57:35 +00:00
|
|
|
# Retry action.
|
2013-07-16 00:48:29 +00:00
|
|
|
if self.apply_button.get_sensitive():
|
|
|
|
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
|
|
|
|
self.apply_button.set_sensitive(False)
|
|
|
|
self.play()
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 02:57:35 +00:00
|
|
|
# Record (Capture) action.
|
2013-07-16 00:48:29 +00:00
|
|
|
else:
|
2013-07-16 02:57:35 +00:00
|
|
|
# Create a new temporary file.
|
2014-03-29 15:29:49 +00:00
|
|
|
self.filename = helpers.new_tempfile('camera')
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 02:57:35 +00:00
|
|
|
# Capture the current image.
|
2013-07-16 00:48:29 +00:00
|
|
|
self.take_picture(self.filename)
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 02:57:35 +00:00
|
|
|
# Set the record button to retry, and disable it until the capture
|
|
|
|
# finishes.
|
2013-07-16 00:48:29 +00:00
|
|
|
self.record_button.set_label(_("Retry"))
|
|
|
|
self.record_button.set_sensitive(False)
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 00:48:29 +00:00
|
|
|
def on_camera_apply_clicked(self, widget):
|
2013-07-16 02:57:35 +00:00
|
|
|
"""When the camera Apply button is clicked, crop the current photo and
|
|
|
|
emit a signal to let the main application know there is a new file
|
|
|
|
available. Then close the camera dialog."""
|
|
|
|
self.emit("apply", self.filename)
|
2013-07-16 00:48:29 +00:00
|
|
|
self.hide()
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-16 00:48:29 +00:00
|
|
|
def on_camera_cancel_clicked(self, widget):
|
2013-07-16 02:57:35 +00:00
|
|
|
"""When the Cancel button is clicked, just hide the dialog."""
|
2013-07-16 00:48:29 +00:00
|
|
|
self.hide()
|
2015-09-01 02:31:02 +00:00
|
|
|
|
2013-07-15 23:12:55 +00:00
|
|
|
def on_camera_mugshot_dialog_destroy(self, widget, data=None):
|
2013-07-16 02:57:35 +00:00
|
|
|
"""When the application exits, remove the current temporary file and
|
|
|
|
stop the gstreamer element."""
|
2013-07-16 02:15:31 +00:00
|
|
|
# Clear away the temp file.
|
2013-07-16 02:57:35 +00:00
|
|
|
if self.filename and os.path.isfile(self.filename):
|
|
|
|
os.remove(self.filename)
|
|
|
|
# Clean up the camera before exiting
|
2015-08-30 02:24:42 +00:00
|
|
|
self.camera.stop()
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-15 23:12:55 +00:00
|
|
|
def on_camera_mugshot_dialog_hide(self, widget, data=None):
|
2013-07-16 02:57:35 +00:00
|
|
|
"""When the dialog is hidden, pause the camera recording."""
|
2013-07-15 23:12:55 +00:00
|
|
|
self.pause()
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-15 23:12:55 +00:00
|
|
|
def on_camera_mugshot_dialog_show(self, widget, data=None):
|
2014-01-25 22:21:53 +00:00
|
|
|
"""When the dialog is shown, set the record button to record, disable
|
2013-07-16 02:57:35 +00:00
|
|
|
the apply button, and start the camera."""
|
2013-07-16 00:48:29 +00:00
|
|
|
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
|
|
|
|
self.apply_button.set_sensitive(False)
|
2013-07-15 23:12:55 +00:00
|
|
|
self.show_all()
|
|
|
|
self.play()
|
2014-01-25 22:21:53 +00:00
|
|
|
|
2013-07-15 23:12:55 +00:00
|
|
|
def on_camera_mugshot_dialog_delete_event(self, widget, data=None):
|
2013-07-16 02:57:35 +00:00
|
|
|
"""Override the dialog delete event to just hide the window."""
|
2013-07-15 23:12:55 +00:00
|
|
|
self.hide()
|
|
|
|
return True
|