- initial import

This commit is contained in:
2023-08-09 19:53:31 +03:00
commit b701793923
73 changed files with 5835 additions and 0 deletions

271
src/utils_sevana.py Normal file
View File

@@ -0,0 +1,271 @@
#!/usr/bin/python
import utils
import re
import subprocess
import typing
import csv
import platform
import json
import os
import sys
import time
import urllib
from pathlib import Path
from colorama import Fore, Style
PVQA_CMD = "{pvqa} --license {pvqa_lic} --config {pvqa_cfg} --mode analysis --channel 0 " \
"--report {output} --input {input} --cut-begin {cut_begin} --cut-end {cut_end}"
PVQA_CMD_LIC_SERVER = "{pvqa} --license-server {pvqa_lic} --config {pvqa_cfg} --mode analysis --channel 0 " \
"--report {output} --input {input}"
AQUA_CMD = ("{aqua} {aqua_lic} -mode files -src file \"{reference}\" -tstf \"{input}\" -avlp off -smtnrm on "
"-decor off -mprio off -acr auto -npnt auto -voip on -enorm rms -g711 off "
"-spfrcor on -grad off -tmc on -hist-pitch on on -hist-levels on on on -miter 1 -specp 32 {spectrum} "
"-ratem %%m -fau {faults} -output json -trim r 15 -cut-tst {cut_begin} {cut_end} -cut-src {cut_begin_src} {cut_end_src}")
PVQA_PATH = ""
PVQA_LIC_PATH = "pvqa.lic"
PVQA_CFG_PATH = "pvqa.cfg"
AQUA_PATH = ""
AQUA_LIC_PATH = "aqua-wb.lic"
SILER_PATH = ""
if platform.system() == 'Windows':
PVQA_OUTPUT = 'pvqa_output.txt'
AQUA_FAULTS = 'aqua_faults.txt'
AQUA_SPECTRUM = 'aqua_spectrum.csv'
else:
PVQA_OUTPUT = '/dev/shm/pvqa_output.txt'
AQUA_FAULTS = '/dev/shm/aqua_faults.txt'
AQUA_SPECTRUM = '/dev/shm/aqua_spectrum.csv'
if platform.system() == 'Windows':
PVQA_PATH = 'pvqa.exe'
AQUA_PATH = 'aqua-wb.exe'
SILER_PATH = 'silence_eraser.exe'
SPEECH_DETECTOR_PATH = 'speech_detector.exe'
else:
PVQA_PATH = 'pvqa'
AQUA_PATH = 'aqua-wb'
SILER_PATH = 'silence_eraser'
SPEECH_DETECTOR_PATH = 'speech_detector'
def load_file(url: str, output_path: str):
try:
response = urllib.request.urlopen(url, timeout=utils.NETWORK_TIMEOUT)
if response.getcode() != 200:
utils.log_error(f'Fetch file {output_path} from URL {url} failed with code {response.getcode()}')
return
except urllib.error.HTTPError as e:
utils.log_error(f'Fetch file {output_path} from URL {url} failed with code {e.code}')
return
# Write downloaded content to file
response_content = response.read()
open(output_path, 'wb').write(response_content)
def load_config_and_licenses(server: str):
load_file(utils.join_host_and_path(server, '/deploy/pvqa.cfg'), PVQA_CFG_PATH)
load_file(utils.join_host_and_path(server, '/deploy/pvqa.lic'), PVQA_LIC_PATH)
load_file(utils.join_host_and_path(server, '/deploy/aqua-wb.lic'), AQUA_LIC_PATH)
def find_binaries(directory: str, license_server: str = None):
# Update path to pvqa/aqua-wb
global PVQA_CFG_PATH, PVQA_LIC_PATH, AQUA_LIC_PATH, PVQA_PATH, AQUA_PATH, PVQA_CMD, AQUA_CMD, SILER_PATH, SPEECH_DETECTOR_PATH
# Find platform prefix
platform_prefix = platform.system().lower()
if utils.is_raspberrypi():
platform_prefix = 'rpi'
bin_directory = Path(directory)
PVQA_PATH = bin_directory / platform_prefix / PVQA_PATH
PVQA_LIC_PATH = bin_directory / PVQA_LIC_PATH
PVQA_CFG_PATH = bin_directory / PVQA_CFG_PATH
AQUA_PATH = bin_directory / platform_prefix / AQUA_PATH
AQUA_LIC_PATH = bin_directory / AQUA_LIC_PATH
SILER_PATH = bin_directory / platform_prefix / SILER_PATH
SPEECH_DETECTOR_PATH = bin_directory / platform_prefix / SPEECH_DETECTOR_PATH
print(f'Looking for binaries/licenses/configs at {directory}...', end=' ')
# Check if binaries exist
if not PVQA_PATH.exists():
print(f'Failed to find pvqa binary at {PVQA_PATH}. Exiting.')
sys.exit(1)
if not PVQA_CFG_PATH.exists():
PVQA_CFG_PATH = Path(utils.get_script_path()) / 'pvqa.cfg'
if not PVQA_CFG_PATH.exists():
print(f'Failed to find pvqa config. Exiting.')
sys.exit(1)
if not AQUA_PATH.exists():
print(f'Failed to find aqua-wb binary. Exiting.')
sys.exit(1)
if not SILER_PATH.exists():
print(f'Failed to find silence_eraser binary. Exiting.')
sys.exit(1)
if license_server is not None:
AQUA_LIC_PATH = '"license://' + license_server + '"'
PVQA_LIC_PATH = license_server
PVQA_CMD = PVQA_CMD_LIC_SERVER
else:
if not PVQA_LIC_PATH.exists():
PVQA_LIC_PATH = Path(utils.get_script_path()) / 'pvqa.lic'
if not PVQA_LIC_PATH.exists():
print(f'Failed to find pvqa license. Exiting.')
sys.exit(1)
if not AQUA_LIC_PATH.exists():
AQUA_LIC_PATH = Path(utils.get_script_path()) / 'aqua-wb.lic'
if not AQUA_LIC_PATH.exists():
print(f'Failed to find AQuA license. Exiting.')
sys.exit(1)
print(f'Found all analyzers.')
def speech_detector(test_path: str):
cmd = f'{SPEECH_DETECTOR_PATH} --input "{test_path}"'
utils.log_verbose(cmd)
retcode, output = subprocess.getstatusoutput(cmd)
if retcode != 0:
return retcode
utils.log_verbose(output)
r = json.loads(output)
utils.log_verbose(f'Parsed: {r}')
if 'error' in r:
return r['error']
if 'offset_start' in r and 'offset_end' in r:
return r
return None
# Erases silence on the begin & end of audio file
def silence_eraser(test_path: str, file_offset_begin: float = 0.0, file_offset_end: float = 0.0) -> int:
TEMP_FILE = 'silence_removed.wav'
if os.path.exists(TEMP_FILE):
os.remove(TEMP_FILE)
# Find total duration of audio
duration = utils.get_wav_length(test_path)
# Find correct end file offset
if file_offset_end is None:
cmd = f'{SILER_PATH} {test_path} {TEMP_FILE} --process-body off --starttime {file_offset_begin}'
else:
file_offset_end = duration - file_offset_end
cmd = f'{SILER_PATH} {test_path} {TEMP_FILE} --process-body off --starttime {file_offset_begin} --endtime {file_offset_end}'
utils.log(f'Silence eraser command: {cmd}')
retcode = os.system(cmd)
if retcode == 0 and os.path.exists(TEMP_FILE):
os.remove(test_path)
os.rename(TEMP_FILE, test_path)
utils.log(f'Prefix/suffix silence is removed on: {test_path}')
return 0
else:
return retcode
def find_pvqa_mos(test_path: str, file_offset_begin: float = 0.0, file_offset_end: float = 0.0):
cmd = PVQA_CMD.format(pvqa=PVQA_PATH, pvqa_lic=PVQA_LIC_PATH, pvqa_cfg=PVQA_CFG_PATH,
output=PVQA_OUTPUT, input=test_path, cut_begin=file_offset_begin, cut_end=file_offset_end)
utils.log_verbose(cmd)
# print(cmd)
exit_code, out_data = subprocess.getstatusoutput(cmd)
# Check if failed
if exit_code != 0:
utils.log_error(f'PVQA returned exit code {exit_code} and message {out_data}')
return 0.0, '', 0
# Verbose logging
utils.log_verbose(out_data)
# print(out_data)
p_mos = re.compile(r".*= ([\d\.]+)", re.MULTILINE)
m = p_mos.search(out_data)
if m:
with open(PVQA_OUTPUT, 'r') as report_file:
content = report_file.read()
# Find R-factor from content
count_intervals = 0
count_bad = 0
csv_parser = csv.reader(open(PVQA_OUTPUT, newline=''), delimiter=';')
for row in csv_parser:
# Check status
status = row[-1].strip()
# log_verbose("New CSV row is read. Last two items: %s and %s" % (status_0, status))
if status in ['Poor', 'Ok', 'Uncertain']:
count_intervals += 1
if status == 'Poor':
count_bad += 1
utils.log_verbose(f'Nr of intervals {count_intervals}, nr of bad intervals {count_bad}')
if count_intervals > 0:
r_factor = float(count_intervals - count_bad) / float(count_intervals)
else:
r_factor = 0.0
return round(float(m.group(1)), 3), content, int(r_factor * 100)
return 0.0, out_data, 0
# Runs AQuA utility on reference and test files. file_offset_begin / file_offset_end are offsets in seconds
def find_aqua_mos(good_path, test_path, test_file_offset_begin: float = 0.0, test_file_offset_end: float = 0.0,
good_file_offset_begin: float = 0.0, good_file_offset_end: float = 0.0):
try:
out_data = ""
cmd = AQUA_CMD.format(aqua=AQUA_PATH, aqua_lic=AQUA_LIC_PATH,
reference=good_path, input=test_path, spectrum=AQUA_SPECTRUM,
faults=AQUA_FAULTS,
cut_begin=int(test_file_offset_begin * 1000), cut_end=int(test_file_offset_end * 1000),
cut_begin_src=int(good_file_offset_begin * 1000), cut_end_src=int(good_file_offset_end * 1000))
utils.log_verbose(cmd)
# print(cmd)
exit_code, out_data = subprocess.getstatusoutput(cmd)
# Return
if exit_code != 0:
utils.log_error(f'AQuA returned error code {exit_code} with message {out_data}')
return 0.0, 0, '{}'
# Log for debugging purposes
utils.log_verbose(out_data)
with open(AQUA_FAULTS, 'r') as f:
report = f.read()
json_data = json.loads(report)
# print (out_data)
if 'AQuAReport' in json_data:
aqua_report = json_data['AQuAReport']
if 'QualityResults' in aqua_report:
qr = aqua_report['QualityResults']
return round(qr['MOS'], 3), round(qr['Percent'], 3), report
except Exception as err:
utils.log_error(message='Unexpected error.', err=err)
return 0.0, 0.0, out_data
return 0.0, 0.0, out_data