peace2024 commited on
Commit
6d01d5b
·
1 Parent(s): 0448876

Upload 23 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a lightweight Python image
2
+ FROM python:3.10-slim
3
+
4
+ # Create app directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Copy dependency files
11
+ COPY requirements.txt .
12
+
13
+ # Install Python deps
14
+ RUN pip install --upgrade pip && pip install -r requirements.txt
15
+
16
+ # Copy the source code
17
+ COPY . .
18
+
19
+ # Expose port (default for Hugging Face Spaces)
20
+ EXPOSE 7860
21
+
22
+ # Run app using Uvicorn
23
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
Readme.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Dubsway Video AI
2
+
3
+ This FastAPI app handles authentication, video uploads, and PDF analysis using Whisper and Transformers.
4
+
5
+ - 🔐 Auth with email
6
+ - 📤 Upload any video file
7
+ - 📄 Generates summary PDFs
app/__init__.py ADDED
File without changes
app/__pycache__/__init__.cpython-39.pyc ADDED
Binary file (185 Bytes). View file
 
app/__pycache__/auth.cpython-39.pyc ADDED
Binary file (2.46 kB). View file
 
app/__pycache__/dashboard.cpython-39.pyc ADDED
Binary file (1.43 kB). View file
 
app/__pycache__/database.cpython-39.pyc ADDED
Binary file (834 Bytes). View file
 
app/__pycache__/main.cpython-39.pyc ADDED
Binary file (1.31 kB). View file
 
app/__pycache__/models.cpython-39.pyc ADDED
Binary file (1.16 kB). View file
 
app/__pycache__/upload.cpython-39.pyc ADDED
Binary file (1.47 kB). View file
 
