File size: 2,950 Bytes
c25f760
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
"""v1 contribution loop — predict -> confirm/correct -> writeback (PRD §18.3, §13).

Anonymous by default (D12): no login, dedup via audio_sha256, optional handle for
attribution only. Rights gate (§12): nothing is saved unless the contributor
affirms it's their own performance / they have the right to share it.

Writes {id}.flac + {id}.json into incoming/ of the dataset repo; the consolidate
cron promotes verified clips into shards (pipeline/consolidate.py).
"""
from __future__ import annotations

import datetime as dt
import hashlib
import io
import json
import os
import uuid

# Neutral org (D19). The Space holds an HF write token scoped to this dataset repo.
DATASET_REPO = "twelveswaras/twelveswaras-commons"
CONSENT_VERSION = "2026-07-01-v1"


def utcnow() -> str:
    return dt.datetime.utcnow().replace(microsecond=0).isoformat() + "Z"


def pseudonym(handle: str) -> str:
    # Optional, never PII (D12). Random per-clip id if no handle given.
    return "u_" + hashlib.sha256(handle.encode()).hexdigest()[:8]


def submit(audio, chosen_raaga, is_own, license_, handle, instrument, form, ctx) -> str:
    if not is_own:
        return "❌ Not saved — you must confirm this is your own performance / you may share it."

    import soundfile as sf
    from huggingface_hub import CommitOperationAdd, HfApi

    api = HfApi(token=os.environ["HF_WRITE_TOKEN"])  # Space secret, scoped to the dataset repo
    sr, wav = audio
    buf = io.BytesIO()
    sf.write(buf, wav, sr, format="FLAC")
    raw = buf.getvalue()
    rid = uuid.uuid4().hex[:8]
    meta = {
        "id": rid,
        "audio_file": f"{rid}.flac",
        "audio_sha256": hashlib.sha256(raw).hexdigest(),
        "raaga": chosen_raaga,
        "tradition": "carnatic",
        "contributor_id": pseudonym(handle or rid),
        "is_own_performance": True,
        "license": license_,
        "consent_version": CONSENT_VERSION,
        "created_at": utcnow(),
        "label_source": "model_confirmed" if chosen_raaga == ctx.get("pred") else "contributor_declared",
        "model_prediction": ctx.get("pred"),
        "model_confidence": float(ctx.get("conf", 0.0)),
        "verification_status": "unverified",
        "split": "pending",
        "votes_agree": 0,
        "votes_disagree": 0,
        "instrument": instrument,
        "form": form,
    }
    api.create_commit(
        repo_id=DATASET_REPO,
        repo_type="dataset",
        commit_message=f"contrib {rid} ({chosen_raaga})",
        operations=[
            CommitOperationAdd(f"incoming/{rid}.flac", raw),
            CommitOperationAdd(f"incoming/{rid}.json", json.dumps(meta, ensure_ascii=False).encode()),
        ],
    )
    return f"✅ Saved as {chosen_raaga} — thank you! It enters the verification queue."


# TODO(v1): assemble the Gradio Blocks UI (predict button + confirm/correct form).
# The identify half already lives in apps/identify.py; this file owns the writeback.