def print_config_overrides(): # Leave prints separated so that it can be executed on demand # by one single process instead of each import for env_var, config_param in ENV_CONFIG_OVERRIDES: override_value = os.environ.get(env_var, None) if override_value is not None: print(f"\nConfig override {env_var}={override_value}")
def handle_file_saving(video_period, video_duration, ram_dir, hdd_dir, force_save, mqtt_client=None): period = timedelta(seconds=video_period) duration = timedelta(seconds=video_duration) latest_start = None latest_number = 0 # Handle termination of previous file-saving processes and move files RAM->HDD terminated_idxs = [] for idx, active_process in enumerate(active_filesave_processes): if datetime.now() - active_process["started"] >= duration: finish_filesave_process(active_process, hdd_dir, force_save, mqtt_client=mqtt_client) terminated_idxs.append(idx) if latest_start is None or active_process["started"] > latest_start: latest_start = active_process["started"] latest_number = active_process["number"] # Remove terminated processes from list in a separated loop for idx in sorted(terminated_idxs, reverse=True): del active_filesave_processes[idx] # Start new file-saving process if time has elapsed if latest_start is None or (datetime.now() - latest_start >= period): print("[green]Time to start a new video file [/green]" f" (latest started at: {format_tdelta(latest_start)})") new_process_number = latest_number + 1 new_process_name = f"{P_FILESAVE_PREFIX}{new_process_number}" new_filename = f"{datetime.today().strftime('%Y%m%d_%H%M%S')}_{new_process_number}.mp4" new_filepath = f"{ram_dir}/{new_filename}" new_udp_port = allocate_free_udp_port() process_handler, e_interrupt_process = start_process( new_process_name, filesave_main, config, output_filename=new_filepath, udp_port=new_udp_port, ) active_filesave_processes.append( dict( number=new_process_number, name=new_process_name, filepath=new_filepath, filename=new_filename, started=datetime.now(), process_handler=process_handler, e_interrupt=e_interrupt_process, flag_keep_file=False, udp_port=new_udp_port, ))
def start_process(name, target_function, config, **kwargs): e_interrupt_process = mp.Event() process = mp.Process( name=name, target=target_function, kwargs=dict( e_external_interrupt=e_interrupt_process, config=config, **kwargs, ), ) processes_info[name] = {"started": datetime.now(), "running": True} process.start() print(f"Process [yellow]{name}[/yellow] started with PID: {process.pid}") return process, e_interrupt_process
def is_alert_condition(statistics, config): # Thresholds config max_total_people = int(config["maskcam"]["alert-max-total-people"]) min_visible_people = int(config["maskcam"]["alert-min-visible-people"]) max_no_mask = float(config["maskcam"]["alert-no-mask-fraction"]) # Calculate visible people without_mask = int(statistics["people_without_mask"]) with_mask = int(statistics["people_with_mask"]) visible_people = with_mask + without_mask is_alert = False if statistics["people_total"] > max_total_people: is_alert = True elif visible_people >= min_visible_people: no_mask_fraction = float( statistics["people_without_mask"]) / visible_people is_alert = no_mask_fraction > max_no_mask print(f"[yellow]ALERT condition: {is_alert}[/yellow]") return is_alert
def finish_filesave_process(active_process, hdd_dir, force_filesave, mqtt_client=None): terminate_process( active_process["name"], active_process["process_handler"], active_process["e_interrupt"], delete_info=True, ) release_udp_port(active_process["udp_port"]) # Move file to its definitive place if flagged, otherwise remove it if active_process["flag_keep_file"] or force_filesave: definitive_filepath = f"{hdd_dir}/{active_process['filename']}" print(f"Force file saving: {bool(force_filesave)}") print( f"Permanent video file created: [green]{definitive_filepath}[/green]" ) # Must use shutil here to move RAM->HDD shutil.move(active_process["filepath"], definitive_filepath) # Send updated file list via MQTT (prints ignore if mqtt_client is None) mqtt_send_file_list(mqtt_client) else: print(f"Removing RAM video file: {active_process['filepath']}") os.remove(active_process["filepath"])
def terminate_process(name, process, e_interrupt_process, delete_info=False): print(f"Sending interrupt to {name} process") e_interrupt_process.set() print(f"Waiting for process [yellow]{name}[/yellow] to terminate...") process.join(timeout=10) if process.is_alive(): print( f"[red]Forcing termination of process:[/red] [bold]{name}[/bold]", warning=True, ) process.terminate() if name in processes_info: if delete_info: del processes_info[ name] # Sequential processes, avoid filling memory else: processes_info[name].update({ "ended": datetime.now(), "running": False }) print(f"Process terminated: [yellow]{name}[/yellow]\n")
def mqtt_init(config): if MQTT_BROKER_IP is None or MQTT_DEVICE_NAME is None: print( "[red]MQTT is DISABLED[/red]" " since MQTT_BROKER_IP or MQTT_DEVICE_NAME env vars are not defined\n", warning=True, ) mqtt_client = None else: print(f"Connecting to MQTT server {MQTT_BROKER_IP}:{MQTT_BROKER_PORT}") print(f"Device name: [green]{MQTT_DEVICE_NAME}[/green]\n\n") mqtt_client = mqtt_connect_broker( client_id=MQTT_DEVICE_NAME, broker_ip=MQTT_BROKER_IP, broker_port=MQTT_BROKER_PORT, subscribe_to=[(MQTT_TOPIC_COMMANDS, 2)], # handles re-subscription cb_success=mqtt_on_connect, ) mqtt_client.on_message = mqtt_process_message return mqtt_client
def sigint_handler(sig, frame): print("[red]Ctrl+C pressed. Interrupting all processes...[/red]") e_interrupt.set()
def flag_keep_current_files(): print("Request to [green]save current video files[/green]") for process in active_filesave_processes: print(f"Set flag to keep: [green]{process['filename']}[/green]") process["flag_keep_file"] = True
def release_udp_port(port_number): print(f"Releasing UDP port: {port_number}") udp_ports_pool.add(port_number)
def allocate_free_udp_port(): new_port = udp_ports_pool.pop() print(f"Allocating UDP port: {new_port}") return new_port
def new_command(command): if q_commands.full(): print(f"Command {command} IGNORED. Queue is full.", error=True) return print(f"Received command: [yellow]{command}[/yellow]") q_commands.put_nowait(command)
def flag_keep_current_files(): print("Request to [green]save current video files[/green]") for process in active_filesave_processes: print(f"Set flag to keep: [green]{process['filename']}[/green]") process["flag_keep_file"] = True if __name__ == "__main__": if len(sys.argv) > 2: print("""Usage: python3 maskcam_run.py [ URI ] Examples: \t$ python3 maskcam_run.py \t$ python3 maskcam_run.py file:///absolute/path/to/file.mp4 \t$ python3 maskcam_run.py v4l2:///dev/video1 \t$ python3 maskcam_run.py argus://0 Notes: \t - If no URI is provided, will use default-input defined in config_maskcam.txt \t - If a file:///path/file.mp4 is provided, the output will be ./output_file.mp4 \t - If the input is a live camera, the output will be consecutive \t video files under /dev/shm/date_time.mp4 \t according to the time interval defined in output-chunks-duration in config_maskcam.txt. """) sys.exit(0) try: # Print any ENV var config override to avoid confusions print_config_overrides() # Input source if len(sys.argv) > 1: input_filename = sys.argv[1] print(f"Provided input source: {input_filename}")