Sulitha commited on
Commit
880916c
·
1 Parent(s): 4ea0a00

chore: simplify to MongoDB-only uploads; remove Hub/Drive and checkboxes; docs+deps updated

Browse files
Files changed (3) hide show
  1. README.md +7 -40
  2. app.py +15 -188
  3. requirements.txt +0 -5
README.md CHANGED
@@ -15,49 +15,16 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
15
 
16
  ## Persistence of Recordings
17
 
18
- Recordings created via the UI are written at runtime into the `recordings/` folder inside the Space container. These files are NOT automatically versioned or shown in the repository file browser. To make them visible in the repo you must either:
19
 
20
- 1. Commit them manually (e.g., pull the Space locally, copy files, `git add recordings/*.wav`, push).
21
- 2. Or enable automatic upload using a Hugging Face token.
22
 
23
- ### Automatic Upload (Recommended)
24
-
25
- Set a secret named `HF_TOKEN` in the Space settings (must have write access). Optionally set:
26
-
27
- - `HF_UPLOAD_REPO` target repo id (recommended: a dataset like `username/spell-recordings`).
28
- - `HF_UPLOAD_REPO_TYPE` one of `dataset` (default), `space`, or `model`.
29
-
30
- If `HF_UPLOAD_REPO` is omitted the current Space id is used (uploading into the Space repo when `HF_UPLOAD_REPO_TYPE=space`).
31
-
32
- Then check the "Upload to Hub" box before submitting. Each saved `.wav` file will be committed via the Hub API with a message like `Add recordings <timestamp>`.
33
-
34
- Uploads may take a few seconds. Large batches could hit rate limits; keep per-submit sizes modest.
35
-
36
- ### Why You Don't See Runtime Files
37
-
38
- The repository view shows only Git-tracked content. Runtime-generated files live only in the ephemeral container filesystem until the Space restarts. Upload or commit them if you need persistence.
39
-
40
- ## Google Drive Upload (Alternative)
41
-
42
- If you prefer uploading to Google Drive:
43
-
44
- 1. Create a Google Cloud service account with Drive API enabled and grant it access to a Drive folder.
45
- 2. Put the service account JSON in a Space secret named `GDRIVE_SERVICE_ACCOUNT_JSON`. You can paste the JSON string or mount a path and store the path in the secret.
46
- 3. Add another secret `GDRIVE_FOLDER_ID` with the target folder ID.
47
- 4. In the app UI, tick "Upload to Google Drive" before Submit.
48
-
49
- The app uses `google-api-python-client` to upload each WAV file into that folder. Errors will be shown in the results area if credentials or permissions are incorrect.
50
-
51
- ## MongoDB Upload (Alternative)
52
-
53
- You can also upload recordings to MongoDB using GridFS.
54
-
55
- Secrets to configure in your Space:
56
  - `MONGO_URI`: your MongoDB connection string (supports `mongodb+srv://`)
57
- - `MONGO_DB`: database name (default: `spells`)
58
- - `GRIDFS_BUCKET`: GridFS bucket prefix (default: `fs`)
59
 
60
- Then in the UI, tick "Upload to MongoDB (GridFS)" before Submit.
61
 
62
- Each file is stored in GridFS with metadata: `spell`, `username`, `timestamp`, and original `filename`.
63
 
 
15
 
16
  ## Persistence of Recordings
17
 
18
+ Recordings created via the UI are written at runtime into the `recordings/` folder inside the Space container. In addition, this app uploads each saved WAV file to MongoDB using GridFS (if configured).
19
 
20
+ ### MongoDB configuration
 
21
 
22
+ Set the following Space secrets:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  - `MONGO_URI`: your MongoDB connection string (supports `mongodb+srv://`)
24
+ - `MONGO_DB` (optional): database name, default `spells`
25
+ - `GRIDFS_BUCKET` (optional): GridFS bucket prefix, default `fs`
26
 
27
+ On submit, each provided spell is saved locally and uploaded to your Mongo database with metadata: `spell`, `username`, `timestamp`, and original `filename`.
28
 
29
+ If Mongo is not configured, files are still saved locally under `recordings/`.
30
 
app.py CHANGED
@@ -4,34 +4,12 @@ import re
4
  import time
5
  import math
6
  from typing import List, Tuple, Optional, Sequence
7
-
8
  import numpy as np
9
  import gradio as gr
10
  import soundfile as sf
11
  from scipy.signal import resample_poly
