| """ |
| Audio Recording Studio — collaborative audio collection tool. |
| Flask + SQLite backend. Deploy on HF Spaces (Docker) or any server. |
| """ |
|
|
| import os |
| import sqlite3 |
| import uuid |
| import zipfile |
| from datetime import datetime |
|
|
| from flask import ( |
| Flask, render_template, request, jsonify, send_from_directory, send_file |
| ) |
|
|
| app = Flask(__name__) |
|
|
| |
| |
| def pick_data_dir(): |
| for d in [os.environ.get("DATA_DIR", "/data"), "/tmp/audio_studio"]: |
| try: |
| os.makedirs(os.path.join(d, "audio"), exist_ok=True) |
| test = os.path.join(d, ".write_test") |
| with open(test, "w") as f: |
| f.write("ok") |
| os.remove(test) |
| return d |
| except OSError: |
| continue |
| return "/tmp/audio_studio" |
|
|
| DATA_DIR = pick_data_dir() |
| DB_PATH = os.path.join(DATA_DIR, "recordings.db") |
| AUDIO_DIR = os.path.join(DATA_DIR, "audio") |
|
|
|
|
| |
|
|
| def get_db(): |
| conn = sqlite3.connect(DB_PATH) |
| conn.row_factory = sqlite3.Row |
| return conn |
|
|
|
|
| def init_db(): |
| |
| os.makedirs(AUDIO_DIR, exist_ok=True) |
| with get_db() as db: |
| db.execute(""" |
| CREATE TABLE IF NOT EXISTS recordings ( |
| id TEXT PRIMARY KEY, |
| user TEXT NOT NULL, |
| name TEXT NOT NULL, |
| filename TEXT NOT NULL, |
| duration REAL DEFAULT 0, |
| created_at TEXT NOT NULL, |
| notes TEXT DEFAULT '' |
| ) |
| """) |
|
|
|
|
| init_db() |
|
|
|
|
| |
|
|
| @app.route("/") |
| def index(): |
| return render_template("index.html") |
|
|
|
|
| @app.route("/api/recordings", methods=["GET"]) |
| def list_recordings(): |
| with get_db() as db: |
| rows = db.execute( |
| "SELECT * FROM recordings ORDER BY created_at DESC" |
| ).fetchall() |
| return jsonify([dict(r) for r in rows]) |
|
|
|
|
| @app.route("/api/recordings", methods=["POST"]) |
| def create_recording(): |
| audio = request.files.get("audio") |
| if not audio: |
| return jsonify({"error": "No audio file"}), 400 |
|
|
| user = request.form.get("user", "Anonymous").strip() or "Anonymous" |
| name = request.form.get("name", "").strip() |
| duration = float(request.form.get("duration", 0)) |
| notes = request.form.get("notes", "").strip() |
|
|
| rec_id = uuid.uuid4().hex[:12] |
| ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
|
| if not name: |
| name = f"Recording {ts}" |
|
|
| filename = f"{rec_id}.webm" |
| audio.save(os.path.join(AUDIO_DIR, filename)) |
|
|
| with get_db() as db: |
| db.execute( |
| "INSERT INTO recordings (id, user, name, filename, duration, created_at, notes) " |
| "VALUES (?, ?, ?, ?, ?, ?, ?)", |
| (rec_id, user, name, filename, duration, ts, notes), |
| ) |
|
|
| return jsonify({ |
| "id": rec_id, "user": user, "name": name, |
| "filename": filename, "duration": duration, |
| "created_at": ts, "notes": notes, |
| }), 201 |
|
|
|
|
| @app.route("/api/recordings/<rec_id>", methods=["PATCH"]) |
| def update_recording(rec_id): |
| data = request.get_json(force=True) |
| fields, values = [], [] |
| for col in ("name", "notes", "user"): |
| if col in data: |
| fields.append(f"{col} = ?") |
| values.append(data[col]) |
| if not fields: |
| return jsonify({"error": "Nothing to update"}), 400 |
|
|
| values.append(rec_id) |
| with get_db() as db: |
| db.execute(f"UPDATE recordings SET {', '.join(fields)} WHERE id = ?", values) |
| return jsonify({"ok": True}) |
|
|
|
|
| @app.route("/api/recordings/<rec_id>", methods=["DELETE"]) |
| def delete_recording(rec_id): |
| with get_db() as db: |
| row = db.execute("SELECT filename FROM recordings WHERE id = ?", (rec_id,)).fetchone() |
| if row: |
| path = os.path.join(AUDIO_DIR, row["filename"]) |
| if os.path.exists(path): |
| os.remove(path) |
| db.execute("DELETE FROM recordings WHERE id = ?", (rec_id,)) |
| return jsonify({"ok": True}) |
|
|
|
|
| @app.route("/audio/<filename>") |
| def serve_audio(filename): |
| return send_from_directory(AUDIO_DIR, filename) |
|
|
|
|
| @app.route("/api/export") |
| def export_excel(): |
| """Export Excel only (with playback URLs).""" |
| import pandas as pd |
|
|
| base_url = request.host_url.rstrip("/") |
| with get_db() as db: |
| rows = db.execute("SELECT * FROM recordings ORDER BY created_at DESC").fetchall() |
|
|
| data = [] |
| for r in rows: |
| data.append({ |
| "Name": r["name"], |
| "User": r["user"], |
| "Timestamp": r["created_at"], |
| "Duration (s)": round(r["duration"], 1), |
| "Notes": r["notes"], |
| "Audio File": r["filename"], |
| "Audio URL": f"{base_url}/audio/{r['filename']}", |
| }) |
|
|
| df = pd.DataFrame(data) if data else pd.DataFrame( |
| columns=["Name", "User", "Timestamp", "Duration (s)", "Notes", "Audio File", "Audio URL"] |
| ) |
| path = os.path.join(DATA_DIR, "export.xlsx") |
| df.to_excel(path, index=False) |
| return send_file(path, as_attachment=True, download_name="recordings.xlsx") |
|
|
|
|
| @app.route("/api/export-zip") |
| def export_zip(): |
| """Export a ZIP containing the Excel + all audio files.""" |
| import pandas as pd |
|
|
| base_url = request.host_url.rstrip("/") |
| with get_db() as db: |
| rows = db.execute("SELECT * FROM recordings ORDER BY created_at DESC").fetchall() |
|
|
| zip_path = os.path.join(DATA_DIR, "recordings_bundle.zip") |
| excel_path = os.path.join(DATA_DIR, "recordings.xlsx") |
|
|
| |
| data = [] |
| for r in rows: |
| data.append({ |
| "Name": r["name"], |
| "User": r["user"], |
| "Timestamp": r["created_at"], |
| "Duration (s)": round(r["duration"], 1), |
| "Notes": r["notes"], |
| "Audio File": r["filename"], |
| "Audio URL": f"{base_url}/audio/{r['filename']}", |
| }) |
|
|
| df = pd.DataFrame(data) if data else pd.DataFrame( |
| columns=["Name", "User", "Timestamp", "Duration (s)", "Notes", "Audio File", "Audio URL"] |
| ) |
| df.to_excel(excel_path, index=False) |
|
|
| |
| with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: |
| zf.write(excel_path, "recordings.xlsx") |
| for r in rows: |
| audio_path = os.path.join(AUDIO_DIR, r["filename"]) |
| if os.path.exists(audio_path): |
| |
| safe_name = r["name"].replace("/", "_").replace("\\", "_") |
| zf.write(audio_path, f"audio/{safe_name}.webm") |
|
|
| return send_file(zip_path, as_attachment=True, download_name="recordings_bundle.zip") |
|
|
|
|
| if __name__ == "__main__": |
| port = int(os.environ.get("PORT", 7860)) |
| app.run(host="0.0.0.0", port=port, debug=False) |
|
|