Verbosity
This commit is contained in:
401
__init__.py
401
__init__.py
@@ -46,11 +46,13 @@ class AcousticBrainzNG:
|
|||||||
|
|
||||||
return musicnn_binary_path, gaia_binary_path
|
return musicnn_binary_path, gaia_binary_path
|
||||||
|
|
||||||
def _run_musicnn_models(self, models: List[Tuple[str, str]], musicnn_binary_path: str, file: str, output_path: str) -> None:
|
def _run_musicnn_models(self, models: List[Tuple[str, str]], musicnn_binary_path: str, file: str, output_path: str) -> bool:
|
||||||
models_path = config.setting["acousticbrainz_ng_models_path"]
|
models_path = config.setting["acousticbrainz_ng_models_path"]
|
||||||
if not models_path:
|
if not models_path:
|
||||||
raise ValueError("Models path not configured")
|
log.error("Models path not configured")
|
||||||
|
return False
|
||||||
|
|
||||||
|
success_results = {}
|
||||||
def run_musicnn_model(model_info):
|
def run_musicnn_model(model_info):
|
||||||
model_name, output_file = model_info
|
model_name, output_file = model_info
|
||||||
try:
|
try:
|
||||||
@@ -62,94 +64,138 @@ class AcousticBrainzNG:
|
|||||||
output_file_path = os.path.join(output_path, f"{output_file}.json")
|
output_file_path = os.path.join(output_path, f"{output_file}.json")
|
||||||
|
|
||||||
if os.path.exists(output_file_path):
|
if os.path.exists(output_file_path):
|
||||||
|
success_results[model_name] = True
|
||||||
return
|
return
|
||||||
|
|
||||||
subprocess.run(
|
result = subprocess.run(
|
||||||
[musicnn_binary_path, model_path, file, output_file_path],
|
[musicnn_binary_path, model_path, file, output_file_path],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
env=ENV,
|
env=ENV,
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
success_results[model_name] = False
|
||||||
|
log.error(f"MusicNN binary {musicnn_binary_path} failed for model {model_name} on file {file} with exit code {result.returncode}")
|
||||||
|
if result.stdout:
|
||||||
|
log.error(f"MusicNN stdout: {result.stdout}")
|
||||||
|
if result.stderr:
|
||||||
|
log.error(f"MusicNN stderr: {result.stderr}")
|
||||||
|
else:
|
||||||
|
success_results[model_name] = True
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
|
success_results[model_name] = False
|
||||||
log.error(f"Model {model_name} not found: {e}")
|
log.error(f"Model {model_name} not found: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
success_results[model_name] = False
|
||||||
log.error(f"Error processing model {model_name}: {e}")
|
log.error(f"Error processing model {model_name}: {e}")
|
||||||
|
|
||||||
max_workers = config.setting["acousticbrainz_ng_max_musicnn_workers"] or 4
|
max_workers = config.setting["acousticbrainz_ng_max_musicnn_workers"] or 4
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||||
futures = [executor.submit(run_musicnn_model, model) for model in models]
|
futures = [executor.submit(run_musicnn_model, model) for model in models]
|
||||||
concurrent.futures.wait(futures)
|
concurrent.futures.wait(futures)
|
||||||
|
|
||||||
|
return all(success_results.get(model[0], False) for model in models)
|
||||||
|
|
||||||
def analyze_required(self, metadata: Dict, file: str) -> None:
|
def analyze_required(self, metadata: Dict, file: str) -> bool:
|
||||||
if not self._check_binaries():
|
if not self._check_binaries():
|
||||||
log.error("Essentia binaries not found")
|
log.error("Essentia binaries not found")
|
||||||
return
|
return False
|
||||||
|
|
||||||
if not self._check_required_models():
|
if not self._check_required_models():
|
||||||
log.error("Required models not found")
|
log.error("Required models not found")
|
||||||
return
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
musicnn_binary_path, gaia_binary_path = self._get_binary_paths()
|
musicnn_binary_path, gaia_binary_path = self._get_binary_paths()
|
||||||
except (ValueError, FileNotFoundError) as e:
|
except (ValueError, FileNotFoundError) as e:
|
||||||
log.error(str(e))
|
log.error(str(e))
|
||||||
return
|
return False
|
||||||
|
|
||||||
output_path = self._generate_cache_folder(metadata, file)
|
try:
|
||||||
if not output_path:
|
output_path = self._generate_cache_folder(metadata, file)
|
||||||
raise ValueError("Failed to generate cache folder path")
|
if not output_path:
|
||||||
|
log.error("Failed to generate cache folder path")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Error generating cache folder: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
gaia_success = True
|
||||||
def run_gaia():
|
def run_gaia():
|
||||||
|
nonlocal gaia_success
|
||||||
if os.path.exists(os.path.join(output_path, "gaia.json")):
|
if os.path.exists(os.path.join(output_path, "gaia.json")):
|
||||||
return
|
return
|
||||||
|
|
||||||
subprocess.run(
|
result = subprocess.run(
|
||||||
[gaia_binary_path, file, os.path.join(output_path, "gaia.json")],
|
[gaia_binary_path, file, os.path.join(output_path, "gaia.json")],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
env=ENV,
|
env=ENV,
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
gaia_success = False
|
||||||
|
log.error(f"Gaia binary {gaia_binary_path} failed on file {file} with exit code {result.returncode}")
|
||||||
|
if result.stdout:
|
||||||
|
log.error(f"Gaia stdout: {result.stdout}")
|
||||||
|
if result.stderr:
|
||||||
|
log.error(f"Gaia stderr: {result.stderr}")
|
||||||
|
|
||||||
gaia_thread = threading.Thread(target=run_gaia)
|
gaia_thread = threading.Thread(target=run_gaia)
|
||||||
gaia_thread.start()
|
gaia_thread.start()
|
||||||
|
|
||||||
self._run_musicnn_models(REQUIRED_MODELS, musicnn_binary_path, file, output_path)
|
musicnn_success = self._run_musicnn_models(REQUIRED_MODELS, musicnn_binary_path, file, output_path)
|
||||||
gaia_thread.join()
|
gaia_thread.join()
|
||||||
|
|
||||||
|
return gaia_success and musicnn_success
|
||||||
|
|
||||||
def analyze_optional(self, metadata: Dict, file: str) -> None:
|
def analyze_optional(self, metadata: Dict, file: str) -> bool:
|
||||||
if not self._check_binaries():
|
if not self._check_binaries():
|
||||||
log.error("Essentia binaries not found")
|
log.error("Essentia binaries not found")
|
||||||
return
|
return False
|
||||||
|
|
||||||
if not self._check_optional_models():
|
if not self._check_optional_models():
|
||||||
log.error("Optional models not found")
|
log.error("Optional models not found")
|
||||||
return
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
musicnn_binary_path, _ = self._get_binary_paths()
|
musicnn_binary_path, _ = self._get_binary_paths()
|
||||||
except (ValueError, FileNotFoundError) as e:
|
except (ValueError, FileNotFoundError) as e:
|
||||||
log.error(str(e))
|
log.error(str(e))
|
||||||
return
|
return False
|
||||||
|
|
||||||
output_path = self._generate_cache_folder(metadata, file)
|
try:
|
||||||
if not output_path:
|
output_path = self._generate_cache_folder(metadata, file)
|
||||||
raise ValueError("Failed to generate cache folder path")
|
if not output_path:
|
||||||
|
log.error("Failed to generate cache folder path")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Error generating cache folder: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
self._run_musicnn_models(OPTIONAL_MODELS, musicnn_binary_path, file, output_path)
|
return self._run_musicnn_models(OPTIONAL_MODELS, musicnn_binary_path, file, output_path)
|
||||||
|
|
||||||
def parse_required(self, metadata: Dict, file: str) -> None:
|
def parse_required(self, metadata: Dict, file: str) -> bool:
|
||||||
if not self._check_required_models():
|
if not self._check_required_models():
|
||||||
raise ValueError("Required models not found")
|
log.error("Required models not found")
|
||||||
|
return False
|
||||||
|
|
||||||
models_path = config.setting["acousticbrainz_ng_models_path"]
|
models_path = config.setting["acousticbrainz_ng_models_path"]
|
||||||
if not models_path:
|
if not models_path:
|
||||||
raise ValueError("Models path not configured")
|
log.error("Models path not configured")
|
||||||
|
return False
|
||||||
|
|
||||||
output_path = self._generate_cache_folder(metadata, file)
|
try:
|
||||||
if not output_path:
|
output_path = self._generate_cache_folder(metadata, file)
|
||||||
raise ValueError("Failed to generate cache folder path")
|
if not output_path:
|
||||||
|
log.error("Failed to generate cache folder path")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Error generating cache folder: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
moods = []
|
moods = []
|
||||||
tags = []
|
tags = []
|
||||||
@@ -158,12 +204,12 @@ class AcousticBrainzNG:
|
|||||||
model_json_path = os.path.join(models_path, f"{model}.json")
|
model_json_path = os.path.join(models_path, f"{model}.json")
|
||||||
if not os.path.exists(model_json_path):
|
if not os.path.exists(model_json_path):
|
||||||
log.error(f"Model JSON metadata not found: {model_json_path}")
|
log.error(f"Model JSON metadata not found: {model_json_path}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
output_file_path = os.path.join(output_path, f"{output}.json")
|
output_file_path = os.path.join(output_path, f"{output}.json")
|
||||||
if not os.path.exists(output_file_path):
|
if not os.path.exists(output_file_path):
|
||||||
log.error(f"Output file not found: {output_file_path}")
|
log.error(f"Output file not found: {output_file_path}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
output_data = {}
|
output_data = {}
|
||||||
model_metadata = {}
|
model_metadata = {}
|
||||||
@@ -176,15 +222,15 @@ class AcousticBrainzNG:
|
|||||||
output_data = json.load(f)
|
output_data = json.load(f)
|
||||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
log.error(f"Error reading model or output file: {e}")
|
log.error(f"Error reading model or output file: {e}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
if not output_data["predictions"] or not output_data["predictions"]["mean"]:
|
if not output_data["predictions"] or not output_data["predictions"]["mean"]:
|
||||||
log.error(f"No predictions found in output data for {model}")
|
log.error(f"No predictions found in output data for {model}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
if not model_metadata["classes"] or len(model_metadata["classes"]) != len(output_data["predictions"]["mean"]):
|
if not model_metadata["classes"] or len(model_metadata["classes"]) != len(output_data["predictions"]["mean"]):
|
||||||
log.error(f"No or invalid classes defined in model metadata for {model}")
|
log.error(f"No or invalid classes defined in model metadata for {model}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
if len(model_metadata["classes"]) == 2:
|
if len(model_metadata["classes"]) == 2:
|
||||||
values = output_data["predictions"]["mean"]
|
values = output_data["predictions"]["mean"]
|
||||||
@@ -222,59 +268,72 @@ class AcousticBrainzNG:
|
|||||||
gaia_data = json.load(f)
|
gaia_data = json.load(f)
|
||||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
log.error(f"Error reading Gaia JSON file: {e}")
|
log.error(f"Error reading Gaia JSON file: {e}")
|
||||||
return
|
return False
|
||||||
else:
|
else:
|
||||||
log.error(f"Gaia JSON file not found: {gaia_json_path}")
|
log.error(f"Gaia JSON file not found: {gaia_json_path}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
metadata["bpm"] = int(round(gaia_data["rhythm"]["bpm"]))
|
|
||||||
|
|
||||||
if config.setting["acousticbrainz_ng_save_raw"]:
|
|
||||||
metadata["ab:lo:tonal:chords_changes_rate"] = gaia_data["tonal"]["chords_changes_rate"]
|
|
||||||
metadata["ab:lo:tonal:chords_key"] = gaia_data["tonal"]["chords_key"]
|
|
||||||
metadata["ab:lo:tonal:chords_scale"] = gaia_data["tonal"]["chords_scale"]
|
|
||||||
|
|
||||||
highestStrength = -1
|
|
||||||
selectedAlgorithm = None
|
|
||||||
|
|
||||||
for algorithm in GAIA_KEY_ALGORITHMS:
|
|
||||||
key_data = gaia_data["tonal"][f"key_{algorithm}"]
|
|
||||||
|
|
||||||
if key_data["strength"] > highestStrength:
|
|
||||||
highestStrength = key_data["strength"]
|
|
||||||
selectedAlgorithm = algorithm
|
|
||||||
|
|
||||||
if selectedAlgorithm:
|
|
||||||
selected_key_data = gaia_data["tonal"][f"key_{selectedAlgorithm}"]
|
|
||||||
|
|
||||||
metadata["key"] = "o" if selected_key_data["scale"] == "off" else f"{selected_key_data['key']}{'m' if selected_key_data['scale'] == 'minor' else ''}"
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
metadata["bpm"] = int(round(gaia_data["rhythm"]["bpm"]))
|
||||||
|
|
||||||
if config.setting["acousticbrainz_ng_save_raw"]:
|
if config.setting["acousticbrainz_ng_save_raw"]:
|
||||||
metadata["ab:lo:tonal:key_scale"] = selected_key_data["scale"]
|
metadata["ab:lo:tonal:chords_changes_rate"] = gaia_data["tonal"]["chords_changes_rate"]
|
||||||
metadata["ab:lo:tonal:key_key"] = selected_key_data["key"]
|
metadata["ab:lo:tonal:chords_key"] = gaia_data["tonal"]["chords_key"]
|
||||||
|
metadata["ab:lo:tonal:chords_scale"] = gaia_data["tonal"]["chords_scale"]
|
||||||
|
|
||||||
|
highestStrength = -1
|
||||||
|
selectedAlgorithm = None
|
||||||
|
|
||||||
|
for algorithm in GAIA_KEY_ALGORITHMS:
|
||||||
|
key_data = gaia_data["tonal"][f"key_{algorithm}"]
|
||||||
|
|
||||||
|
if key_data["strength"] > highestStrength:
|
||||||
|
highestStrength = key_data["strength"]
|
||||||
|
selectedAlgorithm = algorithm
|
||||||
|
|
||||||
|
if selectedAlgorithm:
|
||||||
|
selected_key_data = gaia_data["tonal"][f"key_{selectedAlgorithm}"]
|
||||||
|
|
||||||
|
metadata["key"] = "o" if selected_key_data["scale"] == "off" else f"{selected_key_data['key']}{'m' if selected_key_data['scale'] == 'minor' else ''}"
|
||||||
|
|
||||||
|
if config.setting["acousticbrainz_ng_save_raw"]:
|
||||||
|
metadata["ab:lo:tonal:key_scale"] = selected_key_data["scale"]
|
||||||
|
metadata["ab:lo:tonal:key_key"] = selected_key_data["key"]
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Error processing gaia data: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def parse_optional(self, metadata: Dict, file: str) -> None:
|
def parse_optional(self, metadata: Dict, file: str) -> bool:
|
||||||
if not self._check_optional_models():
|
if not self._check_optional_models():
|
||||||
raise ValueError("Optional models not found")
|
log.error("Optional models not found")
|
||||||
|
return False
|
||||||
|
|
||||||
models_path = config.setting["acousticbrainz_ng_models_path"]
|
models_path = config.setting["acousticbrainz_ng_models_path"]
|
||||||
if not models_path:
|
if not models_path:
|
||||||
raise ValueError("Models path not configured")
|
log.error("Models path not configured")
|
||||||
|
return False
|
||||||
|
|
||||||
output_path = self._generate_cache_folder(metadata, file)
|
try:
|
||||||
if not output_path:
|
output_path = self._generate_cache_folder(metadata, file)
|
||||||
raise ValueError("Failed to generate cache folder path")
|
if not output_path:
|
||||||
|
log.error("Failed to generate cache folder path")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Error generating cache folder: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
for model, output in OPTIONAL_MODELS:
|
for model, output in OPTIONAL_MODELS:
|
||||||
model_json_path = os.path.join(models_path, f"{model}.json")
|
model_json_path = os.path.join(models_path, f"{model}.json")
|
||||||
if not os.path.exists(model_json_path):
|
if not os.path.exists(model_json_path):
|
||||||
log.error(f"Model JSON metadata not found: {model_json_path}")
|
log.error(f"Model JSON metadata not found: {model_json_path}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
output_file_path = os.path.join(output_path, f"{output}.json")
|
output_file_path = os.path.join(output_path, f"{output}.json")
|
||||||
if not os.path.exists(output_file_path):
|
if not os.path.exists(output_file_path):
|
||||||
log.error(f"Output file not found: {output_file_path}")
|
log.error(f"Output file not found: {output_file_path}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
output_data = {}
|
output_data = {}
|
||||||
model_metadata = {}
|
model_metadata = {}
|
||||||
@@ -287,20 +346,22 @@ class AcousticBrainzNG:
|
|||||||
output_data = json.load(f)
|
output_data = json.load(f)
|
||||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
log.error(f"Error reading model or output file: {e}")
|
log.error(f"Error reading model or output file: {e}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
if not output_data["predictions"] or not output_data["predictions"]["mean"]:
|
if not output_data["predictions"] or not output_data["predictions"]["mean"]:
|
||||||
log.error(f"No predictions found in output data for {model}")
|
log.error(f"No predictions found in output data for {model}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
if not model_metadata["classes"] or len(model_metadata["classes"]) != len(output_data["predictions"]["mean"]):
|
if not model_metadata["classes"] or len(model_metadata["classes"]) != len(output_data["predictions"]["mean"]):
|
||||||
log.error(f"No or invalid classes defined in model metadata for {model}")
|
log.error(f"No or invalid classes defined in model metadata for {model}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
if config.setting["acousticbrainz_ng_save_raw"]:
|
if config.setting["acousticbrainz_ng_save_raw"]:
|
||||||
for i in range(len(output_data["predictions"]["mean"])):
|
for i in range(len(output_data["predictions"]["mean"])):
|
||||||
metadata[f"ab:hi:{output}:{model_metadata['classes'][i].replace('non', 'not').replace('_', ' ').lower()}"] = output_data["predictions"]["mean"][i]
|
metadata[f"ab:hi:{output}:{model_metadata['classes'][i].replace('non', 'not').replace('_', ' ').lower()}"] = output_data["predictions"]["mean"][i]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _format_class(class_name: str) -> str:
|
def _format_class(class_name: str) -> str:
|
||||||
return class_name.replace("non", "not").replace("_", " ").capitalize()
|
return class_name.replace("non", "not").replace("_", " ").capitalize()
|
||||||
@@ -350,8 +411,14 @@ class AcousticBrainzNG:
|
|||||||
for line in result.stdout.strip().split('\n'):
|
for line in result.stdout.strip().split('\n'):
|
||||||
if line.startswith('MD5:'):
|
if line.startswith('MD5:'):
|
||||||
return line.split('MD5:')[1].strip()
|
return line.split('MD5:')[1].strip()
|
||||||
|
else:
|
||||||
|
log.error(f"MD5 binary {binary_path} failed on file {file_path} with exit code {result.returncode}")
|
||||||
|
if result.stdout:
|
||||||
|
log.error(f"MD5 stdout: {result.stdout}")
|
||||||
|
if result.stderr:
|
||||||
|
log.error(f"MD5 stderr: {result.stderr}")
|
||||||
|
|
||||||
log.error(f"Failed to calculate audio hash: {result.stderr}")
|
log.error(f"Failed to calculate audio hash for file {file_path}: MD5 not found in output")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Error calculating audio hash: {e}")
|
log.error(f"Error calculating audio hash: {e}")
|
||||||
@@ -407,6 +474,14 @@ class AcousticBrainzNG:
|
|||||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if replaygain_lufs_result.returncode != 0:
|
||||||
|
log.error(f"FFmpeg failed for ReplayGain LUFS calculation on file {file_path} with exit code {replaygain_lufs_result.returncode}")
|
||||||
|
if replaygain_lufs_result.stdout:
|
||||||
|
log.error(f"FFmpeg stdout: {replaygain_lufs_result.stdout}")
|
||||||
|
if replaygain_lufs_result.stderr:
|
||||||
|
log.error(f"FFmpeg stderr: {replaygain_lufs_result.stderr}")
|
||||||
|
return {}
|
||||||
|
|
||||||
replaygain_gain = None
|
replaygain_gain = None
|
||||||
replaygain_peak = None
|
replaygain_peak = None
|
||||||
replaygain_range = None
|
replaygain_range = None
|
||||||
@@ -449,6 +524,14 @@ class AcousticBrainzNG:
|
|||||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if r128_result.returncode != 0:
|
||||||
|
log.error(f"FFmpeg failed for R128 calculation on file {file_path} with exit code {r128_result.returncode}")
|
||||||
|
if r128_result.stdout:
|
||||||
|
log.error(f"FFmpeg stdout: {r128_result.stdout}")
|
||||||
|
if r128_result.stderr:
|
||||||
|
log.error(f"FFmpeg stderr: {r128_result.stderr}")
|
||||||
|
return result
|
||||||
|
|
||||||
r128_track_gain = None
|
r128_track_gain = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -508,6 +591,15 @@ class AcousticBrainzNG:
|
|||||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if album_replaygain_result.returncode != 0:
|
||||||
|
log.error(f"FFmpeg failed for album ReplayGain calculation on {len(album_track_files)} files with exit code {album_replaygain_result.returncode}")
|
||||||
|
log.error(f"Album files: {', '.join(album_track_files)}")
|
||||||
|
if album_replaygain_result.stdout:
|
||||||
|
log.error(f"FFmpeg stdout: {album_replaygain_result.stdout}")
|
||||||
|
if album_replaygain_result.stderr:
|
||||||
|
log.error(f"FFmpeg stderr: {album_replaygain_result.stderr}")
|
||||||
|
return {}
|
||||||
|
|
||||||
album_gain = None
|
album_gain = None
|
||||||
album_peak = None
|
album_peak = None
|
||||||
album_range = None
|
album_range = None
|
||||||
@@ -550,6 +642,15 @@ class AcousticBrainzNG:
|
|||||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if album_r128_result.returncode != 0:
|
||||||
|
log.error(f"FFmpeg failed for album R128 calculation on {len(album_track_files)} files with exit code {album_r128_result.returncode}")
|
||||||
|
log.error(f"Album files: {', '.join(album_track_files)}")
|
||||||
|
if album_r128_result.stdout:
|
||||||
|
log.error(f"FFmpeg stdout: {album_r128_result.stdout}")
|
||||||
|
if album_r128_result.stderr:
|
||||||
|
log.error(f"FFmpeg stderr: {album_r128_result.stderr}")
|
||||||
|
return result
|
||||||
|
|
||||||
r128_album_gain = None
|
r128_album_gain = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -584,15 +685,20 @@ class AcousticBrainzNG:
|
|||||||
log.error(f"Error calculating album loudness: {e}")
|
log.error(f"Error calculating album loudness: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def calculate_loudness(self, metadata: Dict, file_path: str, album: Optional[Album] = None) -> None:
|
def calculate_loudness(self, metadata: Dict, file_path: str, album: Optional[Album] = None) -> bool:
|
||||||
try:
|
try:
|
||||||
cache_folder = self._generate_cache_folder(metadata, file_path)
|
cache_folder = self._generate_cache_folder(metadata, file_path)
|
||||||
loudness_file = os.path.join(cache_folder, f"loudness_{config.setting['acousticbrainz_ng_replaygain_reference_loudness'] or -18}.json")
|
loudness_file = os.path.join(cache_folder, f"loudness_{config.setting['acousticbrainz_ng_replaygain_reference_loudness'] or -18}.json")
|
||||||
|
|
||||||
if os.path.exists(loudness_file):
|
if os.path.exists(loudness_file):
|
||||||
return
|
return True
|
||||||
|
|
||||||
track_loudness = self.calculate_track_loudness(file_path)
|
track_loudness = self.calculate_track_loudness(file_path)
|
||||||
|
|
||||||
|
# Check if track loudness calculation failed
|
||||||
|
if not track_loudness:
|
||||||
|
log.error("Failed to calculate track loudness")
|
||||||
|
return False
|
||||||
|
|
||||||
album_loudness = {}
|
album_loudness = {}
|
||||||
if album is not None:
|
if album is not None:
|
||||||
@@ -616,6 +722,10 @@ class AcousticBrainzNG:
|
|||||||
album_loudness["r128_album_gain"] = track_loudness.get("r128_track_gain")
|
album_loudness["r128_album_gain"] = track_loudness.get("r128_track_gain")
|
||||||
else:
|
else:
|
||||||
album_loudness = self.calculate_album_loudness(album_track_files)
|
album_loudness = self.calculate_album_loudness(album_track_files)
|
||||||
|
# Check if album loudness calculation failed
|
||||||
|
if not album_loudness:
|
||||||
|
log.error("Failed to calculate album loudness")
|
||||||
|
# Continue with track-only data
|
||||||
|
|
||||||
album_data = {
|
album_data = {
|
||||||
"track_count": len(album_track_files),
|
"track_count": len(album_track_files),
|
||||||
@@ -638,6 +748,7 @@ class AcousticBrainzNG:
|
|||||||
album_loudness["r128_album_gain"] = album_data.get('r128_album_gain')
|
album_loudness["r128_album_gain"] = album_data.get('r128_album_gain')
|
||||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
log.error(f"Error reading album data file: {e}")
|
log.error(f"Error reading album data file: {e}")
|
||||||
|
# Continue without album loudness data
|
||||||
|
|
||||||
loudness_data = {
|
loudness_data = {
|
||||||
**track_loudness,
|
**track_loudness,
|
||||||
@@ -646,18 +757,26 @@ class AcousticBrainzNG:
|
|||||||
|
|
||||||
with open(loudness_file, 'w', encoding='utf-8') as f:
|
with open(loudness_file, 'w', encoding='utf-8') as f:
|
||||||
json.dump(loudness_data, f, indent=2)
|
json.dump(loudness_data, f, indent=2)
|
||||||
|
|
||||||
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Error calculating loudness: {e}")
|
log.error(f"Error calculating loudness: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def parse_loudness(self, metadata: Dict, file: str) -> None:
|
def parse_loudness(self, metadata: Dict, file: str) -> bool:
|
||||||
output_path = self._generate_cache_folder(metadata, file)
|
try:
|
||||||
if not output_path:
|
output_path = self._generate_cache_folder(metadata, file)
|
||||||
raise ValueError("Failed to generate cache folder path")
|
if not output_path:
|
||||||
|
log.error("Failed to generate cache folder path")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Error generating cache folder: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
loudness_file = os.path.join(output_path, f"loudness_{config.setting['acousticbrainz_ng_replaygain_reference_loudness'] or -18}.json")
|
loudness_file = os.path.join(output_path, f"loudness_{config.setting['acousticbrainz_ng_replaygain_reference_loudness'] or -18}.json")
|
||||||
if not os.path.exists(loudness_file):
|
if not os.path.exists(loudness_file):
|
||||||
log.error(f"Loudness file not found: {loudness_file}")
|
log.error(f"Loudness file not found: {loudness_file}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
loudness_data = {}
|
loudness_data = {}
|
||||||
|
|
||||||
@@ -666,46 +785,52 @@ class AcousticBrainzNG:
|
|||||||
loudness_data = json.load(f)
|
loudness_data = json.load(f)
|
||||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
log.error(f"Error reading loudness file: {e}")
|
log.error(f"Error reading loudness file: {e}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
is_opus = self._is_opus_file(file)
|
try:
|
||||||
|
is_opus = self._is_opus_file(file)
|
||||||
|
|
||||||
replaygain_track_gain = loudness_data.get("replaygain_track_gain")
|
replaygain_track_gain = loudness_data.get("replaygain_track_gain")
|
||||||
if replaygain_track_gain is not None:
|
if replaygain_track_gain is not None:
|
||||||
metadata["replaygain_track_gain"] = f"{replaygain_track_gain} dB"
|
metadata["replaygain_track_gain"] = f"{replaygain_track_gain} dB"
|
||||||
|
|
||||||
replaygain_track_peak = loudness_data.get("replaygain_track_peak")
|
|
||||||
if replaygain_track_peak is not None:
|
|
||||||
metadata["replaygain_track_peak"] = replaygain_track_peak
|
|
||||||
|
|
||||||
replaygain_track_range = loudness_data.get("replaygain_track_range")
|
|
||||||
if replaygain_track_range is not None:
|
|
||||||
metadata["replaygain_track_range"] = f"{replaygain_track_range} dB"
|
|
||||||
|
|
||||||
replaygain_album_gain = loudness_data.get("replaygain_album_gain")
|
|
||||||
if replaygain_album_gain is not None:
|
|
||||||
metadata["replaygain_album_gain"] = f"{replaygain_album_gain} dB"
|
|
||||||
|
|
||||||
replaygain_album_peak = loudness_data.get("replaygain_album_peak")
|
|
||||||
if replaygain_album_peak is not None:
|
|
||||||
metadata["replaygain_album_peak"] = replaygain_album_peak
|
|
||||||
|
|
||||||
replaygain_album_range = loudness_data.get("replaygain_album_range")
|
|
||||||
if replaygain_album_range is not None:
|
|
||||||
metadata["replaygain_album_range"] = f"{replaygain_album_range} dB"
|
|
||||||
|
|
||||||
replaygain_reference_loudness = loudness_data.get("replaygain_reference_loudness")
|
|
||||||
if replaygain_reference_loudness is not None:
|
|
||||||
metadata["replaygain_reference_loudness"] = f"{replaygain_reference_loudness} LUFS"
|
|
||||||
|
|
||||||
if is_opus:
|
|
||||||
r128_track_gain = loudness_data.get("r128_track_gain")
|
|
||||||
if r128_track_gain is not None:
|
|
||||||
metadata["r128_track_gain"] = r128_track_gain
|
|
||||||
|
|
||||||
r128_album_gain = loudness_data.get("r128_album_gain")
|
replaygain_track_peak = loudness_data.get("replaygain_track_peak")
|
||||||
if r128_album_gain is not None:
|
if replaygain_track_peak is not None:
|
||||||
metadata["r128_album_gain"] = r128_album_gain
|
metadata["replaygain_track_peak"] = replaygain_track_peak
|
||||||
|
|
||||||
|
replaygain_track_range = loudness_data.get("replaygain_track_range")
|
||||||
|
if replaygain_track_range is not None:
|
||||||
|
metadata["replaygain_track_range"] = f"{replaygain_track_range} dB"
|
||||||
|
|
||||||
|
replaygain_album_gain = loudness_data.get("replaygain_album_gain")
|
||||||
|
if replaygain_album_gain is not None:
|
||||||
|
metadata["replaygain_album_gain"] = f"{replaygain_album_gain} dB"
|
||||||
|
|
||||||
|
replaygain_album_peak = loudness_data.get("replaygain_album_peak")
|
||||||
|
if replaygain_album_peak is not None:
|
||||||
|
metadata["replaygain_album_peak"] = replaygain_album_peak
|
||||||
|
|
||||||
|
replaygain_album_range = loudness_data.get("replaygain_album_range")
|
||||||
|
if replaygain_album_range is not None:
|
||||||
|
metadata["replaygain_album_range"] = f"{replaygain_album_range} dB"
|
||||||
|
|
||||||
|
replaygain_reference_loudness = loudness_data.get("replaygain_reference_loudness")
|
||||||
|
if replaygain_reference_loudness is not None:
|
||||||
|
metadata["replaygain_reference_loudness"] = f"{replaygain_reference_loudness} LUFS"
|
||||||
|
|
||||||
|
if is_opus:
|
||||||
|
r128_track_gain = loudness_data.get("r128_track_gain")
|
||||||
|
if r128_track_gain is not None:
|
||||||
|
metadata["r128_track_gain"] = r128_track_gain
|
||||||
|
|
||||||
|
r128_album_gain = loudness_data.get("r128_album_gain")
|
||||||
|
if r128_album_gain is not None:
|
||||||
|
metadata["r128_album_gain"] = r128_album_gain
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Error parsing loudness data: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
acousticbrainz_ng = AcousticBrainzNG()
|
acousticbrainz_ng = AcousticBrainzNG()
|
||||||
|
|
||||||
@@ -713,18 +838,41 @@ class AcousticBrainzNGAction(BaseAction):
|
|||||||
NAME = f"Analyze with {PLUGIN_NAME}"
|
NAME = f"Analyze with {PLUGIN_NAME}"
|
||||||
|
|
||||||
def _process_track(self, track: Track, album: Optional[Album] = None) -> None:
|
def _process_track(self, track: Track, album: Optional[Album] = None) -> None:
|
||||||
|
window = self.tagger.window
|
||||||
|
|
||||||
for file in track.files:
|
for file in track.files:
|
||||||
acousticbrainz_ng.analyze_required(file.metadata, file.filename)
|
ar_result = acousticbrainz_ng.analyze_required(file.metadata, file.filename)
|
||||||
acousticbrainz_ng.parse_required(file.metadata, file.filename)
|
pr_result = acousticbrainz_ng.parse_required(file.metadata, file.filename)
|
||||||
|
|
||||||
|
if not ar_result or not pr_result:
|
||||||
|
log.error(f"Failed to analyze required models for {file.filename}")
|
||||||
|
window.set_statusbar_message(f"Failed to analyze required models for {file.filename}")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
window.set_statusbar_message(f"Analyzed required models for {file.filename}")
|
||||||
|
|
||||||
if config.setting["acousticbrainz_ng_analyze_optional"]:
|
if config.setting["acousticbrainz_ng_analyze_optional"]:
|
||||||
acousticbrainz_ng.analyze_optional(file.metadata, file.filename)
|
ao_result = acousticbrainz_ng.analyze_optional(file.metadata, file.filename)
|
||||||
acousticbrainz_ng.parse_optional(file.metadata, file.filename)
|
ap_result = acousticbrainz_ng.parse_optional(file.metadata, file.filename)
|
||||||
|
|
||||||
|
if not ao_result or not ap_result:
|
||||||
|
log.error(f"Failed to analyze optional models for {file.filename}")
|
||||||
|
window.set_statusbar_message(f"Failed to analyze optional models for {file.filename}")
|
||||||
|
else:
|
||||||
|
window.set_statusbar_message(f"Analyzed optional models for {file.filename}")
|
||||||
|
|
||||||
if config.setting["acousticbrainz_ng_calculate_replaygain"]:
|
if config.setting["acousticbrainz_ng_calculate_replaygain"]:
|
||||||
acousticbrainz_ng.calculate_loudness(file.metadata, file.filename, album)
|
cl_result = acousticbrainz_ng.calculate_loudness(file.metadata, file.filename, album)
|
||||||
acousticbrainz_ng.parse_loudness(file.metadata, file.filename)
|
pl_result = acousticbrainz_ng.parse_loudness(file.metadata, file.filename)
|
||||||
|
|
||||||
|
if not cl_result or not pl_result:
|
||||||
|
log.error(f"Failed to calculate loudness for {file.filename}")
|
||||||
|
window.set_statusbar_message(f"Failed to calculate loudness for {file.filename}")
|
||||||
|
else:
|
||||||
|
window.set_statusbar_message(f"Analyzed loudness for {file.filename}")
|
||||||
|
|
||||||
|
window.set_statusbar_message(f"Analyzed {file.filename} with {PLUGIN_NAME}")
|
||||||
|
|
||||||
def callback(self, objs):
|
def callback(self, objs):
|
||||||
for item in (t for t in objs if isinstance(t, Track) or isinstance(t, Album)):
|
for item in (t for t in objs if isinstance(t, Track) or isinstance(t, Album)):
|
||||||
if isinstance(item, Track):
|
if isinstance(item, Track):
|
||||||
@@ -885,8 +1033,15 @@ class AcousticBrainzNGOptionsPage(OptionsPage):
|
|||||||
result = subprocess.run([ffmpeg_path, "-version"], capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0)
|
result = subprocess.run([ffmpeg_path, "-version"], capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0)
|
||||||
if result.returncode != 0 or "ffmpeg version" not in result.stdout:
|
if result.returncode != 0 or "ffmpeg version" not in result.stdout:
|
||||||
missing_binaries.append("FFmpeg (invalid executable)")
|
missing_binaries.append("FFmpeg (invalid executable)")
|
||||||
except Exception:
|
if result.returncode != 0:
|
||||||
|
log.error(f"FFmpeg version check failed with exit code {result.returncode}")
|
||||||
|
if result.stdout:
|
||||||
|
log.error(f"FFmpeg stdout: {result.stdout}")
|
||||||
|
if result.stderr:
|
||||||
|
log.error(f"FFmpeg stderr: {result.stderr}")
|
||||||
|
except Exception as e:
|
||||||
missing_binaries.append("FFmpeg (unable to execute)")
|
missing_binaries.append("FFmpeg (unable to execute)")
|
||||||
|
log.error(f"Exception running FFmpeg version check: {e}")
|
||||||
|
|
||||||
if missing_binaries:
|
if missing_binaries:
|
||||||
message = f"Missing binaries:\n" + "\n".join(f"• {binary}" for binary in missing_binaries)
|
message = f"Missing binaries:\n" + "\n".join(f"• {binary}" for binary in missing_binaries)
|
||||||
|
|||||||
Reference in New Issue
Block a user