12
- try:
13
- from huggingface_hub import HfApi, HfFolder
14
- except Exception: # package might be missing in some local runs
15
- HfApi = None
16
- HfFolder = None
17
-
18
- # Google Drive API (service account) optional imports
19
- try:
20
- from google.oauth2 import service_account
21
- from googleapiclient.discovery import build
22
- from googleapiclient.http import MediaFileUpload
23
- except Exception:
24
- service_account = None
25
- build = None
26
- MediaFileUpload = None
27
-
28
- # MongoDB (GridFS) optional imports
29
- try:
30
- from pymongo import MongoClient
31
- import gridfs
32
- except Exception:
33
- MongoClient = None
34
- gridfs = None
35
 
36
  # Output directory for saved recordings
37
  OUT_DIR = "recordings"
@@ -108,118 +86,8 @@ def save_one_from_path(filepath: Optional[str], spell: str, username: str) -> Op
108
  return out_path
109
 
110
 
111
- def upload_recordings(paths: Sequence[str]) -> Tuple[int, Optional[str]]:
112
- """Upload given file paths to the Hub repo indicated by env HF_UPLOAD_REPO or the current Space repo.
113
-
114
- Returns (uploaded_count, error_message). error_message is None on success.
115
- Requires HF_TOKEN secret configured with write permission.
116
- """
117
- if not paths:
118
- return 0, None
119
- if HfApi is None:
120
- return 0, "huggingface_hub not installed."
121
- token = os.getenv("HF_TOKEN") or (HfFolder.get_token() if HfFolder else None)
122
- if not token:
123
- return 0, "No HF_TOKEN available (set as Space secret to enable uploads)."
124
-
125
- repo_id = os.getenv("HF_UPLOAD_REPO")
126
- # Best-effort infer the current Space repo id from environment if not provided
127
- if not repo_id:
128
- # In Spaces, SPACE_ID is like "username/space_name" for the current space.
129
- # Use that as default so users can upload back to their Space if they want.
130
- repo_id = os.getenv("SPACE_ID") or os.getenv("REPO_ID")
131
- if not repo_id:
132
- return 0, "Unable to infer target repo id (set HF_UPLOAD_REPO)."
133
-
134
- api = HfApi(token=token)
135
- uploaded = 0
136
- commit_msg = f"Add recordings {int(time.time())}"
137
- # Determine repo_type. If user provided HF_UPLOAD_REPO, default to dataset.
138
- # If we inferred the current Space id, default to space so it "just works".
139
- repo_type_env = os.getenv("HF_UPLOAD_REPO_TYPE")
140
- if repo_type_env:
141
- repo_type = repo_type_env.lower()
142
- else:
143
- if os.getenv("HF_UPLOAD_REPO"):
144
- repo_type = "dataset"
145
- elif os.getenv("SPACE_ID") or os.getenv("REPO_ID"):
146
- repo_type = "space"
147
- else:
148
- repo_type = "dataset"
149
- if repo_type not in {"dataset", "space", "model"}:
150
- repo_type = "dataset"
151
- try:
152
- for p in paths:
153
- if not os.path.isfile(p):
154
- continue
155
- api.upload_file(
156
- path_or_fileobj=p,
157
- path_in_repo=f"recordings/{os.path.basename(p)}",
158
- repo_id=repo_id,
159
- repo_type=repo_type,
160
- commit_message=commit_msg,
161
- )
162
- uploaded += 1
163
- except Exception as e: # broad catch to surface error in UI
164
- return uploaded, f"Upload error: {e}"
165
- return uploaded, None
166
-
167
-
168
- def upload_recordings_to_gdrive(paths: Sequence[str]) -> Tuple[int, Optional[str]]:
169
- """Upload files to Google Drive into a folder using a service account.
170
-
171
- Requires secrets:
172
- - GDRIVE_SERVICE_ACCOUNT_JSON: full JSON credentials for a service account
173
- - GDRIVE_FOLDER_ID: target Drive folder ID
174
- Returns (uploaded_count, error_message).
175
- """
176
- if not paths:
177
- return 0, None
178
- if not (service_account and build and MediaFileUpload):
179
- return 0, "Google API client not installed."
180
- svc_json = os.getenv("GDRIVE_SERVICE_ACCOUNT_JSON")
181
- folder_id = os.getenv("GDRIVE_FOLDER_ID")
182
- if not svc_json:
183
- return 0, "Missing GDRIVE_SERVICE_ACCOUNT_JSON secret."
184
- if not folder_id:
185
- return 0, "Missing GDRIVE_FOLDER_ID secret."
186
-
187
- try:
188
- creds = None
189
- if svc_json.strip().startswith("{"):
190
- data = json.loads(svc_json)
191
- creds = service_account.Credentials.from_service_account_info(
192
- data,
193
- scopes=["https://www.googleapis.com/auth/drive.file"],
194
- )
195
- else:
196
- # if not JSON string, maybe it's a file path provided via secret
197
- creds = service_account.Credentials.from_service_account_file(
198
- svc_json,
199
- scopes=["https://www.googleapis.com/auth/drive.file"],
200
- )
201
- drive = build("drive", "v3", credentials=creds)
202
- except Exception as e:
203
- return 0, f"Auth error: {e}"
204
-
205
- uploaded = 0
206
- try:
207
- for p in paths:
208
- if not os.path.isfile(p):
209
- continue
210
- media = MediaFileUpload(p, mimetype="audio/wav", resumable=False)
211
- body = {"name": os.path.basename(p), "parents": [folder_id]}
212
- drive.files().create(body=body, media_body=media, fields="id").execute()
213
- uploaded += 1
214
- except Exception as e:
215
- return uploaded, f"Drive upload error: {e}"
216
- return uploaded, None
217
-
218
-
219
  def _parse_meta_from_filename(basename: str) -> Tuple[str, str, Optional[int]]:
220
- """Parse (spell_slug, username, timestamp) from `<spell_slug>_<username>_<ts>.wav`.
221
- Username and spell slug can contain underscores; timestamp is the last token.
222
- """
223
  name = basename
224
  if name.endswith(".wav"):
225
  name = name[:-4]
@@ -301,9 +169,6 @@ def submit_recordings(
301
  wingardium_path: Optional[str],
302
  accio_path: Optional[str],
303
  reparo_path: Optional[str],
304
- upload_flag: bool,
305
- gdrive_flag: bool,
306
- mongo_flag: bool,
307
  ) -> str:
308
  user = sanitize_username(username)
309
 
@@ -317,9 +182,8 @@ def submit_recordings(
317
  ]
318
 
319
  saved = []
320
- skipped = []
321
-
322
  saved_paths: List[str] = []
 
323
  for spell, path in pairs:
324
  out = save_one_from_path(path, spell, user)
325
  if out:
@@ -328,7 +192,7 @@ def submit_recordings(
328
  else:
329
  skipped.append(spell)
330
 
331
- lines = []
332
  if saved:
333
  lines.append("Saved recordings (local runtime):")
334
  lines += [f"- {s}" for s in saved]
@@ -339,45 +203,20 @@ def submit_recordings(
339
  if not lines:
340
  return "No audio captured. Please record at least one spell."
341
 
342
- if upload_flag:
343
- uploaded, err = upload_recordings(saved_paths)
344
- lines.append("")
345
- if err:
346
- lines.append(f"Hub upload attempted: {uploaded} succeeded, error: {err}")
347
- else:
348
- lines.append(f"Hub upload: {uploaded} file(s) committed to repo.")
349
- lines.append("(It may take a few seconds to appear in the file browser.)")
350
-
351
- if gdrive_flag:
352
- gup, gerr = upload_recordings_to_gdrive(saved_paths)
353
- lines.append("")
354
- if gerr:
355
- lines.append(f"Drive upload attempted: {gup} succeeded, error: {gerr}")
356
- else:
357
- lines.append(f"Drive upload: {gup} file(s) uploaded to folder.")
358
-
359
- if mongo_flag:
360
- mup, merr = upload_recordings_to_mongo(saved_paths)
361
- lines.append("")
362
- if merr:
363
- lines.append(f"Mongo upload attempted: {mup} succeeded, error: {merr}")
364
- else:
365
- lines.append(f"Mongo upload: {mup} file(s) stored in GridFS.")
366
 
367
  return "\n".join(lines)
368
-
369
-
370
  def build_ui() -> gr.Blocks:
371
  with gr.Blocks(title="Spell Recorder") as demo:
372
- gr.Markdown("""
373
- # Spell Recorder
374
- Record any of the listed spells and press Submit. You can use your microphone directly (preferred) or upload a file.
375
-
376
- Spells to collect: Lumos, Nox, Alohomora, Wingardium Leviosa, Accio, Reparo.
377
- """)
378
 
379
  with gr.Row():
380
- username = gr.Textbox(label="Your Name (for filename)", placeholder="e.g., harry_p" , autofocus=True)
381
 
382
  with gr.Row():
383
  with gr.Column():
@@ -389,28 +228,16 @@ def build_ui() -> gr.Blocks:
389
  accio = gr.Audio(label="Accio", sources=["microphone", "upload"], type="filepath")
390
  reparo = gr.Audio(label="Reparo", sources=["microphone", "upload"], type="filepath")
391
 
392
- with gr.Row():
393
- upload_checkbox = gr.Checkbox(label="Upload to Hub (requires HF_TOKEN)", value=False)
394
- gdrive_checkbox = gr.Checkbox(label="Upload to Google Drive (service account)", value=False)
395
- mongo_checkbox = gr.Checkbox(label="Upload to MongoDB (GridFS)", value=False)
396
  submit = gr.Button("Submit")
397
  result = gr.Markdown()
398
 
399
  submit.click(
400
  fn=submit_recordings,
401
- inputs=[username, lumos, nox, alohomora, wingardium, accio, reparo, upload_checkbox, gdrive_checkbox, mongo_checkbox],
402
  outputs=[result],
403
  )
404
 
405
- gr.Markdown("""
406
- Notes:
407
- - Files are saved locally in `recordings/` with `<spell>_<username>_<timestamp>.wav`.
408
- - Check "Upload to Hub" to commit them to the repo (needs HF_TOKEN secret).
409
- - Or check "Upload to Google Drive" to upload via a service account.
410
- - Or check "Upload to MongoDB (GridFS)" to store in your database.
411
- - 16 kHz mono WAV ensures consistent model training.
412
- - You can submit partial sets; only provided spells are saved.
413
- """)
414
 
415
  return demo
416
 
 
4
  import time
5
  import math
6
  from typing import List, Tuple, Optional, Sequence
 
7
  import numpy as np
8
  import gradio as gr
9
  import soundfile as sf
10
  from scipy.signal import resample_poly
11
+ from pymongo import MongoClient
12
+ import gridfs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  # Output directory for saved recordings
15
  OUT_DIR = "recordings"
 
86
  return out_path
87
 
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  def _parse_meta_from_filename(basename: str) -> Tuple[str, str, Optional[int]]:
90
+ """Parse (spell_slug, username, timestamp) from `<spell_slug>_<username>_<ts>.wav`."""
 
 
91
  name = basename
92
  if name.endswith(".wav"):
93
  name = name[:-4]
 
169
  wingardium_path: Optional[str],
170
  accio_path: Optional[str],
171
  reparo_path: Optional[str],
 
 
 
172
  ) -> str:
173
  user = sanitize_username(username)
174
 
 
182
  ]
183
 
184
  saved = []
 
 
185
  saved_paths: List[str] = []
186
+ skipped = []
187
  for spell, path in pairs:
188
  out = save_one_from_path(path, spell, user)
189
  if out:
 
192
  else:
193
  skipped.append(spell)
194
 
195
+ lines: List[str] = []
196
  if saved:
197
  lines.append("Saved recordings (local runtime):")
198
  lines += [f"- {s}" for s in saved]
 
203
  if not lines:
204
  return "No audio captured. Please record at least one spell."
205
 
206
+ mup, merr = upload_recordings_to_mongo(saved_paths)
207
+ lines.append("")
208
+ if merr:
209
+ lines.append(f"Mongo upload attempted: {mup} succeeded, error: {merr}")
210
+ else:
211
+ lines.append(f"Mongo upload: {mup} file(s) stored in GridFS.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
  return "\n".join(lines)
 
 
214
  def build_ui() -> gr.Blocks:
215
  with gr.Blocks(title="Spell Recorder") as demo:
216
+ gr.Markdown("""# Spell Recorder\nRecord any of the listed spells and press Submit. You can use your microphone directly (preferred) or upload a file.\n\nSpells to collect: Lumos, Nox, Alohomora, Wingardium Leviosa, Accio, Reparo.""")
 
 
 
 
 
217
 
218
  with gr.Row():
219
+ username = gr.Textbox(label="Your Name (for filename)", placeholder="e.g., harry_p", autofocus=True)
220
 
221
  with gr.Row():
222
  with gr.Column():
 
228
  accio = gr.Audio(label="Accio", sources=["microphone", "upload"], type="filepath")
229
  reparo = gr.Audio(label="Reparo", sources=["microphone", "upload"], type="filepath")
230
 
 
 
 
 
231
  submit = gr.Button("Submit")
232
  result = gr.Markdown()
233
 
234
  submit.click(
235
  fn=submit_recordings,
236
+ inputs=[username, lumos, nox, alohomora, wingardium, accio, reparo],
237
  outputs=[result],
238
  )
239
 
240
+ gr.Markdown("""Notes:\n- Files are saved locally in `recordings/` with `<spell>_<username>_<timestamp>.wav`.\n- Files are also uploaded to MongoDB (GridFS) automatically if MONGO_URI is configured.\n- 16 kHz mono WAV ensures consistent model training.\n- You can submit partial sets; only provided spells are saved.""")
 
 
 
 
 
 
 
 
241
 
242
  return demo
243
 
requirements.txt CHANGED
@@ -2,9 +2,4 @@ gradio
2
  numpy
3
  soundfile
4
  scipy
5
- huggingface_hub
6
- google-api-python-client
7
- google-auth
8
- google-auth-httplib2
9
- google-auth-oauthlib
10
  pymongo
 
2
  numpy
3
  soundfile
4
  scipy
 
 
 
 
 
5
  pymongo