Spaces:
Paused
Paused
Update main.py
Browse files
main.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import os, io, uuid, json, wave
|
| 2 |
import aiohttp
|
| 3 |
import aiofiles
|
| 4 |
import asyncio
|
|
@@ -84,7 +84,6 @@ else:
|
|
| 84 |
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 85 |
Base = declarative_base()
|
| 86 |
|
| 87 |
-
# SQLAlchemy Models
|
| 88 |
# SQLAlchemy Models
|
| 89 |
class UserModel(Base):
|
| 90 |
__tablename__ = "users"
|
|
@@ -161,6 +160,25 @@ class Transcription(TranscriptionBase):
|
|
| 161 |
class Config:
|
| 162 |
from_attributes = True
|
| 163 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
# Add these utilities
|
| 165 |
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
| 166 |
|
|
@@ -185,7 +203,7 @@ os.makedirs("uploads", exist_ok=True)
|
|
| 185 |
|
| 186 |
# Mount static files
|
| 187 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 188 |
-
app.mount("/uploads", StaticFiles(directory=
|
| 189 |
|
| 190 |
# Initialize database
|
| 191 |
def init_db():
|
|
@@ -426,21 +444,6 @@ async def login_for_access_token(
|
|
| 426 |
detail=f"Internal server error: {str(e)}"
|
| 427 |
)
|
| 428 |
|
| 429 |
-
# @app.post("/token", response_model=Token)
|
| 430 |
-
# async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
|
| 431 |
-
# user = authenticate_user(db, form_data.username, form_data.password)
|
| 432 |
-
# if not user:
|
| 433 |
-
# raise HTTPException(
|
| 434 |
-
# status_code=401,
|
| 435 |
-
# detail="Incorrect username or password",
|
| 436 |
-
# headers={"WWW-Authenticate": "Bearer"},
|
| 437 |
-
# )
|
| 438 |
-
# access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
| 439 |
-
# access_token = create_access_token(
|
| 440 |
-
# data={"sub": user.username}, expires_delta=access_token_expires
|
| 441 |
-
# )
|
| 442 |
-
# return {"access_token": access_token, "token_type": "bearer"}
|
| 443 |
-
|
| 444 |
@app.post("/api/signup")
|
| 445 |
async def signup(user: UserCreate, db: Session = Depends(get_db)):
|
| 446 |
try:
|
|
@@ -482,17 +485,6 @@ async def signup(user: UserCreate, db: Session = Depends(get_db)):
|
|
| 482 |
detail=f"An error occurred: {str(e)}"
|
| 483 |
)
|
| 484 |
|
| 485 |
-
# Modify the existing routes to require authentication
|
| 486 |
-
@app.get("/api/me", response_model=User)
|
| 487 |
-
async def read_users_me(current_user: User = Depends(get_current_user)):
|
| 488 |
-
return current_user
|
| 489 |
-
|
| 490 |
-
@app.get("/", response_class=HTMLResponse)
|
| 491 |
-
async def read_index():
|
| 492 |
-
async with aiofiles.open("index.html", mode="r") as f:
|
| 493 |
-
content = await f.read()
|
| 494 |
-
return HTMLResponse(content=content)
|
| 495 |
-
|
| 496 |
# Helper function to split audio into chunks
|
| 497 |
def split_audio_chunks(
|
| 498 |
file_path: str,
|
|
@@ -648,6 +640,244 @@ def combine_results(results: list, response_format: str):
|
|
| 648 |
|
| 649 |
return final
|
| 650 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
@app.post("/api/upload")
|
| 652 |
async def create_upload_file(
|
| 653 |
current_user: User = Depends(get_current_user),
|
|
@@ -696,6 +926,40 @@ async def create_upload_file(
|
|
| 696 |
if start_sample >= end_sample:
|
| 697 |
raise HTTPException(400, "Invalid time range")
|
| 698 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
# Slice the audio data
|
| 700 |
audio_data = audio_data[start_sample:end_sample]
|
| 701 |
|
|
@@ -703,6 +967,12 @@ async def create_upload_file(
|
|
| 703 |
sliced_path = f"{temp_path}_sliced.flac"
|
| 704 |
sf.write(sliced_path, audio_data, sample_rate, format='FLAC')
|
| 705 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 706 |
# Split into FLAC chunks
|
| 707 |
chunks = split_audio_chunks(
|
| 708 |
sliced_path,
|
|
@@ -728,6 +998,14 @@ async def create_upload_file(
|
|
| 728 |
# Combine results
|
| 729 |
combined = combine_results(results, response_format)
|
| 730 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 731 |
# Cleanup
|
| 732 |
for chunk in chunks:
|
| 733 |
os.remove(chunk["path"])
|
|
@@ -744,29 +1022,6 @@ async def create_upload_file(
|
|
| 744 |
os.remove(sliced_path)
|
| 745 |
raise HTTPException(500, str(e))
|
| 746 |
|
| 747 |
-
@app.post("/api/upload-audio")
|
| 748 |
-
async def upload_audio(
|
| 749 |
-
request: Request,
|
| 750 |
-
current_user: User = Depends(get_current_user),
|
| 751 |
-
db: Session = Depends(get_db)
|
| 752 |
-
):
|
| 753 |
-
try:
|
| 754 |
-
form_data = await request.form()
|
| 755 |
-
file = form_data["file"]
|
| 756 |
-
|
| 757 |
-
file_id = str(uuid.uuid4())
|
| 758 |
-
filename = f"{file_id}_{file.filename}"
|
| 759 |
-
file_path = os.path.join("uploads", filename)
|
| 760 |
-
print(f"file_path: ${file_path}")
|
| 761 |
-
|
| 762 |
-
contents = await file.read()
|
| 763 |
-
async with aiofiles.open(file_path, "wb") as f:
|
| 764 |
-
await f.write(contents)
|
| 765 |
-
|
| 766 |
-
return JSONResponse({"filename": filename})
|
| 767 |
-
except Exception as e:
|
| 768 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 769 |
-
|
| 770 |
@app.post("/api/upload-audio-chunk")
|
| 771 |
async def upload_audio_chunk(
|
| 772 |
file: UploadFile = File(...),
|
|
@@ -812,8 +1067,11 @@ async def finalize_audio_upload(
|
|
| 812 |
metadata_path = os.path.join(upload_path, "metadata.txt")
|
| 813 |
filename = f"{uuid.uuid4()}.mp3" # Default filename
|
| 814 |
|
|
|
|
|
|
|
|
|
|
| 815 |
# Combine all chunks into final file
|
| 816 |
-
output_path = os.path.join(
|
| 817 |
|
| 818 |
with open(output_path, "wb") as outfile:
|
| 819 |
# Get all chunks sorted by offset
|
|
@@ -851,24 +1109,52 @@ async def get_audio_files(
|
|
| 851 |
current_user: User = Depends(get_current_user)
|
| 852 |
):
|
| 853 |
try:
|
| 854 |
-
# Scan uploads directory for audio files
|
| 855 |
-
upload_dir = os.path.join(os.getcwd(), UPLOAD_DIR)
|
| 856 |
file_list = []
|
| 857 |
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
|
| 873 |
# Sort by upload date (newest first)
|
| 874 |
file_list.sort(key=lambda x: x["uploaded_at"], reverse=True)
|
|
@@ -881,11 +1167,25 @@ async def get_audio_files(
|
|
| 881 |
@app.delete("/api/audio-files/{filename}")
|
| 882 |
async def delete_audio_file(
|
| 883 |
filename: str,
|
| 884 |
-
|
|
|
|
|
|
|
| 885 |
):
|
| 886 |
try:
|
| 887 |
-
#
|
| 888 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 889 |
if not os.path.exists(file_path):
|
| 890 |
raise HTTPException(status_code=404, detail="Audio file not found")
|
| 891 |
|
|
|
|
| 1 |
+
import os, io, uuid, json, wave, math
|
| 2 |
import aiohttp
|
| 3 |
import aiofiles
|
| 4 |
import asyncio
|
|
|
|
| 84 |
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 85 |
Base = declarative_base()
|
| 86 |
|
|
|
|
| 87 |
# SQLAlchemy Models
|
| 88 |
class UserModel(Base):
|
| 89 |
__tablename__ = "users"
|
|
|
|
| 160 |
class Config:
|
| 161 |
from_attributes = True
|
| 162 |
|
| 163 |
+
# Add this with other SQLAlchemy models
|
| 164 |
+
class UserCreditModel(Base):
|
| 165 |
+
__tablename__ = "user_credits"
|
| 166 |
+
|
| 167 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 168 |
+
user_id = Column(Integer, ForeignKey("users.id"))
|
| 169 |
+
minutes_used = Column(Integer, default=0)
|
| 170 |
+
minutes_quota = Column(Integer, default=10) # Default to 10 minutes
|
| 171 |
+
last_updated = Column(String, default=lambda: datetime.utcnow().isoformat())
|
| 172 |
+
|
| 173 |
+
class UserCredit(BaseModel):
|
| 174 |
+
user_id: int
|
| 175 |
+
minutes_used: int
|
| 176 |
+
minutes_quota: int
|
| 177 |
+
last_updated: str
|
| 178 |
+
|
| 179 |
+
class Config:
|
| 180 |
+
from_attributes = True
|
| 181 |
+
|
| 182 |
# Add these utilities
|
| 183 |
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
| 184 |
|
|
|
|
| 203 |
|
| 204 |
# Mount static files
|
| 205 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 206 |
+
app.mount("/uploads", StaticFiles(directory=UPLOAD_DIR), name="uploads")
|
| 207 |
|
| 208 |
# Initialize database
|
| 209 |
def init_db():
|
|
|
|
| 444 |
detail=f"Internal server error: {str(e)}"
|
| 445 |
)
|
| 446 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
@app.post("/api/signup")
|
| 448 |
async def signup(user: UserCreate, db: Session = Depends(get_db)):
|
| 449 |
try:
|
|
|
|
| 485 |
detail=f"An error occurred: {str(e)}"
|
| 486 |
)
|
| 487 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
# Helper function to split audio into chunks
|
| 489 |
def split_audio_chunks(
|
| 490 |
file_path: str,
|
|
|
|
| 640 |
|
| 641 |
return final
|
| 642 |
|
| 643 |
+
def get_user_upload_dir(user_id):
|
| 644 |
+
"""Get the upload directory for a specific user"""
|
| 645 |
+
user_dir = os.path.join(UPLOAD_DIR, str(user_id))
|
| 646 |
+
os.makedirs(user_dir, exist_ok=True)
|
| 647 |
+
return user_dir
|
| 648 |
+
|
| 649 |
+
@app.get("/api/me", response_model=User)
|
| 650 |
+
async def read_users_me(current_user: User = Depends(get_current_user)):
|
| 651 |
+
return current_user
|
| 652 |
+
|
| 653 |
+
@app.get("/", response_class=HTMLResponse)
|
| 654 |
+
async def read_index():
|
| 655 |
+
async with aiofiles.open("index.html", mode="r") as f:
|
| 656 |
+
content = await f.read()
|
| 657 |
+
return HTMLResponse(content=content)
|
| 658 |
+
|
| 659 |
+
@app.get("/api/credits")
|
| 660 |
+
async def get_user_credits(
|
| 661 |
+
current_user: User = Depends(get_current_user),
|
| 662 |
+
db: Session = Depends(get_db)
|
| 663 |
+
):
|
| 664 |
+
try:
|
| 665 |
+
# Get user credit information
|
| 666 |
+
user_credit = db.query(UserCreditModel).filter(
|
| 667 |
+
UserCreditModel.user_id == current_user.id
|
| 668 |
+
).first()
|
| 669 |
+
|
| 670 |
+
if not user_credit:
|
| 671 |
+
# Create new credit record if it doesn't exist
|
| 672 |
+
user_credit = UserCreditModel(
|
| 673 |
+
user_id=current_user.id,
|
| 674 |
+
minutes_used=0,
|
| 675 |
+
minutes_quota=10,
|
| 676 |
+
last_updated=datetime.utcnow().isoformat()
|
| 677 |
+
)
|
| 678 |
+
db.add(user_credit)
|
| 679 |
+
db.commit()
|
| 680 |
+
db.refresh(user_credit)
|
| 681 |
+
|
| 682 |
+
return {
|
| 683 |
+
"user_id": user_credit.user_id,
|
| 684 |
+
"minutes_used": user_credit.minutes_used,
|
| 685 |
+
"minutes_quota": user_credit.minutes_quota,
|
| 686 |
+
"minutes_remaining": max(0, user_credit.minutes_quota - user_credit.minutes_used),
|
| 687 |
+
"last_updated": user_credit.last_updated
|
| 688 |
+
}
|
| 689 |
+
except Exception as e:
|
| 690 |
+
print(f"Error getting user credits: {str(e)}")
|
| 691 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 692 |
+
|
| 693 |
+
@app.get("/api/admin/credits")
|
| 694 |
+
async def get_all_user_credits(
|
| 695 |
+
current_user: User = Depends(get_current_active_admin),
|
| 696 |
+
db: Session = Depends(get_db)
|
| 697 |
+
):
|
| 698 |
+
try:
|
| 699 |
+
# Get all user credit information
|
| 700 |
+
user_credits = db.query(UserCreditModel).all()
|
| 701 |
+
|
| 702 |
+
# Get usernames for each user_id
|
| 703 |
+
user_data = {}
|
| 704 |
+
for user in db.query(UserModel).all():
|
| 705 |
+
user_data[user.id] = user.username
|
| 706 |
+
|
| 707 |
+
return [
|
| 708 |
+
{
|
| 709 |
+
"user_id": credit.user_id,
|
| 710 |
+
"username": user_data.get(credit.user_id, "Unknown"),
|
| 711 |
+
"minutes_used": credit.minutes_used,
|
| 712 |
+
"minutes_quota": credit.minutes_quota,
|
| 713 |
+
"minutes_remaining": max(0, credit.minutes_quota - credit.minutes_used),
|
| 714 |
+
"last_updated": credit.last_updated
|
| 715 |
+
}
|
| 716 |
+
for credit in user_credits
|
| 717 |
+
]
|
| 718 |
+
except Exception as e:
|
| 719 |
+
print(f"Error getting all user credits: {str(e)}")
|
| 720 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 721 |
+
|
| 722 |
+
@app.put("/api/admin/credits/{user_id}")
|
| 723 |
+
async def update_user_credits(
|
| 724 |
+
user_id: int,
|
| 725 |
+
credit_update: dict,
|
| 726 |
+
current_user: User = Depends(get_current_active_admin),
|
| 727 |
+
db: Session = Depends(get_db)
|
| 728 |
+
):
|
| 729 |
+
try:
|
| 730 |
+
# Validate input
|
| 731 |
+
if not any(field in credit_update for field in ["minutes_used", "minutes_quota"]):
|
| 732 |
+
raise HTTPException(status_code=400, detail="At least one of minutes_used or minutes_quota is required")
|
| 733 |
+
|
| 734 |
+
# Get user credit record
|
| 735 |
+
user_credit = db.query(UserCreditModel).filter(
|
| 736 |
+
UserCreditModel.user_id == user_id
|
| 737 |
+
).first()
|
| 738 |
+
|
| 739 |
+
if not user_credit:
|
| 740 |
+
# Create new credit record if it doesn't exist
|
| 741 |
+
user = db.query(UserModel).filter(UserModel.id == user_id).first()
|
| 742 |
+
if not user:
|
| 743 |
+
raise HTTPException(status_code=404, detail="User not found")
|
| 744 |
+
|
| 745 |
+
minutes_used = credit_update.get("minutes_used", 0)
|
| 746 |
+
minutes_quota = credit_update.get("minutes_quota", 60) # Default quota
|
| 747 |
+
|
| 748 |
+
if not isinstance(minutes_used, int) or minutes_used < 0:
|
| 749 |
+
raise HTTPException(status_code=400, detail="minutes_used must be a non-negative integer")
|
| 750 |
+
|
| 751 |
+
if not isinstance(minutes_quota, int) or minutes_quota < 0:
|
| 752 |
+
raise HTTPException(status_code=400, detail="minutes_quota must be a non-negative integer")
|
| 753 |
+
|
| 754 |
+
user_credit = UserCreditModel(
|
| 755 |
+
user_id=user_id,
|
| 756 |
+
minutes_used=minutes_used,
|
| 757 |
+
minutes_quota=minutes_quota,
|
| 758 |
+
last_updated=datetime.utcnow().isoformat()
|
| 759 |
+
)
|
| 760 |
+
db.add(user_credit)
|
| 761 |
+
else:
|
| 762 |
+
# Update existing credit record
|
| 763 |
+
if "minutes_used" in credit_update:
|
| 764 |
+
minutes_used = credit_update["minutes_used"]
|
| 765 |
+
if not isinstance(minutes_used, int) or minutes_used < 0:
|
| 766 |
+
raise HTTPException(status_code=400, detail="minutes_used must be a non-negative integer")
|
| 767 |
+
user_credit.minutes_used = minutes_used
|
| 768 |
+
|
| 769 |
+
if "minutes_quota" in credit_update:
|
| 770 |
+
minutes_quota = credit_update["minutes_quota"]
|
| 771 |
+
if not isinstance(minutes_quota, int) or minutes_quota < 0:
|
| 772 |
+
raise HTTPException(status_code=400, detail="minutes_quota must be a non-negative integer")
|
| 773 |
+
user_credit.minutes_quota = minutes_quota
|
| 774 |
+
|
| 775 |
+
user_credit.last_updated = datetime.utcnow().isoformat()
|
| 776 |
+
|
| 777 |
+
db.commit()
|
| 778 |
+
db.refresh(user_credit)
|
| 779 |
+
|
| 780 |
+
# Get username for response
|
| 781 |
+
user = db.query(UserModel).filter(UserModel.id == user_id).first()
|
| 782 |
+
username = user.username if user else "Unknown"
|
| 783 |
+
|
| 784 |
+
return {
|
| 785 |
+
"user_id": user_credit.user_id,
|
| 786 |
+
"username": username,
|
| 787 |
+
"minutes_used": user_credit.minutes_used,
|
| 788 |
+
"minutes_quota": user_credit.minutes_quota,
|
| 789 |
+
"minutes_remaining": max(0, user_credit.minutes_quota - user_credit.minutes_used),
|
| 790 |
+
"last_updated": user_credit.last_updated
|
| 791 |
+
}
|
| 792 |
+
except Exception as e:
|
| 793 |
+
print(f"Error updating user credits: {str(e)}")
|
| 794 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 795 |
+
|
| 796 |
+
@app.post("/api/admin/credits/{user_id}/reset-quota")
|
| 797 |
+
async def reset_user_quota(
|
| 798 |
+
user_id: int,
|
| 799 |
+
quota_data: dict,
|
| 800 |
+
current_user: User = Depends(get_current_active_admin),
|
| 801 |
+
db: Session = Depends(get_db)
|
| 802 |
+
):
|
| 803 |
+
try:
|
| 804 |
+
new_quota = quota_data.get("new_quota")
|
| 805 |
+
if new_quota is None:
|
| 806 |
+
raise HTTPException(status_code=400, detail="new_quota field is required")
|
| 807 |
+
|
| 808 |
+
if not isinstance(new_quota, int) or new_quota < 0:
|
| 809 |
+
raise HTTPException(status_code=400, detail="new_quota must be a non-negative integer")
|
| 810 |
+
|
| 811 |
+
# Get user credit record
|
| 812 |
+
user_credit = db.query(UserCreditModel).filter(
|
| 813 |
+
UserCreditModel.user_id == user_id
|
| 814 |
+
).first()
|
| 815 |
+
|
| 816 |
+
if not user_credit:
|
| 817 |
+
# Create new credit record if it doesn't exist
|
| 818 |
+
user = db.query(UserModel).filter(UserModel.id == user_id).first()
|
| 819 |
+
if not user:
|
| 820 |
+
raise HTTPException(status_code=404, detail="User not found")
|
| 821 |
+
|
| 822 |
+
user_credit = UserCreditModel(
|
| 823 |
+
user_id=user_id,
|
| 824 |
+
minutes_used=0,
|
| 825 |
+
minutes_quota=new_quota,
|
| 826 |
+
last_updated=datetime.utcnow().isoformat()
|
| 827 |
+
)
|
| 828 |
+
db.add(user_credit)
|
| 829 |
+
else:
|
| 830 |
+
# Update existing credit record
|
| 831 |
+
user_credit.minutes_quota = new_quota
|
| 832 |
+
user_credit.last_updated = datetime.utcnow().isoformat()
|
| 833 |
+
|
| 834 |
+
db.commit()
|
| 835 |
+
db.refresh(user_credit)
|
| 836 |
+
|
| 837 |
+
# Get username for response
|
| 838 |
+
user = db.query(UserModel).filter(UserModel.id == user_id).first()
|
| 839 |
+
username = user.username if user else "Unknown"
|
| 840 |
+
|
| 841 |
+
return {
|
| 842 |
+
"user_id": user_credit.user_id,
|
| 843 |
+
"username": username,
|
| 844 |
+
"minutes_used": user_credit.minutes_used,
|
| 845 |
+
"minutes_quota": user_credit.minutes_quota,
|
| 846 |
+
"minutes_remaining": max(0, user_credit.minutes_quota - user_credit.minutes_used),
|
| 847 |
+
"last_updated": user_credit.last_updated
|
| 848 |
+
}
|
| 849 |
+
except Exception as e:
|
| 850 |
+
print(f"Error resetting user quota: {str(e)}")
|
| 851 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 852 |
+
|
| 853 |
+
@app.post("/api/upload-audio")
|
| 854 |
+
async def upload_audio(
|
| 855 |
+
request: Request,
|
| 856 |
+
current_user: User = Depends(get_current_user),
|
| 857 |
+
db: Session = Depends(get_db)
|
| 858 |
+
):
|
| 859 |
+
try:
|
| 860 |
+
form_data = await request.form()
|
| 861 |
+
file = form_data["file"]
|
| 862 |
+
|
| 863 |
+
file_id = str(uuid.uuid4())
|
| 864 |
+
filename = f"{file_id}_{file.filename}"
|
| 865 |
+
|
| 866 |
+
# Get user-specific upload directory
|
| 867 |
+
user_upload_dir = get_user_upload_dir(current_user.id)
|
| 868 |
+
|
| 869 |
+
# Create the full file path in the user's directory
|
| 870 |
+
file_path = os.path.join(user_upload_dir, filename)
|
| 871 |
+
print(f"file_path: ${file_path}")
|
| 872 |
+
|
| 873 |
+
contents = await file.read()
|
| 874 |
+
async with aiofiles.open(file_path, "wb") as f:
|
| 875 |
+
await f.write(contents)
|
| 876 |
+
|
| 877 |
+
return JSONResponse({"filename": filename})
|
| 878 |
+
except Exception as e:
|
| 879 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 880 |
+
|
| 881 |
@app.post("/api/upload")
|
| 882 |
async def create_upload_file(
|
| 883 |
current_user: User = Depends(get_current_user),
|
|
|
|
| 926 |
if start_sample >= end_sample:
|
| 927 |
raise HTTPException(400, "Invalid time range")
|
| 928 |
|
| 929 |
+
# Calculate audio duration in minutes
|
| 930 |
+
audio_duration_seconds = (end_sample - start_sample) / sample_rate
|
| 931 |
+
audio_duration_minutes = math.ceil(audio_duration_seconds / 60) # Round up to nearest minute
|
| 932 |
+
|
| 933 |
+
# Check user quota
|
| 934 |
+
user_credit = db.query(UserCreditModel).filter(
|
| 935 |
+
UserCreditModel.user_id == current_user.id
|
| 936 |
+
).first()
|
| 937 |
+
|
| 938 |
+
if not user_credit:
|
| 939 |
+
# Create new credit record if it doesn't exist
|
| 940 |
+
user_credit = UserCreditModel(
|
| 941 |
+
user_id=current_user.id,
|
| 942 |
+
minutes_used=0,
|
| 943 |
+
minutes_quota=60, # Default quota
|
| 944 |
+
last_updated=datetime.utcnow().isoformat()
|
| 945 |
+
)
|
| 946 |
+
db.add(user_credit)
|
| 947 |
+
db.commit()
|
| 948 |
+
db.refresh(user_credit)
|
| 949 |
+
|
| 950 |
+
# Check if user has enough quota
|
| 951 |
+
minutes_remaining = max(0, user_credit.minutes_quota - user_credit.minutes_used)
|
| 952 |
+
if audio_duration_minutes > minutes_remaining:
|
| 953 |
+
raise HTTPException(
|
| 954 |
+
status_code=403,
|
| 955 |
+
detail=f"Quota exceeded. You have {minutes_remaining} minutes remaining, but this audio requires {audio_duration_minutes} minutes."
|
| 956 |
+
)
|
| 957 |
+
|
| 958 |
+
# Update user credits
|
| 959 |
+
user_credit.minutes_used += audio_duration_minutes
|
| 960 |
+
user_credit.last_updated = datetime.utcnow().isoformat()
|
| 961 |
+
db.commit()
|
| 962 |
+
|
| 963 |
# Slice the audio data
|
| 964 |
audio_data = audio_data[start_sample:end_sample]
|
| 965 |
|
|
|
|
| 967 |
sliced_path = f"{temp_path}_sliced.flac"
|
| 968 |
sf.write(sliced_path, audio_data, sample_rate, format='FLAC')
|
| 969 |
|
| 970 |
+
# check sliced_path file size if it exceeded the limit
|
| 971 |
+
file_size = os.path.getsize(sliced_path)
|
| 972 |
+
if file_size > MAX_FILE_SIZE:
|
| 973 |
+
print(f"Slice: {sliced_path} exceeds the limit of {MAX_FILE_SIZE / (1024 * 1024)}MB")
|
| 974 |
+
# raise HTTPException(400, f"File size exceeds the limit of {MAX_FILE_SIZE / (1024 * 1024)}MB")
|
| 975 |
+
|
| 976 |
# Split into FLAC chunks
|
| 977 |
chunks = split_audio_chunks(
|
| 978 |
sliced_path,
|
|
|
|
| 998 |
# Combine results
|
| 999 |
combined = combine_results(results, response_format)
|
| 1000 |
|
| 1001 |
+
# Add credit usage information to response
|
| 1002 |
+
combined["metadata"]["credit_usage"] = {
|
| 1003 |
+
"minutes_used": audio_duration_minutes,
|
| 1004 |
+
"total_minutes_used": user_credit.minutes_used,
|
| 1005 |
+
"minutes_quota": user_credit.minutes_quota,
|
| 1006 |
+
"minutes_remaining": max(0, user_credit.minutes_quota - user_credit.minutes_used)
|
| 1007 |
+
}
|
| 1008 |
+
|
| 1009 |
# Cleanup
|
| 1010 |
for chunk in chunks:
|
| 1011 |
os.remove(chunk["path"])
|
|
|
|
| 1022 |
os.remove(sliced_path)
|
| 1023 |
raise HTTPException(500, str(e))
|
| 1024 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1025 |
@app.post("/api/upload-audio-chunk")
|
| 1026 |
async def upload_audio_chunk(
|
| 1027 |
file: UploadFile = File(...),
|
|
|
|
| 1067 |
metadata_path = os.path.join(upload_path, "metadata.txt")
|
| 1068 |
filename = f"{uuid.uuid4()}.mp3" # Default filename
|
| 1069 |
|
| 1070 |
+
# Get user-specific upload directory
|
| 1071 |
+
user_upload_dir = get_user_upload_dir(current_user.id)
|
| 1072 |
+
|
| 1073 |
# Combine all chunks into final file
|
| 1074 |
+
output_path = os.path.join(user_upload_dir, filename)
|
| 1075 |
|
| 1076 |
with open(output_path, "wb") as outfile:
|
| 1077 |
# Get all chunks sorted by offset
|
|
|
|
| 1109 |
current_user: User = Depends(get_current_user)
|
| 1110 |
):
|
| 1111 |
try:
|
|
|
|
|
|
|
| 1112 |
file_list = []
|
| 1113 |
|
| 1114 |
+
if current_user.is_admin:
|
| 1115 |
+
# Admin can see all files
|
| 1116 |
+
for user_id in os.listdir(UPLOAD_DIR):
|
| 1117 |
+
user_dir = os.path.join(UPLOAD_DIR, user_id)
|
| 1118 |
+
if os.path.isdir(user_dir):
|
| 1119 |
+
# Get username for this user_id
|
| 1120 |
+
db = next(get_db())
|
| 1121 |
+
user = db.query(UserModel).filter(UserModel.id == int(user_id)).first()
|
| 1122 |
+
username = user.username if user else f"User {user_id}"
|
| 1123 |
+
|
| 1124 |
+
for filename in os.listdir(user_dir):
|
| 1125 |
+
file_path = os.path.join(user_dir, filename)
|
| 1126 |
+
if os.path.isfile(file_path) and not filename.startswith('.'):
|
| 1127 |
+
# Get file stats
|
| 1128 |
+
file_stats = os.stat(file_path)
|
| 1129 |
+
file_size = os.path.getsize(file_path)
|
| 1130 |
+
|
| 1131 |
+
file_list.append({
|
| 1132 |
+
"id": filename,
|
| 1133 |
+
"filename": filename,
|
| 1134 |
+
"original_filename": filename,
|
| 1135 |
+
"size": file_size,
|
| 1136 |
+
"uploaded_at": datetime.fromtimestamp(file_stats.st_mtime).isoformat(),
|
| 1137 |
+
"user_id": int(user_id),
|
| 1138 |
+
"username": username
|
| 1139 |
+
})
|
| 1140 |
+
else:
|
| 1141 |
+
# Regular users can only see their own files
|
| 1142 |
+
user_dir = get_user_upload_dir(current_user.id)
|
| 1143 |
+
|
| 1144 |
+
for filename in os.listdir(user_dir):
|
| 1145 |
+
file_path = os.path.join(user_dir, filename)
|
| 1146 |
+
if os.path.isfile(file_path) and not filename.startswith('.'):
|
| 1147 |
+
# Get file stats
|
| 1148 |
+
file_stats = os.stat(file_path)
|
| 1149 |
+
file_size = os.path.getsize(file_path)
|
| 1150 |
+
|
| 1151 |
+
file_list.append({
|
| 1152 |
+
"id": filename,
|
| 1153 |
+
"filename": filename,
|
| 1154 |
+
"original_filename": filename,
|
| 1155 |
+
"size": file_size,
|
| 1156 |
+
"uploaded_at": datetime.fromtimestamp(file_stats.st_mtime).isoformat()
|
| 1157 |
+
})
|
| 1158 |
|
| 1159 |
# Sort by upload date (newest first)
|
| 1160 |
file_list.sort(key=lambda x: x["uploaded_at"], reverse=True)
|
|
|
|
| 1167 |
@app.delete("/api/audio-files/{filename}")
|
| 1168 |
async def delete_audio_file(
|
| 1169 |
filename: str,
|
| 1170 |
+
user_id: int = None,
|
| 1171 |
+
current_user: User = Depends(get_current_user),
|
| 1172 |
+
db: Session = Depends(get_db)
|
| 1173 |
):
|
| 1174 |
try:
|
| 1175 |
+
# Determine which user's file to delete
|
| 1176 |
+
target_user_id = user_id if current_user.is_admin and user_id else current_user.id
|
| 1177 |
+
|
| 1178 |
+
# Check if user exists if admin is deleting another user's file
|
| 1179 |
+
if current_user.is_admin and user_id and user_id != current_user.id:
|
| 1180 |
+
user = db.query(UserModel).filter(UserModel.id == user_id).first()
|
| 1181 |
+
if not user:
|
| 1182 |
+
raise HTTPException(status_code=404, detail="User not found")
|
| 1183 |
+
|
| 1184 |
+
# Get the user's upload directory
|
| 1185 |
+
user_upload_dir = get_user_upload_dir(target_user_id)
|
| 1186 |
+
|
| 1187 |
+
# Check if file exists in the user's directory
|
| 1188 |
+
file_path = os.path.join(user_upload_dir, filename)
|
| 1189 |
if not os.path.exists(file_path):
|
| 1190 |
raise HTTPException(status_code=404, detail="Audio file not found")
|
| 1191 |
|