Skip to content

Commit 73683cb

Browse files
Merge branch 'ftr/media-player-v2' into test-deployment
2 parents 8d385ce + 89e3d77 commit 73683cb

File tree

4 files changed

+51
-239
lines changed

4 files changed

+51
-239
lines changed

Makefile

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Holmes Development Makefile
22
# Cross-language orchestration for the monorepo
33

4-
.PHONY: install hooks dev-db stop-db adminer generate-types lint format migrate migrate-auth dev-backend dev-frontend
4+
.PHONY: install hooks dev-db stop-db adminer generate-types lint format migrate migrate-auth dev-backend dev-frontend check compliance
55

66
# Install all dependencies
77
install:
@@ -50,6 +50,24 @@ format:
5050
bun run format:frontend || true
5151
cd backend && uv run ruff format .
5252

53+
# Run comprehensive compliance checks (lint, format check, type generation)
54+
check: compliance
55+
56+
compliance:
57+
@echo "Running compliance checks..."
58+
@echo "\n==> Checking backend formatting..."
59+
cd backend && uv run ruff format --check .
60+
@echo "\n==> Checking backend linting..."
61+
cd backend && uv run ruff check .
62+
@echo "\n==> Checking type generation..."
63+
@$(MAKE) generate-types
64+
@if [ -n "$$(git status --porcelain packages/types/)" ]; then \
65+
echo "\n❌ Type generation produced changes. Run 'make generate-types' and commit."; \
66+
git diff -- packages/types/; \
67+
exit 1; \
68+
fi
69+
@echo "\n✅ All compliance checks passed!"
70+
5371
# Run Better Auth migrations (creates auth tables)
5472
migrate-auth:
5573
cd frontend && bunx @better-auth/cli migrate

backend/app/api/redaction.py

