Implement threading, logging, and CJK font support

Major improvements to GUI stability and internationalization:

- Fix GUI freezing by implementing threaded audio analysis
  - Add AudioAnalysisWorker thread for background processing
  - Progress signals with percentage updates
  - Thread-safe communication via Qt signals

- Add comprehensive CLI logging system
  - 5 log levels: ERROR, WARN, INFO, DEBUG, TRACE
  - Command line control: --log-level, --log-file
  - Real-time feedback during analysis operations

- Implement CJK font fallback system
  - FontManager with 3-tier fallback (custom → system → default)
  - Cross-platform CJK font detection (Windows/macOS/Linux)
  - Licensing-safe fonts/ directory with gitignored font files
  - Setup utility and comprehensive documentation

- Fix numpy array formatting issue with BPM detection
- Add progress indicators for long-running operations
- Preserve fonts directory structure with placeholder file

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2025-08-21 23:39:53 +09:00
parent b07b363454
commit 4337a31b80
13 changed files with 1007 additions and 42 deletions
+14 -7
View File
@@ -8,6 +8,7 @@ import matplotlib.cm as cm
from mutagen.mp3 import MP3
from mutagen.easyid3 import EasyID3
from font_manager import safe_title, initialize_fonts
def try_mp3_tags(file_path):
try:
@@ -20,19 +21,19 @@ def try_mp3_tags(file_path):
def read_mp3_tags(file_path):
if (audio := try_mp3_tags(file_path)) is not None:
print(f"File name: {os.path.basename(file_path)}")
print(f"{audio['artist'][0]} - {audio['title'][0]}")
print(f"File name: {safe_title(os.path.basename(file_path))}")
print(f"{safe_title(audio['artist'][0])} - {safe_title(audio['title'][0])}")
else:
print(f"File name: {os.path.basename(file_path)}")
print(f"File name: {safe_title(os.path.basename(file_path))}")
class AudioFile:
def __init__(self, file_path):
self.file_path = file_path
# file name / song name
if (audio := try_mp3_tags(self.file_path)) is not None:
self.song_name = f"{audio['artist'][0]} - {audio['title'][0]}"
self.song_name = safe_title(f"{audio['artist'][0]} - {audio['title'][0]}")
else:
self.song_name = os.path.basename(self.file_path)
self.song_name = safe_title(os.path.basename(self.file_path))
self.y, self.sr = librosa.load(file_path)
# load automatically normalises everything to [-1.0, 1.0]
@@ -49,7 +50,10 @@ class AudioFile:
return self.max_amplitude, self.avg_amplitude
def get_bpm(self):
return self.bpm
# librosa.beat.beat_track returns numpy array - extract scalar value
if isinstance(self.bpm, np.ndarray):
return float(self.bpm[0]) if len(self.bpm) > 0 else 0.0
return float(self.bpm)
def get_energy_levels_over_time(self, window = 10, hop = 2):
"""_summary_
@@ -121,7 +125,7 @@ class AudioFile:
ax.set_ylabel('Power')
ax.set_xlabel('Time')
ax.set_title(f'{os.path.basename(self.file_path)}')
ax.set_title(safe_title(os.path.basename(self.file_path)))
plt.show(block=False)
plt.pause(0.001)
@@ -216,6 +220,9 @@ if __name__ == '__main__':
print("For the new GUI interface, please run: python main.py")
print()
# Initialize fonts for matplotlib
initialize_fonts()
# Replace 'path/to/your/audiofile.mp3' with the path to your audio file
file_path = []
with open('./files.txt', 'r') as f: