bitsnaps commited on
Commit
cf71139
·
verified ·
1 Parent(s): 7212f16

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +168 -7
main.py CHANGED
@@ -5,8 +5,10 @@ import asyncio
5
  import sqlite3
6
  import bcrypt
7
  import tempfile
 
 
8
  from fastapi import FastAPI, Request, Query, Form, UploadFile, File, HTTPException, Depends
9
- from fastapi.responses import HTMLResponse, JSONResponse
10
  from fastapi.staticfiles import StaticFiles
11
  from fastapi.middleware.cors import CORSMiddleware
12
  from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
@@ -47,7 +49,11 @@ the following products are spelled correctly
47
  # Authentication & Security
48
  SECRET_KEY = os.getenv("SECRET_KEY", None) # Change in production
49
  ALGORITHM = "HS256"
50
- ACCESS_TOKEN_EXPIRE_MINUTES = 30
 
 
 
 
51
 
52
  # Database setup
53
  DB_TYPE = os.getenv("DB_TYPE", "sqlite").lower() # Default to SQLite if not specified
@@ -90,6 +96,7 @@ class UserModel(Base):
90
  disabled = Column(Boolean, default=True) # Default to disabled
91
  is_admin = Column(Boolean, default=False) # Add admin field
92
 
 
93
  # Create tables
94
  Base.metadata.create_all(bind=engine)
95
 
@@ -115,7 +122,7 @@ class User(UserBase):
115
  is_admin: bool = False # Add admin field
116
 
117
  class Config:
118
- orm_mode = True
119
 
120
  class Token(BaseModel):
121
  access_token: str
@@ -152,7 +159,7 @@ class Transcription(TranscriptionBase):
152
  created_at: str
153
 
154
  class Config:
155
- orm_mode = True
156
 
157
  # Add these utilities
158
  oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@@ -346,7 +353,7 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None):
346
  if expires_delta:
347
  expire = datetime.utcnow() + expires_delta
348
  else:
349
- expire = datetime.utcnow() + timedelta(minutes=15)
350
  to_encode.update({"exp": expire})
351
  encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
352
  return encoded_jwt
@@ -391,7 +398,7 @@ app.add_middleware(
391
  )
392
 
393
  # Also ensure your login form data is properly sent
394
- @app.post("/token")
395
  async def login_for_access_token(
396
  form_data: OAuth2PasswordRequestForm = Depends(),
397
  db: Session = Depends(get_db)
@@ -409,7 +416,8 @@ async def login_for_access_token(
409
  status_code=400,
410
  detail="User is disabled"
411
  )
412
- access_token = create_access_token(data={"sub": user.username})
 
413
  return {"access_token": access_token, "token_type": "bearer"}
414
  except Exception as e:
415
  print(f"Login error: {str(e)}") # Add logging for debugging
@@ -759,6 +767,159 @@ async def upload_audio(
759
  except Exception as e:
760
  raise HTTPException(status_code=500, detail=str(e))
761
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
  @app.get("/api/transcriptions")
763
  async def get_transcriptions(
764
  current_user: User = Depends(get_current_user),
 
5
  import sqlite3
6
  import bcrypt
7
  import tempfile
8
+ import shutil
9
+ import subprocess # to use "ffmpeg" in order to use "ffmpeg-python" library for video to audio conversion
10
  from fastapi import FastAPI, Request, Query, Form, UploadFile, File, HTTPException, Depends
11
+ from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
12
  from fastapi.staticfiles import StaticFiles
13
  from fastapi.middleware.cors import CORSMiddleware
14
  from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
 
49
  # Authentication & Security
50
  SECRET_KEY = os.getenv("SECRET_KEY", None) # Change in production
51
  ALGORITHM = "HS256"
52
+ ACCESS_TOKEN_EXPIRE_MINUTES = 60
53
+
54
+ # Directory definitions
55
+ UPLOAD_DIR = "uploads"
56
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
57
 
58
  # Database setup
59
  DB_TYPE = os.getenv("DB_TYPE", "sqlite").lower() # Default to SQLite if not specified
 
96
  disabled = Column(Boolean, default=True) # Default to disabled
97
  is_admin = Column(Boolean, default=False) # Add admin field
98
 
99
+
100
  # Create tables
101
  Base.metadata.create_all(bind=engine)
102
 
 
122
  is_admin: bool = False # Add admin field
123
 
124
  class Config:
125
+ from_attributes = True
126
 
127
  class Token(BaseModel):
128
  access_token: str
 
159
  created_at: str
160
 
161
  class Config:
162
+ from_attributes = True
163
 
164
  # Add these utilities
165
  oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
 
353
  if expires_delta:
354
  expire = datetime.utcnow() + expires_delta
355
  else:
356
+ expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
357
  to_encode.update({"exp": expire})
358
  encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
359
  return encoded_jwt
 
398
  )
399
 
400
  # Also ensure your login form data is properly sent
401
+ @app.post("/token") #, response_model=Token)
402
  async def login_for_access_token(
403
  form_data: OAuth2PasswordRequestForm = Depends(),
404
  db: Session = Depends(get_db)
 
416
  status_code=400,
417
  detail="User is disabled"
418
  )
419
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
420
+ access_token = create_access_token(data={"sub": user.username}, expires_delta=access_token_expires)
421
  return {"access_token": access_token, "token_type": "bearer"}
422
  except Exception as e:
423
  print(f"Login error: {str(e)}") # Add logging for debugging
 
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(...),
773
+ upload_id: str = Form(...),
774
+ offset: int = Form(...),
775
+ total_size: int = Form(...),
776
+ current_user: User = Depends(get_current_user)
777
+ ):
778
+ # Create upload directory for this file in system temp directory
779
+ temp_dir = tempfile.gettempdir()
780
+ upload_path = os.path.join(temp_dir, f"transtudio_upload_{upload_id}")
781
+ os.makedirs(upload_path, exist_ok=True)
782
+
783
+ # Path for this specific chunk
784
+ chunk_path = os.path.join(upload_path, f"chunk_{offset}")
785
+
786
+ # Save the chunk
787
+ with open(chunk_path, "wb") as buffer:
788
+ shutil.copyfileobj(file.file, buffer)
789
+
790
+ # Update metadata file with progress info
791
+ metadata_path = os.path.join(upload_path, "metadata.txt")
792
+ with open(metadata_path, "w") as f:
793
+ f.write(f"{offset + file.size}/{total_size}")
794
+
795
+ return {"success": True, "received_bytes": offset + file.size}
796
+
797
+ @app.post("/api/finalize-audio-upload")
798
+ async def finalize_audio_upload(
799
+ upload_info: dict,
800
+ current_user: User = Depends(get_current_user)
801
+ ):
802
+ upload_id = upload_info.get("upload_id")
803
+ if not upload_id:
804
+ raise HTTPException(status_code=400, detail="Upload ID is required")
805
+
806
+ temp_dir = tempfile.gettempdir()
807
+ upload_path = os.path.join(temp_dir, f"transtudio_upload_{upload_id}")
808
+ if not os.path.exists(upload_path):
809
+ raise HTTPException(status_code=404, detail="Upload not found")
810
+
811
+ # Read metadata to get original filename if available
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(UPLOAD_DIR, filename)
817
+
818
+ with open(output_path, "wb") as outfile:
819
+ # Get all chunks sorted by offset
820
+ chunks = [f for f in os.listdir(upload_path) if f.startswith("chunk_")]
821
+ chunks.sort(key=lambda x: int(x.split("_")[1]))
822
+
823
+ for chunk_name in chunks:
824
+ chunk_path = os.path.join(upload_path, chunk_name)
825
+ with open(chunk_path, "rb") as infile:
826
+ shutil.copyfileobj(infile, outfile)
827
+
828
+ # Clean up temporary files
829
+ shutil.rmtree(upload_path)
830
+
831
+ return {"success": True, "filename": filename}
832
+
833
+ @app.post("/api/cancel-audio-upload")
834
+ async def cancel_audio_upload(
835
+ upload_info: dict,
836
+ current_user: User = Depends(get_current_user)
837
+ ):
838
+ upload_id = upload_info.get("upload_id")
839
+ if not upload_id:
840
+ raise HTTPException(status_code=400, detail="Upload ID is required")
841
+
842
+ temp_dir = tempfile.gettempdir()
843
+ upload_path = os.path.join(temp_dir, f"transtudio_upload_{upload_id}")
844
+ if os.path.exists(upload_path):
845
+ shutil.rmtree(upload_path)
846
+
847
+ return {"success": True}
848
+
849
+ @app.get("/api/audio-files")
850
+ 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
+ for filename in os.listdir(upload_dir):
859
+ file_path = os.path.join(upload_dir, filename)
860
+ if os.path.isfile(file_path) and not filename.startswith('.'):
861
+ # Get file stats
862
+ file_stats = os.stat(file_path)
863
+ file_size = os.path.getsize(file_path)
864
+
865
+ file_list.append({
866
+ "id": filename, # Use filename as ID
867
+ "filename": filename,
868
+ "original_filename": filename,
869
+ "size": file_size,
870
+ "uploaded_at": datetime.fromtimestamp(file_stats.st_mtime).isoformat()
871
+ })
872
+
873
+ # Sort by upload date (newest first)
874
+ file_list.sort(key=lambda x: x["uploaded_at"], reverse=True)
875
+
876
+ return file_list
877
+ except Exception as e:
878
+ print(f"Error getting audio files: {str(e)}")
879
+ raise HTTPException(status_code=500, detail=str(e))
880
+
881
+ @app.delete("/api/audio-files/{filename}")
882
+ async def delete_audio_file(
883
+ filename: str,
884
+ current_user: User = Depends(get_current_user)
885
+ ):
886
+ try:
887
+ # Check if file exists in uploads directory
888
+ file_path = os.path.join(os.getcwd(), UPLOAD_DIR, filename)
889
+ if not os.path.exists(file_path):
890
+ raise HTTPException(status_code=404, detail="Audio file not found")
891
+
892
+ # Delete the physical file
893
+ os.remove(file_path)
894
+
895
+ return {"status": "success", "message": "Audio file deleted successfully"}
896
+ except HTTPException:
897
+ raise
898
+ except Exception as e:
899
+ print(f"Error deleting audio file: {str(e)}")
900
+ raise HTTPException(status_code=500, detail=str(e))
901
+
902
+ @app.post("/extract-audio/")
903
+ async def extract_audio(file: UploadFile = File(...)):
904
+ # Save uploaded video temporarily
905
+ video_path = f"temp/{file.filename}"
906
+ audio_path = f"temp/{os.path.splitext(file.filename)[0]}.mp3"
907
+
908
+ with open(video_path, "wb") as buffer:
909
+ buffer.write(await file.read())
910
+
911
+ # Extract audio using FFmpeg
912
+ subprocess.run([
913
+ "ffmpeg", "-i", video_path,
914
+ "-q:a", "0", "-map", "a", audio_path
915
+ ])
916
+
917
+ # Clean up video file
918
+ os.remove(video_path)
919
+
920
+ # Return audio file
921
+ return FileResponse(audio_path)
922
+
923
  @app.get("/api/transcriptions")
924
  async def get_transcriptions(
925
  current_user: User = Depends(get_current_user),