Skip to content

Commit

Permalink
[ocr] v2 (#49)
Browse files Browse the repository at this point in the history
dont update , last version works fine
  • Loading branch information
owocado authored May 4, 2024
1 parent e27340a commit 3a20355
Show file tree
Hide file tree
Showing 7 changed files with 616 additions and 155 deletions.
6 changes: 4 additions & 2 deletions ocr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from discord.utils import maybe_coroutine
from redbot.core.errors import CogLoadError

from .ocr import OCR

__red_end_user_data_statement__ = "This cog does not persistently store any data or metadata about users."


async def setup(bot):
await maybe_coroutine(bot.add_cog, OCR())
if not getattr(bot, "session", None):
raise CogLoadError("This cog requires bot.session attr to be set.")
await bot.add_cog(OCR(bot))
106 changes: 59 additions & 47 deletions ocr/converter.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,75 @@
# This is a slightly modified version of converter originally made by TrustyJAID for his NotSoBot cog
# Attribution at: https://github.com/TrustyJAID/Trusty-cogs/blob/master/notsobot/converter.py
# I have included original LICENSE notice bundled with this cog to adhere to the license terms.
# I am forever indebted to and wholeheartedly thank TrustyJAID for providing this converter.
# originally made by TrustyJAID for his NotSoBot cog
# https://github.com/TrustyJAID/Trusty-cogs/blob/master/notsobot/converter.py
import re

from typing import Pattern, List

import discord
from redbot.core import commands
from redbot.core.commands import BadArgument, Context, Converter

IMAGE_LINKS: re.Pattern[str] = re.compile(
r"(https?:\/\/[^\"\'\s]*\.(?:png|jpg|jpeg|webp)(\?size=[0-9]{1,4})?)", flags=re.I
)

IMAGE_LINKS: Pattern = re.compile(
r"(https?:\/\/[^\"\'\s]*\.(?:png|jpg|jpeg|webp|gif)(\?size=[0-9]*)?)", flags=re.I
DISCORD_CDN: tuple[str, str] = (
"https://cdn.discordapp.com/attachments",
"https://media.discordapp.net/attachments",
)


class ImageFinder(commands.Converter):
class ImageFinder(Converter):
"""
This is a class to convert NotSoBot's image searching
capabilities into a more general converter class
"""

async def convert(self, ctx: commands.Context, argument: str) -> List[str]:
attachments = ctx.message.attachments
matches = IMAGE_LINKS.finditer(argument)
urls = []
if matches:
async def convert(self, ctx: Context, argument: str) -> list[str]:
urls: list[str] = []
if argument.startswith(DISCORD_CDN):
urls.append(argument.split()[0])
if matches := IMAGE_LINKS.finditer(argument):
urls.extend(match.group(1) for match in matches)
if attachments:
urls.extend(
match.group(1)
for attachment in attachments
if (match := IMAGE_LINKS.match(attachment.url))
)

if attachments := ctx.message.attachments:
urls.extend(img.url for img in attachments if img.content_type and img.content_type.startswith("image"))
if (e := ctx.message.embeds) and e[0].image:
urls.append(e[0].image.url)
if not urls:
if ctx.message.reference and (message := ctx.message.reference.resolved):
urls = await find_images_in_replies(message)
else:
urls = await search_for_images(ctx)
if not urls:
raise BadArgument("No images or image links found in chat bro 🥸")
return urls

async def find_images_in_replies(self, reference: discord.Message) -> List[str]:
urls = []
if match := IMAGE_LINKS.search(reference.content):
urls.append(match.group(1))
if reference.attachments:
if match := IMAGE_LINKS.match(reference.attachments[0].url):
urls.append(match.group(1))
if reference.embeds and reference.embeds[0].image:
urls.append(reference.embeds[0].image.url)
return urls

async def search_for_images(self, ctx: commands.Context) -> List[str]:
urls = []
async for message in ctx.channel.history(limit=20):
if message.embeds and message.embeds[0].image:
urls.append(message.embeds[0].image.url)
if message.attachments:
urls.extend(
match.group(1)
for attachment in message.attachments
if (match := IMAGE_LINKS.match(attachment.url))
)

if match := IMAGE_LINKS.search(message.content):
urls.append(match.group(1))
return urls
async def find_images_in_replies(reference: discord.DeletedReferencedMessage | discord.Message | None) -> list[str]:
if not reference or not isinstance(reference, discord.Message):
return []
urls = []
argument = reference.system_content
if argument.startswith(DISCORD_CDN):
urls.append(argument.split()[0])
if match := IMAGE_LINKS.search(argument):
urls.append(match.group(1))
if reference.attachments:
urls.extend(
img.url for img in reference.attachments if img.content_type and img.content_type.startswith("image")
)
if reference.embeds and reference.embeds[0].image:
urls.append(reference.embeds[0].image.url)
return urls


async def search_for_images(ctx: Context) -> list[str]:
urls = []
async for message in ctx.channel.history(limit=20):
if message.embeds and message.embeds[0].image:
urls.append(message.embeds[0].image.url)
if message.attachments:
urls.extend(
img.url for img in message.attachments if img.content_type and img.content_type.startswith("image")
)
if message.system_content.startswith(DISCORD_CDN):
urls.append(message.system_content.split()[0])
if match := IMAGE_LINKS.search(message.system_content):
urls.append(match.group(1))
return urls
4 changes: 2 additions & 2 deletions ocr/info.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "OCR",
"author": ["ow0x", "TrustyJAID"],
"author": ["owocado", "TrustyJAID"],
"short": "Detect text in images through OCR.",
"description": "Detect text in images through OCR.",
"install_msg": "**NOTE FOR BOT OWNER:**\n\nThis cog uses free ocr.space API which may give subpar results.\n\n**`[OPTIONAL]`**\nThere is optional support for Google Cloud Vision OCR API for improved text detection.\n(requires you to have a Google cloud project with active enabled billing account).\nYou may read more on that over at:\n<https://gist.github.com/ow0x/a7f17deaea4612a7dba4ba707210f7d8>",
"end_user_data_statement": "This cog does not persistently store any data or metadata about users.",
"tags": ["ocr", "image to text"],
"min_bot_version": "3.4.12",
"min_bot_version": "3.6.0",
"hidden": false,
"disabled": false,
"type": "COG"
Expand Down
147 changes: 147 additions & 0 deletions ocr/iso639.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from typing import Mapping


ISO639_MAP: Mapping[str, str] = {
"af": "Afrikaans",
"ak": "Twi (Akan)",
"am": "Amharic",
"ar": "Arabic",
"as": "Assamese",
"ay": "Aymara",
"az": "Azerbaijani",
"be": "Belarusian",
"bg": "Bulgarian",
"bho": "Bhojpuri",
"bm": "Bambara",
"bn": "Bengali",
"bs": "Bosnian",
"ca": "Catalan",
"ceb": "Cebuano",
"ckb": "Kurdish (Sorani)",
"co": "Corsican",
"cs": "Czech",
"cy": "Welsh",
"da": "Danish",
"de": "German",
"doi": "Dogri",
"dv": "Divehi",
"ee": "Ewe",
"el": "Greek",
"en": "English",
"eo": "Esperanto",
"es": "Spanish",
"et": "Estonian",
"eu": "Basque",
"fa": "Persian",
"fi": "Finnish",
"fo": "Faroese",
"fr": "French",
"fy": "Frisian",
"ga": "Irish Gaelic",
"gd": "Scottish Gaelic",
"gl": "Galician",
"gn": "Guarani",
"gom": "Konkani",
"gu": "Gujarati",
"ha": "Hausa",
"haw": "Hawaiian",
"he": "Hebrew",
"hi": "Hindi",
"hmn": "Hmong",
"hr": "Croatian",
"ht": "Haitian Creole",
"hu": "Hungarian",
"hy": "Armenian",
"id": "Indonesian",
"ig": "Igbo",
"ilo": "Ilocano",
"is": "Icelandic",
"it": "Italian",
"iw": "Hebrew",
"ja": "Japanese",
"jv": "Javanese",
"jw": "Javanese",
"ka": "Georgian",
"kk": "Kazakh",
"kl": "Kalaallisut",
"km": "Khmer",
"kn": "Kannada",
"ko": "Korean",
"kri": "Krio",
"ku": "Kurdish (Kurmanji)",
"ky": "Kyrgyz",
"la": "Latin",
"lb": "Luxembourgish",
"lg": "Luganda",
"ln": "Lingala",
"lo": "Lao",
"lt": "Lithuanian",
"lus": "Mizo",
"lv": "Latvian",
"mai": "Maithili",
"mg": "Malagasy",
"mi": "Māori",
"mk": "Macedonian",
"ml": "Malayalam",
"mn": "Mongolian",
"mni-Mtei": "Meitei (Manipuri)",
"mr": "Marathi",
"ms": "Malay",
"mt": "Maltese",
"my": "Burmese",
"ne": "Nepali",
"nl": "Dutch",
"no": "Norwegian",
"nso": "Sepedi (Northern Sotho)",
"ny": "Chichewa",
"om": "Oromo",
"or": "Odia (Oriya)",
"pa": "Punjabi",
"pl": "Polish",
"ps": "Pashto",
"pt": "Portuguese",
"qu": "Quechua",
"ro": "Romanian",
"ru": "Russian",
"rw": "Kinyarwanda",
"sa": "Sanskrit",
"sd": "Sindhi",
"si": "Sinhalese",
"sk": "Slovak",
"sl": "Slovenian",
"sm": "Samoan",
"sn": "Shona",
"so": "Somali",
"sq": "Albanian",
"sr": "Serbian",
"st": "Sesotho",
"su": "Sundanese",
"sv": "Swedish",
"sw": "Swahili",
"ta": "Tamil",
"te": "Telugu",
"tg": "Tajik",
"th": "Thai",
"ti": "Tigrinya",
"tk": "Turkmen",
"tl": "Filipino",
"tr": "Turkish",
"ts": "Tsonga",
"tt": "Tatar",
"ug": "Uyghur",
"uk": "Ukrainian",
"ur": "Urdu",
"uz": "Uzbek",
"vi": "Vietnamese",
"xh": "Xhosa",
"yi": "Yiddish",
"yo": "Yoruba",
"zu": "Zulu",
"zh": "Chinese Simplified",
"zh-cn": "Chinese Simplified",
"zh-tw": "Chinese Traditional",
"zh-CN": "Chinese Simplified",
"zh-TW": "Chinese Traditional",
"auto": "Autodetect",
}

68 changes: 68 additions & 0 deletions ocr/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from dataclasses import dataclass, field
from typing import List


@dataclass(slots=True)
class TextAnnotation:
locale: str = "en"
description: str = ""


@dataclass(slots=True)
class DetectedLanguage:
languageCode: str
confidence: float


@dataclass(slots=True)
class Property:
detectedLanguages: List[DetectedLanguage]


@dataclass(slots=True)
class Page:
width: int
height: int
confidence: float
property: Property | None = None


@dataclass(slots=True)
class FullTextAnnotation:
pages: List[Page] = field(default_factory=list)
text: str = ""

@property
def language_code(self) -> str:
if not self.pages:
return "auto"
if not self.pages[0].property:
return "auto"
if not self.pages[0].property.detectedLanguages:
return "auto"
return self.pages[0].property.detectedLanguages[0].languageCode


@dataclass(slots=True)
class VisionError:
code: int
message: str
status: str | None

def __str__(self) -> str:
return f"Error code: {self.code} ({self.message})"


@dataclass(slots=True)
class VisionPayload:
fullTextAnnotation: FullTextAnnotation | None
error: VisionError | None
textAnnotations: List[TextAnnotation] = field(default_factory=list)

@property
def text_value(self) -> str | None:
if not self.fullTextAnnotation:
if self.error:
return self.error.message
return None
return self.fullTextAnnotation.text or self.textAnnotations[0].description
Loading

0 comments on commit 3a20355

Please sign in to comment.