Skip to content

Commit 1ff28cb

Browse files
committed
UPDATE: code has been refactore for UUID for diffrent audio upload sources
1 parent 6534288 commit 1ff28cb

6 files changed

Lines changed: 171 additions & 19 deletions

File tree

‎backends/advanced/src/advanced_omi_backend/controllers/audio_controller.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ async def upload_and_process_audio_files(
4040
device_name: str = "upload",
4141
auto_generate_client: bool = True,
4242
folder: str = None,
43+
source: str = "upload"
4344
) -> dict:
4445
"""
4546
Upload audio files and process them directly.
@@ -81,10 +82,12 @@ async def upload_and_process_audio_files(
8182
# Read file content
8283
content = await file.read()
8384

84-
gdrive_file_id = getattr(file, "gdrive_file_id", None)
8585

8686
# Generate audio UUID and timestamp
87-
audio_uuid = str(uuid.uuid4())
87+
if source == "gdrive":
88+
audio_uuid = getattr(file, "audio_uuid", None)
89+
else:
90+
audio_uuid = str(uuid.uuid4())
8891
timestamp = int(time.time() * 1000)
8992

9093
# Determine output directory (with optional subfolder)
@@ -100,13 +103,13 @@ async def upload_and_process_audio_files(
100103
relative_audio_path, file_path, duration = await write_audio_file(
101104
raw_audio_data=content,
102105
audio_uuid=audio_uuid,
106+
source=source,
103107
client_id=client_id,
104108
user_id=user.user_id,
105109
user_email=user.email,
106110
timestamp=timestamp,
107111
chunk_dir=chunk_dir,
108112
validate=True, # Validate WAV format, convert stereo→mono
109-
gdrive_file_id=gdrive_file_id
110113
)
111114
except AudioValidationError as e:
112115
processed_files.append({

‎backends/advanced/src/advanced_omi_backend/models/audio_file.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ class AudioFile(Document):
2929

3030
# Core identifiers
3131
audio_uuid: Indexed(str, unique=True) = Field(description="Unique audio identifier")
32+
source: Indexed(str) = Field(
33+
default="upload",
34+
description="Source of the audio (upload, gdrive, etc.)"
35+
)
3236
audio_path: str = Field(description="Path to raw audio file")
3337
client_id: Indexed(str) = Field(description="Client device identifier")
3438
timestamp: Indexed(int) = Field(description="Unix timestamp in milliseconds")
@@ -51,7 +55,6 @@ class AudioFile(Document):
5155
description="Speech detection results"
5256
)
5357

54-
gdrive_file_id: Optional[str] = Field(default=None, description="Google Drive file id")
5558

5659

5760
class Settings:
@@ -61,5 +64,4 @@ class Settings:
6164
"client_id",
6265
"user_id",
6366
"timestamp",
64-
"gdrive_file_id"
6567
]

‎backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async def upload_audio_from_drive_folder(
3030
raise HTTPException(status_code=400, detail=str(e))
3131

3232
return await audio_controller.upload_and_process_audio_files(
33-
current_user, files, device_name, auto_generate_client
33+
current_user, files, device_name, auto_generate_client, source="gdrive"
3434
)
3535

3636

‎backends/advanced/src/advanced_omi_backend/utils/audio_utils.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,13 @@ async def validate_and_prepare_audio(
102102
async def write_audio_file(
103103
raw_audio_data: bytes,
104104
audio_uuid: str,
105+
source: str,
105106
client_id: str,
106107
user_id: str,
107108
user_email: str,
108109
timestamp: int,
109110
chunk_dir: Optional[Path] = None,
110111
validate: bool = True,
111-
gdrive_file_id: Optional[str] = None,
112112
) -> tuple[str, str, float]:
113113
"""
114114
Validate, write audio data to WAV file, and create AudioSession database entry.
@@ -198,14 +198,14 @@ async def write_audio_file(
198198
# Create AudioFile database entry using Beanie model
199199
audio_file = AudioFile(
200200
audio_uuid=audio_uuid,
201+
source=source,
201202
audio_path=wav_filename,
202203
client_id=client_id,
203204
timestamp=timestamp,
204205
user_id=user_id,
205206
user_email=user_email,
206207
has_speech=False, # Will be updated by transcription
207208
speech_analysis={},
208-
gdrive_file_id=gdrive_file_id
209209
)
210210
await audio_file.insert()
211211

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import io
2+
import tempfile
3+
from typing import List
4+
import logging
5+
from starlette.datastructures import UploadFile as StarletteUploadFile
6+
import requests
7+
from advanced_omi_backend.app_config import get_app_config
8+
9+
logger = logging.getLogger(__name__)
10+
audio_logger = logging.getLogger("audio_processing")
11+
12+
AUDIO_EXTENSIONS = (".wav", ".mp3", ".flac", ".ogg", ".m4a")
13+
14+
class AudioValidationError(Exception):
15+
pass
16+
17+
18+
async def download_and_wrap_dropbox_file(file_metadata: dict):
19+
access_token = get_app_config().dropbox_access_token
20+
if not access_token:
21+
raise AudioValidationError("Dropbox access token is missing.")
22+
23+
file_path = file_metadata["path_lower"]
24+
name = file_metadata["name"]
25+
26+
headers = {
27+
"Authorization": f"Bearer {access_token}",
28+
"Dropbox-API-Arg": f'{{"path": "{file_path}"}}'
29+
}
30+
31+
response = requests.post(
32+
"https://content.dropboxapi.com/2/files/download",
33+
headers=headers
34+
)
35+
36+
if response.status_code != 200:
37+
raise AudioValidationError(
38+
f"Failed to download Dropbox file {name}: {response.text}"
39+
)
40+
41+
content = response.content
42+
if not content:
43+
raise AudioValidationError(f"Downloaded Dropbox file '{name}' was empty")
44+
45+
tmp_file = tempfile.SpooledTemporaryFile(max_size=10 * 1024 * 1024)
46+
tmp_file.write(content)
47+
tmp_file.seek(0)
48+
49+
upload_file = StarletteUploadFile(filename=name, file=tmp_file)
50+
51+
original_close = upload_file.close
52+
53+
def wrapped_close():
54+
try:
55+
original_close()
56+
finally:
57+
pass
58+
59+
upload_file.close = wrapped_close
60+
return upload_file
61+
62+
63+
async def download_audio_files_from_dropbox(folder_path: str) -> List[StarletteUploadFile]:
64+
if not folder_path:
65+
raise AudioValidationError("Dropbox folder path is required.")
66+
67+
access_token = get_app_config().dropbox_access_token
68+
if not access_token:
69+
raise AudioValidationError("Dropbox access token is missing.")
70+
71+
try:
72+
# -----------------------------------------------
73+
# Step 1: List files in folder
74+
# -----------------------------------------------
75+
list_headers = {
76+
"Authorization": f"Bearer {access_token}",
77+
"Content-Type": "application/json"
78+
}
79+
80+
list_body = {
81+
"path": folder_path,
82+
"recursive": False
83+
}
84+
85+
list_res = requests.post(
86+
"https://api.dropboxapi.com/2/files/list_folder",
87+
headers=list_headers,
88+
json=list_body
89+
)
90+
91+
if list_res.status_code != 200:
92+
raise AudioValidationError(f"Dropbox API list_folder error: {list_res.text}")
93+
94+
items = list_res.json().get("entries", [])
95+
96+
# Filter audio files
97+
audio_files_metadata = [
98+
f for f in items
99+
if f[".tag"] == "file" and f["name"].lower().endswith(AUDIO_EXTENSIONS)
100+
]
101+
102+
if not audio_files_metadata:
103+
raise AudioValidationError("No audio files found in folder.")
104+
105+
wrapped_files = []
106+
skipped_count = 0
107+
108+
# -----------------------------------------------
109+
# Step 2: Download each audio file
110+
# -----------------------------------------------
111+
for item in audio_files_metadata:
112+
dropbox_id = item["id"] # Dropbox file ID
113+
114+
# Check if already processed
115+
if await is_dropbox_file_already_processed(dropbox_id):
116+
audio_logger.info(f"Skipping already processed file: {item['name']}")
117+
skipped_count += 1
118+
continue
119+
120+
wrapped_file = await download_and_wrap_dropbox_file(access_token, item)
121+
122+
# Attach Dropbox file ID
123+
setattr(wrapped_file, "dropbox_file_id", dropbox_id)
124+
125+
wrapped_files.append(wrapped_file)
126+
127+
if not wrapped_files and skipped_count > 0:
128+
raise AudioValidationError(
129+
f"All {skipped_count} files in the folder have already been processed."
130+
)
131+
132+
return wrapped_files
133+
134+
except Exception as e:
135+
if isinstance(e, AudioValidationError):
136+
raise
137+
raise AudioValidationError(f"Dropbox API Error: {e}") from e
138+
139+
140+
async def is_dropbox_file_already_processed(dropbox_file_id: str) -> bool:
141+
if not dropbox_file_id:
142+
return False
143+
144+
from advanced_omi_backend.models.audio_file import AudioFile
145+
146+
existing_file = await AudioFile.find_one(
147+
AudioFile.dropbox_file_id == dropbox_file_id
148+
)
149+
150+
return existing_file is not None

‎backends/advanced/src/advanced_omi_backend/utils/gdrive_audio_utils.py‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,20 @@ async def download_audio_files_from_drive(folder_id: str) -> List[StarletteUploa
9090
file_id = item["id"] # Get the Google Drive File ID
9191

9292
# Check if the file is already processed
93-
if await is_drive_file_already_processed(file_id):
94-
audio_logger.info(f"Skipping already processed file: {item['name']}") # Use your logger
93+
existing = await AudioFile.find_one({
94+
"audio_uuid": file_id,
95+
"source": "gdrive"
96+
})
97+
98+
if existing:
99+
audio_logger.info(f"Skipping already processed file: {item['name']}")
95100
skipped_count += 1
96101
continue
97102

98103
# synchronous call now (but make the parent function async)
99104
wrapped_file = await download_and_wrap_drive_file(service, item)
100105
# Attach the file_id to the UploadFile object for later use
101-
wrapped_file.gdrive_file_id = file_id
106+
wrapped_file.audio_uuid = file_id
102107
wrapped_files.append(wrapped_file)
103108

104109
if not wrapped_files and skipped_count > 0:
@@ -112,11 +117,3 @@ async def download_audio_files_from_drive(folder_id: str) -> List[StarletteUploa
112117
raise AudioValidationError(f"Google Drive API Error: {e}") from e
113118

114119

115-
async def is_drive_file_already_processed(gdrive_file_id: str) -> bool:
116-
"""Check if an AudioFile document already exists for the given GDrive File ID."""
117-
if not gdrive_file_id:
118-
return False
119-
existing_file = await AudioFile.find_one(
120-
AudioFile.gdrive_file_id == gdrive_file_id
121-
)
122-
return existing_file is not None

0 commit comments

Comments
 (0)