test / app.py
XUUUUSID's picture
Upload 2 files
bf9f830 verified
"""
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__)
# Persistent storage mounted at /data (HF Spaces Storage Bucket).
# Falls back to /tmp if /data is not writable.
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")
# ── Database ────────────────────────────────────────────────────
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
# Create dirs at runtime (the /data mount isn't available at build time)
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()
# ── Routes ──────────────────────────────────────────────────────
@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")
# Build Excel
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)
# Build ZIP: excel + audio files
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):
# Store as audio/<original_name>.webm inside the zip
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)