Added getext translating function
This commit is contained in:
parent
473aa9807e
commit
55f3753260
15 changed files with 152 additions and 115 deletions
30
src/cli.py
30
src/cli.py
|
|
@ -8,6 +8,8 @@ import getpass
|
||||||
import argparse
|
import argparse
|
||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Try to get the original username (not "root") from shell
|
# Try to get the original username (not "root") from shell
|
||||||
try:
|
try:
|
||||||
user = os.getlogin()
|
user = os.getlogin()
|
||||||
|
|
@ -20,48 +22,48 @@ if user == "root" or user is None:
|
||||||
|
|
||||||
# If even that fails, error out
|
# If even that fails, error out
|
||||||
if env_user == "":
|
if env_user == "":
|
||||||
print("Could not determine user, please use the --user flag")
|
print(_("Could not determine user, please use the --user flag"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
user = env_user
|
user = env_user
|
||||||
|
|
||||||
# Basic command setup
|
# Basic command setup
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Command line interface for Howdy face authentication.",
|
description=_("Command line interface for Howdy face authentication."),
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
add_help=False,
|
add_help=False,
|
||||||
prog="howdy",
|
prog="howdy",
|
||||||
epilog="For support please visit\nhttps://github.com/boltgolt/howdy")
|
epilog=_("For support please visit\nhttps://github.com/boltgolt/howdy"))
|
||||||
|
|
||||||
# Add an argument for the command
|
# Add an argument for the command
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"command",
|
_("command"),
|
||||||
help="The command option to execute, can be one of the following: add, clear, config, disable, list, remove, snapshot, set, test or version.",
|
help=_("The command option to execute, can be one of the following: add, clear, config, disable, list, remove, snapshot, set, test or version."),
|
||||||
metavar="command",
|
metavar="command",
|
||||||
choices=["add", "clear", "config", "disable", "list", "remove", "set", "snapshot", "test", "version"])
|
choices=["add", "clear", "config", "disable", "list", "remove", "set", "snapshot", "test", "version"])
|
||||||
|
|
||||||
# Add an argument for the extra arguments of diable and remove
|
# Add an argument for the extra arguments of diable and remove
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"arguments",
|
_("arguments"),
|
||||||
help="Optional arguments for the add, disable, remove and set commands.",
|
help=_("Optional arguments for the add, disable, remove and set commands."),
|
||||||
nargs="*")
|
nargs="*")
|
||||||
|
|
||||||
# Add the user flag
|
# Add the user flag
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-U", "--user",
|
"-U", "--user",
|
||||||
default=user,
|
default=user,
|
||||||
help="Set the user account to use.")
|
help=_("Set the user account to use."))
|
||||||
|
|
||||||
# Add the -y flag
|
# Add the -y flag
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-y",
|
"-y",
|
||||||
help="Skip all questions.",
|
help=_("Skip all questions."),
|
||||||
action="store_true")
|
action="store_true")
|
||||||
|
|
||||||
# Add the --plain flag
|
# Add the --plain flag
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--plain",
|
"--plain",
|
||||||
help="Print machine-friendly output.",
|
help=_("Print machine-friendly output."),
|
||||||
action="store_true")
|
action="store_true")
|
||||||
|
|
||||||
# Overwrite the default help message so we can use a uppercase S
|
# Overwrite the default help message so we can use a uppercase S
|
||||||
|
|
@ -69,11 +71,11 @@ parser.add_argument(
|
||||||
"-h", "--help",
|
"-h", "--help",
|
||||||
action="help",
|
action="help",
|
||||||
default=argparse.SUPPRESS,
|
default=argparse.SUPPRESS,
|
||||||
help="Show this help message and exit.")
|
help=_("Show this help message and exit."))
|
||||||
|
|
||||||
# If we only have 1 argument we print the help text
|
# If we only have 1 argument we print the help text
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
print("current active user: " + user + "\n")
|
print(_("current active user: ") + user + "\n")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
@ -87,13 +89,13 @@ builtins.howdy_user = args.user
|
||||||
# Check if we have rootish rights
|
# Check if we have rootish rights
|
||||||
# This is this far down the file so running the command for help is always possible
|
# This is this far down the file so running the command for help is always possible
|
||||||
if os.geteuid() != 0:
|
if os.geteuid() != 0:
|
||||||
print("Please run this command as root:\n")
|
print(_("Please run this command as root:\n"))
|
||||||
print("\tsudo howdy " + " ".join(sys.argv[1:]))
|
print("\tsudo howdy " + " ".join(sys.argv[1:]))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Beond this point the user can't change anymore, if we still have root as user we need to abort
|
# Beond this point the user can't change anymore, if we still have root as user we need to abort
|
||||||
if args.user == "root":
|
if args.user == "root":
|
||||||
print("Can't run howdy commands as root, please run this command with the --user flag")
|
print(_("Can't run howdy commands as root, please run this command with the --user flag"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Execute the right command
|
# Execute the right command
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ import json
|
||||||
import configparser
|
import configparser
|
||||||
import builtins
|
import builtins
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from recorders.video_capture import VideoCapture
|
from recorders.video_capture import VideoCapture
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Try to import dlib and give a nice error if we can't
|
# Try to import dlib and give a nice error if we can't
|
||||||
# Add should be the first point where import issues show up
|
# Add should be the first point where import issues show up
|
||||||
|
|
@ -17,7 +19,7 @@ try:
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
print(err)
|
print(err)
|
||||||
|
|
||||||
print("\nCan't import the dlib module, check the output of")
|
print(_("\nCan't import the dlib module, check the output of"))
|
||||||
print("pip3 show dlib")
|
print("pip3 show dlib")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -29,7 +31,7 @@ path = os.path.abspath(__file__ + "/..")
|
||||||
|
|
||||||
# Test if at lest 1 of the data files is there and abort if it's not
|
# Test if at lest 1 of the data files is there and abort if it's not
|
||||||
if not os.path.isfile(path + "/../dlib-data/shape_predictor_5_face_landmarks.dat"):
|
if not os.path.isfile(path + "/../dlib-data/shape_predictor_5_face_landmarks.dat"):
|
||||||
print("Data files have not been downloaded, please run the following commands:")
|
print(_("Data files have not been downloaded, please run the following commands:"))
|
||||||
print("\n\tcd " + os.path.realpath(path + "/../dlib-data"))
|
print("\n\tcd " + os.path.realpath(path + "/../dlib-data"))
|
||||||
print("\tsudo ./install.sh\n")
|
print("\tsudo ./install.sh\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
@ -55,7 +57,7 @@ encodings = []
|
||||||
|
|
||||||
# Make the ./models folder if it doesn't already exist
|
# Make the ./models folder if it doesn't already exist
|
||||||
if not os.path.exists(path + "/../models"):
|
if not os.path.exists(path + "/../models"):
|
||||||
print("No face model folder found, creating one")
|
print(_("No face model folder found, creating one"))
|
||||||
os.makedirs(path + "/../models")
|
os.makedirs(path + "/../models")
|
||||||
|
|
||||||
# To try read a premade encodings file if it exists
|
# To try read a premade encodings file if it exists
|
||||||
|
|
@ -66,30 +68,30 @@ except FileNotFoundError:
|
||||||
|
|
||||||
# Print a warning if too many encodings are being added
|
# Print a warning if too many encodings are being added
|
||||||
if len(encodings) > 3:
|
if len(encodings) > 3:
|
||||||
print("NOTICE: Each additional model slows down the face recognition engine slightly")
|
print(_("NOTICE: Each additional model slows down the face recognition engine slightly"))
|
||||||
print("Press Ctrl+C to cancel\n")
|
print(_("Press Ctrl+C to cancel\n"))
|
||||||
|
|
||||||
# Make clear what we are doing if not human
|
# Make clear what we are doing if not human
|
||||||
if not builtins.howdy_args.plain:
|
if not builtins.howdy_args.plain:
|
||||||
print("Adding face model for the user " + user)
|
print(_("Adding face model for the user " + user))
|
||||||
|
|
||||||
# Set the default label
|
# Set the default label
|
||||||
label = "Initial model"
|
label = "Initial model"
|
||||||
|
|
||||||
# Get the label from the cli arguments if provided
|
# Get the label from the cli arguments if provided
|
||||||
if builtins.howdy_args.arguments:
|
if builtins.howdy_args.arguments:
|
||||||
label = builtins.howdy_args.arguments[0]
|
label = builtins.howdy_args.arguments[0]
|
||||||
|
|
||||||
# If models already exist, set that default label
|
# If models already exist, set that default label
|
||||||
elif encodings:
|
elif encodings:
|
||||||
label = "Model #" + str(len(encodings) + 1)
|
label = _("Model #") + str(len(encodings) + 1)
|
||||||
|
|
||||||
# Keep de default name if we can't ask questions
|
# Keep de default name if we can't ask questions
|
||||||
if builtins.howdy_args.y:
|
if builtins.howdy_args.y:
|
||||||
print('Using default label "%s" because of -y flag' % (label, ))
|
print(_('Using default label "%s" because of -y flag') % (label, ))
|
||||||
else:
|
else:
|
||||||
# Ask the user for a custom label
|
# Ask the user for a custom label
|
||||||
label_in = input("Enter a label for this new model [" + label + "]: ")
|
label_in = input(_("Enter a label for this new model [{}]: ").format(label))
|
||||||
|
|
||||||
# Set the custom label (if any) and limit it to 24 characters
|
# Set the custom label (if any) and limit it to 24 characters
|
||||||
if label_in != "":
|
if label_in != "":
|
||||||
|
|
@ -97,7 +99,7 @@ else:
|
||||||
|
|
||||||
# Remove illegal characters
|
# Remove illegal characters
|
||||||
if "," in label:
|
if "," in label:
|
||||||
print("NOTICE: Removing illegal character \",\" from model name")
|
print(_("NOTICE: Removing illegal character \",\" from model name"))
|
||||||
label = label.replace(",", "")
|
label = label.replace(",", "")
|
||||||
|
|
||||||
# Prepare the metadata for insertion
|
# Prepare the metadata for insertion
|
||||||
|
|
@ -111,7 +113,7 @@ insert_model = {
|
||||||
# Set up video_capture
|
# Set up video_capture
|
||||||
video_capture = VideoCapture(config)
|
video_capture = VideoCapture(config)
|
||||||
|
|
||||||
print("\nPlease look straight into the camera")
|
print(_("\nPlease look straight into the camera"))
|
||||||
|
|
||||||
# Give the user time to read
|
# Give the user time to read
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
@ -176,17 +178,17 @@ video_capture.release()
|
||||||
# If we've found no faces, try to determine why
|
# If we've found no faces, try to determine why
|
||||||
if face_locations is None or not face_locations:
|
if face_locations is None or not face_locations:
|
||||||
if valid_frames == 0:
|
if valid_frames == 0:
|
||||||
print("Camera saw only black frames - is IR emitter working?")
|
print(_("Camera saw only black frames - is IR emitter working?"))
|
||||||
elif valid_frames == dark_tries:
|
elif valid_frames == dark_tries:
|
||||||
print("All frames were too dark, please check dark_threshold in config")
|
print(_("All frames were too dark, please check dark_threshold in config"))
|
||||||
print("Average darkness: " + str(dark_running_total / valid_frames) + ", Threshold: " + str(dark_threshold))
|
print(_("Average darkness: {avg}, Threshold: {threshold}").format(avg=str(dark_running_total / valid_frames), threshold=str(dark_threshold)))
|
||||||
else:
|
else:
|
||||||
print("No face detected, aborting")
|
print(_("No face detected, aborting"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# If more than 1 faces are detected we can't know wich one belongs to the user
|
# If more than 1 faces are detected we can't know wich one belongs to the user
|
||||||
elif len(face_locations) > 1:
|
elif len(face_locations) > 1:
|
||||||
print("Multiple faces detected, aborting")
|
print(_("Multiple faces detected, aborting"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
face_location = face_locations[0]
|
face_location = face_locations[0]
|
||||||
|
|
@ -207,6 +209,5 @@ with open(enc_file, "w") as datafile:
|
||||||
json.dump(encodings, datafile)
|
json.dump(encodings, datafile)
|
||||||
|
|
||||||
# Give let the user know how it went
|
# Give let the user know how it went
|
||||||
print("""Scan complete
|
print(_("""\nScan complete
|
||||||
|
Added a new model to """) + user)
|
||||||
Added a new model to """ + user)
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Get the full path to this file
|
# Get the full path to this file
|
||||||
path = os.path.dirname(os.path.abspath(__file__))
|
path = os.path.dirname(os.path.abspath(__file__))
|
||||||
# Get the passed user
|
# Get the passed user
|
||||||
|
|
@ -12,25 +14,25 @@ user = builtins.howdy_user
|
||||||
|
|
||||||
# Check if the models folder is there
|
# Check if the models folder is there
|
||||||
if not os.path.exists(path + "/../models"):
|
if not os.path.exists(path + "/../models"):
|
||||||
print("No models created yet, can't clear them if they don't exist")
|
print(_("No models created yet, can't clear them if they don't exist"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Check if the user has a models file to delete
|
# Check if the user has a models file to delete
|
||||||
if not os.path.isfile(path + "/../models/" + user + ".dat"):
|
if not os.path.isfile(path + "/../models/" + user + ".dat"):
|
||||||
print(user + " has no models or they have been cleared already")
|
print(_("{} has no models or they have been cleared already").format(user))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Only ask the user if there's no -y flag
|
# Only ask the user if there's no -y flag
|
||||||
if not builtins.howdy_args.y:
|
if not builtins.howdy_args.y:
|
||||||
# Double check with the user
|
# Double check with the user
|
||||||
print("This will clear all models for " + user)
|
print(_("This will clear all models for ") + user)
|
||||||
ans = input("Do you want to continue [y/N]: ")
|
ans = input(_("Do you want to continue [y/N]: "))
|
||||||
|
|
||||||
# Abort if they don't answer y or Y
|
# Abort if they don't answer y or Y
|
||||||
if (ans.lower() != "y"):
|
if (ans.lower() != "y"):
|
||||||
print('\nInerpeting as a "NO"')
|
print(_('\nInerpeting as a "NO", aborting'))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Delete otherwise
|
# Delete otherwise
|
||||||
os.remove(path + "/../models/" + user + ".dat")
|
os.remove(path + "/../models/" + user + ".dat")
|
||||||
print("\nModels cleared")
|
print(_("\nModels cleared"))
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Let the user know what we're doing
|
# Let the user know what we're doing
|
||||||
print("Opening config.ini in the default editor")
|
print(_("Opening config.ini in the default editor"))
|
||||||
|
|
||||||
# Default to the nano editor
|
# Default to the nano editor
|
||||||
editor = "/bin/nano"
|
editor = "/bin/nano"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import builtins
|
||||||
import fileinput
|
import fileinput
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute filepath
|
# Get the absolute filepath
|
||||||
config_path = os.path.dirname(os.path.abspath(__file__)) + "/../config.ini"
|
config_path = os.path.dirname(os.path.abspath(__file__)) + "/../config.ini"
|
||||||
|
|
||||||
|
|
@ -16,7 +18,7 @@ config.read(config_path)
|
||||||
|
|
||||||
# Check if enough arguments have been passed
|
# Check if enough arguments have been passed
|
||||||
if not builtins.howdy_args.arguments:
|
if not builtins.howdy_args.arguments:
|
||||||
print("Please add a 0 (enable) or a 1 (disable) as an argument")
|
print(_("Please add a 0 (enable) or a 1 (disable) as an argument"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Get the cli argument
|
# Get the cli argument
|
||||||
|
|
@ -29,12 +31,12 @@ elif argument == "0" or argument.lower() == "false":
|
||||||
out_value = "false"
|
out_value = "false"
|
||||||
else:
|
else:
|
||||||
# Of it's not a 0 or a 1, it's invalid
|
# Of it's not a 0 or a 1, it's invalid
|
||||||
print("Please only use 0 (enable) or 1 (disable) as an argument")
|
print(_("Please only use 0 (enable) or 1 (disable) as an argument"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Don't do anything when the state is already the requested one
|
# Don't do anything when the state is already the requested one
|
||||||
if out_value == config.get("core", "disabled"):
|
if out_value == config.get("core", "disabled"):
|
||||||
print("The disable option has already been set to " + out_value)
|
print(_("The disable option has already been set to ") + out_value)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Loop though the config file and only replace the line containing the disable config
|
# Loop though the config file and only replace the line containing the disable config
|
||||||
|
|
@ -43,6 +45,6 @@ for line in fileinput.input([config_path], inplace=1):
|
||||||
|
|
||||||
# Print what we just did
|
# Print what we just did
|
||||||
if out_value == "true":
|
if out_value == "true":
|
||||||
print("Howdy has been disabled")
|
print(_("Howdy has been disabled"))
|
||||||
else:
|
else:
|
||||||
print("Howdy has been enabled")
|
print(_("Howdy has been enabled"))
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,15 @@ import json
|
||||||
import time
|
import time
|
||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute path and the username
|
# Get the absolute path and the username
|
||||||
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
|
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
|
||||||
user = builtins.howdy_user
|
user = builtins.howdy_user
|
||||||
|
|
||||||
# Check if the models file has been created yet
|
# Check if the models file has been created yet
|
||||||
if not os.path.exists(path + "/models"):
|
if not os.path.exists(path + "/models"):
|
||||||
print("Face models have not been initialized yet, please run:")
|
print(_("Face models have not been initialized yet, please run:"))
|
||||||
print("\n\tsudo howdy -U " + user + " add\n")
|
print("\n\tsudo howdy -U " + user + " add\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -24,14 +26,14 @@ enc_file = path + "/models/" + user + ".dat"
|
||||||
try:
|
try:
|
||||||
encodings = json.load(open(enc_file))
|
encodings = json.load(open(enc_file))
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("No face model known for the user " + user + ", please run:")
|
print(_("No face model known for the user {}, please run:").format(user))
|
||||||
print("\n\tsudo howdy -U " + user + " add\n")
|
print("\n\tsudo howdy -U " + user + " add\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Print a header if we're not in plain mode
|
# Print a header if we're not in plain mode
|
||||||
if not builtins.howdy_args.plain:
|
if not builtins.howdy_args.plain:
|
||||||
print("Known face models for " + user + ":")
|
print(_("Known face models for {}:").format(user))
|
||||||
print("\n\033[1;29mID Date Label\033[0m")
|
print("\n\033[1;29m" + _("ID Date Label\033[0m"))
|
||||||
|
|
||||||
# Loop through all encodings and print info about them
|
# Loop through all encodings and print info about them
|
||||||
for enc in encodings:
|
for enc in encodings:
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,24 @@ import os
|
||||||
import json
|
import json
|
||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute path and the username
|
# Get the absolute path and the username
|
||||||
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
|
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
|
||||||
user = builtins.howdy_user
|
user = builtins.howdy_user
|
||||||
|
|
||||||
# Check if enough arguments have been passed
|
# Check if enough arguments have been passed
|
||||||
if not builtins.howdy_args.arguments:
|
if not builtins.howdy_args.arguments:
|
||||||
print("Please add the ID of the model you want to remove as an argument")
|
print(_("Please add the ID of the model you want to remove as an argument"))
|
||||||
print("For example:")
|
print(_("For example:"))
|
||||||
print("\n\thowdy remove 0\n")
|
print("\n\thowdy remove 0\n")
|
||||||
print("You can find the IDs by running:")
|
print(_("You can find the IDs by running:"))
|
||||||
print("\n\thowdy list\n")
|
print("\n\thowdy list\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Check if the models file has been created yet
|
# Check if the models file has been created yet
|
||||||
if not os.path.exists(path + "/models"):
|
if not os.path.exists(path + "/models"):
|
||||||
print("Face models have not been initialized yet, please run:")
|
print(_("Face models have not been initialized yet, please run:"))
|
||||||
print("\n\thowdy add\n")
|
print("\n\thowdy add\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -32,7 +34,7 @@ enc_file = path + "/models/" + user + ".dat"
|
||||||
try:
|
try:
|
||||||
encodings = json.load(open(enc_file))
|
encodings = json.load(open(enc_file))
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("No face model known for the user " + user + ", please run:")
|
print(_("No face model known for the user " + user + ", please run:"))
|
||||||
print("\n\thowdy add\n")
|
print("\n\thowdy add\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -48,12 +50,12 @@ for enc in encodings:
|
||||||
# Only ask the user if there's no -y flag
|
# Only ask the user if there's no -y flag
|
||||||
if not builtins.howdy_args.y:
|
if not builtins.howdy_args.y:
|
||||||
# Double check with the user
|
# Double check with the user
|
||||||
print('This will remove the model called "' + enc["label"] + '" for ' + user)
|
print(_('This will remove the model called "{label}" for {user}').format(label=enc["label"], user=user))
|
||||||
ans = input("Do you want to continue [y/N]: ")
|
ans = input(_("Do you want to continue [y/N]: "))
|
||||||
|
|
||||||
# Abort if the answer isn't yes
|
# Abort if the answer isn't yes
|
||||||
if (ans.lower() != "y"):
|
if (ans.lower() != "y"):
|
||||||
print('\nInterpreting as a "NO", aborting')
|
print(_('\nInterpreting as a "NO", aborting'))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Add a padding empty line
|
# Add a padding empty line
|
||||||
|
|
@ -65,13 +67,13 @@ for enc in encodings:
|
||||||
|
|
||||||
# Abort if no matching id was found
|
# Abort if no matching id was found
|
||||||
if not found:
|
if not found:
|
||||||
print("No model with ID " + id + " exists for " + user)
|
print(_("No model with ID {id} exists for {user}").format(id=id, user=user))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Remove the entire file if this encoding is the only one
|
# Remove the entire file if this encoding is the only one
|
||||||
if len(encodings) == 1:
|
if len(encodings) == 1:
|
||||||
os.remove(path + "/models/" + user + ".dat")
|
os.remove(path + "/models/" + user + ".dat")
|
||||||
print("Removed last model, howdy disabled for user")
|
print(_("Removed last model, howdy disabled for user"))
|
||||||
else:
|
else:
|
||||||
# A place holder to contain the encodings that will remain
|
# A place holder to contain the encodings that will remain
|
||||||
new_encodings = []
|
new_encodings = []
|
||||||
|
|
@ -85,4 +87,4 @@ else:
|
||||||
with open(enc_file, "w") as datafile:
|
with open(enc_file, "w") as datafile:
|
||||||
json.dump(new_encodings, datafile)
|
json.dump(new_encodings, datafile)
|
||||||
|
|
||||||
print("Removed model " + id)
|
print(_("Removed model {}").format(id))
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,15 @@ import os
|
||||||
import builtins
|
import builtins
|
||||||
import fileinput
|
import fileinput
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute filepath
|
# Get the absolute filepath
|
||||||
config_path = os.path.dirname(os.path.abspath(__file__)) + "/../config.ini"
|
config_path = os.path.dirname(os.path.abspath(__file__)) + "/../config.ini"
|
||||||
|
|
||||||
# Check if enough arguments have been passed
|
# Check if enough arguments have been passed
|
||||||
if len(builtins.howdy_args.arguments) < 2:
|
if len(builtins.howdy_args.arguments) < 2:
|
||||||
print("Please add a setting you would like to change and the value to set it to")
|
print(_("Please add a setting you would like to change and the value to set it to"))
|
||||||
print("For example:")
|
print(_("For example:"))
|
||||||
print("\n\thowdy set certainty 3\n")
|
print("\n\thowdy set certainty 3\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -31,11 +33,11 @@ for line in fileinput.input([config_path]):
|
||||||
|
|
||||||
# If we don't have the line it is not in the config file
|
# If we don't have the line it is not in the config file
|
||||||
if not found_line:
|
if not found_line:
|
||||||
print('Could not find a "' + set_name + '" config option to set')
|
print(_('Could not find a "{}" config option to set').format(set_name))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Go through the file again and update the correct line
|
# Go through the file again and update the correct line
|
||||||
for line in fileinput.input([config_path], inplace=1):
|
for line in fileinput.input([config_path], inplace=1):
|
||||||
print(line.replace(found_line, set_name + " = " + set_value + "\n"), end="")
|
print(line.replace(found_line, set_name + " = " + set_value + "\n"), end="")
|
||||||
|
|
||||||
print("Config option updated")
|
print(_("Config option updated"))
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import datetime
|
||||||
import snapshot
|
import snapshot
|
||||||
from recorders.video_capture import VideoCapture
|
from recorders.video_capture import VideoCapture
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute path to the current directory
|
# Get the absolute path to the current directory
|
||||||
path = os.path.abspath(__file__ + "/..")
|
path = os.path.abspath(__file__ + "/..")
|
||||||
|
|
||||||
|
|
@ -40,12 +42,12 @@ while True:
|
||||||
|
|
||||||
# Generate a snapshot image from the frames
|
# Generate a snapshot image from the frames
|
||||||
file = snapshot.generate(frames, [
|
file = snapshot.generate(frames, [
|
||||||
"GENERATED SNAPSHOT",
|
_("GENERATED SNAPSHOT"),
|
||||||
"Date: " + datetime.datetime.utcnow().strftime("%Y/%m/%d %H:%M:%S UTC"),
|
_("Date: ") + datetime.datetime.utcnow().strftime("%Y/%m/%d %H:%M:%S UTC"),
|
||||||
"Dark threshold config: " + str(config.getfloat("video", "dark_threshold", fallback=50.0)),
|
_("Dark threshold config: ") + str(config.getfloat("video", "dark_threshold", fallback=50.0)),
|
||||||
"Certainty config: " + str(config.getfloat("video", "certainty", fallback=3.5))
|
_("Certainty config: ") + str(config.getfloat("video", "certainty", fallback=3.5))
|
||||||
])
|
])
|
||||||
|
|
||||||
# Show the file location in console
|
# Show the file location in console
|
||||||
print("Generated snapshot saved as")
|
print(_("Generated snapshot saved as"))
|
||||||
print(file)
|
print(file)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import dlib
|
||||||
import cv2
|
import cv2
|
||||||
from recorders.video_capture import VideoCapture
|
from recorders.video_capture import VideoCapture
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Get the absolute path to the current file
|
# Get the absolute path to the current file
|
||||||
path = os.path.dirname(os.path.abspath(__file__))
|
path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
@ -17,8 +19,7 @@ config = configparser.ConfigParser()
|
||||||
config.read(path + "/../config.ini")
|
config.read(path + "/../config.ini")
|
||||||
|
|
||||||
if config.get("video", "recording_plugin") != "opencv":
|
if config.get("video", "recording_plugin") != "opencv":
|
||||||
print("Howdy has been configured to use a recorder which doesn't support the test command yet")
|
print(_("Howdy has been configured to use a recorder which doesn't support the test command yet, aborting"))
|
||||||
print("Aborting")
|
|
||||||
sys.exit(12)
|
sys.exit(12)
|
||||||
|
|
||||||
video_capture = VideoCapture(config)
|
video_capture = VideoCapture(config)
|
||||||
|
|
@ -28,12 +29,12 @@ exposure = config.getint("video", "exposure", fallback=-1)
|
||||||
dark_threshold = config.getfloat("video", "dark_threshold")
|
dark_threshold = config.getfloat("video", "dark_threshold")
|
||||||
|
|
||||||
# Let the user know what's up
|
# Let the user know what's up
|
||||||
print("""
|
print(_("""
|
||||||
Opening a window with a test feed
|
Opening a window with a test feed
|
||||||
|
|
||||||
Press ctrl+C in this terminal to quit
|
Press ctrl+C in this terminal to quit
|
||||||
Click on the image to enable or disable slow mode
|
Click on the image to enable or disable slow mode
|
||||||
""")
|
"""))
|
||||||
|
|
||||||
|
|
||||||
def mouse(event, x, y, flags, param):
|
def mouse(event, x, y, flags, param):
|
||||||
|
|
@ -97,7 +98,7 @@ try:
|
||||||
sec_frames = 0
|
sec_frames = 0
|
||||||
|
|
||||||
# Grab a single frame of video
|
# Grab a single frame of video
|
||||||
_, frame = video_capture.read_frame()
|
ret, frame = video_capture.read_frame()
|
||||||
|
|
||||||
frame = clahe.apply(frame)
|
frame = clahe.apply(frame)
|
||||||
# Make a frame to put overlays in
|
# Make a frame to put overlays in
|
||||||
|
|
@ -127,22 +128,22 @@ try:
|
||||||
cv2.rectangle(overlay, p1, p2, (0, 200, 0), thickness=cv2.FILLED)
|
cv2.rectangle(overlay, p1, p2, (0, 200, 0), thickness=cv2.FILLED)
|
||||||
|
|
||||||
# Print the statis in the bottom left
|
# Print the statis in the bottom left
|
||||||
print_text(0, "RESOLUTION: %dx%d" % (height, width))
|
print_text(0, _("RESOLUTION: %dx%d") % (height, width))
|
||||||
print_text(1, "FPS: %d" % (fps, ))
|
print_text(1, _("FPS: %d") % (fps, ))
|
||||||
print_text(2, "FRAMES: %d" % (total_frames, ))
|
print_text(2, _("FRAMES: %d") % (total_frames, ))
|
||||||
print_text(3, "RECOGNITION: %dms" % (round(rec_tm * 1000), ))
|
print_text(3, _("RECOGNITION: %dms") % (round(rec_tm * 1000), ))
|
||||||
|
|
||||||
# Show that slow mode is on, if it's on
|
# Show that slow mode is on, if it's on
|
||||||
if slow_mode:
|
if slow_mode:
|
||||||
cv2.putText(overlay, "SLOW MODE", (width - 66, height - 10), cv2.FONT_HERSHEY_SIMPLEX, .3, (0, 0, 255), 0, cv2.LINE_AA)
|
cv2.putText(overlay, _("SLOW MODE"), (width - 66, height - 10), cv2.FONT_HERSHEY_SIMPLEX, .3, (0, 0, 255), 0, cv2.LINE_AA)
|
||||||
|
|
||||||
# Ignore dark frames
|
# Ignore dark frames
|
||||||
if hist_perc[0] > dark_threshold:
|
if hist_perc[0] > dark_threshold:
|
||||||
# Show that this is an ignored frame in the top right
|
# Show that this is an ignored frame in the top right
|
||||||
cv2.putText(overlay, "DARK FRAME", (width - 68, 16), cv2.FONT_HERSHEY_SIMPLEX, .3, (0, 0, 255), 0, cv2.LINE_AA)
|
cv2.putText(overlay, _("DARK FRAME"), (width - 68, 16), cv2.FONT_HERSHEY_SIMPLEX, .3, (0, 0, 255), 0, cv2.LINE_AA)
|
||||||
else:
|
else:
|
||||||
# SHow that this is an active frame
|
# SHow that this is an active frame
|
||||||
cv2.putText(overlay, "SCAN FRAME", (width - 68, 16), cv2.FONT_HERSHEY_SIMPLEX, .3, (0, 255, 0), 0, cv2.LINE_AA)
|
cv2.putText(overlay, _("SCAN FRAME"), (width - 68, 16), cv2.FONT_HERSHEY_SIMPLEX, .3, (0, 255, 0), 0, cv2.LINE_AA)
|
||||||
|
|
||||||
rec_tm = time.time()
|
rec_tm = time.time()
|
||||||
# Get the locations of all faces and their locations
|
# Get the locations of all faces and their locations
|
||||||
|
|
@ -197,7 +198,7 @@ try:
|
||||||
# On ctrl+C
|
# On ctrl+C
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
# Let the user know we're stopping
|
# Let the user know we're stopping
|
||||||
print("\nClosing window")
|
print(_("\nClosing window"))
|
||||||
|
|
||||||
# Release handle to the webcam
|
# Release handle to the webcam
|
||||||
cv2.destroyAllWindows()
|
cv2.destroyAllWindows()
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ import datetime
|
||||||
import snapshot
|
import snapshot
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import _thread as thread
|
import _thread as thread
|
||||||
|
|
||||||
from recorders.video_capture import VideoCapture
|
from recorders.video_capture import VideoCapture
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
|
|
||||||
def init_detector(lock):
|
def init_detector(lock):
|
||||||
|
|
@ -29,7 +31,7 @@ def init_detector(lock):
|
||||||
|
|
||||||
# Test if at lest 1 of the data files is there and abort if it's not
|
# Test if at lest 1 of the data files is there and abort if it's not
|
||||||
if not os.path.isfile(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat"):
|
if not os.path.isfile(PATH + "/dlib-data/shape_predictor_5_face_landmarks.dat"):
|
||||||
print("Data files have not been downloaded, please run the following commands:")
|
print(_("Data files have not been downloaded, please run the following commands:"))
|
||||||
print("\n\tcd " + PATH + "/dlib-data")
|
print("\n\tcd " + PATH + "/dlib-data")
|
||||||
print("\tsudo ./install.sh\n")
|
print("\tsudo ./install.sh\n")
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
@ -53,12 +55,12 @@ def init_detector(lock):
|
||||||
def make_snapshot(type):
|
def make_snapshot(type):
|
||||||
"""Generate snapshot after detection"""
|
"""Generate snapshot after detection"""
|
||||||
snapshot.generate(snapframes, [
|
snapshot.generate(snapframes, [
|
||||||
type + " LOGIN",
|
type + _(" LOGIN"),
|
||||||
"Date: " + datetime.datetime.utcnow().strftime("%Y/%m/%d %H:%M:%S UTC"),
|
_("Date: ") + datetime.datetime.utcnow().strftime("%Y/%m/%d %H:%M:%S UTC"),
|
||||||
"Scan time: " + str(round(time.time() - timings["fr"], 2)) + "s",
|
_("Scan time: ") + str(round(time.time() - timings["fr"], 2)) + "s",
|
||||||
"Frames: " + str(frames) + " (" + str(round(frames / (time.time() - timings["fr"]), 2)) + "FPS)",
|
_("Frames: ") + str(frames) + " (" + str(round(frames / (time.time() - timings["fr"]), 2)) + "FPS)",
|
||||||
"Hostname: " + os.uname().nodename,
|
_("Hostname: ") + os.uname().nodename,
|
||||||
"Best certainty value: " + str(round(lowest_certainty * 10, 1))
|
_("Best certainty value: ") + str(round(lowest_certainty * 10, 1))
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -172,11 +174,11 @@ while True:
|
||||||
if time.time() - timings["fr"] > timeout:
|
if time.time() - timings["fr"] > timeout:
|
||||||
# Create a timeout snapshot if enabled
|
# Create a timeout snapshot if enabled
|
||||||
if capture_failed:
|
if capture_failed:
|
||||||
make_snapshot("FAILED")
|
make_snapshot(_("FAILED"))
|
||||||
|
|
||||||
if dark_tries == valid_frames:
|
if dark_tries == valid_frames:
|
||||||
print("All frames were too dark, please check dark_threshold in config")
|
print(_("All frames were too dark, please check dark_threshold in config"))
|
||||||
print("Average darkness: " + str(dark_running_total / max(1, valid_frames)) + ", Threshold: " + str(dark_threshold))
|
print(_("Average darkness: {avg}, Threshold: {threshold}").format(avg=str(dark_running_total / max(1, valid_frames)), threshold=str(dark_threshold)))
|
||||||
sys.exit(13)
|
sys.exit(13)
|
||||||
else:
|
else:
|
||||||
sys.exit(11)
|
sys.exit(11)
|
||||||
|
|
@ -255,32 +257,32 @@ while True:
|
||||||
print(" %s: %dms" % (label, round(timings[k] * 1000)))
|
print(" %s: %dms" % (label, round(timings[k] * 1000)))
|
||||||
|
|
||||||
# Print a nice timing report
|
# Print a nice timing report
|
||||||
print("Time spent")
|
print(_("Time spent"))
|
||||||
print_timing("Starting up", "in")
|
print_timing(_("Starting up"), "in")
|
||||||
print(" Open cam + load libs: %dms" % (round(max(timings["ll"], timings["ic"]) * 1000, )))
|
print(_(" Open cam + load libs: %dms") % (round(max(timings["ll"], timings["ic"]) * 1000, )))
|
||||||
print_timing(" Opening the camera", "ic")
|
print_timing(_(" Opening the camera"), "ic")
|
||||||
print_timing(" Importing recognition libs", "ll")
|
print_timing(_(" Importing recognition libs"), "ll")
|
||||||
print_timing("Searching for known face", "fl")
|
print_timing(_("Searching for known face"), "fl")
|
||||||
print_timing("Total time", "tt")
|
print_timing(_("Total time"), "tt")
|
||||||
|
|
||||||
print("\nResolution")
|
print(_("\nResolution"))
|
||||||
width = video_capture.fw or 1
|
width = video_capture.fw or 1
|
||||||
print(" Native: %dx%d" % (height, width))
|
print(_(" Native: %dx%d") % (height, width))
|
||||||
# Save the new size for diagnostics
|
# Save the new size for diagnostics
|
||||||
scale_height, scale_width = frame.shape[:2]
|
scale_height, scale_width = frame.shape[:2]
|
||||||
print(" Used: %dx%d" % (scale_height, scale_width))
|
print(_(" Used: %dx%d") % (scale_height, scale_width))
|
||||||
|
|
||||||
# Show the total number of frames and calculate the FPS by deviding it by the total scan time
|
# Show the total number of frames and calculate the FPS by deviding it by the total scan time
|
||||||
print("\nFrames searched: %d (%.2f fps)" % (frames, frames / timings["fl"]))
|
print(_("\nFrames searched: %d (%.2f fps)") % (frames, frames / timings["fl"]))
|
||||||
print("Black frames ignored: %d " % (black_tries, ))
|
print(_("Black frames ignored: %d ") % (black_tries, ))
|
||||||
print("Dark frames ignored: %d " % (dark_tries, ))
|
print(_("Dark frames ignored: %d ") % (dark_tries, ))
|
||||||
print("Certainty of winning frame: %.3f" % (match * 10, ))
|
print(_("Certainty of winning frame: %.3f") % (match * 10, ))
|
||||||
|
|
||||||
print("Winning model: %d (\"%s\")" % (match_index, models[match_index]["label"]))
|
print(_("Winning model: %d (\"%s\")") % (match_index, models[match_index]["label"]))
|
||||||
|
|
||||||
# Make snapshot if enabled
|
# Make snapshot if enabled
|
||||||
if capture_successful:
|
if capture_successful:
|
||||||
make_snapshot("SUCCESSFUL")
|
make_snapshot(_("SUCCESSFUL"))
|
||||||
|
|
||||||
# Run rubberstamps if enabled
|
# Run rubberstamps if enabled
|
||||||
if config.getboolean("rubberstamps", "enabled", fallback=False):
|
if config.getboolean("rubberstamps", "enabled", fallback=False):
|
||||||
|
|
|
||||||
12
src/i18n.py
Normal file
12
src/i18n.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Support file for translations
|
||||||
|
|
||||||
|
# Import modules
|
||||||
|
import gettext
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Get the right translation based on locale, falling back to base if none found
|
||||||
|
translation = gettext.translation("core", localedir=os.path.join(os.path.dirname(__file__), 'locales'), fallback=True)
|
||||||
|
translation.install()
|
||||||
|
|
||||||
|
# Export translation function as _
|
||||||
|
_ = translation.gettext
|
||||||
|
|
@ -4,14 +4,16 @@
|
||||||
import numpy
|
import numpy
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
from cv2 import CAP_PROP_FRAME_WIDTH
|
from cv2 import CAP_PROP_FRAME_WIDTH
|
||||||
from cv2 import CAP_PROP_FRAME_HEIGHT
|
from cv2 import CAP_PROP_FRAME_HEIGHT
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ffmpeg
|
import ffmpeg
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Missing ffmpeg module, please run:")
|
print(_("Missing ffmpeg module, please run:"))
|
||||||
print(" pip3 install ffmpeg-python\n")
|
print(" pip3 install ffmpeg-python\n")
|
||||||
sys.exit(12)
|
sys.exit(12)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
# Class that simulates the functionality of opencv so howdy can use v4l2 devices seamlessly
|
# Class that simulates the functionality of opencv so howdy can use v4l2 devices seamlessly
|
||||||
|
|
||||||
# Import required modules. lib4l-dev package is also required.
|
# Import required modules. lib4l-dev package is also required.
|
||||||
from recorders import v4l2
|
|
||||||
import fcntl
|
import fcntl
|
||||||
import numpy
|
import numpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from recorders import v4l2
|
||||||
from cv2 import cvtColor, COLOR_GRAY2BGR, CAP_PROP_FRAME_WIDTH, CAP_PROP_FRAME_HEIGHT
|
from cv2 import cvtColor, COLOR_GRAY2BGR, CAP_PROP_FRAME_WIDTH, CAP_PROP_FRAME_HEIGHT
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pyv4l2.frame import Frame
|
from pyv4l2.frame import Frame
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Missing pyv4l2 module, please run:")
|
print(_("Missing pyv4l2 module, please run:"))
|
||||||
print(" pip3 install pyv4l2\n")
|
print(" pip3 install pyv4l2\n")
|
||||||
sys.exit(13)
|
sys.exit(13)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import cv2
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from i18n import _
|
||||||
|
|
||||||
# Class to provide boilerplate code to build a video recorder with the
|
# Class to provide boilerplate code to build a video recorder with the
|
||||||
# correct settings from the config file.
|
# correct settings from the config file.
|
||||||
|
|
@ -33,8 +34,8 @@ class VideoCapture:
|
||||||
# Check device path
|
# Check device path
|
||||||
if not os.path.exists(self.config.get("video", "device_path")):
|
if not os.path.exists(self.config.get("video", "device_path")):
|
||||||
if self.config.getboolean("video", "warn_no_device"):
|
if self.config.getboolean("video", "warn_no_device"):
|
||||||
print("Howdy could not find a camera device at the path specified in the config file.")
|
print(_("Howdy could not find a camera device at the path specified in the config file."))
|
||||||
print("It is very likely that the path is not configured correctly, please edit the 'device_path' config value by running:")
|
print(_("It is very likely that the path is not configured correctly, please edit the 'device_path' config value by running:"))
|
||||||
print("\n\tsudo howdy config\n")
|
print("\n\tsudo howdy config\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -81,7 +82,7 @@ class VideoCapture:
|
||||||
# Don't remove ret, it doesn't work without it
|
# Don't remove ret, it doesn't work without it
|
||||||
ret, frame = self.internal.read()
|
ret, frame = self.internal.read()
|
||||||
if not ret:
|
if not ret:
|
||||||
print("Failed to read camera specified in your 'device_path', aborting")
|
print(_("Failed to read camera specified in the 'device_path' config option, aborting"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue