Spaces:
Running
Running
Commit ·
47bd5bb
1
Parent(s): ff6b176
ok
Browse files- app/api/server.py +0 -2
- app/api/v1/router.py +2 -0
- app/config.py +0 -2
- app/services/converter_service.py +49 -31
app/api/server.py
CHANGED
|
@@ -11,7 +11,6 @@ from app.config import get_settings
|
|
| 11 |
from app.core.database import pool_manager
|
| 12 |
from app.core.logger import get_logger
|
| 13 |
from app.api.v1.router import api_v1_router
|
| 14 |
-
from app.api.verify import router as verify_router
|
| 15 |
|
| 16 |
_logger = get_logger(__name__)
|
| 17 |
_settings = get_settings()
|
|
@@ -66,7 +65,6 @@ def create_application() -> FastAPI:
|
|
| 66 |
)
|
| 67 |
|
| 68 |
app.include_router(api_v1_router, prefix="/api/v1")
|
| 69 |
-
app.include_router(verify_router, prefix="/verify")
|
| 70 |
|
| 71 |
@app.get("/", include_in_schema=False)
|
| 72 |
async def root():
|
|
|
|
| 11 |
from app.core.database import pool_manager
|
| 12 |
from app.core.logger import get_logger
|
| 13 |
from app.api.v1.router import api_v1_router
|
|
|
|
| 14 |
|
| 15 |
_logger = get_logger(__name__)
|
| 16 |
_settings = get_settings()
|
|
|
|
| 65 |
)
|
| 66 |
|
| 67 |
app.include_router(api_v1_router, prefix="/api/v1")
|
|
|
|
| 68 |
|
| 69 |
@app.get("/", include_in_schema=False)
|
| 70 |
async def root():
|
app/api/v1/router.py
CHANGED
|
@@ -3,9 +3,11 @@ from __future__ import annotations
|
|
| 3 |
from fastapi import APIRouter
|
| 4 |
|
| 5 |
from app.api.v1 import batch, convert, database, system
|
|
|
|
| 6 |
|
| 7 |
api_v1_router = APIRouter()
|
| 8 |
api_v1_router.include_router(convert.router, tags=["Convert"])
|
| 9 |
api_v1_router.include_router(batch.router, tags=["Batch"])
|
| 10 |
api_v1_router.include_router(system.router, tags=["System"])
|
| 11 |
api_v1_router.include_router(database.router, tags=["Database"])
|
|
|
|
|
|
| 3 |
from fastapi import APIRouter
|
| 4 |
|
| 5 |
from app.api.v1 import batch, convert, database, system
|
| 6 |
+
from app.api.verify import router as verify_router
|
| 7 |
|
| 8 |
api_v1_router = APIRouter()
|
| 9 |
api_v1_router.include_router(convert.router, tags=["Convert"])
|
| 10 |
api_v1_router.include_router(batch.router, tags=["Batch"])
|
| 11 |
api_v1_router.include_router(system.router, tags=["System"])
|
| 12 |
api_v1_router.include_router(database.router, tags=["Database"])
|
| 13 |
+
api_v1_router.include_router(verify_router, prefix="/verify", tags=["Verify"])
|
app/config.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from functools import lru_cache
|
| 4 |
-
from typing import Optional
|
| 5 |
|
| 6 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 7 |
|
|
@@ -36,7 +35,6 @@ class Settings(BaseSettings):
|
|
| 36 |
|
| 37 |
self_ping_url: str = "https://aetherbase-llm-ready-data.hf.space/ping"
|
| 38 |
spacy_model: str = "en_core_web_sm"
|
| 39 |
-
|
| 40 |
@property
|
| 41 |
def max_upload_mb(self) -> int:
|
| 42 |
return self.max_upload_bytes // (1024 * 1024)
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from functools import lru_cache
|
|
|
|
| 4 |
|
| 5 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 6 |
|
|
|
|
| 35 |
|
| 36 |
self_ping_url: str = "https://aetherbase-llm-ready-data.hf.space/ping"
|
| 37 |
spacy_model: str = "en_core_web_sm"
|
|
|
|
| 38 |
@property
|
| 39 |
def max_upload_mb(self) -> int:
|
| 40 |
return self.max_upload_bytes // (1024 * 1024)
|
app/services/converter_service.py
CHANGED
|
@@ -6,44 +6,27 @@ import mimetypes
|
|
| 6 |
import re
|
| 7 |
import time
|
| 8 |
from pathlib import Path
|
| 9 |
-
from
|
| 10 |
-
from urllib.parse import urlparse, parse_qs
|
| 11 |
|
| 12 |
import httpx
|
| 13 |
from markitdown import MarkItDown
|
| 14 |
|
| 15 |
-
from app.config import get_settings
|
| 16 |
from app.core.constants import IMAGE_EXTENSIONS, IMAGE_MIME_PREFIXES
|
| 17 |
from app.core.logger import get_logger
|
| 18 |
from app.models.domain import ConversionError, ConversionResult
|
| 19 |
from app.services.ocr_service import ocr_image, ocr_pdf
|
| 20 |
|
| 21 |
_logger = get_logger(__name__)
|
| 22 |
-
_settings = get_settings()
|
| 23 |
|
| 24 |
-
_YOUTUBE_URL_PATTERN = re.compile(
|
| 25 |
-
r"(?:https?://)?"
|
| 26 |
-
r"(?:www\.|m\.)?"
|
| 27 |
-
r"(?:youtube\.com/(?:watch\?v=|embed/|v/|shorts/|live/)|youtu\.be/)"
|
| 28 |
-
r"([\w-]{11})"
|
| 29 |
-
)
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
def _extract_youtube_video_id(url: str) -> str | None:
|
| 39 |
-
match = _YOUTUBE_URL_PATTERN.match(url)
|
| 40 |
if match:
|
| 41 |
return match.group(1)
|
| 42 |
-
|
| 43 |
-
if parsed.hostname in _YOUTUBE_DOMAINS:
|
| 44 |
-
qs = parse_qs(parsed.query)
|
| 45 |
-
return qs.get("v", [None])[0]
|
| 46 |
-
return None
|
| 47 |
|
| 48 |
|
| 49 |
def _fetch_youtube_oembed(video_id: str) -> dict | None:
|
|
@@ -59,18 +42,49 @@ def _fetch_youtube_oembed(video_id: str) -> dict | None:
|
|
| 59 |
|
| 60 |
def _fetch_youtube_transcript(video_id: str) -> str | None:
|
| 61 |
try:
|
|
|
|
|
|
|
|
|
|
| 62 |
from youtube_transcript_api import YouTubeTranscriptApi
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
except Exception as exc:
|
| 67 |
_logger.warning("YouTube transcript failed for %s: %s", video_id, exc)
|
| 68 |
-
|
| 69 |
|
| 70 |
|
| 71 |
def _convert_youtube(url: str) -> ConversionResult | None:
|
| 72 |
-
|
| 73 |
-
|
|
|
|
| 74 |
return None
|
| 75 |
|
| 76 |
oembed = _fetch_youtube_oembed(video_id)
|
|
@@ -179,7 +193,11 @@ class ConverterService:
|
|
| 179 |
|
| 180 |
start = time.perf_counter()
|
| 181 |
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
result = _convert_youtube(url)
|
| 184 |
if result is not None:
|
| 185 |
result = ConversionResult(
|
|
|
|
| 6 |
import re
|
| 7 |
import time
|
| 8 |
from pathlib import Path
|
| 9 |
+
from urllib.parse import urlparse
|
|
|
|
| 10 |
|
| 11 |
import httpx
|
| 12 |
from markitdown import MarkItDown
|
| 13 |
|
|
|
|
| 14 |
from app.core.constants import IMAGE_EXTENSIONS, IMAGE_MIME_PREFIXES
|
| 15 |
from app.core.logger import get_logger
|
| 16 |
from app.models.domain import ConversionError, ConversionResult
|
| 17 |
from app.services.ocr_service import ocr_image, ocr_pdf
|
| 18 |
|
| 19 |
_logger = get_logger(__name__)
|
|
|
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
+
def _extract_youtube_video_id(url_or_id: str) -> str:
|
| 23 |
+
if len(url_or_id) == 11 and not url_or_id.startswith("http"):
|
| 24 |
+
return url_or_id
|
| 25 |
+
pattern = r"(?:v=|\/)([0-9A-Za-z_-]{11}).*"
|
| 26 |
+
match = re.search(pattern, url_or_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
if match:
|
| 28 |
return match.group(1)
|
| 29 |
+
raise ValueError(f"Could not extract a valid YouTube ID from: {url_or_id}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
def _fetch_youtube_oembed(video_id: str) -> dict | None:
|
|
|
|
| 42 |
|
| 43 |
def _fetch_youtube_transcript(video_id: str) -> str | None:
|
| 44 |
try:
|
| 45 |
+
import json as _json
|
| 46 |
+
import os as _os
|
| 47 |
+
|
| 48 |
from youtube_transcript_api import YouTubeTranscriptApi
|
| 49 |
+
from youtube_transcript_api.formatters import TextFormatter
|
| 50 |
+
from youtube_transcript_api._errors import (
|
| 51 |
+
TranscriptsDisabled,
|
| 52 |
+
NoTranscriptFound,
|
| 53 |
+
VideoUnavailable,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
kwargs: dict = {}
|
| 57 |
+
cookies_raw = _os.environ.get("YOUTUBE_COOKIES", "").strip()
|
| 58 |
+
if cookies_raw:
|
| 59 |
+
try:
|
| 60 |
+
import requests as _requests
|
| 61 |
+
session = _requests.Session()
|
| 62 |
+
session.cookies.update(_json.loads(cookies_raw))
|
| 63 |
+
kwargs["http_client"] = session
|
| 64 |
+
except Exception as exc:
|
| 65 |
+
_logger.warning("Failed to apply YOUTUBE_COOKIES: %s", exc)
|
| 66 |
+
|
| 67 |
+
ytt_api = YouTubeTranscriptApi(**kwargs)
|
| 68 |
+
transcript_data = ytt_api.fetch(video_id, languages=["en"])
|
| 69 |
+
formatter = TextFormatter()
|
| 70 |
+
return formatter.format_transcript(transcript_data)
|
| 71 |
+
except TranscriptsDisabled:
|
| 72 |
+
_logger.warning("Transcripts disabled for video %s", video_id)
|
| 73 |
+
except NoTranscriptFound:
|
| 74 |
+
_logger.warning("No transcript found for video %s in language 'en'", video_id)
|
| 75 |
+
except VideoUnavailable:
|
| 76 |
+
_logger.warning("Video %s is unavailable, deleted, or private", video_id)
|
| 77 |
+
except ValueError as ve:
|
| 78 |
+
_logger.warning("Invalid video ID %s: %s", video_id, ve)
|
| 79 |
except Exception as exc:
|
| 80 |
_logger.warning("YouTube transcript failed for %s: %s", video_id, exc)
|
| 81 |
+
return None
|
| 82 |
|
| 83 |
|
| 84 |
def _convert_youtube(url: str) -> ConversionResult | None:
|
| 85 |
+
try:
|
| 86 |
+
video_id = _extract_youtube_video_id(url)
|
| 87 |
+
except ValueError:
|
| 88 |
return None
|
| 89 |
|
| 90 |
oembed = _fetch_youtube_oembed(video_id)
|
|
|
|
| 193 |
|
| 194 |
start = time.perf_counter()
|
| 195 |
|
| 196 |
+
try:
|
| 197 |
+
_extract_youtube_video_id(url)
|
| 198 |
+
except ValueError:
|
| 199 |
+
pass
|
| 200 |
+
else:
|
| 201 |
result = _convert_youtube(url)
|
| 202 |
if result is not None:
|
| 203 |
result = ConversionResult(
|