From 1657a242fd86d9f9fe8df84e4a62d6d0c6bc1efe Mon Sep 17 00:00:00 2001 From: grabowski Date: Fri, 24 Oct 2025 14:43:57 +0700 Subject: [PATCH] Add UV package management and volume control feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features: - Add pyproject.toml for UV package management - Volume control with real-time slider (0-100%) - Backend volume adjustment with numpy audio scaling - Volume setting persists in config.json - Debounced API calls for smooth slider interaction - Enhanced audio playback with volume multiplier - Update README with UV installation instructions - Add volume control documentation API Changes: - GET /api/volume - Get current volume setting - POST /api/volume - Set volume level 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 49 +++++++++++--- pyproject.toml | 24 +++++++ rotary_phone_web.py | 159 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 pyproject.toml diff --git a/README.md b/README.md index 1c8e91d..a8c06f5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A Raspberry Pi-based rotary phone system for weddings and events. Guests can pic - **Voice Recording**: Automatically records guest messages after the greeting - **Web Interface**: Beautiful, responsive web UI for managing the system - **Audio Playback**: Play recordings and greetings directly in the browser +- **Volume Control**: Adjust playback volume with real-time slider (0-100%) - **Multiple Message Support**: Upload and manage multiple greeting messages - **Active Message Selector**: Choose which greeting plays when the phone is picked up - **HiFiBerry Support**: Optimized for HiFiBerry DAC+ADC Pro audio quality @@ -43,7 +44,30 @@ git clone https://git.b4l.co.th/grabowski/wedding-phone.git cd wedding-phone ``` -### 2. Install Dependencies +### 2. Install UV (Recommended) + +UV is a fast Python package installer and resolver. Install it: + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +# Or on Raspberry Pi with pip: +pip3 install uv +``` + +### 3. Install Dependencies + +#### Option A: Using UV (Recommended) + +```bash +# Install system dependencies +sudo apt-get update +sudo apt-get install -y python3-pyaudio portaudio19-dev + +# Install Python dependencies with UV +uv pip install -e . +``` + +#### Option B: Using pip ```bash sudo apt-get update @@ -51,7 +75,7 @@ sudo apt-get install -y python3-pip python3-pyaudio portaudio19-dev pip3 install flask numpy RPi.GPIO ``` -### 3. Configure HiFiBerry +### 4. Configure HiFiBerry Run the automatic configuration script: @@ -62,7 +86,7 @@ chmod +x configure_hifiberry.sh Or follow the manual instructions in `AUDIO_FIX.md`. -### 4. Test Your Audio +### 5. Test Your Audio ```bash python3 test_complete.py @@ -73,7 +97,7 @@ This will test: - Dial tone generation - Microphone recording -### 5. Configure GPIO Pin +### 6. Configure GPIO Pin Edit `rotary_phone_web.py` and set your hookswitch GPIO pin: @@ -82,7 +106,7 @@ HOOK_PIN = 17 # Change to your GPIO pin number HOOK_PRESSED = GPIO.LOW # Or GPIO.HIGH depending on your switch ``` -### 6. Run the System +### 7. Run the System ```bash python3 rotary_phone_web.py @@ -96,21 +120,27 @@ The web interface will be available at: ### Web Interface -The web interface provides three main sections: +The web interface provides four main sections: #### 1. Phone Status - Shows current phone state (on-hook/off-hook/recording) - Displays active recording filename - Auto-refreshes every 5 seconds -#### 2. Greeting Messages +#### 2. Volume Control +- **Adjust Volume**: Drag slider to set playback volume (0-100%) +- Real-time visual feedback with percentage display +- Changes apply immediately to greeting playback +- Volume setting persists across restarts + +#### 3. Greeting Messages - **Upload**: Click "Choose WAV File(s)" to upload one or multiple greeting messages - **Play**: Click "▶️ Play" to preview any greeting in your browser - **Set Active**: Click "⭐ Set Active" to select which greeting plays when the phone is picked up - **Delete**: Remove unwanted greetings (cannot delete the active one) - **Default Tone**: Generate a classic telephone dial tone -#### 3. Recordings +#### 4. Recordings - **Play**: Listen to recordings directly in the browser - **Download**: Save recordings to your computer - **Delete**: Remove unwanted recordings @@ -131,6 +161,7 @@ wedding-phone/ ├── rotary_phone_web.py # Main application ├── test_complete.py # Audio testing script ├── configure_hifiberry.sh # HiFiBerry setup script +├── pyproject.toml # UV/pip package configuration ├── AUDIO_FIX.md # Audio configuration guide ├── README.md # This file ├── .gitignore # Git ignore rules @@ -279,6 +310,8 @@ The system provides REST API endpoints: - `GET /api/status` - Phone status JSON - `GET /api/recordings` - List all recordings - `GET /api/greetings` - List all greeting messages +- `GET /api/volume` - Get current volume setting +- `POST /api/volume` - Set volume level (0-100) - `POST /upload_greeting` - Upload new greeting - `POST /set_active_greeting` - Set active greeting - `POST /delete_greeting/` - Delete greeting diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..feb8be3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[project] +name = "wedding-phone" +version = "1.0.0" +description = "Vintage rotary phone audio system for weddings and events" +readme = "README.md" +requires-python = ">=3.7" +dependencies = [ + "flask>=2.3.0", + "numpy>=1.21.0", + "pyaudio>=0.2.13", + "RPi.GPIO>=0.7.1", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.uv] +dev-dependencies = [] diff --git a/rotary_phone_web.py b/rotary_phone_web.py index 1cee28e..cd52202 100644 --- a/rotary_phone_web.py +++ b/rotary_phone_web.py @@ -67,13 +67,18 @@ class RotaryPhone: """Load configuration from JSON file""" default_config = { "active_greeting": "dialtone.wav", - "greetings": [] + "greetings": [], + "volume": 70 # Default volume percentage (0-100) } if os.path.exists(CONFIG_FILE): try: with open(CONFIG_FILE, 'r') as f: - return json.load(f) + config = json.load(f) + # Ensure volume key exists + if "volume" not in config: + config["volume"] = 70 + return config except: pass @@ -93,6 +98,17 @@ class RotaryPhone: """Set which greeting message to play""" self.config["active_greeting"] = filename self.save_config() + + def set_volume(self, volume): + """Set playback volume (0-100)""" + volume = max(0, min(100, int(volume))) # Clamp between 0-100 + self.config["volume"] = volume + self.save_config() + return volume + + def get_volume(self): + """Get current volume setting""" + return self.config.get("volume", 70) def generate_default_dialtone(self): """Generate a classic dial tone (350Hz + 440Hz) and save as default""" @@ -119,16 +135,16 @@ class RotaryPhone: print(f"Default dial tone saved to {DIALTONE_FILE}") def play_sound_file(self, filepath): - """Play a WAV file""" + """Play a WAV file with volume control""" if not os.path.exists(filepath): print(f"Sound file not found: {filepath}") return False - + print(f"Playing sound: {filepath}") - + try: wf = wave.open(filepath, 'rb') - + # Use device index 1 for HiFiBerry (change if needed) stream = self.audio.open( format=self.audio.get_format_from_width(wf.getsampwidth()), @@ -138,19 +154,28 @@ class RotaryPhone: output_device_index=1, # HiFiBerry device index frames_per_buffer=CHUNK ) - - # Play the sound + + # Get volume multiplier (0.0 to 1.0) + volume = self.get_volume() / 100.0 + + # Play the sound with volume control data = wf.readframes(CHUNK) while data and self.phone_status == "off_hook": + # Apply volume by converting to numpy array and scaling + if volume < 1.0: + audio_data = np.frombuffer(data, dtype=np.int16) + audio_data = (audio_data * volume).astype(np.int16) + data = audio_data.tobytes() + stream.write(data) data = wf.readframes(CHUNK) - + stream.stop_stream() stream.close() wf.close() print("Sound playback finished") return True - + except Exception as e: print(f"Error playing sound: {e}") return False @@ -269,12 +294,14 @@ def index(): greetings = get_greetings() status = phone.get_status() active_greeting = phone.config.get("active_greeting", "dialtone.wav") + volume = phone.get_volume() return render_template('index.html', recordings=recordings, greetings=greetings, active_greeting=active_greeting, - status=status) + status=status, + volume=volume) @app.route('/api/status') def api_status(): @@ -291,6 +318,19 @@ def api_greetings(): """API endpoint for greetings list""" return jsonify(get_greetings()) +@app.route('/api/volume', methods=['GET']) +def api_get_volume(): + """Get current volume setting""" + return jsonify({"volume": phone.get_volume()}) + +@app.route('/api/volume', methods=['POST']) +def api_set_volume(): + """Set volume level""" + data = request.get_json() + volume = data.get('volume', 70) + new_volume = phone.set_volume(volume) + return jsonify({"success": True, "volume": new_volume}) + @app.route('/upload_greeting', methods=['POST']) def upload_greeting(): """Upload a new greeting message""" @@ -835,6 +875,46 @@ if __name__ == "__main__": margin-bottom: 10px; word-break: break-all; } + + /* Volume slider */ + input[type="range"] { + -webkit-appearance: none; + appearance: none; + cursor: pointer; + } + + input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + border-radius: 50%; + background: #667eea; + cursor: pointer; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + transition: all 0.2s; + } + + input[type="range"]::-webkit-slider-thumb:hover { + background: #5568d3; + transform: scale(1.2); + } + + input[type="range"]::-moz-range-thumb { + width: 20px; + height: 20px; + border-radius: 50%; + background: #667eea; + cursor: pointer; + border: none; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + transition: all 0.2s; + } + + input[type="range"]::-moz-range-thumb:hover { + background: #5568d3; + transform: scale(1.2); + } @@ -870,7 +950,24 @@ if __name__ == "__main__": - + + +
+

🔊 Volume Control

+
+
+ 🔇 + + 🔊 + {{ volume }}% +
+

+ Adjust the playback volume for greeting messages +

+
+
+

🎵 Greeting Messages

@@ -1017,7 +1114,43 @@ if __name__ == "__main__":