Add bundled default sound pack with auto-copy functionality

Features:
- Complete default sound pack with 14 OGG audio files covering all Bifrost events
- Auto-copy bundled pack to user data directory on first run (XDG compliant)
- Graceful fallback if bundled pack is missing or copy fails
- No network dependency for immediate audio feedback

Sound Pack Contents:
- Core sounds: startup, shutdown, success, error
- Communication: private_message, mention, boost, reply, post_sent
- Interface: timeline_update, notification
- Interaction: autocomplete, autocomplete_end

Technical Implementation:
- Enhanced SoundManager.create_default_pack() to copy from bundled location
- Smart path detection for bundled pack across different installation scenarios
- Robust error handling with minimal fallback pack.json creation
- Total pack size: ~260KB (suitable for regular Git without LFS)

User Experience:
- Immediate audio feedback from first application launch
- No setup or configuration required
- Sounds install to proper XDG data directory (~/.local/share/bifrost/sounds/)
- Users can still customize or replace the default pack

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Storm Dragon
2025-08-17 01:50:37 -04:00
parent eae3191081
commit f0f761a118
15 changed files with 73 additions and 21 deletions

Binary file not shown.

Binary file not shown.

BIN
sounds/default/boost.ogg Normal file

Binary file not shown.

BIN
sounds/default/error.ogg Normal file

Binary file not shown.

BIN
sounds/default/mention.ogg Normal file

Binary file not shown.

Binary file not shown.

21
sounds/default/pack.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "Default",
"description": "Clean, accessible default sounds for Bifrost",
"author": "Bifrost Development Team",
"version": "1.0",
"sounds": {
"private_message": "private_message.ogg",
"mention": "mention.ogg",
"boost": "boost.ogg",
"reply": "reply.ogg",
"post_sent": "post_sent.ogg",
"timeline_update": "timeline_update.ogg",
"notification": "notification.ogg",
"autocomplete": "autocomplete.ogg",
"autocomplete_end": "autocomplete_end.ogg",
"startup": "startup.ogg",
"shutdown": "shutdown.ogg",
"success": "success.ogg",
"error": "error.ogg"
}
}

Binary file not shown.

Binary file not shown.

BIN
sounds/default/reply.ogg Normal file

Binary file not shown.

BIN
sounds/default/shutdown.ogg Normal file

Binary file not shown.

BIN
sounds/default/startup.ogg Normal file

Binary file not shown.

BIN
sounds/default/success.ogg Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -8,6 +8,7 @@ import json
import wave import wave
import numpy as np import numpy as np
import logging import logging
import shutil
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional from typing import Dict, List, Optional
from threading import Thread from threading import Thread
@@ -138,43 +139,73 @@ class SoundManager:
self.sound_packs[pack_dir.name] = pack self.sound_packs[pack_dir.name] = pack
def create_default_pack(self): def create_default_pack(self):
"""Create default sound pack if it doesn't exist""" """Create default sound pack if it doesn't exist by copying from bundled pack"""
default_dir = self.settings.get_sounds_dir() / "default" default_dir = self.settings.get_sounds_dir() / "default"
default_dir.mkdir(parents=True, exist_ok=True) default_dir.mkdir(parents=True, exist_ok=True)
pack_file = default_dir / "pack.json" pack_file = default_dir / "pack.json"
if not pack_file.exists(): if not pack_file.exists():
# Try to copy from bundled default pack
bundled_pack_dir = self._find_bundled_default_pack()
if bundled_pack_dir and bundled_pack_dir.exists():
self.logger.info(f"Copying bundled default sound pack from {bundled_pack_dir}")
try:
# Copy all files from bundled pack
for file_path in bundled_pack_dir.iterdir():
if file_path.is_file():
shutil.copy2(file_path, default_dir / file_path.name)
self.logger.info("Successfully copied bundled default sound pack")
return
except Exception as e:
self.logger.warning(f"Failed to copy bundled pack: {e}, creating minimal fallback")
# Fallback: create minimal pack.json (no actual sound files)
self.logger.info("Creating minimal fallback default pack")
pack_data = { pack_data = {
"name": "Default", "name": "Default",
"description": "Default system sounds", "description": "Default system sounds",
"author": "Bifrost", "author": "Bifrost",
"version": "1.0", "version": "1.0",
"sounds": { "sounds": {
"private_message": "private_message.wav", "private_message": "private_message.ogg",
"direct_message": "direct_message.wav", "mention": "mention.ogg",
"mention": "mention.wav", "boost": "boost.ogg",
"boost": "boost.wav", "reply": "reply.ogg",
"reply": "reply.wav", "post_sent": "post_sent.ogg",
"favorite": "favorite.wav", "timeline_update": "timeline_update.ogg",
"follow": "follow.wav", "notification": "notification.ogg",
"unfollow": "unfollow.wav", "startup": "startup.ogg",
"post_sent": "post_sent.wav", "shutdown": "shutdown.ogg",
"post": "post.wav", "success": "success.ogg",
"timeline_update": "timeline_update.wav", "error": "error.ogg",
"notification": "notification.wav", "autocomplete": "autocomplete.ogg",
"startup": "startup.wav", "autocomplete_end": "autocomplete_end.ogg"
"shutdown": "shutdown.wav",
"success": "success.wav",
"error": "error.wav",
"expand": "expand.wav",
"collapse": "collapse.wav",
"autocomplete": "autocomplete.wav",
"autocomplete_end": "autocomplete_end.wav"
} }
} }
with open(pack_file, 'w') as f: with open(pack_file, 'w') as f:
json.dump(pack_data, f, indent=2) json.dump(pack_data, f, indent=2)
def _find_bundled_default_pack(self) -> Optional[Path]:
"""Find the bundled default sound pack directory"""
# Look for bundled sounds relative to this file
current_file = Path(__file__)
# Try different potential locations relative to the source
potential_paths = [
# From src/audio/sound_manager.py to sounds/default/
current_file.parent.parent.parent / "sounds" / "default",
# From installed location
current_file.parent.parent / "sounds" / "default",
# Current working directory
Path.cwd() / "sounds" / "default"
]
for path in potential_paths:
if path.exists() and (path / "pack.json").exists():
return path
return None
def load_current_pack(self): def load_current_pack(self):
"""Load the currently selected sound pack""" """Load the currently selected sound pack"""