Files
AvianVisitors/scripts/utils/helpers.py
T
2025-11-17 18:44:27 +01:00

160 lines
5.8 KiB
Python

import datetime
import glob
import json
import os
import re
import subprocess
from collections import OrderedDict
from configparser import ConfigParser
from itertools import chain
from tzlocal import get_localzone
_settings = None
DB_PATH = os.path.expanduser('~/BirdNET-Pi/scripts/birds.db')
MODEL_PATH = os.path.expanduser('~/BirdNET-Pi/model')
ANALYZING_NOW = os.path.expanduser('~/BirdSongs/StreamData/analyzing_now.txt')
FONT_DIR = os.path.expanduser('~/BirdNET-Pi/homepage/static')
def get_font():
conf = get_settings()
if conf['DATABASE_LANG'] == 'ar':
ret = {'font.family': 'Noto Sans Arabic', 'path': os.path.join(FONT_DIR, 'NotoSansArabic-Regular.ttf')}
elif conf['DATABASE_LANG'] in ['ja', 'zh']:
ret = {'font.family': 'Noto Sans JP', 'path': os.path.join(FONT_DIR, 'NotoSansJP-Regular.ttf')}
elif conf['DATABASE_LANG'] == 'ko':
ret = {'font.family': 'Noto Sans KR', 'path': os.path.join(FONT_DIR, 'NotoSansKR-Regular.ttf')}
elif conf['DATABASE_LANG'] == 'th':
ret = {'font.family': 'Noto Sans Thai', 'path': os.path.join(FONT_DIR, 'NotoSansThai-Regular.ttf')}
else:
ret = {'font.family': 'Roboto Flex', 'path': os.path.join(FONT_DIR, 'RobotoFlex-Regular.ttf')}
return ret
class PHPConfigParser(ConfigParser):
def get(self, section, option, *, raw=False, vars=None, fallback=None):
value = super().get(section, option, raw=raw, vars=vars, fallback=fallback)
if raw:
return value
else:
return value.strip('"')
def _load_settings(settings_path='/etc/birdnet/birdnet.conf', force_reload=False):
global _settings
if _settings is None or force_reload:
with open(settings_path) as f:
parser = PHPConfigParser(interpolation=None)
# preserve case
parser.optionxform = lambda option: option
lines = chain(("[top]",), f)
parser.read_file(lines)
_settings = parser['top']
return _settings
def get_settings(settings_path='/etc/birdnet/birdnet.conf', force_reload=False):
settings = _load_settings(settings_path, force_reload)
return settings
class Detection:
def __init__(self, file_date, start_time, stop_time, species, confidence):
self.start = float(start_time)
self.stop = float(stop_time)
self.datetime = file_date + datetime.timedelta(seconds=self.start)
self.date = self.datetime.strftime("%Y-%m-%d")
self.time = self.datetime.strftime("%H:%M:%S")
self.iso8601 = self.datetime.astimezone(get_localzone()).isoformat()
self.week = self.datetime.isocalendar()[1]
self.confidence = round(float(confidence), 4)
self.confidence_pct = round(self.confidence * 100)
self.species = species
self.scientific_name = species.split('_')[0]
self.common_name = species.split('_')[1]
self.common_name_safe = self.common_name.replace("'", "").replace(" ", "_")
self.file_name_extr = None
class ParseFileName:
def __init__(self, file_name):
self.file_name = file_name
name = os.path.splitext(os.path.basename(file_name))[0]
date_created = re.search('^[0-9]+-[0-9]+-[0-9]+', name).group()
time_created = re.search('[0-9]+:[0-9]+:[0-9]+$', name).group()
self.file_date = datetime.datetime.strptime(f'{date_created}T{time_created}', "%Y-%m-%dT%H:%M:%S")
self.root = name
ident_match = re.search("RTSP_[0-9]+-", file_name)
self.RTSP_id = ident_match.group() if ident_match is not None else ""
@property
def iso8601(self):
current_iso8601 = self.file_date.astimezone(get_localzone()).isoformat()
return current_iso8601
@property
def week(self):
week = self.file_date.isocalendar()[1]
return week
def get_open_files_in_dir(dir_name):
result = subprocess.run(['lsof', '-w', '-Fn', '+D', f'{dir_name}'], check=False, capture_output=True)
ret = result.stdout.decode('utf-8')
err = result.stderr.decode('utf-8')
if err:
raise RuntimeError(f'{ret}:\n {err}')
names = [line.lstrip('n') for line in ret.splitlines() if line.startswith('n')]
return names
def get_wav_files():
conf = get_settings()
files = (glob.glob(os.path.join(conf['RECS_DIR'], '*/*/*.wav')) +
glob.glob(os.path.join(conf['RECS_DIR'], 'StreamData/*.wav')))
files.sort()
files = [os.path.join(conf['RECS_DIR'], file) for file in files]
rec_dir = os.path.join(conf['RECS_DIR'], 'StreamData')
open_recs = get_open_files_in_dir(rec_dir)
files = [file for file in files if file not in open_recs]
return files
def get_language(language=None):
if language is None:
language = get_settings()['DATABASE_LANG']
file_name = os.path.join(MODEL_PATH, f'l18n/labels_{language}.json')
with open(file_name) as f:
ret = json.loads(f.read())
return ret
def save_language(labels, language):
file_name = os.path.join(MODEL_PATH, f'l18n/labels_{language}.json')
with open(file_name, 'w') as f:
f.write(json.dumps(OrderedDict(sorted(labels.items())), indent=2, ensure_ascii=False))
def get_model_labels(model=None):
if model is None:
model = get_settings()['MODEL']
file_name = os.path.join(MODEL_PATH, f'{model}_Labels.txt')
with open(file_name) as f:
labels = [line.strip() for line in f.readlines()]
if labels and labels[0].count('_') == 1:
labels = [re.sub(r'_.+$', '', label) for label in labels]
return labels
def set_label_file():
lang = get_language()
labels = [f'{label}_{lang[label]}\n' for label in get_model_labels()]
file_name = os.path.join(MODEL_PATH, 'labels.txt')
if os.path.islink(file_name):
os.remove(file_name)
with open(file_name, 'w') as f:
f.writelines(labels)