From c68c8d28855bbaaded951328f8bd707709529043 Mon Sep 17 00:00:00 2001 From: grabowski Date: Mon, 27 Oct 2025 12:24:17 +0700 Subject: [PATCH] Add download all recordings as ZIP feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /download_all endpoint to create in-memory ZIP archive - Include zipfile and io imports for ZIP creation - Add "Download All as ZIP" button in web interface (only shows when recordings exist) - ZIP filename includes timestamp: wedding_recordings_YYYYMMDD_HHMMSS.zip - Only includes .wav files from recordings directory - Update API documentation in README.md - Document feature in CLAUDE.md Allows users to quickly backup all guest recordings in a single download. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 8 +++++++ README.md | 4 +++- rotary_phone_web.py | 55 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5dd1185..df30c02 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -178,6 +178,14 @@ Edit the `RotaryPhone` class methods: - Backup called after recording saves and greeting uploads - Test with `/api/backup/test` endpoint +### Download All Recordings +The `/download_all` endpoint creates a ZIP archive of all recordings in memory: +- Uses `zipfile` and `io.BytesIO()` for in-memory ZIP creation +- Filename includes timestamp: `wedding_recordings_YYYYMMDD_HHMMSS.zip` +- Only includes `.wav` files from recordings directory +- Returns 404 if no recordings exist +- Web UI shows "Download All as ZIP" button when recordings are present + ## Testing ### Audio Testing diff --git a/README.md b/README.md index 5cd8e0c..5e78b40 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,8 @@ The web interface provides four main sections: #### 4. Recordings - **Play**: Listen to recordings directly in the browser -- **Download**: Save recordings to your computer +- **Download**: Save individual recordings to your computer +- **Download All**: Download all recordings as a single ZIP file with timestamp - **Delete**: Remove unwanted recordings - **Statistics**: View total recordings, storage used, and total duration @@ -619,6 +620,7 @@ The system provides REST API endpoints: - `POST /delete_greeting/` - Delete greeting - `GET /play_audio//` - Stream audio file - `GET /download/` - Download recording +- `GET /download_all` - Download all recordings as ZIP - `POST /delete/` - Delete recording - `POST /restore_default_sound` - Generate default dial tone - `GET /api/backup/status` - Get USB backup drive status diff --git a/rotary_phone_web.py b/rotary_phone_web.py index 9db87c3..634cb2b 100644 --- a/rotary_phone_web.py +++ b/rotary_phone_web.py @@ -19,6 +19,8 @@ import json import sys import zlib import shutil +import zipfile +import io # Load configuration def load_system_config(): @@ -798,6 +800,42 @@ def download_recording(filename): return send_file(filepath, as_attachment=True) return "File not found", 404 +@app.route('/download_all') +def download_all_recordings(): + """Download all recordings as a ZIP file""" + if not os.path.exists(OUTPUT_DIR): + return "No recordings found", 404 + + # Get all recording files + recordings = [f for f in os.listdir(OUTPUT_DIR) + if f.endswith('.wav') and os.path.isfile(os.path.join(OUTPUT_DIR, f))] + + if not recordings: + return "No recordings found", 404 + + # Create ZIP file in memory + memory_file = io.BytesIO() + + with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf: + for filename in recordings: + filepath = os.path.join(OUTPUT_DIR, filename) + # Add file to ZIP with just the filename (no path) + zf.write(filepath, arcname=filename) + + # Seek to beginning of file + memory_file.seek(0) + + # Generate filename with timestamp + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + zip_filename = f"wedding_recordings_{timestamp}.zip" + + return send_file( + memory_file, + mimetype='application/zip', + as_attachment=True, + download_name=zip_filename + ) + @app.route('/delete/', methods=['POST']) def delete_recording(filename): """Delete a recording""" @@ -1499,8 +1537,15 @@ def main():
-

🎙️ Recordings

- +
+

🎙️ Recordings

+ {% if recordings %} + + {% endif %} +
+ {% if recordings %}
@@ -1715,7 +1760,11 @@ def main(): function downloadRecording(filename) { window.location.href = '/download/' + filename; } - + + function downloadAllRecordings() { + window.location.href = '/download_all'; + } + function deleteRecording(filename, index) { if (confirm('Delete this recording?')) { fetch('/delete/' + filename, { method: 'POST' })