HUGE COMMIT. Essentially implemented every necessary route. Integrated database functionality. Setup a .env setup. Updated the openapi.yaml.

tyler
xxmistacruzxx 2024-02-29 19:01:00 -05:00
parent 7afe1045c4
commit 49c8d29a79
28 changed files with 1113 additions and 220 deletions

0
.env
View File

39
.env.example Normal file
View File

@ -0,0 +1,39 @@
# DATABASE CONFIG
DATABASE_NAME=postgres
DATABASE_HOST=127.0.0.1
DATABASE_USER=postgres
DATABASE_PASSWORD=testpassword
DATABASE_PORT=5432
# GENERAL CONFIG
ALT_WITH_CONTEXT=1
ALT_WITH_HASH=1
ALT_MULTITHREADED=0
## ALT_VERSION OPTIONS: 1, 2
ALT_VERSION=2
## DESC_ENGINE OPTIONS: replicateapi, bliplocal, googlevertexapi
DESC_ENGINE=replicateapi
## OCR_ENGINE OPTIONS: tesseract
OCR_ENGINE=tesseract
## LANG_ENGINE OPTIONS: privategpt
LANG_ENGINE=privategpt
# DESC_ENGINE CONFIG OPTIONS
## REPLICATEAPI
REPLICATE_KEY=example_key
## BLIPLOCAL
BLIPLOCAL_DIR=/path/to/image-captioning
## GOOGLEVERTEXAPI
VERTEX_PROJECT_ID=example-123456
### VERTEX_LOCATION OPTIONS: https://cloud.google.com/vertex-ai/docs/general/locations
VERTEX_LOCATION=us-central1
VERTEX_GAC_PATH=/path/to/vertex-key.json
# OCR_ENGINE CONFIG OPTIONS
## TESSERACT
TESSERACT_PATH=/path/to/tesseract.exe
# LANG_ENGINE CONFIG OPTIONS
## PRIVATEGPT
PRIVATEGPT_HOST=http://localhost:8001

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
*/__pycache__/ **/__pycache__/
.env
/books /books
/covers /covers

View File

@ -0,0 +1,132 @@
import os
import threading
import bs4
from alttext import alttext
from alttext.descengine.bliplocal import BlipLocal
from alttext.descengine.replicateapi import ReplicateAPI
from alttext.langengine.privategpt import PrivateGPT
from alttext.ocrengine.tesseract import Tesseract
from django.core.files.storage import default_storage
from .postgres import books, images
# from alttext.descengine.googlevertexapi import GoogleVertexAPI
def createAnalyzer():
descEngine = None
match os.environ["DESC_ENGINE"].lower():
case "replicateapi":
descEngine = ReplicateAPI(os.environ["REPLICATE_KEY"])
case "bliplocal":
descEngine = BlipLocal(os.environ["BLIPLOCAL_DIR"])
# case "googlevertexapi":
# descEngine = GoogleVertexAPI(os.environ["VERTEX_PROJECT_ID"], os.environ["VERTEX_LOCATION"], os.environ["VERTEX_GAC_PATH"])
case _:
raise ValueError("Invalid description engine")
ocrEngine = None
match os.environ["OCR_ENGINE"].lower():
case "tesseract":
ocrEngine = Tesseract()
case _:
raise ValueError("Invalid OCR engine")
langEngine = None
match os.environ["LANG_ENGINE"].lower():
case "privategpt":
langEngine = PrivateGPT(os.environ["PRIVATEGPT_HOST"])
case _:
raise ValueError("Invalid language engine")
options = {
"withContext": bool(int(os.environ["ALT_WITH_CONTEXT"])),
"withHash": bool(int(os.environ["ALT_WITH_HASH"])),
"multiThreaded": bool(int(os.environ["ALT_MULTITHREADED"])),
"version": int(os.environ["ALT_VERSION"]),
}
return alttext.AltTextHTML(descEngine, ocrEngine, langEngine, options)
def findHTML(path: str):
html_file = None
for root, _, files in os.walk(path):
for file_name in files:
if file_name.endswith(".html"):
html_file = default_storage.path(os.path.join(root, file_name))
break
if html_file:
break
return html_file
def getSize(path: str):
size = 0
for path, _, files in os.walk(path):
for f in files:
fp = os.path.join(path, f)
size += os.path.getsize(fp)
return size
def analyzeImageV2(alt: alttext.AltTextHTML, img: bs4.element.Tag, bookid: str):
imgRecord = images.jsonifyImage(images.getImageByBook(bookid, img["src"]))
context = [imgRecord["beforeContext"], imgRecord["afterContext"]]
imgData = alt.getImgData(img["src"])
desc = alt.genDesc(imgData, img["src"], context)
chars = alt.genChars(imgData, img["src"]).strip()
thisAlt = alt.langEngine.refineAlt(desc, chars, context, None)
images.updateImage(
bookid,
img["src"],
status="available",
genAlt=thisAlt,
genImageCaption=desc,
ocr=chars,
beforeContext=context[0],
afterContext=context[1],
)
return images.jsonifyImage(images.getImageByBook(bookid, img["src"]))
def analyzeSingularImageV2(alt: alttext.AltTextHTML, img: bs4.element.Tag, bookid: str):
books.updateBook(bookid, status="processing")
images.updateImage(
bookid,
img["src"],
status="processing",
)
analyzeImageV2(alt, img, bookid)
books.updateBook(bookid, status="available")
return images.jsonifyImage(images.getImageByBook(bookid, img["src"]))
def analyzeImagesV2(alt: alttext.AltTextHTML, imgs: list[bs4.element.Tag], bookid: str):
books.updateBook(bookid, status="processing")
for img in imgs:
images.updateImage(
bookid,
img["src"],
status="processing",
)
if bool(int(os.environ["ALT_MULTITHREADED"])):
# TODO: TEST WITH OPENAI API
threads = []
for img in imgs:
thread = threading.Thread(target=analyzeImageV2, args=(alt, img, bookid))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
else:
for img in imgs:
analyzeImageV2(alt, img, bookid)
books.updateBook(bookid, status="available")
return books.jsonifyBook(books.getBook(bookid))

View File

@ -0,0 +1,127 @@
import uuid
try:
from .config import Database
except ImportError:
from config import Database
"""
BOOKS DATABASE ATTRIBUTES
*id: str
title: str
size: str
status: str
numImages: int
coverExt: str
"""
def createBookTable():
db = Database()
query = "CREATE TABLE books (id varchar(255) NOT NULL PRIMARY KEY, title varchar(255), size varchar(255), status varchar(255), numImages int, coverExt varchar(255));"
db.sendQuery(query)
db.commit()
db.close()
def jsonifyBook(book: tuple):
return {
"id": book[0],
"title": book[1],
"size": book[2],
"status": book[3],
"numImages": book[4],
"coverExt": book[5],
}
def getBook(id: str):
db = Database()
query = "SELECT * FROM books WHERE id = %s"
params = (id,)
db.sendQuery(query, params)
book = db.fetchOne()
db.close()
return book
def getBooks(titleQ: str = None, limit: int = None, skip: int = None):
db = Database()
params = []
query = "SELECT * FROM books"
if titleQ:
lowerTitleQ = f"%{titleQ.lower()}%"
query += " WHERE LOWER(title) LIKE %s"
params.append(lowerTitleQ)
if limit is not None:
query += " LIMIT %s"
params.append(limit)
if skip is not None:
query += " OFFSET %s"
params.append(skip)
db.sendQuery(query, params)
books = db.fetchAll()
db.close()
return books
def addBook(
title: str,
size: str,
numImages: int,
id: str = None,
status: str = "available",
coverExt: str = None,
):
if id == None:
id = str(uuid.uuid4())
db = Database()
query = "INSERT INTO books (id, title, status, numimages, size, coverext) VALUES (%s, %s, %s, %s, %s, %s);"
params = (id, title, status, numImages, size, coverExt)
db.sendQuery(query, params)
db.commit()
db.close()
return getBook(id)
def deleteBook(id: str):
db = Database()
query = "DELETE FROM books WHERE id = %s"
params = (id,)
db.sendQuery(query, params)
db.commit()
db.close()
def updateBook(id: str, title: str = None, status: str = None, coverExt: str = None):
db = Database()
if title or status or coverExt:
params = []
query = "UPDATE books SET"
if title:
query += " title = %s,"
params.append(title)
if status:
query += " status = %s,"
params.append(status)
if coverExt:
query += " coverext = %s,"
params.append(coverExt)
query = query[:-1]
query += " WHERE id = %s"
params.append(id)
db.sendQuery(query, params)
db.commit()
db.close()

View File

@ -0,0 +1,32 @@
import psycopg2
import os
class Database:
def __init__(self):
self.conn = psycopg2.connect(
database=os.environ['DATABASE_NAME'],
host=os.environ['DATABASE_HOST'],
user=os.environ['DATABASE_USER'],
password=os.environ['DATABASE_PASSWORD'],
port=os.environ['DATABASE_PORT']
)
self.cursor = self.conn.cursor()
def sendQuery(self, query:str, params = None):
self.cursor.execute(query, params)
def commit(self):
self.conn.commit()
def fetchOne(self):
return self.cursor.fetchone()
def fetchAll(self):
return self.cursor.fetchall()
def fetchMany(self, size:int):
return self.cursor.fetchmany(size=size)
def close(self):
self.cursor.close()
self.conn.close()

View File

@ -0,0 +1,208 @@
try:
from .config import Database
except ImportError:
from config import Database
"""
IMAGE DATABASE ATTRIBUTES
*bookid: str
*src: str
hash: str
status: str
alt: str
originalAlt: str
genAlt: str
genImageCaption: str
ocr: str
beforeContext: str
afterContext: str
additionalContext: str
"""
def createImageTable():
db = Database()
query = "CREATE TABLE images (bookid varchar(255) NOT NULL, src varchar(255) NOT NULL, hash varchar(255), status varchar(255), alt varchar(1000), originalAlt varchar(1000), genAlt varchar(1000), genImageCaption varchar(1000), ocr varchar(1000), beforeContext varchar(2000), afterContext varchar(2000), additionalContext varchar(1000), CONSTRAINT PK_Image PRIMARY KEY (bookid, src), FOREIGN KEY (bookid) REFERENCES books(id) ON DELETE CASCADE);"
db.sendQuery(query)
db.commit()
db.close()
def jsonifyImage(image: tuple):
return {
"bookid": image[0],
"src": image[1],
"hash": image[2],
"status": image[3],
"alt": image[4],
"originalAlt": image[5],
"genAlt": image[6],
"genImageCaption": image[7],
"ocr": image[8],
"beforeContext": image[9],
"afterContext": image[10],
"additionalContext": image[11],
}
def getImageByBook(bookid: str, src: str):
db = Database()
query = "SELECT * FROM images WHERE bookid = %s AND src = %s"
params = (bookid, src)
db.sendQuery(query, params)
image = db.fetchOne()
db.close
return image
def getImagesByBook(bookid: str):
db = Database()
query = "SELECT * FROM images WHERE bookid = %s"
params = (bookid,)
db.sendQuery(query, params)
images = db.fetchAll()
db.close()
return images
def getImagesByHash(hash: str):
db = Database()
query = "SELECT * FROM images WHERE hash = %s"
params = (hash,)
db.sendQuery(query, params)
images = db.fetchAll()
db.close()
return images
def addImage(
bookid: str,
src: str,
hash: str = None,
status: str = "available",
alt: str = "",
originalAlt: str = None,
genAlt: str = None,
genImageCaption: str = None,
ocr: str = None,
beforeContext: str = None,
afterContext: str = None,
additionalContext: str = None,
):
db = Database()
query = "INSERT INTO images (bookid, src, hash, status, alt, originalalt, genalt, genimagecaption, ocr, beforecontext, aftercontext, additionalcontext) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);"
if status != "available" and status != "processing":
status = "available"
if alt is not None:
alt = alt[:1000]
if originalAlt is not None:
originalAlt = originalAlt[:1000]
if genAlt is not None:
genAlt = genAlt[:1000]
if genImageCaption is not None:
genImageCaption = genImageCaption[:1000]
if ocr is not None:
ocr = ocr[:1000]
if beforeContext is not None:
beforeContext = beforeContext[:2000]
if afterContext is not None:
afterContext = afterContext[:2000]
if additionalContext is not None:
additionalContext = additionalContext[:1000]
params = (
bookid,
src,
hash,
status,
alt,
originalAlt,
genAlt,
genImageCaption,
ocr,
beforeContext,
afterContext,
additionalContext,
)
db.sendQuery(query, params)
db.commit()
db.close()
return getImageByBook(bookid, src)
def deleteImage(bookid: str, src: str):
db = Database()
query = "DELETE FROM images WHERE bookid = %s AND src = %s;"
params = (bookid, src)
db.sendQuery(query, params)
db.commit()
db.close()
def updateImage(
bookid: str,
src: str,
status: str = None,
alt: str = None,
genAlt: str = None,
genImageCaption: str = None,
ocr: str = None,
beforeContext: str = None,
afterContext: str = None,
additionalContext: str = None,
):
db = Database()
if (
status
or alt
or genAlt
or genImageCaption
or ocr
or beforeContext
or afterContext
or additionalContext
):
params = []
query = "UPDATE images SET"
if status:
query += " status = %s,"
params.append(status)
if alt:
query += " alt = %s,"
params.append(alt)
if genAlt:
query += " genalt = %s,"
params.append(genAlt)
if genImageCaption:
query += " genimagecaption = %s,"
params.append(genImageCaption)
if ocr:
query += " ocr = %s,"
params.append(ocr)
if beforeContext:
query += " beforecontext = %s,"
params.append(beforeContext)
if afterContext:
query += " aftercontext = %s,"
params.append(afterContext)
if additionalContext:
query += " additionalcontext = %s,"
params.append(additionalContext)
query = query[:-1]
query += " WHERE bookid = %s AND src = %s"
params.append(bookid)
params.append(src)
db.sendQuery(query, params)
db.commit()
db.close()

View File

@ -0,0 +1,57 @@
import dotenv
from books import addBook, getBooks, getBook, updateBook
from images import (
addImage,
getImagesByBook,
getImageByBook,
getImagesByHash,
updateImage,
)
from config import Database
dotenv.load_dotenv()
"""
createBookTable = "CREATE TABLE books (id varchar(255) NOT NULL PRIMARY KEY, title varchar(255), size varchar(255), status varchar(255), numImages int, coverExt varchar(255));"
createImageTable = "CREATE TABLE images (bookid varchar(255) NOT NULL, src varchar(255) NOT NULL, hash varchar(255), status varchar(255), alt varchar(255), originalAlt varchar(255), genAlt varchar(255), genImageCaption varchar(255), ocr varchar(255), beforeContext varchar(255), afterContext varchar(255), additionalContext varchar(255), CONSTRAINT PK_Image PRIMARY KEY (bookid, src), FOREIGN KEY (bookid) REFERENCES books(id) ON DELETE CASCADE);"
"""
# db.sendQuery("SELECT * FROM books")
# print(db.fetchOne())
# addBook(title="Harry Potter", size="300kb", numImages=25)
"""
addBook(title="Harry Potter", size="300kb", numImages=25)
addBook(title="Harraoeu", size="300kb", numImages=25)
addBook(title="Hartter", size="300kb", numImages=25)
"""
# getBooks(titleQ="Harry Potter", limit=1, skip=2)
"""
addImage(
bookid="f1ac43cc-9f6d-4dc8-ac4f-aea0c4af5198",
src="sampleSrcMEOW",
hash="brown",
status="available",
)
"""
# getImagesByBook("fa47d830-586a-485f-a579-67b33fd3eae3")
# print(getImagesByHash("brown"))
updateImage(
bookid="f1ac43cc-9f6d-4dc8-ac4f-aea0c4af5198",
src="sampleSrcMEOW",
status="bruh2",
beforeContext="before context be like",
)
# updateBook(id="72950", title="Test Title Two", status="available")
db = Database()
# db.sendQuery("SELECT * FROM images;")
# print(db.fetchAll())
db.close()

View File