Lines changed: 0 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -908,211 +908,3 @@ async def redact_audio_download(
908908
raise HTTPException(
909909
status_code=500, detail=f"Audio redaction failed: {str(e)}"
910910
) from e
911-
912-
913-
@router.post("/redact/audio", status_code=status.HTTP_200_OK)
914-
async def redact_audio_direct(
915-
file: UploadFile = File(..., description="Audio file to redact"),
916-
prompt: str = Form(..., description="Natural language redaction instructions"),
917-
):
918-
"""Redact/censor sensitive information from an audio file.
919-
920-
This endpoint:
921-
1. Accepts an audio file upload and redaction prompt
922-
2. Uses Gemini to transcribe and identify content to censor
923-
3. Replaces identified segments with beep sounds
924-
4. Returns the censored audio as base64 encoded data
925-
926-
Args:
927-
file: Audio file to redact (MP3, WAV, OGG, M4A, FLAC, AAC, WebM)
928-
prompt: Natural language description of what to censor
929-
930-
Returns:
931-
JSON with:
932-
- censored_audio: base64 encoded censored audio
933-
- segments_censored: number of segments censored
934-
- total_censored_duration: total duration censored in seconds
935-
- transcript: full transcript of the audio
936-
- reasoning: explanation of censorship decisions
937-
- targets: list of censored segments with timestamps
938-
"""
939-
# Validate file type
940-
if not file.filename:
941-
raise HTTPException(status_code=400, detail="Filename is required")
942-
943-
# Check file extension
944-
file_ext = file.filename.lower().split(".")[-1]
945-
if file_ext not in {"mp3", "wav", "ogg", "m4a", "flac", "aac", "webm"}:
946-
raise HTTPException(
947-
status_code=400,
948-
detail=f"Unsupported file type: .{file_ext}. Supported: mp3, wav, ogg, m4a, flac, aac, webm",
949-
)
950-
951-
# Validate content type if provided
952-
if file.content_type and file.content_type not in SUPPORTED_AUDIO_TYPES:
953-
logger.warning(f"Unexpected content type: {file.content_type}")
954-
955-
# Check for API key
956-
api_key = os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
957-
if not api_key:
958-
raise HTTPException(
959-
status_code=503,
960-
detail="Audio redaction service unavailable: GOOGLE_API_KEY or GEMINI_API_KEY not configured",
961-
)
962-
963-
try:
964-
# Read audio content
965-
content = await file.read()
966-
967-
logger.info(f"Processing audio redaction for: {file.filename}")
968-
logger.info(f"Prompt: {prompt}")
969-
logger.info(f"File size: {len(content) / 1024:.1f} KB")
970-
971-
# Import agent
972-
from app.agents.audio_redaction import AudioRedactionAgent
973-
974-
# Create agent and process
975-
agent = AudioRedactionAgent(api_key=api_key)
976-
977-
# Determine output format (keep same as input for common formats)
978-
output_format = file_ext if file_ext in {"mp3", "wav", "ogg", "flac"} else "mp3"
979-
980-
# Run redaction
981-
response = agent.redact_audio(
982-
audio_data=content,
983-
prompt=prompt,
984-
file_ext=file_ext,
985-
output_format=output_format,
986-
)
987-
988-
logger.info(
989-
f"Audio redaction complete: {response.segments_censored} segments censored"
990-
)
991-
992-
return {
993-
"censored_audio": response.censored_audio,
994-
"segments_censored": response.segments_censored,
995-
"segments_found": response.segments_found,
996-
"total_censored_duration": response.total_censored_duration,
997-
"audio_duration_seconds": response.audio_duration_seconds,
998-
"processing_time_seconds": response.processing_time_seconds,
999-
"transcript": response.transcript,
1000-
"reasoning": response.reasoning,
1001-
"targets": [
1002-
{
1003-
"start_time": t.start_time,
1004-
"end_time": t.end_time,
1005-
"text": t.text[:100] + "..." if len(t.text) > 100 else t.text,
1006-
"reason": t.reason,
1007-
}
1008-
for t in response.targets
1009-
],
1010-
"output_format": output_format,
1011-
}
1012-
1013-
except ImportError as e:
1014-
logger.error(f"Missing dependency: {e}")
1015-
raise HTTPException(
1016-
status_code=503,
1017-
detail=f"Audio redaction service unavailable: missing dependency ({str(e)}). Make sure pydub and ffmpeg are installed.",
1018-
) from e
1019-
except ValueError as e:
1020-
logger.error(f"Audio redaction failed: {e}")
1021-
raise HTTPException(status_code=400, detail=str(e)) from e
1022-
except Exception as e:
1023-
logger.error(f"Audio redaction failed: {e}", exc_info=True)
1024-
raise HTTPException(
1025-
status_code=500, detail=f"Audio redaction failed: {str(e)}"
1026-
) from e
1027-
1028-
1029-
@router.post("/redact/audio/download", status_code=status.HTTP_200_OK)
1030-
async def redact_audio_download(
1031-
file: UploadFile = File(..., description="Audio file to redact"),
1032-
prompt: str = Form(..., description="Natural language redaction instructions"),
1033-
):
1034-
"""Redact an audio file and return it as a downloadable file.
1035-
1036-
Same as /redact/audio but returns the audio directly for download
1037-
instead of base64 encoded JSON.
1038-
"""
1039-
# Validate file type
1040-
if not file.filename:
1041-
raise HTTPException(status_code=400, detail="Filename is required")
1042-
1043-
file_ext = file.filename.lower().split(".")[-1]
1044-
if file_ext not in {"mp3", "wav", "ogg", "m4a", "flac", "aac", "webm"}:
1045-
raise HTTPException(
1046-
status_code=400, detail=f"Unsupported file type: .{file_ext}"
1047-
)
1048-
1049-
# Check for API key
1050-
api_key = os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
1051-
if not api_key:
1052-
raise HTTPException(
1053-
status_code=503,
1054-
detail="Audio redaction service unavailable: GOOGLE_API_KEY or GEMINI_API_KEY not configured",
1055-
)
1056-
1057-
try:
1058-
# Read audio content
1059-
content = await file.read()
1060-
1061-
# Import agent
1062-
from app.agents.audio_redaction import AudioRedactionAgent
1063-
1064-
# Create agent and process
1065-
agent = AudioRedactionAgent(api_key=api_key)
1066-
1067-
# Determine output format
1068-
output_format = file_ext if file_ext in {"mp3", "wav", "ogg", "flac"} else "mp3"
1069-
1070-
# Run redaction
1071-
response = agent.redact_audio(
1072-
audio_data=content,
1073-
prompt=prompt,
1074-
file_ext=file_ext,
1075-
output_format=output_format,
1076-
)
1077-
1078-
# Decode base64 audio
1079-
censored_audio_bytes = base64.b64decode(response.censored_audio)
1080-
1081-
# Generate output filename
1082-
original_name = file.filename
1083-
name_parts = original_name.rsplit(".", 1)
1084-
if len(name_parts) == 2:
1085-
output_name = f"{name_parts[0]}_censored.{output_format}"
1086-
else:
1087-
output_name = f"{original_name}_censored.{output_format}"
1088-
1089-
# Determine content type
1090-
content_type_map = {
1091-
"mp3": "audio/mpeg",
1092-
"wav": "audio/wav",
1093-
"ogg": "audio/ogg",
1094-
"flac": "audio/flac",
1095-
"m4a": "audio/mp4",
1096-
"aac": "audio/aac",
1097-
"webm": "audio/webm",
1098-
}
1099-
content_type = content_type_map.get(output_format, "audio/mpeg")
1100-
1101-
return Response(
1102-
content=censored_audio_bytes,
1103-
media_type=content_type,
1104-
headers={"Content-Disposition": f'attachment; filename="{output_name}"'},
1105-
)
1106-
1107-
except ImportError as e:
1108-
raise HTTPException(
1109-
status_code=503,
1110-
detail=f"Audio redaction service unavailable: missing dependency ({str(e)})",
1111-
) from e
1112-
except ValueError as e:
1113-
raise HTTPException(status_code=400, detail=str(e)) from e
1114-
except Exception as e:
1115-
logger.error(f"Audio redaction failed: {e}", exc_info=True)
1116-
raise HTTPException(
1117-
status_code=500, detail=f"Audio redaction failed: {str(e)}"
1118-
) from e

lefthook.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pre-push:
2626
commands:
2727
backend-format-check:
2828
run: cd backend && uv run ruff format --check .
29+
backend-lint-check:
30+
run: cd backend && uv run ruff check .
2931
generate-types:
3032
run: |
3133
make generate-types

packages/types/src/generated/api.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,34 @@ export interface components {
929929
*/
930930
reason?: string | null;
931931
};
932+
/** Body_redact_audio_direct_api_redact_audio_post */
933+
Body_redact_audio_direct_api_redact_audio_post: {
934+
/**
935+
* File
936+
* Format: binary
937+
* @description Audio file to redact
938+
*/
939+
file: string;
940+
/**
941+
* Prompt
942+
* @description Natural language redaction instructions
943+
*/
944+
prompt: string;
945+
};
946+
/** Body_redact_audio_download_api_redact_audio_download_post */
947+
Body_redact_audio_download_api_redact_audio_download_post: {
948+
/**
949+
* File
950+
* Format: binary
951+
* @description Audio file to redact
952+
*/
953+
file: string;
954+
/**
955+
* Prompt
956+
* @description Natural language redaction instructions
957+
*/
958+
prompt: string;
959+
};
932960
/** Body_redact_case_file_api_cases__case_id__files__file_id__redact_post */
933961
Body_redact_case_file_api_cases__case_id__files__file_id__redact_post: {
934962
/**
@@ -1990,34 +2018,6 @@ export interface components {
19902018
/** Error Type */
19912019
type: string;
19922020
};
1993-
/** Body_redact_audio_direct_api_redact_audio_post */
1994-
fastapi___compat__v2__Body_redact_audio_direct_api_redact_audio_post: {
1995-
/**
1996-
* File
1997-
* Format: binary
1998-
* @description Audio file to redact
1999-
*/
2000-
file: string;
2001-
/**
2002-
* Prompt
2003-
* @description Natural language redaction instructions
2004-
*/
2005-
prompt: string;
2006-
};
2007-
/** Body_redact_audio_download_api_redact_audio_download_post */
2008-
fastapi___compat__v2__Body_redact_audio_download_api_redact_audio_download_post: {
2009-
/**
2010-
* File
2011-
* Format: binary
2012-
* @description Audio file to redact
2013-
*/
2014-
file: string;
2015-
/**
2016-
* Prompt
2017-
* @description Natural language redaction instructions
2018-
*/
2019-
prompt: string;
2020-
};
20212021
};
20222022
responses: never;
20232023
parameters: never;
@@ -3377,7 +3377,7 @@ export interface operations {
33773377
};
33783378
requestBody: {
33793379
content: {
3380-
"multipart/form-data": components["schemas"]["fastapi___compat__v2__Body_redact_audio_direct_api_redact_audio_post"];
3380+
"multipart/form-data": components["schemas"]["Body_redact_audio_direct_api_redact_audio_post"];
33813381
};
33823382
};
33833383
responses: {
@@ -3410,7 +3410,7 @@ export interface operations {
34103410
};
34113411
requestBody: {
34123412
content: {
3413-
"multipart/form-data": components["schemas"]["fastapi___compat__v2__Body_redact_audio_download_api_redact_audio_download_post"];
3413+
"multipart/form-data": components["schemas"]["Body_redact_audio_download_api_redact_audio_download_post"];
34143414
};
34153415
};
34163416
responses: {

0 commit comments

Comments
 (0)