app/auth.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Depends
2
+ from sqlalchemy.ext.asyncio import AsyncSession
3
+ from sqlalchemy.future import select
4
+ from passlib.context import CryptContext
5
+ from jose import jwt
6
+ from pydantic import BaseModel, EmailStr
7
+ from app.database import get_db # Updated: use the correct async session dependency
8
+ from app.models import User
9
+ import os
10
+ import logging
11
+ from dotenv import load_dotenv
12
+
13
+ router = APIRouter()
14
+ logger = logging.getLogger(__name__)
15
+
16
+ load_dotenv()
17
+
18
+ # Load secret key and JWT algorithm
19
+ SECRET_KEY = os.getenv("SECRET_KEY", "secret")
20
+ ALGORITHM = "HS256"
21
+
22
+ # Password hashing config
23
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
24
+
25
+
26
+ # Request Schemas
27
+ class SignUp(BaseModel):
28
+ email: EmailStr
29
+ password: str
30
+
31
+
32
+ class Login(BaseModel):
33
+ email: EmailStr
34
+ password: str
35
+
36
+
37
+ @router.post("/auth/signup")
38
+ async def signup(data: SignUp, db: AsyncSession = Depends(get_db)):
39
+ # Check if user already exists
40
+ result = await db.execute(select(User).where(User.email == data.email))
41
+ existing_user = result.scalar_one_or_none()
42
+
43
+ if existing_user:
44
+ raise HTTPException(status_code=400, detail="Email already exists")
45
+
46
+ hashed_password = pwd_context.hash(data.password)
47
+ new_user = User(email=data.email, hashed_password=hashed_password)
48
+
49
+ try:
50
+ db.add(new_user)
51
+ await db.commit()
52
+ await db.refresh(new_user)
53
+ return {"message": "User created", "user_id": new_user.id}
54
+ except Exception as e:
55
+ await db.rollback()
56
+ logger.error(f"Signup error: {e}")
57
+ raise HTTPException(status_code=500, detail="Internal Server Error")
58
+
59
+
60
+ @router.post("/auth/login")
61
+ async def login(data: Login, db: AsyncSession = Depends(get_db)):
62
+ result = await db.execute(select(User).where(User.email == data.email))
63
+ user = result.scalar_one_or_none()
64
+
65
+ if not user or not pwd_context.verify(data.password, user.hashed_password):
66
+ raise HTTPException(status_code=401, detail="Invalid credentials")
67
+
68
+ token = jwt.encode({"user_id": user.id}, SECRET_KEY, algorithm=ALGORITHM)
69
+ return {
70
+ "access_token": token,
71
+ "token_type": "bearer",
72
+ "user": {"id": user.id, "email": user.email},
73
+ }
app/dashboard.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from sqlalchemy.ext.asyncio import AsyncSession
3
+ from sqlalchemy.future import select
4
+ from sqlalchemy.orm import sessionmaker
5
+ from sqlalchemy import desc
6
+ from app.database import engine
7
+ from app.models import VideoUpload
8
+
9
+ router = APIRouter()
10
+
11
+ # Create async DB session
12
+ async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
13
+
14
+
15
+ @router.get("/dashboard/{user_id}")
16
+ async def get_user_dashboard(user_id: int):
17
+ try:
18
+ async with async_session() as session:
19
+ query = (
20
+ select(VideoUpload)
21
+ .where(VideoUpload.user_id == user_id)
22
+ .order_by(desc(VideoUpload.created_at))
23
+ )
24
+ result = await session.execute(query)
25
+ uploads = result.scalars().all()
26
+
27
+ # Convert SQLAlchemy objects to dicts for response
28
+ return [
29
+ {
30
+ "id": upload.id,
31
+ "video_url": upload.video_url,
32
+ "pdf_url": upload.pdf_url,
33
+ "status": upload.status,
34
+ "created_at": upload.created_at,
35
+ }
36
+ for upload in uploads
37
+ ]
38
+ except Exception as e:
39
+ raise HTTPException(
40
+ status_code=500, detail=f"Error fetching dashboard data: {str(e)}"
41
+ )
app/database.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
3
+ from sqlalchemy.orm import sessionmaker, declarative_base
4
+ from dotenv import load_dotenv
5
+
6
+ # Load .env variables
7
+ load_dotenv()
8
+
9
+ # Ensure your DATABASE_URL is in correct asyncpg format
10
+ DATABASE_URL = os.getenv("DATABASE_URL")
11
+
12
+ if not DATABASE_URL:
13
+ raise RuntimeError("DATABASE_URL is not set in environment.")
14
+
15
+ # Create the async engine
16
+ engine = create_async_engine(
17
+ DATABASE_URL, echo=True, future=True # Set echo=False in production
18
+ )
19
+
20
+ # Session factory
21
+ AsyncSessionLocal = sessionmaker(
22
+ bind=engine, class_=AsyncSession, expire_on_commit=False
23
+ )
24
+
25
+ # Base class for models
26
+ Base = declarative_base()
27
+
28
+
29
+ # Dependency for routes to get the async session
30
+ async def get_db():
31
+ async with AsyncSessionLocal() as session:
32
+ yield session
app/main.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import logging
4
+
5
+ from app.auth import router as auth_router
6
+ from app.upload import router as upload_router
7
+ from app.dashboard import router as dashboard_router
8
+
9
+ # Initialize logger
10
+ logging.basicConfig(level=logging.INFO)
11
+ logger = logging.getLogger(__name__)
12
+
13
+ app = FastAPI(
14
+ title="Dubsway Video AI",
15
+ description="Production-ready API for auth, video upload, and analysis",
16
+ version="1.0.0",
17
+ docs_url="/docs", # Optional: secure this in prod
18
+ redoc_url=None,
19
+ )
20
+
21
+ # Allow frontend (adjust in prod!)
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["*"], # REPLACE with frontend URL in production!
25
+ allow_credentials=True,
26
+ allow_methods=["*"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ # Include API routes
31
+ app.include_router(auth_router, prefix="/api", tags=["Auth"])
32
+ app.include_router(upload_router, prefix="/api", tags=["Upload"])
33
+ app.include_router(dashboard_router, prefix="/api", tags=["Dashboard"])
34
+
35
+
36
+ @app.on_event("startup")
37
+ async def startup_event():
38
+ logger.info("✅ FastAPI app started")
39
+
40
+
41
+ @app.on_event("shutdown")
42
+ async def shutdown_event():
43
+ logger.info("🛑 FastAPI app shutdown")
app/models.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime
2
+ from sqlalchemy.sql import func
3
+ from .database import Base
4
+
5
+
6
+ class User(Base):
7
+ __tablename__ = "users"
8
+ id = Column(Integer, primary_key=True, index=True)
9
+ email = Column(String, unique=True, index=True)
10
+ hashed_password = Column(String)
11
+
12
+
13
+ class VideoUpload(Base):
14
+ __tablename__ = "video_uploads"
15
+ id = Column(Integer, primary_key=True, index=True)
16
+ user_id = Column(Integer, ForeignKey("users.id"))
17
+ video_url = Column(Text)
18
+ pdf_url = Column(Text)
19
+ status = Column(String, default="pending") # pending, processing, completed
20
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
21
+ updated_at = Column(DateTime(timezone=True), onupdate=func.now())
app/run_once.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # run_once.py
2
+ from app.database import Base, engine
3
+ from app import models
4
+
5
+
6
+ async def init():
7
+ async with engine.begin() as conn:
8
+ await conn.run_sync(Base.metadata.create_all)
9
+
10
+
11
+ import asyncio
12
+
13
+ asyncio.run(init())
app/testdb.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # test_db.py
2
+ from app.database import engine
3
+ from sqlalchemy import text
4
+ import asyncio
5
+
6
+
7
+ async def test_connection():
8
+ async with engine.connect() as conn:
9
+ result = await conn.execute(text("SELECT 1"))
10
+ print("DB Connection OK:", result.scalar())
11
+
12
+
13
+ asyncio.run(test_connection())
app/upload.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, UploadFile, Form, HTTPException, Depends
2
+ from sqlalchemy.ext.asyncio import AsyncSession
3
+ from sqlalchemy.future import select
4
+ from app.database import get_db
5
+ from app.models import VideoUpload
6
+ from .utils.s3 import upload_to_s3
7
+ import uuid
8
+ import os
9
+
10
+ router = APIRouter()
11
+
12
+ # Accept any video file
13
+ ALLOWED_VIDEO_MIME_TYPES = {
14
+ "video/mp4",
15
+ "video/x-matroska", # mkv
16
+ "video/quicktime", # mov
17
+ "video/x-msvideo", # avi
18
+ "video/webm",
19
+ "video/mpeg",
20
+ }
21
+
22
+
23
+ @router.post("/upload")
24
+ async def upload_video(
25
+ user_id: int = Form(...),
26
+ file: UploadFile = Form(...),
27
+ db: AsyncSession = Depends(get_db),
28
+ ):
29
+ if file.content_type not in ALLOWED_VIDEO_MIME_TYPES:
30
+ raise HTTPException(
31
+ status_code=400, detail=f"Unsupported file type: {file.content_type}"
32
+ )
33
+
34
+ try:
35
+ uid = str(uuid.uuid4())
36
+ s3_key = f"videos/{uid}/{file.filename}"
37
+ video_url = upload_to_s3(file, s3_key)
38
+
39
+ new_video = VideoUpload(
40
+ user_id=user_id,
41
+ video_url=video_url,
42
+ pdf_url="", # will be set after analysis
43
+ status="pending",
44
+ )
45
+ db.add(new_video)
46
+ await db.commit()
47
+ await db.refresh(new_video)
48
+
49
+ return {"status": "uploaded", "video_url": video_url, "video_id": new_video.id}
50
+
51
+ except Exception as e:
52
+ await db.rollback()
53
+ raise HTTPException(status_code=500, detail=str(e))
app/utils/__pycache__/s3.cpython-39.pyc ADDED
Binary file (930 Bytes). View file
 
app/utils/__pycache__/whisper_llm.cpython-39.pyc ADDED
Binary file (936 Bytes). View file
 
app/utils/pdf.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from reportlab.pdfgen import canvas
2
+ from io import BytesIO
3
+
4
+
5
+ def generate(transcription: str, summary: str):
6
+ buffer = BytesIO()
7
+ c = canvas.Canvas(buffer)
8
+ c.drawString(100, 800, "📄 Video Summary Report")
9
+ c.drawString(100, 770, "Transcription:")
10
+ c.drawString(100, 750, transcription[:1000])
11
+ c.drawString(100, 700, "Summary:")
12
+ c.drawString(100, 680, summary[:1000])
13
+ c.save()
14
+ buffer.seek(0)
15
+ return buffer.read()
app/utils/s3.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import boto3, os
2
+ from dotenv import load_dotenv
3
+
4
+ s3 = boto3.client(
5
+ "s3",
6
+ aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
7
+ aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
8
+ region_name=os.getenv("AWS_REGION"),
9
+ )
10
+ bucket = os.getenv("S3_BUCKET")
11
+
12
+
13
+ def upload_to_s3(file, key):
14
+ s3.upload_fileobj(file.file, bucket, key)
15
+ return f"https://{bucket}.s3.amazonaws.com/{key}"
16
+
17
+
18
+ def upload_pdf_bytes(data: bytes, key: str):
19
+ s3.put_object(Bucket=bucket, Key=key, Body=data, ContentType="application/pdf")
20
+ return f"https://{bucket}.s3.amazonaws.com/{key}"
app/utils/whisper_llm.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import whisper
2
+ from transformers import pipeline
3
+ import requests
4
+ import tempfile
5
+
6
+
7
+ def analyze(video_url: str):
8
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
9
+ with requests.get(video_url, stream=True) as r:
10
+ for chunk in r.iter_content(8192):
11
+ tmp.write(chunk)
12
+ tmp.close()
13
+
14
+ model = whisper.load_model("base")
15
+ result = model.transcribe(tmp.name)
16
+ text = result["text"]
17
+
18
+ summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
19
+ summary = summarizer(text, max_length=512, min_length=128, do_sample=False)[0][
20
+ "summary_text"
21
+ ]
22
+ return text, summary