@ -1,35 +1,41 @@
from rest_framework.views import APIView import sys
from rest_framework.response import Response import zipfile
from rest_framework import status, permissions, serializers
from rest_framework.exceptions import ValidationError
from rest_framework.parsers import FormParser, MultiPartParser
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from uuid import uuid4 from uuid import uuid4
import alttextbackend.data.analyze as analyze
import alttextbackend.data.postgres.books as books
import alttextbackend.data.postgres.images as images
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from rest_framework import serializers, status
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.response import Response
from rest_framework.views import APIView
sys.path.append("../")
class GetBooksSerializer(serializers.Serializer): class GetBooksSerializer(serializers.Serializer):
titleQ = serializers.CharField(required=False) titleQ = serializers.CharField(required=False)
authorQ = serializers.CharField(required=False)
sortBy = serializers.ChoiceField(choices=['title', 'author'], style={'base_template': 'radio.html'}, default = 'title')
sortOrder = serializers.ChoiceField(choices=['asc', 'desc'], style={'base_template': 'radio.html'}, default = 'asc')
limit = serializers.IntegerField(min_value=1, required=False) limit = serializers.IntegerField(min_value=1, required=False)
skip = serializers.IntegerField(min_value=0, required=False) skip = serializers.IntegerField(min_value=0, required=False)
class AddBookSerializer(serializers.Serializer): class AddBookSerializer(serializers.Serializer):
id = serializers.CharField(required=False)
title = serializers.CharField(required=True, allow_blank=False) title = serializers.CharField(required=True, allow_blank=False)
author = serializers.CharField(required=True, allow_blank=False) book = serializers.FileField(required=True)
description = serializers.CharField(required=False, allow_blank=True)
file = serializers.FileField(required=True)
cover = serializers.ImageField(required=False) cover = serializers.ImageField(required=False)
class BooksView(APIView): class BooksView(APIView):
parser_classes = (FormParser, MultiPartParser) parser_classes = (FormParser, MultiPartParser)
serializer_class = AddBookSerializer serializer_class = AddBookSerializer
def get_serializer_class(self): def get_serializer_class(self):
if self.request.method == 'GET': if self.request.method == "GET":
return GetBooksSerializer return GetBooksSerializer
elif self.request.method == 'POST': elif self.request.method == "POST":
return AddBookSerializer return AddBookSerializer
return super().get_serializer_class() return super().get_serializer_class()
@ -41,17 +47,14 @@ class BooksView(APIView):
# Access validated data # Access validated data
validated_data = serializer.validated_data validated_data = serializer.validated_data
title_query = validated_data.get('titleQ') titleQ = validated_data.get("titleQ", None)
author_query = validated_data.get('authorQ') limit = validated_data.get("limit", None)
sort_by = validated_data.get('sortBy') skip = validated_data.get("skip", None)
sort_order = validated_data.get('sortOrder')
limit = validated_data.get('limit')
skip = validated_data.get('skip')
# TODO: perform logic # get array of books
result = books.getBooks(titleQ, limit, skip)
# TODO: return books return Response(map(books.jsonifyBook, result), status=status.HTTP_200_OK)
return Response(validated_data, status=status.HTTP_200_OK)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
# validate request data # validate request data
@ -61,31 +64,72 @@ class BooksView(APIView):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
id = validated_data.get("id", uuid4())
# check if id is already in use
book = books.getBook(id)
if book:
return Response(
{"error": "id already in use"}, status=status.HTTP_400_BAD_REQUEST
)
# perform initial book processing # perform initial book processing
file = validated_data["file"] file = validated_data["book"]
if not file.name.endswith(".zip"): if not file.name.endswith(".zip"):
return Response( return Response(
{"file": ["file must be a zip"]}, status=status.HTTP_400_BAD_REQUEST {"file": ["file must be a zip"]}, status=status.HTTP_400_BAD_REQUEST
) )
id = uuid4() book_path = f"./books/{str(id)}"
books_path = "./books/" default_storage.save(f"{book_path}.zip", ContentFile(file.read()))
default_storage.save(f"{books_path}{str(id)}.zip", ContentFile(file.read())) with zipfile.ZipFile(default_storage.path(f"{book_path}.zip"), "r") as zip_ref:
zip_ref.extractall(default_storage.path(f"{book_path}"))
default_storage.delete(f"{book_path}.zip")
# TODO: ensure book has valid root html file # ensure book has valid root html file
html_file = analyze.findHTML(book_path)
# TODO: analyze book and images, store them in database if html_file == None:
default_storage.delete(book_path)
return Response(
{"error": "No HTML file found in the extracted folder"},
status=status.HTTP_400_BAD_REQUEST,
)
# save cover image # save cover image
covers_path = "./covers/" coverExt = None
if "cover" in validated_data and validated_data["cover"] is not None:
coverExt = validated_data["cover"].name.split(".")[-1]
default_storage.save( default_storage.save(
f"{covers_path}{str(id)}.{validated_data['cover'].name.split('.')[-1]}", f"./covers/{str(id)}.{coverExt}",
ContentFile(validated_data["cover"].read()), ContentFile(validated_data["cover"].read()),
) )
alt = analyze.createAnalyzer()
alt.parseFile(html_file)
# store basic book info into database
size = analyze.getSize(book_path)
imgs = alt.getAllImgs()
books.addBook(
title=validated_data["title"],
size=str(size),
numImages=len(imgs),
id=id,
coverExt=coverExt,
)
# store info for all images in database
for img in imgs:
context = alt.getContext(img)
thisHash = hash(alt.getImgData(img["src"]))
images.addImage(
bookid=id,
src=img["src"],
hash=thisHash,
alt=img["alt"],
originalAlt=img["alt"],
beforeContext=context[0],
afterContext=context[1],
)
book = books.getBook(id)
return Response( return Response(
{ books.jsonifyBook(book),
"book": validated_data.get("title"),
"description": validated_data.get("description"),
},
status=status.HTTP_201_CREATED, status=status.HTTP_201_CREATED,
) )

View File

