Add download all recordings as ZIP feature
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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/<filename>` - Delete greeting
|
||||
- `GET /play_audio/<type>/<filename>` - Stream audio file
|
||||
- `GET /download/<filename>` - Download recording
|
||||
- `GET /download_all` - Download all recordings as ZIP
|
||||
- `POST /delete/<filename>` - Delete recording
|
||||
- `POST /restore_default_sound` - Generate default dial tone
|
||||
- `GET /api/backup/status` - Get USB backup drive status
|
||||
|
||||
@@ -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/<filename>', methods=['POST'])
|
||||
def delete_recording(filename):
|
||||
"""Delete a recording"""
|
||||
@@ -1499,8 +1537,15 @@ def main():
|
||||
|
||||
<!-- Recordings Card -->
|
||||
<div class="card">
|
||||
<h2>🎙️ Recordings</h2>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<h2 style="margin: 0;">🎙️ Recordings</h2>
|
||||
{% if recordings %}
|
||||
<button class="btn btn-primary" onclick="downloadAllRecordings()" style="font-size: 0.9em;">
|
||||
📦 Download All as ZIP
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if recordings %}
|
||||
<div class="stats">
|
||||
<div class="stat-box">
|
||||
@@ -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' })
|
||||
|
||||
Reference in New Issue
Block a user