update
This commit is contained in:
@@ -10,4 +10,4 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
CMD ["uvicorn", "base.app:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||||
|
|||||||
13
backend/api/profile_controller.py
Normal file
13
backend/api/profile_controller.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from api.schemas import ProfileResponse
|
||||||
|
from database.database import get_db
|
||||||
|
from model.profile import Profile
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/profiles", tags=["profiles"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("", response_model=list[ProfileResponse])
|
||||||
|
def get_profiles(db: Session = Depends(get_db)):
|
||||||
|
return Profile.get_all(db)
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
import asyncio
|
|
||||||
import threading
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from fastapi.responses import FileResponse, StreamingResponse
|
from fastapi.responses import FileResponse, StreamingResponse
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from database import get_db
|
from api.schemas import CleanupRequest, VideoCreate, VideoResponse
|
||||||
from schemas import CleanupRequest, ProfileResponse, VideoCreate, VideoResponse
|
from database.database import SessionLocal, get_db
|
||||||
from services import video_service
|
from download.download_service import download_video
|
||||||
from services.download_service import download_video
|
from model.video import Video
|
||||||
from services.stream_service import stream_video_live
|
from notify.notify_clients import notify_clients
|
||||||
from services.video_service import update_file_path
|
from stream.stream_service import stream_video_live
|
||||||
|
|
||||||
router = APIRouter(prefix="/videos", tags=["videos"])
|
router = APIRouter(prefix="/videos", tags=["videos"])
|
||||||
|
|
||||||
@@ -23,14 +22,14 @@ async def create_videos(videos_data: list[VideoCreate], db: Session = Depends(ge
|
|||||||
profile_ids = set()
|
profile_ids = set()
|
||||||
for video_data in reversed(videos_data):
|
for video_data in reversed(videos_data):
|
||||||
video_id_match = video_data.youtube_url.split("v=")[-1].split("&")[0]
|
video_id_match = video_data.youtube_url.split("v=")[-1].split("&")[0]
|
||||||
video_service.delete_by_youtube_id(db, video_id_match)
|
Video.delete_by_youtube_id(db, video_id_match)
|
||||||
video = video_service.create_video(db, video_data)
|
data = video_data.model_dump(exclude={"profile_id"})
|
||||||
|
video = Video.create_from_dict(db, data, video_data.profile_id)
|
||||||
created_ids.append(video.id)
|
created_ids.append(video.id)
|
||||||
profile_ids.add(video_data.profile_id or 1)
|
profile_ids.add(video_data.profile_id or 1)
|
||||||
videos = [video_service.get_video(db, vid) for vid in created_ids]
|
videos = [Video.get_by_id(db, vid) for vid in created_ids]
|
||||||
|
|
||||||
if profile_ids:
|
if profile_ids:
|
||||||
from main import notify_clients
|
|
||||||
await notify_clients(list(profile_ids))
|
await notify_clients(list(profile_ids))
|
||||||
|
|
||||||
return [VideoResponse.from_model(v) for v in videos if v]
|
return [VideoResponse.from_model(v) for v in videos if v]
|
||||||
@@ -38,25 +37,25 @@ async def create_videos(videos_data: list[VideoCreate], db: Session = Depends(ge
|
|||||||
|
|
||||||
@router.get("", response_model=list[VideoResponse])
|
@router.get("", response_model=list[VideoResponse])
|
||||||
def get_all_videos(profile_id: Optional[int] = Query(None), db: Session = Depends(get_db)):
|
def get_all_videos(profile_id: Optional[int] = Query(None), db: Session = Depends(get_db)):
|
||||||
videos = video_service.get_all_videos(db, profile_id=profile_id)
|
videos = Video.get_all(db, profile_id=profile_id)
|
||||||
return [VideoResponse.from_model(v) for v in videos]
|
return [VideoResponse.from_model(v) for v in videos]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/downloaded", response_model=list[VideoResponse])
|
@router.get("/downloaded", response_model=list[VideoResponse])
|
||||||
def get_downloaded_videos(profile_id: Optional[int] = Query(None), db: Session = Depends(get_db)):
|
def get_downloaded_videos(profile_id: Optional[int] = Query(None), db: Session = Depends(get_db)):
|
||||||
videos = video_service.get_downloaded_videos(db, profile_id=profile_id)
|
videos = Video.get_downloaded(db, profile_id=profile_id)
|
||||||
return [VideoResponse.from_model(v) for v in videos]
|
return [VideoResponse.from_model(v) for v in videos]
|
||||||
|
|
||||||
|
|
||||||
@router.post("/cleanup")
|
@router.post("/cleanup")
|
||||||
def cleanup_videos(request: CleanupRequest, db: Session = Depends(get_db)):
|
def cleanup_videos(request: CleanupRequest, db: Session = Depends(get_db)):
|
||||||
count = video_service.delete_not_downloaded(db, request.profile_id, request.exclude_ids or None)
|
count = Video.delete_not_downloaded(db, request.profile_id, request.exclude_ids or None)
|
||||||
return {"deleted": count}
|
return {"deleted": count}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{video_id}/download")
|
@router.post("/{video_id}/download")
|
||||||
def trigger_download(video_id: int, db: Session = Depends(get_db)):
|
def trigger_download(video_id: int, db: Session = Depends(get_db)):
|
||||||
video = video_service.get_video(db, video_id)
|
video = Video.get_by_id(db, video_id)
|
||||||
if not video:
|
if not video:
|
||||||
raise HTTPException(status_code=404, detail="Video nicht gefunden")
|
raise HTTPException(status_code=404, detail="Video nicht gefunden")
|
||||||
if video.file_path:
|
if video.file_path:
|
||||||
@@ -69,7 +68,7 @@ def trigger_download(video_id: int, db: Session = Depends(get_db)):
|
|||||||
|
|
||||||
@router.get("/{video_id}/stream")
|
@router.get("/{video_id}/stream")
|
||||||
def stream_video(video_id: int, db: Session = Depends(get_db)):
|
def stream_video(video_id: int, db: Session = Depends(get_db)):
|
||||||
video = video_service.get_video(db, video_id)
|
video = Video.get_by_id(db, video_id)
|
||||||
if not video:
|
if not video:
|
||||||
raise HTTPException(status_code=404, detail="Video nicht gefunden")
|
raise HTTPException(status_code=404, detail="Video nicht gefunden")
|
||||||
|
|
||||||
@@ -78,9 +77,9 @@ def stream_video(video_id: int, db: Session = Depends(get_db)):
|
|||||||
output_path = f"/videos/{video_id}.mp4"
|
output_path = f"/videos/{video_id}.mp4"
|
||||||
yield from stream_video_live(video_id, video.youtube_url)
|
yield from stream_video_live(video_id, video.youtube_url)
|
||||||
if Path(output_path).exists():
|
if Path(output_path).exists():
|
||||||
sdb = __import__("database").SessionLocal()
|
sdb = SessionLocal()
|
||||||
try:
|
try:
|
||||||
update_file_path(sdb, video_id, output_path)
|
Video.update_file_path(sdb, video_id, output_path)
|
||||||
finally:
|
finally:
|
||||||
sdb.close()
|
sdb.close()
|
||||||
|
|
||||||
@@ -95,7 +94,7 @@ def stream_video(video_id: int, db: Session = Depends(get_db)):
|
|||||||
|
|
||||||
@router.get("/{video_id}/file")
|
@router.get("/{video_id}/file")
|
||||||
def download_file(video_id: int, db: Session = Depends(get_db)):
|
def download_file(video_id: int, db: Session = Depends(get_db)):
|
||||||
video = video_service.get_video(db, video_id)
|
video = Video.get_by_id(db, video_id)
|
||||||
if not video:
|
if not video:
|
||||||
raise HTTPException(status_code=404, detail="Video nicht gefunden")
|
raise HTTPException(status_code=404, detail="Video nicht gefunden")
|
||||||
if not video.file_path:
|
if not video.file_path:
|
||||||
@@ -103,7 +102,7 @@ def download_file(video_id: int, db: Session = Depends(get_db)):
|
|||||||
|
|
||||||
path = Path(video.file_path)
|
path = Path(video.file_path)
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
video_service.update_file_path(db, video_id, None)
|
Video.update_file_path(db, video_id, None)
|
||||||
raise HTTPException(status_code=404, detail="Video noch nicht heruntergeladen")
|
raise HTTPException(status_code=404, detail="Video noch nicht heruntergeladen")
|
||||||
|
|
||||||
return FileResponse(path, media_type="video/mp4", filename=f"{video.title}.mp4")
|
return FileResponse(path, media_type="video/mp4", filename=f"{video.title}.mp4")
|
||||||
@@ -111,20 +110,12 @@ def download_file(video_id: int, db: Session = Depends(get_db)):
|
|||||||
|
|
||||||
@router.delete("/{video_id}/file")
|
@router.delete("/{video_id}/file")
|
||||||
def delete_server_file(video_id: int, db: Session = Depends(get_db)):
|
def delete_server_file(video_id: int, db: Session = Depends(get_db)):
|
||||||
video = video_service.get_video(db, video_id)
|
video = Video.get_by_id(db, video_id)
|
||||||
if not video:
|
if not video:
|
||||||
raise HTTPException(status_code=404, detail="Video nicht gefunden")
|
raise HTTPException(status_code=404, detail="Video nicht gefunden")
|
||||||
if video.file_path:
|
if video.file_path:
|
||||||
path = Path(video.file_path)
|
path = Path(video.file_path)
|
||||||
if path.exists():
|
if path.exists():
|
||||||
path.unlink()
|
path.unlink()
|
||||||
video_service.update_file_path(db, video_id, None)
|
Video.update_file_path(db, video_id, None)
|
||||||
return {"status": "deleted"}
|
return {"status": "deleted"}
|
||||||
|
|
||||||
|
|
||||||
profiles_router = APIRouter(prefix="/profiles", tags=["profiles"])
|
|
||||||
|
|
||||||
|
|
||||||
@profiles_router.get("", response_model=list[ProfileResponse])
|
|
||||||
def get_profiles(db: Session = Depends(get_db)):
|
|
||||||
return video_service.get_all_profiles(db)
|
|
||||||
36
backend/base/app.py
Normal file
36
backend/base/app.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
from api.profile_controller import router as profiles_router
|
||||||
|
from api.video_controller import router as videos_router
|
||||||
|
from database.database import SessionLocal, create_tables
|
||||||
|
from model.profile import Profile
|
||||||
|
from notify.notify_clients import register_websocket
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
app.include_router(videos_router)
|
||||||
|
app.include_router(profiles_router)
|
||||||
|
register_websocket(app)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
def startup():
|
||||||
|
create_tables()
|
||||||
|
db = SessionLocal()
|
||||||
|
if db.query(Profile).count() == 0:
|
||||||
|
db.add(Profile(name="Standard"))
|
||||||
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def root():
|
||||||
|
return {"status": "running"}
|
||||||
0
backend/database/__init__.py
Normal file
0
backend/database/__init__.py
Normal file
0
backend/download/__init__.py
Normal file
0
backend/download/__init__.py
Normal file
@@ -1,9 +1,11 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from database import SessionLocal
|
from database.database import SessionLocal
|
||||||
from services.video_service import get_video, update_file_path
|
from model.video import Video
|
||||||
|
|
||||||
VIDEOS_DIR = "/videos"
|
VIDEOS_DIR = "/videos"
|
||||||
|
MIN_VALID_SIZE = 1024 * 100 # 100 KB
|
||||||
|
|
||||||
|
|
||||||
def download_video(video_id: int, youtube_url: str):
|
def download_video(video_id: int, youtube_url: str):
|
||||||
@@ -15,13 +17,19 @@ def download_video(video_id: int, youtube_url: str):
|
|||||||
"-f", "bestvideo[ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/best[ext=mp4]",
|
"-f", "bestvideo[ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/best[ext=mp4]",
|
||||||
"-o", output_path,
|
"-o", output_path,
|
||||||
"--merge-output-format", "mp4",
|
"--merge-output-format", "mp4",
|
||||||
|
"--force-overwrites",
|
||||||
|
"--no-continue",
|
||||||
youtube_url,
|
youtube_url,
|
||||||
],
|
],
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
path = Path(output_path)
|
||||||
|
if not path.exists() or path.stat().st_size < MIN_VALID_SIZE:
|
||||||
|
return
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
update_file_path(db, video_id, output_path)
|
Video.update_file_path(db, video_id, output_path)
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
|
|
||||||
from database import SessionLocal, create_tables
|
|
||||||
from models import Profile
|
|
||||||
from routes.videos import profiles_router, router as videos_router
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["*"],
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
app.include_router(videos_router)
|
|
||||||
app.include_router(profiles_router)
|
|
||||||
|
|
||||||
# --- WebSocket ---
|
|
||||||
|
|
||||||
connected_clients: set[WebSocket] = set()
|
|
||||||
|
|
||||||
|
|
||||||
@app.websocket("/ws")
|
|
||||||
async def websocket_endpoint(websocket: WebSocket):
|
|
||||||
await websocket.accept()
|
|
||||||
connected_clients.add(websocket)
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
await websocket.receive_text()
|
|
||||||
except WebSocketDisconnect:
|
|
||||||
connected_clients.discard(websocket)
|
|
||||||
|
|
||||||
|
|
||||||
async def notify_clients(profile_ids: list[int]):
|
|
||||||
message = ",".join(str(pid) for pid in profile_ids)
|
|
||||||
for client in list(connected_clients):
|
|
||||||
try:
|
|
||||||
await client.send_text(message)
|
|
||||||
except Exception:
|
|
||||||
connected_clients.discard(client)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Startup ---
|
|
||||||
|
|
||||||
@app.on_event("startup")
|
|
||||||
def startup():
|
|
||||||
create_tables()
|
|
||||||
db = SessionLocal()
|
|
||||||
if db.query(Profile).count() == 0:
|
|
||||||
db.add(Profile(name="Standard"))
|
|
||||||
db.commit()
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
|
||||||
def root():
|
|
||||||
return {"status": "running"}
|
|
||||||
0
backend/model/__init__.py
Normal file
0
backend/model/__init__.py
Normal file
15
backend/model/profile.py
Normal file
15
backend/model/profile.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from database.database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class Profile(Base):
|
||||||
|
__tablename__ = "profiles"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
name = Column(String, nullable=False, unique=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all(cls, db: Session) -> list["Profile"]:
|
||||||
|
return db.query(cls).all()
|
||||||
10
backend/model/profile_video.py
Normal file
10
backend/model/profile_video.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from sqlalchemy import Column, ForeignKey, Integer, Table
|
||||||
|
|
||||||
|
from database.database import Base
|
||||||
|
|
||||||
|
video_profiles = Table(
|
||||||
|
"video_profiles",
|
||||||
|
Base.metadata,
|
||||||
|
Column("video_id", Integer, ForeignKey("videos.id", ondelete="CASCADE")),
|
||||||
|
Column("profile_id", Integer, ForeignKey("profiles.id", ondelete="CASCADE")),
|
||||||
|
)
|
||||||
77
backend/model/video.py
Normal file
77
backend/model/video.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String
|
||||||
|
from sqlalchemy.orm import Session, relationship
|
||||||
|
|
||||||
|
from database.database import Base
|
||||||
|
from model.profile import Profile
|
||||||
|
from model.profile_video import video_profiles
|
||||||
|
|
||||||
|
|
||||||
|
class Video(Base):
|
||||||
|
__tablename__ = "videos"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
title = Column(String, nullable=False)
|
||||||
|
youtuber = Column(String, nullable=False)
|
||||||
|
thumbnail_url = Column(String, nullable=False)
|
||||||
|
youtube_url = Column(String, nullable=False)
|
||||||
|
file_path = Column(String, nullable=True)
|
||||||
|
profiles = relationship("Profile", secondary=video_profiles, backref="videos")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_dict(cls, db: Session, data: dict, profile_id: int | None) -> "Video":
|
||||||
|
video = cls(**data)
|
||||||
|
if not profile_id:
|
||||||
|
profile_id = 1
|
||||||
|
profile = db.query(Profile).filter(Profile.id == profile_id).first()
|
||||||
|
if profile:
|
||||||
|
video.profiles.append(profile)
|
||||||
|
db.add(video)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(video)
|
||||||
|
return video
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all(cls, db: Session, profile_id: int | None = None) -> list["Video"]:
|
||||||
|
query = db.query(cls)
|
||||||
|
if profile_id:
|
||||||
|
query = query.filter(cls.profiles.any(Profile.id == profile_id))
|
||||||
|
return query.order_by(cls.id.desc()).all()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_downloaded(cls, db: Session, profile_id: int | None = None) -> list["Video"]:
|
||||||
|
query = db.query(cls).filter(cls.file_path.isnot(None))
|
||||||
|
if profile_id:
|
||||||
|
query = query.filter(cls.profiles.any(Profile.id == profile_id))
|
||||||
|
return query.order_by(cls.id.desc()).all()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_id(cls, db: Session, video_id: int) -> "Video | None":
|
||||||
|
return db.query(cls).filter(cls.id == video_id).first()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_by_youtube_id(cls, db: Session, youtube_id: str):
|
||||||
|
db.query(cls).filter(cls.youtube_url.contains(youtube_id)).delete(synchronize_session=False)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_file_path(cls, db: Session, video_id: int, path: str | None):
|
||||||
|
video = cls.get_by_id(db, video_id)
|
||||||
|
if video:
|
||||||
|
video.file_path = path
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_not_downloaded(cls, db: Session, profile_id: int, exclude_ids: list[int] | None = None) -> int:
|
||||||
|
query = db.query(cls).filter(
|
||||||
|
cls.profiles.any(Profile.id == profile_id),
|
||||||
|
)
|
||||||
|
if exclude_ids:
|
||||||
|
query = query.filter(cls.id.notin_(exclude_ids))
|
||||||
|
videos = query.all()
|
||||||
|
video_ids = [v.id for v in videos]
|
||||||
|
if not video_ids:
|
||||||
|
return 0
|
||||||
|
db.execute(video_profiles.delete().where(video_profiles.c.video_id.in_(video_ids)))
|
||||||
|
db.query(cls).filter(cls.id.in_(video_ids)).delete(synchronize_session=False)
|
||||||
|
db.commit()
|
||||||
|
return len(video_ids)
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
from sqlalchemy import Column, ForeignKey, Integer, String, Table
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
from database import Base
|
|
||||||
|
|
||||||
video_profiles = Table(
|
|
||||||
"video_profiles",
|
|
||||||
Base.metadata,
|
|
||||||
Column("video_id", Integer, ForeignKey("videos.id", ondelete="CASCADE")),
|
|
||||||
Column("profile_id", Integer, ForeignKey("profiles.id", ondelete="CASCADE")),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Profile(Base):
|
|
||||||
__tablename__ = "profiles"
|
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
||||||
name = Column(String, nullable=False, unique=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Video(Base):
|
|
||||||
__tablename__ = "videos"
|
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
||||||
title = Column(String, nullable=False)
|
|
||||||
youtuber = Column(String, nullable=False)
|
|
||||||
thumbnail_url = Column(String, nullable=False)
|
|
||||||
youtube_url = Column(String, nullable=False)
|
|
||||||
file_path = Column(String, nullable=True)
|
|
||||||
profiles = relationship("Profile", secondary=video_profiles, backref="videos")
|
|
||||||
0
backend/notify/__init__.py
Normal file
0
backend/notify/__init__.py
Normal file
24
backend/notify/notify_clients.py
Normal file
24
backend/notify/notify_clients.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||||
|
|
||||||
|
connected_clients: set[WebSocket] = set()
|
||||||
|
|
||||||
|
|
||||||
|
async def notify_clients(profile_ids: list[int]):
|
||||||
|
message = ",".join(str(pid) for pid in profile_ids)
|
||||||
|
for client in list(connected_clients):
|
||||||
|
try:
|
||||||
|
await client.send_text(message)
|
||||||
|
except Exception:
|
||||||
|
connected_clients.discard(client)
|
||||||
|
|
||||||
|
|
||||||
|
def register_websocket(app: FastAPI):
|
||||||
|
@app.websocket("/ws")
|
||||||
|
async def websocket_endpoint(websocket: WebSocket):
|
||||||
|
await websocket.accept()
|
||||||
|
connected_clients.add(websocket)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
await websocket.receive_text()
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
connected_clients.discard(websocket)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,69 +0,0 @@
|
|||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from models import Profile, Video, video_profiles
|
|
||||||
from schemas import VideoCreate
|
|
||||||
|
|
||||||
|
|
||||||
def create_video(db: Session, video_data: VideoCreate) -> Video:
|
|
||||||
profile_id = video_data.profile_id
|
|
||||||
data = video_data.model_dump(exclude={"profile_id"})
|
|
||||||
video = Video(**data)
|
|
||||||
if not profile_id:
|
|
||||||
profile_id = 1
|
|
||||||
profile = db.query(Profile).filter(Profile.id == profile_id).first()
|
|
||||||
if profile:
|
|
||||||
video.profiles.append(profile)
|
|
||||||
db.add(video)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(video)
|
|
||||||
return video
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_videos(db: Session, profile_id: int | None = None) -> list[Video]:
|
|
||||||
query = db.query(Video)
|
|
||||||
if profile_id:
|
|
||||||
query = query.filter(Video.profiles.any(Profile.id == profile_id))
|
|
||||||
return query.order_by(Video.id.desc()).all()
|
|
||||||
|
|
||||||
|
|
||||||
def get_downloaded_videos(db: Session, profile_id: int | None = None) -> list[Video]:
|
|
||||||
query = db.query(Video).filter(Video.file_path.isnot(None))
|
|
||||||
if profile_id:
|
|
||||||
query = query.filter(Video.profiles.any(Profile.id == profile_id))
|
|
||||||
return query.order_by(Video.id.desc()).all()
|
|
||||||
|
|
||||||
|
|
||||||
def get_video(db: Session, video_id: int) -> Video | None:
|
|
||||||
return db.query(Video).filter(Video.id == video_id).first()
|
|
||||||
|
|
||||||
|
|
||||||
def delete_by_youtube_id(db: Session, youtube_id: str):
|
|
||||||
db.query(Video).filter(Video.youtube_url.contains(youtube_id)).delete(synchronize_session=False)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def update_file_path(db: Session, video_id: int, path: str):
|
|
||||||
video = get_video(db, video_id)
|
|
||||||
if video:
|
|
||||||
video.file_path = path
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def delete_not_downloaded(db: Session, profile_id: int, exclude_ids: list[int] | None = None) -> int:
|
|
||||||
query = db.query(Video).filter(
|
|
||||||
Video.profiles.any(Profile.id == profile_id),
|
|
||||||
)
|
|
||||||
if exclude_ids:
|
|
||||||
query = query.filter(Video.id.notin_(exclude_ids))
|
|
||||||
videos = query.all()
|
|
||||||
video_ids = [v.id for v in videos]
|
|
||||||
if not video_ids:
|
|
||||||
return 0
|
|
||||||
db.execute(video_profiles.delete().where(video_profiles.c.video_id.in_(video_ids)))
|
|
||||||
db.query(Video).filter(Video.id.in_(video_ids)).delete(synchronize_session=False)
|
|
||||||
db.commit()
|
|
||||||
return len(video_ids)
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_profiles(db: Session) -> list[Profile]:
|
|
||||||
return db.query(Profile).all()
|
|
||||||
0
backend/stream/__init__.py
Normal file
0
backend/stream/__init__.py
Normal file
@@ -11,14 +11,14 @@
|
|||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["*://www.youtube.com/*"],
|
"matches": ["*://www.youtube.com/*"],
|
||||||
"js": ["content.js"]
|
"js": ["tracking/content.js"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["background.js"]
|
"scripts": ["api/background.js"]
|
||||||
},
|
},
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_popup": "popup.html",
|
"default_popup": "config/popup.html",
|
||||||
"default_title": "Profil auswählen"
|
"default_title": "Profil auswählen"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user