@ -1,102 +1,204 @@
from rest_framework.views import APIView import copy
from rest_framework.response import Response import os
from rest_framework import status, permissions, serializers import shutil
from rest_framework.exceptions import ValidationError import threading
from rest_framework.parsers import FormParser, MultiPartParser import time
import alttextbackend.data.analyze as analyzer
import alttextbackend.data.postgres.books as books
import alttextbackend.data.postgres.images as images
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from rest_framework import serializers, status
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.response import Response
from rest_framework.views import APIView
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from uuid import uuid4
class GetBookSerializer(serializers.Serializer): class GetBookSerializer(serializers.Serializer):
bookid = serializers.CharField(required=True) bookid = serializers.CharField(required=True)
class UpdateBookSerialzer(serializers.Serializer): class UpdateBookSerialzer(serializers.Serializer):
bookid = serializers.CharField(required=True) bookid = serializers.CharField(required=True)
title = serializers.CharField(required=False, allow_blank=False) title = serializers.CharField(required=False, allow_blank=False)
author = serializers.CharField(required=False, allow_blank=False)
description = serializers.CharField(required=False, allow_blank=True)
cover = serializers.ImageField(required=False) cover = serializers.ImageField(required=False)
class AnalyzeBookSerializer(serializers.Serializer): class AnalyzeBookSerializer(serializers.Serializer):
bookid = serializers.CharField(required=True) bookid = serializers.CharField(required=True)
missingOnly = serializers.BooleanField(required=False, default=True)
waitForAnalysis = serializers.BooleanField(required=False, default=False)
class OverwriteBookSerializer(serializers.Serializer):
bookid = serializers.CharField(required=True)
file = serializers.FileField(required=True)
class DeleteBookSerializer(serializers.Serializer): class DeleteBookSerializer(serializers.Serializer):
bookid = serializers.CharField(required=True) bookid = serializers.CharField(required=True)
class BooksBookidView(APIView): class BooksBookidView(APIView):
parser_classes = (FormParser, MultiPartParser) parser_classes = (FormParser, MultiPartParser)
serializer_class = UpdateBookSerialzer serializer_class = UpdateBookSerialzer
def get_serializer_class(self): def get_serializer_class(self):
if self.request.method == 'GET': if self.request.method == "GET":
return GetBookSerializer return GetBookSerializer
elif self.request.method == 'PATCH': elif self.request.method == "PATCH":
return UpdateBookSerialzer return UpdateBookSerialzer
elif self.request.method == 'PUT': elif self.request.method == "PUT":
return AnalyzeBookSerializer return AnalyzeBookSerializer
elif self.request.method == 'DELETE': elif self.request.method == "DELETE":
return DeleteBookSerializer return DeleteBookSerializer
return super().get_serializer_class() return super().get_serializer_class()
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
serializer_class = self.get_serializer_class() serializer_class = self.get_serializer_class()
serializer = serializer_class(data={"bookid": kwargs.get('bookid')}) serializer = serializer_class(data={"bookid": kwargs.get("bookid")})
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
# TODO: IMPLEMENT LOGIC # get book from database
book = books.getBook(validated_data.get("bookid"))
if not book:
return Response(
{"error": "No book of that id was found in database."},
status=status.HTTP_404_BAD_REQUEST,
)
return Response(validated_data, status=status.HTTP_200_OK) return Response(books.jsonifyBook(book), status=status.HTTP_200_OK)
def patch(self, request, *args, **kwargs): def patch(self, request, *args, **kwargs):
serializer_class = self.get_serializer_class() serializer_class = self.get_serializer_class()
data = request.data data = request.data
data['bookid'] = kwargs.get('bookid') data["bookid"] = kwargs.get("bookid")
serializer = serializer_class(data=data) serializer = serializer_class(data=data)
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
# TODO: IMPLEMENT LOGIC # check if book exists in database
book = books.getBook(validated_data.get("bookid"))
if not book:
return Response(
{"error": "No book of that id was found in database."},
status=status.HTTP_404_BAD_REQUEST,
)
book = books.jsonifyBook(book)
return Response(validated_data, status=status.HTTP_200_OK) # update book title and cover
title = validated_data.get("title", None)
coverExt = None
if "cover" in validated_data and validated_data["cover"] is not None:
coverExt = validated_data["cover"].name.split(".")[-1]
default_storage.delete(
f"./covers/{str(validated_data.get('bookid'))}.{book['coverExt']}"
)
default_storage.save(
f"./covers/{str(validated_data.get('bookid'))}.{coverExt}",
ContentFile(validated_data["cover"].read()),
)
books.updateBook(validated_data.get("bookid"), title=title, coverExt=coverExt)
book = books.jsonifyBook(books.getBook(validated_data.get("bookid")))
return Response(book, status=status.HTTP_200_OK)
def put(self, request, *args, **kwargs): def put(self, request, *args, **kwargs):
serializer_class = self.get_serializer_class() serializer_class = self.get_serializer_class()
serializer = serializer_class(data={"bookid": kwargs.get('bookid')}) data = copy.deepcopy(request.query_params)
data["bookid"] = kwargs.get("bookid")
serializer = serializer_class(data=data)
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
# TODO: IMPLEMENT LOGIC bookid = validated_data.get("bookid")
# check for book's existence
book = books.getBook(bookid)
if not book:
return Response(
{"error": "Book not found in database."},
status=status.HTTP_404_BAD_REQUEST,
)
return Response(validated_data, status=status.HTTP_200_OK) html_file = analyzer.findHTML(f"./books/{str(validated_data.get('bookid'))}")
if html_file == None:
return Response(
{"error": "Failed to find HTML file in book directory."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
def post(self, request, *args, **kwargs): alt = analyzer.createAnalyzer()
serializer_class = self.get_serializer_class() alt.parseFile(html_file)
data = request.data imgs = []
data['bookid'] = kwargs.get('bookid') if validated_data.get("missingOnly"):
serializer = serializer_class(data=request.data) imgs = alt.getNoAltImgs()
if not serializer.is_valid(): else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) imgs = alt.getAllImgs()
validated_data = serializer.validated_data
# TODO: IMPLEMENT LOGIC # set book and all images to "processing" status
if validated_data.get("waitForAnalysis"):
analyzer.analyzeImagesV2(alt, imgs, bookid)
else:
threading.Thread(
target=analyzer.analyzeImagesV2, args=(alt, imgs, bookid)
).start()
return Response(validated_data, status=status.HTTP_200_OK) book = books.jsonifyBook(books.getBook(bookid))
if not validated_data.get("waitForAnalysis"):
book["status"] = "processing"
return Response(book, status=status.HTTP_200_OK)
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
serializer_class = self.get_serializer_class() serializer_class = self.get_serializer_class()
serializer = serializer_class(data={"bookid": kwargs.get('bookid')}) serializer = serializer_class(data={"bookid": kwargs.get("bookid")})
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
# TODO: IMPLEMENT LOGIC # check for book's existence
book = books.getBook(validated_data.get("bookid"))
if not book:
return Response(
{"error": "Book not found in database."},
status=status.HTTP_404_BAD_REQUEST,
)
book = books.jsonifyBook(book)
book["status"] = "deleted"
return Response(validated_data, status=status.HTTP_200_OK) # delete book from table (this cascades to images table as well)
books.deleteBook(validated_data.get("bookid"))
# delete book directory and cover image
try:
folder_path = f"./books/{str(validated_data.get('bookid'))}"
if default_storage.exists(folder_path):
shutil.rmtree(default_storage.path(folder_path))
if book["coverExt"]:
try:
default_storage.delete(
f"./covers/{str(validated_data.get('bookid'))}.{book['coverExt']}"
)
except:
return Response(
{"error": "Failed to delete cover image."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
else:
return Response(
{"error": "Failed to find book directory."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
except Exception:
return Response(
{"error": "Failed to delete book directory."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return Response(
book,
status=status.HTTP_200_OK,
)

View File

@ -1,25 +1,102 @@
from rest_framework.views import APIView import copy
from rest_framework.response import Response import os
from rest_framework import status, permissions, serializers import shutil
from rest_framework.exceptions import ValidationError import zipfile
from rest_framework.parsers import FormParser, MultiPartParser
import alttextbackend.data.analyze as analyze
import alttextbackend.data.postgres.books as books
import alttextbackend.data.postgres.images as images
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.core.files.base import ContentFile from django.http import HttpResponse
from uuid import uuid4 from rest_framework import serializers, status
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.response import Response
from rest_framework.views import APIView
class ExportBookSerializer(serializers.Serializer): class ExportBookSerializer(serializers.Serializer):
bookid = serializers.CharField(required=True) bookid = serializers.CharField(required=True)
name = serializers.CharField(required=False)
class BooksBookidExportView(APIView): class BooksBookidExportView(APIView):
parser_classes = (FormParser, MultiPartParser) parser_classes = (FormParser, MultiPartParser)
serializer_class = ExportBookSerializer serializer_class = ExportBookSerializer
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
serializer = self.serializer_class(data={"bookid": kwargs.get('bookid')}) data = copy.deepcopy(request.query_params)
data["bookid"] = kwargs.get("bookid")
serializer = self.serializer_class(data=data)
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
# TODO: IMPLEMENT LOGIC bookid = validated_data.get("bookid")
# check if book exists in database
book = books.getBook(bookid)
if not book:
return Response(
{"error": "Book not found"}, status=status.HTTP_404_NOT_FOUND
)
return Response(validated_data, status=status.HTTP_200_OK) # find HTML file
bookid = str(validated_data.get("bookid"))
html_file = analyze.findHTML(f"./books/{bookid}")
if html_file == None:
return Response(
{"error": "Failed to find HTML file in book directory."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
# get all image tags in book
alt = analyze.createAnalyzer()
alt.parseFile(html_file)
imgs = alt.getAllImgs()
for img in imgs:
databaseImg = images.jsonifyImage(images.getImageByBook(bookid, img["src"]))
alt.setAlt(img["src"], databaseImg["alt"])
try:
shutil.copytree(
default_storage.path(f"./books/{bookid}"), f"./books/{bookid}-t"
)
except Exception as e:
return Response(
{"error": "Failed to copy book into temp folder."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
html_file = analyze.findHTML(f"./books/{bookid}-t")
if html_file == None:
return Response(
{"error": "Failed to find HTML file in temp book directory."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
default_storage.delete(html_file)
alt.exportToFile(html_file)
# Zip the temp folder
zip_filename = f"./books/{bookid}-t.zip"
with zipfile.ZipFile(zip_filename, "w") as zipf:
for root, _, files in os.walk(f"./books/{bookid}-t"):
for file in files:
zipf.write(
os.path.join(root, file),
os.path.relpath(
os.path.join(root, file), f"./books/{bookid}-t"
),
)
# Send the zip file as a response
filename = validated_data.get("name", f"{bookid}")
print(filename)
response = None
with open(zip_filename, "rb") as f:
response = HttpResponse(f, content_type="application/zip")
response["Content-Disposition"] = f"attachment; filename={filename}.zip"
# Delete the temp zip and folder
os.remove(zip_filename)
shutil.rmtree(f"./books/{bookid}-t")
return response

View File

@ -1,74 +1,158 @@
from rest_framework.views import APIView import copy
from rest_framework.response import Response import threading
from rest_framework import status, permissions, serializers
from rest_framework.exceptions import ValidationError import alttextbackend.data.analyze as analyze
import alttextbackend.data.postgres.books as books
import alttextbackend.data.postgres.images as images
from rest_framework import serializers, status
from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.parsers import FormParser, MultiPartParser
from django.core.files.storage import default_storage from rest_framework.response import Response
from django.core.files.base import ContentFile from rest_framework.views import APIView
from uuid import uuid4
class GetImageBySrc(serializers.Serializer): class GetImageBySrc(serializers.Serializer):
bookid = serializers.CharField(required=True) bookid = serializers.CharField(required=True)
src = serializers.CharField(required=True) src = serializers.CharField(required=True)
class UpdateImageBySrc(serializers.Serializer): class UpdateImageBySrc(serializers.Serializer):
bookid = serializers.CharField(required=True) bookid = serializers.CharField(required=True)
src = serializers.CharField(required=True) src = serializers.CharField(required=True)
alt = serializers.CharField(required=True) alt = serializers.CharField(required=True)
beforeContext = serializers.CharField(required=False) beforeContext = serializers.CharField(required=False)
afterContext = serializers.CharField(required=False) afterContext = serializers.CharField(required=False)
additionalContext = serializers.CharField(required=False)
class AnalyzeImageBySrc(serializers.Serializer): class AnalyzeImageBySrc(serializers.Serializer):
bookid = serializers.CharField(required=True) bookid = serializers.CharField(required=True)
src = serializers.CharField(required=True) src = serializers.CharField(required=True)
waitForAnalysis = serializers.BooleanField(required=False, default=False)
class BooksBookidImageView(APIView): class BooksBookidImageView(APIView):
parser_classes = (FormParser, MultiPartParser) parser_classes = (FormParser, MultiPartParser)
def get_serializer_class(self): def get_serializer_class(self):
if self.request.method == 'GET': if self.request.method == "GET":
return GetImageBySrc return GetImageBySrc
elif self.request.method == 'PATCH': elif self.request.method == "PATCH":
return UpdateImageBySrc return UpdateImageBySrc
elif self.request.method == 'PUT': elif self.request.method == "PUT":
return AnalyzeImageBySrc return AnalyzeImageBySrc
return super().get_serializer_class() return super().get_serializer_class()
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
serializer_class = self.get_serializer_class() serializer_class = self.get_serializer_class()
data = request.query_params data = copy.deepcopy(request.query_params)
data['bookid'] = kwargs.get('bookid') data["bookid"] = kwargs.get("bookid")
serializer = serializer_class(data=data) serializer = serializer_class(data=data)
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
# TODO: IMPLEMENT LOGIC # check if book exists in database
book = books.getBook(validated_data.get("bookid"))
if not book:
return Response(
{"error": "Book not found"}, status=status.HTTP_404_NOT_FOUND
)
return Response(validated_data, status=status.HTTP_200_OK) # get image from database
img = images.getImageByBook(
validated_data.get("bookid"), validated_data.get("src")
)
if img == None:
return Response(
{"error": "Image not found"}, status=status.HTTP_404_NOT_FOUND
)
return Response(
images.jsonifyImage(img),
status=status.HTTP_200_OK,
)
def patch(self, request, *args, **kwargs): def patch(self, request, *args, **kwargs):
serializer_class = self.get_serializer_class() serializer_class = self.get_serializer_class()
data = request.data data = copy.deepcopy(request.data)
data.update(request.query_params) data.update(request.query_params)
data['bookid'] = kwargs.get('bookid') data["bookid"] = kwargs.get("bookid")
serializer = serializer_class(data=data) serializer = serializer_class(data=data)
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
# TODO: IMPLEMENT LOGIC alt = validated_data.get("alt", None)
beforeContext = validated_data.get("beforeContext", None)
afterContext = validated_data.get("afterContext", None)
additionalContext = validated_data.get("additionalContext", None)
return Response(validated_data, status=status.HTTP_200_OK) img = images.getImageByBook(
validated_data.get("bookid"), validated_data.get("src")
)
if img == None:
return Response(
{"error": "Image not found"}, status=status.HTTP_404_NOT_FOUND
)
# update image in database
images.updateImage(
bookid=validated_data.get("bookid"),
src=validated_data.get("src"),
alt=alt,
beforeContext=beforeContext,
afterContext=afterContext,
additionalContext=additionalContext,
)
img = images.getImageByBook(
validated_data.get("bookid"), validated_data.get("src")
)
return Response(images.jsonifyImage(img), status=status.HTTP_200_OK)
def put(self, request, *args, **kwargs): def put(self, request, *args, **kwargs):
serializer_class = self.get_serializer_class() serializer_class = self.get_serializer_class()
data = request.query_params data = copy.deepcopy(request.query_params)
data['bookid'] = kwargs.get('bookid') data["bookid"] = kwargs.get("bookid")
serializer = serializer_class(data=data) serializer = serializer_class(data=data)
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
# TODO: IMPLEMENT LOGIC # find HTML file
bookid = str(validated_data.get("bookid"))
html_file = analyze.findHTML(f"./books/{bookid}")
if html_file == None:
return Response(
{"error": "Failed to find HTML file in book directory."},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return Response(validated_data, status=status.HTTP_200_OK) # generate alt for image
alt = analyze.createAnalyzer()
alt.parseFile(html_file)
img = alt.getImg(validated_data.get("src"))
if img == None:
return Response(
{"error": "Failed to find image in book."},
status=status.HTTP_400_BAD_REQUEST,
)
if validated_data.get("waitForAnalysis"):
analyze.analyzeSingularImageV2(alt, img, bookid)
else:
threading.Thread(
target=analyze.analyzeSingularImageV2, args=(alt, img, bookid)
).start()
image = images.jsonifyImage(
images.getImageByBook(
validated_data.get("bookid"), validated_data.get("src")
)
)
if not validated_data.get("waitForAnalysis"):
image["status"] = "processing"
return Response(image, status=status.HTTP_200_OK)

View File

@ -1,25 +1,38 @@
from rest_framework.views import APIView import sys
from rest_framework.response import Response
from rest_framework import status, permissions, serializers import alttextbackend.data.postgres.books as books
from rest_framework.exceptions import ValidationError import alttextbackend.data.postgres.images as images
from rest_framework import serializers, status
from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.parsers import FormParser, MultiPartParser
from django.core.files.storage import default_storage from rest_framework.response import Response
from django.core.files.base import ContentFile from rest_framework.views import APIView
from uuid import uuid4
sys.path.append("../")
class ImagesFromBookSerializer(serializers.Serializer): class ImagesFromBookSerializer(serializers.Serializer):
bookid = serializers.CharField(required=True) bookid = serializers.CharField(required=True)
class BooksBookidImagesView(APIView): class BooksBookidImagesView(APIView):
parser_classes = (FormParser, MultiPartParser) parser_classes = (FormParser, MultiPartParser)
serializer_class = ImagesFromBookSerializer serializer_class = ImagesFromBookSerializer
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
serializer = self.serializer_class(data={"bookid": kwargs.get('bookid')}) serializer = self.serializer_class(data={"bookid": kwargs.get("bookid")})
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
validated_data = serializer.validated_data validated_data = serializer.validated_data
id = validated_data.get("bookid")
# TODO: IMPLEMENT LOGIC # check if book exists in database
book = books.getBook(id)
if not book:
return Response(
{"error": "Book not found"}, status=status.HTTP_404_NOT_FOUND
)
return Response(validated_data, status=status.HTTP_200_OK) # get images from database
imgs = images.getImagesByBook(id)
return Response(map(images.jsonifyImage, imgs), status=status.HTTP_200_OK)

View File

@ -1,26 +1,26 @@
from rest_framework.views import APIView from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework import status, permissions, serializers
from rest_framework.exceptions import ValidationError
from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.parsers import FormParser, MultiPartParser
from django.core.files.storage import default_storage from rest_framework.response import Response
from django.core.files.base import ContentFile from rest_framework.views import APIView
from uuid import uuid4
import alttextbackend.data.postgres.images as images
class GetImagesByHashSerializer(serializers.Serializer): class GetImagesByHashSerializer(serializers.Serializer):
hash = serializers.CharField(required=True) hash = serializers.CharField(required=True)
class ImagesHashView(APIView): class ImagesHashView(APIView):
parser_classes = (FormParser, MultiPartParser) parser_classes = (FormParser, MultiPartParser)
serializer_class = GetImagesByHashSerializer serializer_class = GetImagesByHashSerializer
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
image_hash = kwargs.get('hash') image_hash = kwargs.get("hash")
data = {'hash': image_hash} data = {"hash": image_hash}
serializer = self.serializer_class(data=data) serializer = self.serializer_class(data=data)
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# TODO: IMPLEMENT LOGIC imgs = images.getImagesByHash(image_hash)
return Response(data, status=status.HTTP_200_OK) return Response(map(images.jsonifyImage, imgs), status=status.HTTP_200_OK)

View File

@ -3,18 +3,12 @@ info:
title: Alt-text Backend API title: Alt-text Backend API
description: |- description: |-
This is the Alt-text Backend API based on the OpenAPI 3.0 specification. This is the Alt-text Backend API based on the OpenAPI 3.0 specification.
# termsOfService: http://swagger.io/terms/
contact: contact:
email: da.cruz@aol.com email: da.cruz@aol.com
# license:
# name: Apache 2.0
# url: http://www.apache.org/licenses/LICENSE-2.0.html
version: 1.0.11 version: 1.0.11
externalDocs: externalDocs:
description: Find out more about Alt-text description: Find out more about Alt-text
url: https://github.com/EbookFoundation/alt-text url: https://github.com/EbookFoundation/alt-text
# servers:
# - url: https://petstore3.swagger.io/api/v3
tags: tags:
- name: Books - name: Books
description: Everything regarding books description: Everything regarding books
@ -36,31 +30,6 @@ paths:
explode: true explode: true
schema: schema:
type: string type: string
- name: authorQ
in: query
description: String to match the author to.
required: false
explode: true
schema:
type: string
- name: sortBy
in: query
description: Field to sort by.
required: false
explode: true
schema:
type: string
enum: ["title", "author"]
default: "title"
- name: sortOrder
in: query
description: Order to sort by.
required: false
explode: true
schema:
type: string
enum: ["asc", "desc"]
default: "asc"
- name: limit - name: limit
in: query in: query
description: Max number of books to return. description: Max number of books to return.
@ -100,15 +69,12 @@ paths:
schema: schema:
type: object type: object
properties: properties:
id:
type: string
description: Id of the book (optional).
title: title:
type: string type: string
description: Title of the book. description: Title of the book.
author:
type: string
description: Author of the book.
description:
type: string
description: Description of the book (optional).
book: book:
type: string type: string
description: Zip file of the book. description: Zip file of the book.
@ -167,12 +133,6 @@ paths:
title: title:
type: string type: string
description: Title of the book (optional). description: Title of the book (optional).
author:
type: string
description: Author of the book (optional).
description:
type: string
description: Description of the book (optional).
cover: cover:
type: string type: string
description: Cover image for the book (optional). description: Cover image for the book (optional).
@ -195,32 +155,25 @@ paths:
summary: Re-analyze an entire book. summary: Re-analyze an entire book.
description: Re-analyze an entire book and overwrite current image data by its id. description: Re-analyze an entire book and overwrite current image data by its id.
operationId: analyzeBook operationId: analyzeBook
responses: parameters:
'200': - name: missingOnly
description: Successful operation in: query
content: description: If analyzing on upload, whether to analyze only the images without alt-text.
application/json: required: false
explode: true
schema: schema:
$ref: '#/components/schemas/Book' type: boolean
'500': example: true
description: Internal Server Error default: true
post: - name: waitForAnalysis
tags: in: query
- Books description: Whether to wait for the analysis to complete before returning a response.
summary: Upload a new book file to a book object. required: false
description: Upload a new book to a given book object (by its id), and re-analyze it (essentially creating a new book, except keeping the same bookid). explode: true
operationId: overwriteBook
requestBody:
required: true
content:
multipart/form-data:
schema: schema:
type: object type: boolean
properties: example: false
book: default: false
type: string
description: Zip file of the book.
format: binary
responses: responses:
'200': '200':
description: Successful operation description: Successful operation
@ -255,6 +208,15 @@ paths:
schema: schema:
type: string type: string
example: "123e4567-e89b-12d3-a456-426614174000" example: "123e4567-e89b-12d3-a456-426614174000"
- name: name
in: query
description: Alternative name for file download.
required: false
explode: true
schema:
type: string
default: "{bookid}"
example: "harry_potter"
get: get:
tags: tags:
- Books - Books
@ -270,12 +232,6 @@ paths:
type: string type: string
example: |- example: |-
content of the file content of the file
# headers:
# Content-Disposition:
# description: File name to prompt for download
# schema:
# type: string
# example: attachment; filename="example.txt"
'500': '500':
description: Internal Server Error description: Internal Server Error
/books/{bookid}/images: /books/{bookid}/images:
@ -383,6 +339,9 @@ paths:
afterContext: afterContext:
type: string type: string
description: New afterContext for the image (optional). description: New afterContext for the image (optional).
additionalContext:
type: string
description: New additionalContext for the image (optional).
responses: responses:
'200': '200':
description: Successful operation description: Successful operation
@ -398,6 +357,16 @@ paths:
summary: Re-analyze an image. summary: Re-analyze an image.
description: Generate an image's alt-text (written to genAlt field in image object). description: Generate an image's alt-text (written to genAlt field in image object).
operationId: analyzeImageBySrc operationId: analyzeImageBySrc
parameters:
- name: waitForAnalysis
in: query
description: Whether to wait for the analysis to complete before returning a response (default = false).
required: false
explode: true
schema:
type: boolean
example: false
default: false
responses: responses:
'200': '200':
description: Successful operation description: Successful operation
@ -445,12 +414,6 @@ components:
title: title:
type: string type: string
example: "Diary of an Oxygen Thief" example: "Diary of an Oxygen Thief"
author:
type: string
example: "Anonymous"
description:
type: string
example: "Hurt people hurt people."
size: size:
type: string type: string
example: "1.16MB" example: "1.16MB"
@ -458,39 +421,53 @@ components:
type: string type: string
example: "processing" example: "processing"
enum: ["available", "processing", "deleted"] enum: ["available", "processing", "deleted"]
default: "available"
numImages: numImages:
type: integer type: integer
example: 4 example: 4
Image: Image:
type: object type: object
properties: properties:
bookid:
type: string
example: "123e4567-e89b-12d3-a456-426614174000"
src: src:
type: string type: string
example: "images/cover.png" example: "images/cover.png"
hash: hash:
type: string type: string
example: "" example: ""
size: status:
type: string type: string
example: "24KB" example: "processing"
enum: ["available", "processing", "deleted"]
default: "available"
alt: alt:
type: string type: string
example: "" example: ""
default: "originalAlt"
originalAlt: originalAlt:
type: string type: string
example: "" example: ""
genAlt: genAlt:
type: string type: string
example: "" example: ""
default: ""
genImageCaption: genImageCaption:
type: string type: string
example: "" example: ""
default: ""
ocr: ocr:
type: string type: string
example: "" example: ""
default: ""
beforeContext: beforeContext:
type: string type: string
example: "" example: ""
afterContext: afterContext:
type: string type: string
example: "" example: ""
additionalContext:
type: string
example: ""
default: ""