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' })