From 9ca8008f71785c9f4c7a26fdf51eb3f8662f3099 Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Tue, 12 Nov 2024 22:27:17 +0000 Subject: [PATCH 01/11] Added (smart) translation api (cherry picked from commit 6cb631f17eb1e7568f851b8862a039643bee3c09) --- scratchattach/other/other_apis.py | 54 +++++++++++++++++++++++++-- scratchattach/utils/supportedlangs.py | 29 ++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 scratchattach/utils/supportedlangs.py diff --git a/scratchattach/other/other_apis.py b/scratchattach/other/other_apis.py index df8d4235..973feaa5 100644 --- a/scratchattach/other/other_apis.py +++ b/scratchattach/other/other_apis.py @@ -1,38 +1,52 @@ """Other Scratch API-related functions""" +import json +import warnings + from ..utils import commons +from ..utils.exceptions import BadRequest from ..utils.requests import Requests as requests -import json +from ..utils.supportedlangs import SUPPORTED_CODES, SUPPORTED_NAMES + # --- Front page --- def get_news(*, limit=10, offset=0): - return commons.api_iterative("https://api.scratch.mit.edu/news", limit = limit, offset = offset) + return commons.api_iterative("https://api.scratch.mit.edu/news", limit=limit, offset=offset) + def featured_data(): return requests.get("https://api.scratch.mit.edu/proxy/featured").json() + def featured_projects(): return featured_data()["community_featured_projects"] + def featured_studios(): return featured_data()["community_featured_studios"] + def top_loved(): return featured_data()["community_most_loved_projects"] + def top_remixed(): return featured_data()["community_most_remixed_projects"] + def newest_projects(): return featured_data()["community_newest_projects"] + def curated_projects(): return featured_data()["curator_top_projects"] + def design_studio_projects(): return featured_data()["scratch_design_studio"] + # --- Statistics --- def total_site_stats(): @@ -40,14 +54,17 @@ def total_site_stats(): data.pop("_TS") return data + def monthly_site_traffic(): data = requests.get("https://scratch.mit.edu/statistics/data/monthly-ga/").json() data.pop("_TS") return data + def country_counts(): return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["country_distribution"] + def age_distribution(): data = requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["age_distribution_data"][0]["values"] return_data = {} @@ -55,18 +72,23 @@ def age_distribution(): return_data[value["x"]] = value["y"] return return_data + def monthly_comment_activity(): return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["comment_data"] + def monthly_project_shares(): return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["project_data"] + def monthly_active_users(): return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["active_user_data"] + def monthly_activity_trends(): return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["activity_data"] + # --- CSRF Token Generation API --- def get_csrf_token(): @@ -80,32 +102,41 @@ def get_csrf_token(): "https://scratch.mit.edu/csrf_token/" ).headers["set-cookie"].split(";")[3][len(" Path=/, scratchcsrftoken="):] + # --- Various other api.scratch.mit.edu API endpoints --- def get_health(): return requests.get("https://api.scratch.mit.edu/health").json() + def get_total_project_count() -> int: return requests.get("https://api.scratch.mit.edu/projects/count/all").json()["count"] + def check_username(username): return requests.get(f"https://api.scratch.mit.edu/accounts/checkusername/{username}").json()["msg"] + def check_password(password): - return requests.post("https://api.scratch.mit.edu/accounts/checkpassword/", json={"password":password}).json()["msg"] + return requests.post("https://api.scratch.mit.edu/accounts/checkpassword/", json={"password": password}).json()[ + "msg"] + # --- April fools endpoints --- def aprilfools_get_counter() -> int: return requests.get("https://api.scratch.mit.edu/surprise").json()["surprise"] + def aprilfools_increment_counter() -> int: return requests.post("https://api.scratch.mit.edu/surprise").json()["surprise"] + # --- Resources --- def get_resource_urls(): return requests.get("https://resources.scratch.mit.edu/localized-urls.json").json() + # --- Misc --- # I'm not sure what to label this as def scratch_team_members() -> dict: @@ -115,3 +146,20 @@ def scratch_team_members() -> dict: text = text.split("\"}]')")[0] + "\"}]" return json.loads(text) + + +def translate(language: str, text: str = "hello"): + if language not in SUPPORTED_CODES: + if language.lower() in SUPPORTED_CODES: + language = language.lower() + elif language.title() in SUPPORTED_NAMES: + language = SUPPORTED_CODES[SUPPORTED_NAMES.index(language.title())] + else: + warnings.warn(f"'{language}' is probably not a supported language") + response_json = requests.get( + f"https://translate-service.scratch.mit.edu/translate?language={language}&text={text}").json() + + if "result" in response_json: + return response_json["result"] + else: + raise BadRequest(f"Language '{language}' does not seem to be valid.\nResponse: {response_json}") diff --git a/scratchattach/utils/supportedlangs.py b/scratchattach/utils/supportedlangs.py new file mode 100644 index 00000000..0f3e7f38 --- /dev/null +++ b/scratchattach/utils/supportedlangs.py @@ -0,0 +1,29 @@ +""" +List of supported languages of scratch's translate extension. +Adapted from https://translate-service.scratch.mit.edu/supported?language=en +""" + +SUPPORTED_LANGS = {'sq': 'Albanian', 'am': 'Amharic', 'ar': 'Arabic', 'hy': 'Armenian', 'az': 'Azerbaijani', + 'eu': 'Basque', 'be': 'Belarusian', 'bg': 'Bulgarian', 'ca': 'Catalan', + 'zh-tw': 'Chinese (Traditional)', 'hr': 'Croatian', 'cs': 'Czech', 'da': 'Danish', 'nl': 'Dutch', + 'en': 'English', 'eo': 'Esperanto', 'et': 'Estonian', 'fi': 'Finnish', 'fr': 'French', + 'gl': 'Galician', 'de': 'German', 'el': 'Greek', 'ht': 'Haitian Creole', 'hi': 'Hindi', + 'hu': 'Hungarian', 'is': 'Icelandic', 'id': 'Indonesian', 'ga': 'Irish', 'it': 'Italian', + 'ja': 'Japanese', 'kn': 'Kannada', 'ko': 'Korean', 'ku': 'Kurdish (Kurmanji)', 'la': 'Latin', + 'lv': 'Latvian', 'lt': 'Lithuanian', 'mk': 'Macedonian', 'ms': 'Malay', 'ml': 'Malayalam', + 'mt': 'Maltese', 'mi': 'Maori', 'mr': 'Marathi', 'mn': 'Mongolian', 'my': 'Myanmar (Burmese)', + 'fa': 'Persian', 'pl': 'Polish', 'pt': 'Portuguese', 'ro': 'Romanian', 'ru': 'Russian', + 'gd': 'Scots Gaelic', 'sr': 'Serbian', 'sk': 'Slovak', 'sl': 'Slovenian', 'es': 'Spanish', + 'sv': 'Swedish', 'te': 'Telugu', 'th': 'Thai', 'tr': 'Turkish', 'uk': 'Ukrainian', 'uz': 'Uzbek', + 'vi': 'Vietnamese', 'cy': 'Welsh', 'zu': 'Zulu', 'he': 'Hebrew', 'zh-cn': 'Chinese (Simplified)'} +SUPPORTED_CODES = tuple(SUPPORTED_LANGS.keys()) +SUPPORTED_NAMES = tuple(SUPPORTED_LANGS.values()) + +# Code for generating the dict again: +# import requests +# +# SUPPORTED_LANGS = {} +# raw = requests.get("https://translate-service.scratch.mit.edu/supported").json() +# for lang in raw: +# SUPPORTED_LANGS[lang["code"]] = lang["name"] +# print(SUPPORTED_LANGS) From b7a3249f15ead77a7e5c1351eb6e2ddf4367fcae Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Wed, 13 Nov 2024 17:53:04 +0000 Subject: [PATCH 02/11] Added tts api (cherry picked from commit f3ce6a27098fc4c4acaf4db79e8d0273e75c90f9) --- scratchattach/other/other_apis.py | 40 +++++++- scratchattach/utils/supportedlangs.py | 141 +++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 2 deletions(-) diff --git a/scratchattach/other/other_apis.py b/scratchattach/other/other_apis.py index 973feaa5..25e0c174 100644 --- a/scratchattach/other/other_apis.py +++ b/scratchattach/other/other_apis.py @@ -6,7 +6,7 @@ from ..utils import commons from ..utils.exceptions import BadRequest from ..utils.requests import Requests as requests -from ..utils.supportedlangs import SUPPORTED_CODES, SUPPORTED_NAMES +from ..utils.supportedlangs import SUPPORTED_CODES, SUPPORTED_NAMES, tts_lang # --- Front page --- @@ -163,3 +163,41 @@ def translate(language: str, text: str = "hello"): return response_json["result"] else: raise BadRequest(f"Language '{language}' does not seem to be valid.\nResponse: {response_json}") + + +def text2speech(text: str = "hello", gender: str = "female", language: str = "en-US"): + """ + Sends a request to Scratch's TTS synthesis service. + Returns: + - The TTS audio (mp3) as bytes + - The playback rate (e.g. for giant it would be 0.84) + """ + if gender == "female" or gender == "alto": + gender = ("female", 1) + elif gender == "male" or gender == "tenor": + gender = ("male", 1) + elif gender == "squeak": + gender = ("female", 1.19) + elif gender == "giant": + gender = ("male", .84) + elif gender == "kitten": + gender = ("female", 1.41) + else: + gender = ("female", 1) + + if language not in SUPPORTED_NAMES: + if language.lower() in SUPPORTED_NAMES: + language = language.lower() + + elif language.title() in SUPPORTED_CODES: + language = SUPPORTED_NAMES[SUPPORTED_CODES.index(language.title())] + + lang = tts_lang(language.title()) + if lang is None: + warnings.warn(f"Language '{language}' is probably not a supported language") + else: + language = lang["speechSynthLocale"] + + response = requests.get(f"https://synthesis-service.scratch.mit.edu/synth" + f"?locale={language}&gender={gender[0]}&text={text}") + return response.content, gender[1] diff --git a/scratchattach/utils/supportedlangs.py b/scratchattach/utils/supportedlangs.py index 0f3e7f38..cdb2ed42 100644 --- a/scratchattach/utils/supportedlangs.py +++ b/scratchattach/utils/supportedlangs.py @@ -1,8 +1,9 @@ """ -List of supported languages of scratch's translate extension. +List of supported languages of scratch's translate and text2speech extensions. Adapted from https://translate-service.scratch.mit.edu/supported?language=en """ +# Supported langs for translate SUPPORTED_LANGS = {'sq': 'Albanian', 'am': 'Amharic', 'ar': 'Arabic', 'hy': 'Armenian', 'az': 'Azerbaijani', 'eu': 'Basque', 'be': 'Belarusian', 'bg': 'Bulgarian', 'ca': 'Catalan', 'zh-tw': 'Chinese (Traditional)', 'hr': 'Croatian', 'cs': 'Czech', 'da': 'Danish', 'nl': 'Dutch', @@ -27,3 +28,141 @@ # for lang in raw: # SUPPORTED_LANGS[lang["code"]] = lang["name"] # print(SUPPORTED_LANGS) + +# Language info for tts +TTS_LANGUAGE_INFO = [ + { + "name": 'Arabic', + "locales": ['ar'], + "speechSynthLocale": 'arb', + "singleGender": True + }, + { + "name": 'Chinese (Mandarin)', + "locales": ['zh-cn', 'zh-tw'], + "speechSynthLocale": 'cmn-CN', + "singleGender": True + }, + { + "name": 'Danish', + "locales": ['da'], + "speechSynthLocale": 'da-DK' + }, + { + "name": 'Dutch', + "locales": ['nl'], + "speechSynthLocale": 'nl-NL' + }, + { + "name": 'English', + "locales": ['en'], + "speechSynthLocale": 'en-US' + }, + { + "name": 'French', + "locales": ['fr'], + "speechSynthLocale": 'fr-FR' + }, + { + "name": 'German', + "locales": ['de'], + "speechSynthLocale": 'de-DE' + }, + { + "name": 'Hindi', + "locales": ['hi'], + "speechSynthLocale": 'hi-IN', + "singleGender": True + }, + { + "name": 'Icelandic', + "locales": ['is'], + "speechSynthLocale": 'is-IS' + }, + { + "name": 'Italian', + "locales": ['it'], + "speechSynthLocale": 'it-IT' + }, + { + "name": 'Japanese', + "locales": ['ja', 'ja-hira'], + "speechSynthLocale": 'ja-JP' + }, + { + "name": 'Korean', + "locales": ['ko'], + "speechSynthLocale": 'ko-KR', + "singleGender": True + }, + { + "name": 'Norwegian', + "locales": ['nb', 'nn'], + "speechSynthLocale": 'nb-NO', + "singleGender": True + }, + { + "name": 'Polish', + "locales": ['pl'], + "speechSynthLocale": 'pl-PL' + }, + { + "name": 'Portuguese (Brazilian)', + "locales": ['pt-br'], + "speechSynthLocale": 'pt-BR' + }, + { + "name": 'Portuguese (European)', + "locales": ['pt'], + "speechSynthLocale": 'pt-PT' + }, + { + "name": 'Romanian', + "locales": ['ro'], + "speechSynthLocale": 'ro-RO', + "singleGender": True + }, + { + "name": 'Russian', + "locales": ['ru'], + "speechSynthLocale": 'ru-RU' + }, + { + "name": 'Spanish (European)', + "locales": ['es'], + "speechSynthLocale": 'es-ES' + }, + { + "name": 'Spanish (Latin American)', + "locales": ['es-419'], + "speechSynthLocale": 'es-US' + }, + { + "name": 'Swedish', + "locales": ['sv'], + "speechSynthLocale": 'sv-SE', + "singleGender": True + }, + { + "name": 'Turkish', + "locales": ['tr'], + "speechSynthLocale": 'tr-TR', + "singleGender": True + }, + { + "name": 'Welsh', + "locales": ['cy'], + "speechSynthLocale": 'cy-GB', + "singleGender": True + }] + + +def tts_lang(attribute: str, by: str = None): + for lang in TTS_LANGUAGE_INFO: + if by is None: + if attribute in lang.values(): + return lang + continue + + if lang.get(by) == attribute: + return lang From 2b93c185c3293be700b4e3311b9f13f407134157 Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Wed, 13 Nov 2024 18:09:08 +0000 Subject: [PATCH 03/11] added an enum i guess? --- scratchattach/__init__.py | 1 + scratchattach/other/other_apis.py | 18 ++--- scratchattach/utils/supportedlangs.py | 108 ++++++++++++++++++++++---- 3 files changed, 103 insertions(+), 24 deletions(-) diff --git a/scratchattach/__init__.py b/scratchattach/__init__.py index 3e96d7b4..8c3bd392 100644 --- a/scratchattach/__init__.py +++ b/scratchattach/__init__.py @@ -10,6 +10,7 @@ from .other.other_apis import * from .other.project_json_capabilities import ProjectBody, get_empty_project_pb, get_pb_from_dict, read_sb3_file, download_asset from .utils.encoder import Encoding +from .utils.supportedlangs import TranslateSupportedLangs as TSLangs from .site.activity import Activity from .site.backpack_asset import BackpackAsset diff --git a/scratchattach/other/other_apis.py b/scratchattach/other/other_apis.py index 25e0c174..89180696 100644 --- a/scratchattach/other/other_apis.py +++ b/scratchattach/other/other_apis.py @@ -6,7 +6,7 @@ from ..utils import commons from ..utils.exceptions import BadRequest from ..utils.requests import Requests as requests -from ..utils.supportedlangs import SUPPORTED_CODES, SUPPORTED_NAMES, tts_lang +from ..utils.supportedlangs import tts_lang, TSL_CODES, TSL_NAMES # --- Front page --- @@ -149,11 +149,11 @@ def scratch_team_members() -> dict: def translate(language: str, text: str = "hello"): - if language not in SUPPORTED_CODES: - if language.lower() in SUPPORTED_CODES: + if language not in TSL_CODES: + if language.lower() in TSL_CODES: language = language.lower() - elif language.title() in SUPPORTED_NAMES: - language = SUPPORTED_CODES[SUPPORTED_NAMES.index(language.title())] + elif language.title() in TSL_NAMES: + language = TSL_CODES[TSL_NAMES.index(language.title())] else: warnings.warn(f"'{language}' is probably not a supported language") response_json = requests.get( @@ -185,12 +185,12 @@ def text2speech(text: str = "hello", gender: str = "female", language: str = "en else: gender = ("female", 1) - if language not in SUPPORTED_NAMES: - if language.lower() in SUPPORTED_NAMES: + if language not in TSL_NAMES: + if language.lower() in TSL_NAMES: language = language.lower() - elif language.title() in SUPPORTED_CODES: - language = SUPPORTED_NAMES[SUPPORTED_CODES.index(language.title())] + elif language.title() in TSL_CODES: + language = TSL_NAMES[TSL_CODES.index(language.title())] lang = tts_lang(language.title()) if lang is None: diff --git a/scratchattach/utils/supportedlangs.py b/scratchattach/utils/supportedlangs.py index cdb2ed42..b4a7fe26 100644 --- a/scratchattach/utils/supportedlangs.py +++ b/scratchattach/utils/supportedlangs.py @@ -2,23 +2,101 @@ List of supported languages of scratch's translate and text2speech extensions. Adapted from https://translate-service.scratch.mit.edu/supported?language=en """ +from enum import Enum # Supported langs for translate -SUPPORTED_LANGS = {'sq': 'Albanian', 'am': 'Amharic', 'ar': 'Arabic', 'hy': 'Armenian', 'az': 'Azerbaijani', - 'eu': 'Basque', 'be': 'Belarusian', 'bg': 'Bulgarian', 'ca': 'Catalan', - 'zh-tw': 'Chinese (Traditional)', 'hr': 'Croatian', 'cs': 'Czech', 'da': 'Danish', 'nl': 'Dutch', - 'en': 'English', 'eo': 'Esperanto', 'et': 'Estonian', 'fi': 'Finnish', 'fr': 'French', - 'gl': 'Galician', 'de': 'German', 'el': 'Greek', 'ht': 'Haitian Creole', 'hi': 'Hindi', - 'hu': 'Hungarian', 'is': 'Icelandic', 'id': 'Indonesian', 'ga': 'Irish', 'it': 'Italian', - 'ja': 'Japanese', 'kn': 'Kannada', 'ko': 'Korean', 'ku': 'Kurdish (Kurmanji)', 'la': 'Latin', - 'lv': 'Latvian', 'lt': 'Lithuanian', 'mk': 'Macedonian', 'ms': 'Malay', 'ml': 'Malayalam', - 'mt': 'Maltese', 'mi': 'Maori', 'mr': 'Marathi', 'mn': 'Mongolian', 'my': 'Myanmar (Burmese)', - 'fa': 'Persian', 'pl': 'Polish', 'pt': 'Portuguese', 'ro': 'Romanian', 'ru': 'Russian', - 'gd': 'Scots Gaelic', 'sr': 'Serbian', 'sk': 'Slovak', 'sl': 'Slovenian', 'es': 'Spanish', - 'sv': 'Swedish', 'te': 'Telugu', 'th': 'Thai', 'tr': 'Turkish', 'uk': 'Ukrainian', 'uz': 'Uzbek', - 'vi': 'Vietnamese', 'cy': 'Welsh', 'zu': 'Zulu', 'he': 'Hebrew', 'zh-cn': 'Chinese (Simplified)'} -SUPPORTED_CODES = tuple(SUPPORTED_LANGS.keys()) -SUPPORTED_NAMES = tuple(SUPPORTED_LANGS.values()) +_TRANSLATE_SUPPORTED_LANGS = {'sq': 'Albanian', 'am': 'Amharic', 'ar': 'Arabic', 'hy': 'Armenian', 'az': 'Azerbaijani', + 'eu': 'Basque', 'be': 'Belarusian', 'bg': 'Bulgarian', 'ca': 'Catalan', + 'zh-tw': 'Chinese (Traditional)', 'hr': 'Croatian', 'cs': 'Czech', 'da': 'Danish', + 'nl': 'Dutch', + 'en': 'English', 'eo': 'Esperanto', 'et': 'Estonian', 'fi': 'Finnish', 'fr': 'French', + 'gl': 'Galician', 'de': 'German', 'el': 'Greek', 'ht': 'Haitian Creole', 'hi': 'Hindi', + 'hu': 'Hungarian', 'is': 'Icelandic', 'id': 'Indonesian', 'ga': 'Irish', 'it': 'Italian', + 'ja': 'Japanese', 'kn': 'Kannada', 'ko': 'Korean', 'ku': 'Kurdish (Kurmanji)', + 'la': 'Latin', + 'lv': 'Latvian', 'lt': 'Lithuanian', 'mk': 'Macedonian', 'ms': 'Malay', 'ml': 'Malayalam', + 'mt': 'Maltese', 'mi': 'Maori', 'mr': 'Marathi', 'mn': 'Mongolian', + 'my': 'Myanmar (Burmese)', + 'fa': 'Persian', 'pl': 'Polish', 'pt': 'Portuguese', 'ro': 'Romanian', 'ru': 'Russian', + 'gd': 'Scots Gaelic', 'sr': 'Serbian', 'sk': 'Slovak', 'sl': 'Slovenian', 'es': 'Spanish', + 'sv': 'Swedish', 'te': 'Telugu', 'th': 'Thai', 'tr': 'Turkish', 'uk': 'Ukrainian', + 'uz': 'Uzbek', + 'vi': 'Vietnamese', 'cy': 'Welsh', 'zu': 'Zulu', 'he': 'Hebrew', + 'zh-cn': 'Chinese (Simplified)'} + +TSL_CODES = tuple(_TRANSLATE_SUPPORTED_LANGS.keys()) +TSL_NAMES = tuple(_TRANSLATE_SUPPORTED_LANGS.values()) + + +class TranslateSupportedLangs(Enum): + Albanian = "sq" + Amharic = "am" + Arabic = "ar" + Armenian = "hy" + Azerbaijani = "az" + Basque = "eu" + Belarusian = "be" + Bulgarian = "bg" + Catalan = "ca" + Traditional_Chinese = "zh-tw" + Croatian = "hr" + Czech = "cs" + Danish = "da" + Dutch = "nl" + English = "en" + Esperanto = "eo" + Estonian = "et" + Finnish = "fi" + French = "fr" + Galician = "gl" + German = "de" + Greek = "el" + Haitian_Creole = "ht" + Hindi = "hi" + Hungarian = "hu" + Icelandic = "is" + Indonesian = "id" + Irish = "ga" + Italian = "it" + Japanese = "ja" + Kannada = "kn" + Korean = "ko" + Kurdish = "ku" + Kurmanji = "ku" + Latin = "la" + Latvian = "lv" + Lithuanian = "lt" + Macedonian = "mk" + Malay = "ms" + Malayalam = "ml" + Maltese = "mt" + Maori = "mi" + Marathi = "mr" + Mongolian = "mn" + Myanmar = "my" + Burmese = "my" + Persian = "fa" + Polish = "pl" + Portuguese = "pt" + Romanian = "ro" + Russian = "ru" + Scots_Gaelic = "gd" + Serbian = "sr" + Slovak = "sk" + Slovenian = "sl" + Spanish = "es" + Swedish = "sv" + Telugu = "te" + Thai = "th" + Turkish = "tr" + Ukrainian = "uk" + Uzbek = "uz" + Vietnamese = "vi" + Welsh = "cy" + Zulu = "zu" + Hebrew = "he" + Simplified_Chinese = "zh-cn" + # Code for generating the dict again: # import requests From f91178d1ecdce6bb412bc2fe667a34c93f15ccee Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Sat, 16 Nov 2024 20:18:55 +0000 Subject: [PATCH 04/11] using languages enum now + kitten improvement (cherry picked from commit 765b0682800326f29d8a18a3a7a19d3bae51ba94) --- scratchattach/__init__.py | 2 +- scratchattach/other/other_apis.py | 50 ++-- scratchattach/utils/supportedlangs.py | 354 +++++++++----------------- 3 files changed, 153 insertions(+), 253 deletions(-) diff --git a/scratchattach/__init__.py b/scratchattach/__init__.py index 8c3bd392..8628bddb 100644 --- a/scratchattach/__init__.py +++ b/scratchattach/__init__.py @@ -10,7 +10,7 @@ from .other.other_apis import * from .other.project_json_capabilities import ProjectBody, get_empty_project_pb, get_pb_from_dict, read_sb3_file, download_asset from .utils.encoder import Encoding -from .utils.supportedlangs import TranslateSupportedLangs as TSLangs +from .utils.supportedlangs import Languages from .site.activity import Activity from .site.backpack_asset import BackpackAsset diff --git a/scratchattach/other/other_apis.py b/scratchattach/other/other_apis.py index 89180696..e4401c0b 100644 --- a/scratchattach/other/other_apis.py +++ b/scratchattach/other/other_apis.py @@ -6,7 +6,7 @@ from ..utils import commons from ..utils.exceptions import BadRequest from ..utils.requests import Requests as requests -from ..utils.supportedlangs import tts_lang, TSL_CODES, TSL_NAMES +from ..utils.supportedlangs import Languages # --- Front page --- @@ -148,16 +148,17 @@ def scratch_team_members() -> dict: return json.loads(text) -def translate(language: str, text: str = "hello"): - if language not in TSL_CODES: - if language.lower() in TSL_CODES: - language = language.lower() - elif language.title() in TSL_NAMES: - language = TSL_CODES[TSL_NAMES.index(language.title())] - else: - warnings.warn(f"'{language}' is probably not a supported language") +def translate(language: str | Languages, text: str = "hello"): + if language.lower() not in Languages.all_of("code", str.lower): + if language.lower() in Languages.all_of("name", str.lower): + language = Languages.find(language.lower(), apply_func=str.lower).code + + lang = Languages.find(language, "code", str.lower) + if lang is None: + raise ValueError(f"{language} is not a supported translate language") + response_json = requests.get( - f"https://translate-service.scratch.mit.edu/translate?language={language}&text={text}").json() + f"https://translate-service.scratch.mit.edu/translate?language={lang.code}&text={text}").json() if "result" in response_json: return response_json["result"] @@ -182,22 +183,29 @@ def text2speech(text: str = "hello", gender: str = "female", language: str = "en gender = ("male", .84) elif gender == "kitten": gender = ("female", 1.41) + split = text.split(' ') + text = '' + for token in split: + if token.strip() != '': + text += "meow " else: gender = ("female", 1) - if language not in TSL_NAMES: - if language.lower() in TSL_NAMES: - language = language.lower() + og_lang = language + if isinstance(language, Languages): + language = language.value.tts_locale - elif language.title() in TSL_CODES: - language = TSL_NAMES[TSL_CODES.index(language.title())] + if language is None: + raise ValueError(f"Language '{og_lang}' is not a supported tts language") - lang = tts_lang(language.title()) - if lang is None: - warnings.warn(f"Language '{language}' is probably not a supported language") - else: - language = lang["speechSynthLocale"] + if language.lower() not in Languages.all_of("tts_locale", str.lower): + if language.lower() in Languages.all_of("name", str.lower): + language = Languages.find(language.lower(), apply_func=str.lower).tts_locale + + lang = Languages.find(language, "tts_locale") + if lang is None or language is None: + raise ValueError(f"Language '{og_lang}' is not a supported tts language") response = requests.get(f"https://synthesis-service.scratch.mit.edu/synth" - f"?locale={language}&gender={gender[0]}&text={text}") + f"?locale={lang.tts_locale}&gender={gender[0]}&text={text}") return response.content, gender[1] diff --git a/scratchattach/utils/supportedlangs.py b/scratchattach/utils/supportedlangs.py index b4a7fe26..d6ac0f46 100644 --- a/scratchattach/utils/supportedlangs.py +++ b/scratchattach/utils/supportedlangs.py @@ -2,245 +2,137 @@ List of supported languages of scratch's translate and text2speech extensions. Adapted from https://translate-service.scratch.mit.edu/supported?language=en """ + from enum import Enum +from typing import Callable + + +class _Language: + def __init__(self, name: str = None, code: str = None, locales: list[str] = None, tts_locale: str = None, + single_gender: bool = None): + self.name = name + self.code = code + self.locales = locales + self.tts_locale = tts_locale + self.single_gender = single_gender + + def __repr__(self): + ret = "Language(" + for attr in self.__dict__.keys(): + if not attr.startswith("_"): + val = getattr(self, attr) + ret += f"{repr(val)}, " + if ret.endswith(", "): + ret = ret[:-2] -# Supported langs for translate -_TRANSLATE_SUPPORTED_LANGS = {'sq': 'Albanian', 'am': 'Amharic', 'ar': 'Arabic', 'hy': 'Armenian', 'az': 'Azerbaijani', - 'eu': 'Basque', 'be': 'Belarusian', 'bg': 'Bulgarian', 'ca': 'Catalan', - 'zh-tw': 'Chinese (Traditional)', 'hr': 'Croatian', 'cs': 'Czech', 'da': 'Danish', - 'nl': 'Dutch', - 'en': 'English', 'eo': 'Esperanto', 'et': 'Estonian', 'fi': 'Finnish', 'fr': 'French', - 'gl': 'Galician', 'de': 'German', 'el': 'Greek', 'ht': 'Haitian Creole', 'hi': 'Hindi', - 'hu': 'Hungarian', 'is': 'Icelandic', 'id': 'Indonesian', 'ga': 'Irish', 'it': 'Italian', - 'ja': 'Japanese', 'kn': 'Kannada', 'ko': 'Korean', 'ku': 'Kurdish (Kurmanji)', - 'la': 'Latin', - 'lv': 'Latvian', 'lt': 'Lithuanian', 'mk': 'Macedonian', 'ms': 'Malay', 'ml': 'Malayalam', - 'mt': 'Maltese', 'mi': 'Maori', 'mr': 'Marathi', 'mn': 'Mongolian', - 'my': 'Myanmar (Burmese)', - 'fa': 'Persian', 'pl': 'Polish', 'pt': 'Portuguese', 'ro': 'Romanian', 'ru': 'Russian', - 'gd': 'Scots Gaelic', 'sr': 'Serbian', 'sk': 'Slovak', 'sl': 'Slovenian', 'es': 'Spanish', - 'sv': 'Swedish', 'te': 'Telugu', 'th': 'Thai', 'tr': 'Turkish', 'uk': 'Ukrainian', - 'uz': 'Uzbek', - 'vi': 'Vietnamese', 'cy': 'Welsh', 'zu': 'Zulu', 'he': 'Hebrew', - 'zh-cn': 'Chinese (Simplified)'} + ret += ')' + return ret -TSL_CODES = tuple(_TRANSLATE_SUPPORTED_LANGS.keys()) -TSL_NAMES = tuple(_TRANSLATE_SUPPORTED_LANGS.values()) + def __str__(self): + return f"Language<{self.name} - {self.code}>" -class TranslateSupportedLangs(Enum): - Albanian = "sq" - Amharic = "am" - Arabic = "ar" - Armenian = "hy" - Azerbaijani = "az" - Basque = "eu" - Belarusian = "be" - Bulgarian = "bg" - Catalan = "ca" - Traditional_Chinese = "zh-tw" - Croatian = "hr" - Czech = "cs" - Danish = "da" - Dutch = "nl" - English = "en" - Esperanto = "eo" - Estonian = "et" - Finnish = "fi" - French = "fr" - Galician = "gl" - German = "de" - Greek = "el" - Haitian_Creole = "ht" - Hindi = "hi" - Hungarian = "hu" - Icelandic = "is" - Indonesian = "id" - Irish = "ga" - Italian = "it" - Japanese = "ja" - Kannada = "kn" - Korean = "ko" - Kurdish = "ku" - Kurmanji = "ku" - Latin = "la" - Latvian = "lv" - Lithuanian = "lt" - Macedonian = "mk" - Malay = "ms" - Malayalam = "ml" - Maltese = "mt" - Maori = "mi" - Marathi = "mr" - Mongolian = "mn" - Myanmar = "my" - Burmese = "my" - Persian = "fa" - Polish = "pl" - Portuguese = "pt" - Romanian = "ro" - Russian = "ru" - Scots_Gaelic = "gd" - Serbian = "sr" - Slovak = "sk" - Slovenian = "sl" - Spanish = "es" - Swedish = "sv" - Telugu = "te" - Thai = "th" - Turkish = "tr" - Ukrainian = "uk" - Uzbek = "uz" - Vietnamese = "vi" - Welsh = "cy" - Zulu = "zu" - Hebrew = "he" - Simplified_Chinese = "zh-cn" +class Languages(Enum): + Albanian = _Language('Albanian', 'sq', None, None, None) + Amharic = _Language('Amharic', 'am', None, None, None) + Arabic = _Language('Arabic', 'ar', ['ar'], 'arb', True) + Armenian = _Language('Armenian', 'hy', None, None, None) + Azerbaijani = _Language('Azerbaijani', 'az', None, None, None) + Basque = _Language('Basque', 'eu', None, None, None) + Belarusian = _Language('Belarusian', 'be', None, None, None) + Bulgarian = _Language('Bulgarian', 'bg', None, None, None) + Catalan = _Language('Catalan', 'ca', None, None, None) + Chinese_Traditional = _Language('Chinese (Traditional)', 'zh-tw', None, None, None) + Croatian = _Language('Croatian', 'hr', None, None, None) + Czech = _Language('Czech', 'cs', None, None, None) + Danish = _Language('Danish', 'da', ['da'], 'da-DK', False) + Dutch = _Language('Dutch', 'nl', ['nl'], 'nl-NL', False) + English = _Language('English', 'en', ['en'], 'en-US', False) + Esperanto = _Language('Esperanto', 'eo', None, None, None) + Estonian = _Language('Estonian', 'et', None, None, None) + Finnish = _Language('Finnish', 'fi', None, None, None) + French = _Language('French', 'fr', ['fr'], 'fr-FR', False) + Galician = _Language('Galician', 'gl', None, None, None) + German = _Language('German', 'de', ['de'], 'de-DE', False) + Greek = _Language('Greek', 'el', None, None, None) + Haitian_Creole = _Language('Haitian Creole', 'ht', None, None, None) + Hindi = _Language('Hindi', 'hi', ['hi'], 'hi-IN', True) + Hungarian = _Language('Hungarian', 'hu', None, None, None) + Icelandic = _Language('Icelandic', 'is', ['is'], 'is-IS', False) + Indonesian = _Language('Indonesian', 'id', None, None, None) + Irish = _Language('Irish', 'ga', None, None, None) + Italian = _Language('Italian', 'it', ['it'], 'it-IT', False) + Japanese = _Language('Japanese', 'ja', ['ja', 'ja-hira'], 'ja-JP', False) + Kannada = _Language('Kannada', 'kn', None, None, None) + Korean = _Language('Korean', 'ko', ['ko'], 'ko-KR', True) + Kurdish_Kurmanji = _Language('Kurdish (Kurmanji)', 'ku', None, None, None) + Latin = _Language('Latin', 'la', None, None, None) + Latvian = _Language('Latvian', 'lv', None, None, None) + Lithuanian = _Language('Lithuanian', 'lt', None, None, None) + Macedonian = _Language('Macedonian', 'mk', None, None, None) + Malay = _Language('Malay', 'ms', None, None, None) + Malayalam = _Language('Malayalam', 'ml', None, None, None) + Maltese = _Language('Maltese', 'mt', None, None, None) + Maori = _Language('Maori', 'mi', None, None, None) + Marathi = _Language('Marathi', 'mr', None, None, None) + Mongolian = _Language('Mongolian', 'mn', None, None, None) + Myanmar_Burmese = _Language('Myanmar (Burmese)', 'my', None, None, None) + Persian = _Language('Persian', 'fa', None, None, None) + Polish = _Language('Polish', 'pl', ['pl'], 'pl-PL', False) + Portuguese = _Language('Portuguese', 'pt', None, None, None) + Romanian = _Language('Romanian', 'ro', ['ro'], 'ro-RO', True) + Russian = _Language('Russian', 'ru', ['ru'], 'ru-RU', False) + Scots_Gaelic = _Language('Scots Gaelic', 'gd', None, None, None) + Serbian = _Language('Serbian', 'sr', None, None, None) + Slovak = _Language('Slovak', 'sk', None, None, None) + Slovenian = _Language('Slovenian', 'sl', None, None, None) + Spanish = _Language('Spanish', 'es', None, None, None) + Swedish = _Language('Swedish', 'sv', ['sv'], 'sv-SE', True) + Telugu = _Language('Telugu', 'te', None, None, None) + Thai = _Language('Thai', 'th', None, None, None) + Turkish = _Language('Turkish', 'tr', ['tr'], 'tr-TR', True) + Ukrainian = _Language('Ukrainian', 'uk', None, None, None) + Uzbek = _Language('Uzbek', 'uz', None, None, None) + Vietnamese = _Language('Vietnamese', 'vi', None, None, None) + Welsh = _Language('Welsh', 'cy', ['cy'], 'cy-GB', True) + Zulu = _Language('Zulu', 'zu', None, None, None) + Hebrew = _Language('Hebrew', 'he', None, None, None) + Chinese_Simplified = _Language('Chinese (Simplified)', 'zh-cn', None, None, None) + Mandarin = Chinese_Simplified + cmn_CN = _Language(None, None, ['zh-cn', 'zh-tw'], 'cmn-CN', True) + nb_NO = _Language(None, None, ['nb', 'nn'], 'nb-NO', True) + pt_BR = _Language(None, None, ['pt-br'], 'pt-BR', False) + Brazilian = pt_BR + pt_PT = _Language(None, None, ['pt'], 'pt-PT', False) + es_ES = _Language(None, None, ['es'], 'es-ES', False) + es_US = _Language(None, None, ['es-419'], 'es-US', False) -# Code for generating the dict again: -# import requests -# -# SUPPORTED_LANGS = {} -# raw = requests.get("https://translate-service.scratch.mit.edu/supported").json() -# for lang in raw: -# SUPPORTED_LANGS[lang["code"]] = lang["name"] -# print(SUPPORTED_LANGS) + @staticmethod + def find(value, by: str = "name", apply_func: Callable = None) -> _Language: + if apply_func is None: + def apply_func(x): + return x -# Language info for tts -TTS_LANGUAGE_INFO = [ - { - "name": 'Arabic', - "locales": ['ar'], - "speechSynthLocale": 'arb', - "singleGender": True - }, - { - "name": 'Chinese (Mandarin)', - "locales": ['zh-cn', 'zh-tw'], - "speechSynthLocale": 'cmn-CN', - "singleGender": True - }, - { - "name": 'Danish', - "locales": ['da'], - "speechSynthLocale": 'da-DK' - }, - { - "name": 'Dutch', - "locales": ['nl'], - "speechSynthLocale": 'nl-NL' - }, - { - "name": 'English', - "locales": ['en'], - "speechSynthLocale": 'en-US' - }, - { - "name": 'French', - "locales": ['fr'], - "speechSynthLocale": 'fr-FR' - }, - { - "name": 'German', - "locales": ['de'], - "speechSynthLocale": 'de-DE' - }, - { - "name": 'Hindi', - "locales": ['hi'], - "speechSynthLocale": 'hi-IN', - "singleGender": True - }, - { - "name": 'Icelandic', - "locales": ['is'], - "speechSynthLocale": 'is-IS' - }, - { - "name": 'Italian', - "locales": ['it'], - "speechSynthLocale": 'it-IT' - }, - { - "name": 'Japanese', - "locales": ['ja', 'ja-hira'], - "speechSynthLocale": 'ja-JP' - }, - { - "name": 'Korean', - "locales": ['ko'], - "speechSynthLocale": 'ko-KR', - "singleGender": True - }, - { - "name": 'Norwegian', - "locales": ['nb', 'nn'], - "speechSynthLocale": 'nb-NO', - "singleGender": True - }, - { - "name": 'Polish', - "locales": ['pl'], - "speechSynthLocale": 'pl-PL' - }, - { - "name": 'Portuguese (Brazilian)', - "locales": ['pt-br'], - "speechSynthLocale": 'pt-BR' - }, - { - "name": 'Portuguese (European)', - "locales": ['pt'], - "speechSynthLocale": 'pt-PT' - }, - { - "name": 'Romanian', - "locales": ['ro'], - "speechSynthLocale": 'ro-RO', - "singleGender": True - }, - { - "name": 'Russian', - "locales": ['ru'], - "speechSynthLocale": 'ru-RU' - }, - { - "name": 'Spanish (European)', - "locales": ['es'], - "speechSynthLocale": 'es-ES' - }, - { - "name": 'Spanish (Latin American)', - "locales": ['es-419'], - "speechSynthLocale": 'es-US' - }, - { - "name": 'Swedish', - "locales": ['sv'], - "speechSynthLocale": 'sv-SE', - "singleGender": True - }, - { - "name": 'Turkish', - "locales": ['tr'], - "speechSynthLocale": 'tr-TR', - "singleGender": True - }, - { - "name": 'Welsh', - "locales": ['cy'], - "speechSynthLocale": 'cy-GB', - "singleGender": True - }] + for lang_enum in Languages: + lang = lang_enum.value + try: + if apply_func(getattr(lang, by)) == value: + return lang + except TypeError: + pass + @staticmethod + def all_of(attr_name: str = "name", apply_func: Callable = None): + if apply_func is None: + def apply_func(x): + return x -def tts_lang(attribute: str, by: str = None): - for lang in TTS_LANGUAGE_INFO: - if by is None: - if attribute in lang.values(): - return lang - continue + for lang_enum in Languages: + lang = lang_enum.value + attr = getattr(lang, attr_name) + try: + yield apply_func(attr) - if lang.get(by) == attribute: - return lang + except TypeError: + yield attr From b6c565eb60180e2e9af0657b4beaad69096e4989 Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Wed, 20 Nov 2024 17:35:37 +0000 Subject: [PATCH 05/11] smarter language evaluation in translate function. some credit to thecommcraft (cherry picked from commit 51d342279a28a21c2e1371d11ef8d55470379c03) --- scratchattach/other/other_apis.py | 18 ++++++++++++++---- scratchattach/utils/supportedlangs.py | 1 + 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/scratchattach/other/other_apis.py b/scratchattach/other/other_apis.py index e4401c0b..7360c0ae 100644 --- a/scratchattach/other/other_apis.py +++ b/scratchattach/other/other_apis.py @@ -149,14 +149,24 @@ def scratch_team_members() -> dict: def translate(language: str | Languages, text: str = "hello"): - if language.lower() not in Languages.all_of("code", str.lower): - if language.lower() in Languages.all_of("name", str.lower): - language = Languages.find(language.lower(), apply_func=str.lower).code + lang = language + if isinstance(language, str): + if language.lower() in Languages.all_of("code", str.lower): + lang = Languages.find(language.lower(), "code", apply_func=str.lower) + + elif language.lower() in Languages.all_of("name", str.lower): + lang = Languages.find(language.lower(), apply_func=str.lower) + + elif isinstance(language, Languages): + lang = language.value + else: + # The code will work so long as the language has a 'code' attribute, however, this is bad practice. + warnings.warn(f"{language} is not {str} or {Languages}, but {type(language)}.") - lang = Languages.find(language, "code", str.lower) if lang is None: raise ValueError(f"{language} is not a supported translate language") + print(lang.__dict__) response_json = requests.get( f"https://translate-service.scratch.mit.edu/translate?language={lang.code}&text={text}").json() diff --git a/scratchattach/utils/supportedlangs.py b/scratchattach/utils/supportedlangs.py index d6ac0f46..bdfdba55 100644 --- a/scratchattach/utils/supportedlangs.py +++ b/scratchattach/utils/supportedlangs.py @@ -116,6 +116,7 @@ def apply_func(x): for lang_enum in Languages: lang = lang_enum.value + try: if apply_func(getattr(lang, by)) == value: return lang From e3c476936f68f31dcd26364a3228271a4bfa1a5e Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Wed, 20 Nov 2024 17:41:48 +0000 Subject: [PATCH 06/11] dataclasses = conciseness & clean (cherry picked from commit cec1a1234fe8c27df914aa47fcba3657ea7b4729) --- scratchattach/other/other_apis.py | 1 - scratchattach/utils/supportedlangs.py | 30 +++++++-------------------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/scratchattach/other/other_apis.py b/scratchattach/other/other_apis.py index 7360c0ae..c5fd5491 100644 --- a/scratchattach/other/other_apis.py +++ b/scratchattach/other/other_apis.py @@ -166,7 +166,6 @@ def translate(language: str | Languages, text: str = "hello"): if lang is None: raise ValueError(f"{language} is not a supported translate language") - print(lang.__dict__) response_json = requests.get( f"https://translate-service.scratch.mit.edu/translate?language={lang.code}&text={text}").json() diff --git a/scratchattach/utils/supportedlangs.py b/scratchattach/utils/supportedlangs.py index bdfdba55..f1102209 100644 --- a/scratchattach/utils/supportedlangs.py +++ b/scratchattach/utils/supportedlangs.py @@ -4,32 +4,18 @@ """ from enum import Enum +from dataclasses import dataclass + from typing import Callable +@dataclass(init=True, repr=True) class _Language: - def __init__(self, name: str = None, code: str = None, locales: list[str] = None, tts_locale: str = None, - single_gender: bool = None): - self.name = name - self.code = code - self.locales = locales - self.tts_locale = tts_locale - self.single_gender = single_gender - - def __repr__(self): - ret = "Language(" - for attr in self.__dict__.keys(): - if not attr.startswith("_"): - val = getattr(self, attr) - ret += f"{repr(val)}, " - if ret.endswith(", "): - ret = ret[:-2] - - ret += ')' - return ret - - def __str__(self): - return f"Language<{self.name} - {self.code}>" + name: str = None + code: str = None + locales: list[str] = None + tts_locale: str = None + single_gender: bool = None class Languages(Enum): From 8690df15c80ef8390a2a3f1734c342ea56109a3b Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Wed, 20 Nov 2024 17:48:34 +0000 Subject: [PATCH 07/11] docstrings and removing use of kwarg for all_of (cherry picked from commit a5c48a00b844a55c1211cd01e5d5b441284519ec) --- scratchattach/other/other_apis.py | 4 ++-- scratchattach/utils/supportedlangs.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/scratchattach/other/other_apis.py b/scratchattach/other/other_apis.py index c5fd5491..ee4598a0 100644 --- a/scratchattach/other/other_apis.py +++ b/scratchattach/other/other_apis.py @@ -152,10 +152,10 @@ def translate(language: str | Languages, text: str = "hello"): lang = language if isinstance(language, str): if language.lower() in Languages.all_of("code", str.lower): - lang = Languages.find(language.lower(), "code", apply_func=str.lower) + lang = Languages.find(language.lower(), "code", str.lower) elif language.lower() in Languages.all_of("name", str.lower): - lang = Languages.find(language.lower(), apply_func=str.lower) + lang = Languages.find(language.lower(), "name", str.lower) elif isinstance(language, Languages): lang = language.value diff --git a/scratchattach/utils/supportedlangs.py b/scratchattach/utils/supportedlangs.py index f1102209..549043c6 100644 --- a/scratchattach/utils/supportedlangs.py +++ b/scratchattach/utils/supportedlangs.py @@ -96,6 +96,13 @@ class Languages(Enum): @staticmethod def find(value, by: str = "name", apply_func: Callable = None) -> _Language: + """ + Finds the language with the given attribute that is equal to the given value. + the apply_func will be applied to the attribute of each language object before comparison. + + i.e. Languages.find("ukranian", "name", str.lower) will return the Ukrainian language dataclass object + (even though Ukrainian was spelt lowercase, since str.lower will convert the "Ukrainian" string to lowercase) + """ if apply_func is None: def apply_func(x): return x @@ -111,6 +118,16 @@ def apply_func(x): @staticmethod def all_of(attr_name: str = "name", apply_func: Callable = None): + """ + Returns the list of each listed language's specified attribute by "attr_name" + + i.e. Languages.all_of("name") will return a list of names: + ["Albanian", "Amharic", ...] + + The apply_func function will be applied to every list item, + i.e. Languages.all_of("name", str.lower) will return the same except in lowercase: + ["albanian", "amharic", ...] + """ if apply_func is None: def apply_func(x): return x From 3e9f72e994f7ee1ea1ddc536d5d3958d823e97ac Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Wed, 20 Nov 2024 18:43:00 +0000 Subject: [PATCH 08/11] merge tts chinese with translate versions, same with portuguese (not brazilian) (cherry picked from commit 6ce4ff7c4781b0fd493b864f0aace3e0232fd815) --- scratchattach/utils/supportedlangs.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scratchattach/utils/supportedlangs.py b/scratchattach/utils/supportedlangs.py index 549043c6..2cee9061 100644 --- a/scratchattach/utils/supportedlangs.py +++ b/scratchattach/utils/supportedlangs.py @@ -28,7 +28,7 @@ class Languages(Enum): Belarusian = _Language('Belarusian', 'be', None, None, None) Bulgarian = _Language('Bulgarian', 'bg', None, None, None) Catalan = _Language('Catalan', 'ca', None, None, None) - Chinese_Traditional = _Language('Chinese (Traditional)', 'zh-tw', None, None, None) + Chinese_Traditional = _Language('Chinese (Traditional)', 'zh-tw', ['zh-cn', 'zh-tw'], 'cmn-CN', True) Croatian = _Language('Croatian', 'hr', None, None, None) Czech = _Language('Czech', 'cs', None, None, None) Danish = _Language('Danish', 'da', ['da'], 'da-DK', False) @@ -65,7 +65,7 @@ class Languages(Enum): Myanmar_Burmese = _Language('Myanmar (Burmese)', 'my', None, None, None) Persian = _Language('Persian', 'fa', None, None, None) Polish = _Language('Polish', 'pl', ['pl'], 'pl-PL', False) - Portuguese = _Language('Portuguese', 'pt', None, None, None) + Portuguese = _Language('Portuguese', 'pt', ['pt'], 'pt-PT', False) Romanian = _Language('Romanian', 'ro', ['ro'], 'ro-RO', True) Russian = _Language('Russian', 'ru', ['ru'], 'ru-RU', False) Scots_Gaelic = _Language('Scots Gaelic', 'gd', None, None, None) @@ -83,14 +83,12 @@ class Languages(Enum): Welsh = _Language('Welsh', 'cy', ['cy'], 'cy-GB', True) Zulu = _Language('Zulu', 'zu', None, None, None) Hebrew = _Language('Hebrew', 'he', None, None, None) - Chinese_Simplified = _Language('Chinese (Simplified)', 'zh-cn', None, None, None) + Chinese_Simplified = _Language('Chinese (Simplified)', 'zh-cn', ['zh-cn', 'zh-tw'], 'cmn-CN', True) Mandarin = Chinese_Simplified - cmn_CN = _Language(None, None, ['zh-cn', 'zh-tw'], 'cmn-CN', True) nb_NO = _Language(None, None, ['nb', 'nn'], 'nb-NO', True) pt_BR = _Language(None, None, ['pt-br'], 'pt-BR', False) Brazilian = pt_BR - pt_PT = _Language(None, None, ['pt'], 'pt-PT', False) es_ES = _Language(None, None, ['es'], 'es-ES', False) es_US = _Language(None, None, ['es-419'], 'es-US', False) From 6e92961ea45743726f5aa03d348d8f18685edc8b Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Wed, 20 Nov 2024 21:51:11 +0000 Subject: [PATCH 09/11] using enums for tts genders asw + enum wrapper with finding by multiple attrs (cherry picked from commit a6c6168caed23cf862ac6ec4e4c1e78c718e48e8) --- scratchattach/__init__.py | 2 +- scratchattach/other/other_apis.py | 76 +++++------ scratchattach/utils/enums.py | 187 ++++++++++++++++++++++++++ scratchattach/utils/exceptions.py | 33 ++++- scratchattach/utils/supportedlangs.py | 140 ------------------- 5 files changed, 249 insertions(+), 189 deletions(-) create mode 100644 scratchattach/utils/enums.py delete mode 100644 scratchattach/utils/supportedlangs.py diff --git a/scratchattach/__init__.py b/scratchattach/__init__.py index 8628bddb..0ec563e1 100644 --- a/scratchattach/__init__.py +++ b/scratchattach/__init__.py @@ -10,7 +10,7 @@ from .other.other_apis import * from .other.project_json_capabilities import ProjectBody, get_empty_project_pb, get_pb_from_dict, read_sb3_file, download_asset from .utils.encoder import Encoding -from .utils.supportedlangs import Languages +from .utils.enums import Languages, TTSVoices from .site.activity import Activity from .site.backpack_asset import BackpackAsset diff --git a/scratchattach/other/other_apis.py b/scratchattach/other/other_apis.py index ee4598a0..976910fc 100644 --- a/scratchattach/other/other_apis.py +++ b/scratchattach/other/other_apis.py @@ -4,9 +4,9 @@ import warnings from ..utils import commons -from ..utils.exceptions import BadRequest +from ..utils.exceptions import BadRequest, InvalidLanguage, InvalidTTSGender from ..utils.requests import Requests as requests -from ..utils.supportedlangs import Languages +from ..utils.enums import Languages, Language, TTSVoices, TTSVoice # --- Front page --- @@ -149,22 +149,15 @@ def scratch_team_members() -> dict: def translate(language: str | Languages, text: str = "hello"): - lang = language if isinstance(language, str): - if language.lower() in Languages.all_of("code", str.lower): - lang = Languages.find(language.lower(), "code", str.lower) - - elif language.lower() in Languages.all_of("name", str.lower): - lang = Languages.find(language.lower(), "name", str.lower) - + lang = Languages.find_by_attrs(language.lower(), ["code", "tts_locale", "name"], str.lower) elif isinstance(language, Languages): lang = language.value else: - # The code will work so long as the language has a 'code' attribute, however, this is bad practice. - warnings.warn(f"{language} is not {str} or {Languages}, but {type(language)}.") + lang = language - if lang is None: - raise ValueError(f"{language} is not a supported translate language") + if not isinstance(lang, Language): + raise InvalidLanguage(f"{language} is not a supported translate language") response_json = requests.get( f"https://translate-service.scratch.mit.edu/translate?language={lang.code}&text={text}").json() @@ -175,46 +168,43 @@ def translate(language: str | Languages, text: str = "hello"): raise BadRequest(f"Language '{language}' does not seem to be valid.\nResponse: {response_json}") -def text2speech(text: str = "hello", gender: str = "female", language: str = "en-US"): +def text2speech(text: str = "hello", voice_name: str = "female", language: str = "en-US"): """ Sends a request to Scratch's TTS synthesis service. Returns: - The TTS audio (mp3) as bytes - The playback rate (e.g. for giant it would be 0.84) """ - if gender == "female" or gender == "alto": - gender = ("female", 1) - elif gender == "male" or gender == "tenor": - gender = ("male", 1) - elif gender == "squeak": - gender = ("female", 1.19) - elif gender == "giant": - gender = ("male", .84) - elif gender == "kitten": - gender = ("female", 1.41) - split = text.split(' ') - text = '' - for token in split: - if token.strip() != '': - text += "meow " + if isinstance(voice_name, str): + voice = TTSVoices.find_by_attrs(voice_name.lower(), ["name", "gender"], str.lower) + elif isinstance(voice_name, TTSVoices): + voice = voice_name.value else: - gender = ("female", 1) + voice = voice_name - og_lang = language - if isinstance(language, Languages): - language = language.value.tts_locale + if not isinstance(voice, TTSVoice): + raise InvalidTTSGender(f"TTS Gender {voice_name} is not supported.") - if language is None: - raise ValueError(f"Language '{og_lang}' is not a supported tts language") + # If it's kitten, make sure to change everything to just meows + if voice.name == "kitten": + text = '' + for word in text.split(' '): + if word.strip() != '': + text += "meow " + + if isinstance(language, str): + lang = Languages.find_by_attrs(language.lower(), ["code", "tts_locale", "name"], str.lower) + elif isinstance(language, Languages): + lang = language.value + else: + lang = language - if language.lower() not in Languages.all_of("tts_locale", str.lower): - if language.lower() in Languages.all_of("name", str.lower): - language = Languages.find(language.lower(), apply_func=str.lower).tts_locale + if not isinstance(lang, Language): + raise InvalidLanguage(f"Language '{language}' is not a language") - lang = Languages.find(language, "tts_locale") - if lang is None or language is None: - raise ValueError(f"Language '{og_lang}' is not a supported tts language") + if lang.tts_locale is None: + raise InvalidLanguage(f"Language '{language}' is not a valid TTS language") response = requests.get(f"https://synthesis-service.scratch.mit.edu/synth" - f"?locale={lang.tts_locale}&gender={gender[0]}&text={text}") - return response.content, gender[1] + f"?locale={lang.tts_locale}&gender={voice.gender}&text={text}") + return response.content, voice.playback_rate diff --git a/scratchattach/utils/enums.py b/scratchattach/utils/enums.py new file mode 100644 index 00000000..9a0c36eb --- /dev/null +++ b/scratchattach/utils/enums.py @@ -0,0 +1,187 @@ +""" +List of supported languages of scratch's translate and text2speech extensions. +Adapted from https://translate-service.scratch.mit.edu/supported?language=en +""" + +from enum import Enum +from dataclasses import dataclass + +from typing import Callable, Iterable + + +@dataclass(init=True, repr=True) +class Language: + name: str = None + code: str = None + locales: list[str] = None + tts_locale: str = None + single_gender: bool = None + + +class _EnumWrapper(Enum): + @classmethod + def find(cls, value, by: str, apply_func: Callable = None): + """ + Finds the enum item with the given attribute that is equal to the given value. + the apply_func will be applied to the attribute of each language object before comparison. + + i.e. Languages.find("ukranian", "name", str.lower) will return the Ukrainian language dataclass object + (even though Ukrainian was spelt lowercase, since str.lower will convert the "Ukrainian" string to lowercase) + """ + if apply_func is None: + def apply_func(x): + return x + + for item in cls: + item_obj = item.value + + try: + if apply_func(getattr(item_obj, by)) == value: + return item_obj + except TypeError: + pass + + @classmethod + def all_of(cls, attr_name: str, apply_func: Callable = None) -> Iterable: + """ + Returns the list of each listed enum item's specified attribute by "attr_name" + + i.e. Languages.all_of("name") will return a list of names: + ["Albanian", "Amharic", ...] + + The apply_func function will be applied to every list item, + i.e. Languages.all_of("name", str.lower) will return the same except in lowercase: + ["albanian", "amharic", ...] + """ + if apply_func is None: + def apply_func(x): + return x + + for item in cls: + item_obj = item.value + attr = getattr(item_obj, attr_name) + try: + yield apply_func(attr) + + except TypeError: + yield attr + + @classmethod + def find_by_attrs(cls, value, bys: list[str], apply_func: Callable = None) -> list: + for by in bys: + ret = cls.find(value, by, apply_func) + if ret is not None: + return ret + + +class Languages(_EnumWrapper): + Albanian = Language('Albanian', 'sq', None, None, None) + Amharic = Language('Amharic', 'am', None, None, None) + Arabic = Language('Arabic', 'ar', ['ar'], 'arb', True) + Armenian = Language('Armenian', 'hy', None, None, None) + Azerbaijani = Language('Azerbaijani', 'az', None, None, None) + Basque = Language('Basque', 'eu', None, None, None) + Belarusian = Language('Belarusian', 'be', None, None, None) + Bulgarian = Language('Bulgarian', 'bg', None, None, None) + Catalan = Language('Catalan', 'ca', None, None, None) + Chinese_Traditional = Language('Chinese (Traditional)', 'zh-tw', ['zh-cn', 'zh-tw'], 'cmn-CN', True) + Croatian = Language('Croatian', 'hr', None, None, None) + Czech = Language('Czech', 'cs', None, None, None) + Danish = Language('Danish', 'da', ['da'], 'da-DK', False) + Dutch = Language('Dutch', 'nl', ['nl'], 'nl-NL', False) + English = Language('English', 'en', ['en'], 'en-US', False) + Esperanto = Language('Esperanto', 'eo', None, None, None) + Estonian = Language('Estonian', 'et', None, None, None) + Finnish = Language('Finnish', 'fi', None, None, None) + French = Language('French', 'fr', ['fr'], 'fr-FR', False) + Galician = Language('Galician', 'gl', None, None, None) + German = Language('German', 'de', ['de'], 'de-DE', False) + Greek = Language('Greek', 'el', None, None, None) + Haitian_Creole = Language('Haitian Creole', 'ht', None, None, None) + Hindi = Language('Hindi', 'hi', ['hi'], 'hi-IN', True) + Hungarian = Language('Hungarian', 'hu', None, None, None) + Icelandic = Language('Icelandic', 'is', ['is'], 'is-IS', False) + Indonesian = Language('Indonesian', 'id', None, None, None) + Irish = Language('Irish', 'ga', None, None, None) + Italian = Language('Italian', 'it', ['it'], 'it-IT', False) + Japanese = Language('Japanese', 'ja', ['ja', 'ja-hira'], 'ja-JP', False) + Kannada = Language('Kannada', 'kn', None, None, None) + Korean = Language('Korean', 'ko', ['ko'], 'ko-KR', True) + Kurdish_Kurmanji = Language('Kurdish (Kurmanji)', 'ku', None, None, None) + Latin = Language('Latin', 'la', None, None, None) + Latvian = Language('Latvian', 'lv', None, None, None) + Lithuanian = Language('Lithuanian', 'lt', None, None, None) + Macedonian = Language('Macedonian', 'mk', None, None, None) + Malay = Language('Malay', 'ms', None, None, None) + Malayalam = Language('Malayalam', 'ml', None, None, None) + Maltese = Language('Maltese', 'mt', None, None, None) + Maori = Language('Maori', 'mi', None, None, None) + Marathi = Language('Marathi', 'mr', None, None, None) + Mongolian = Language('Mongolian', 'mn', None, None, None) + Myanmar_Burmese = Language('Myanmar (Burmese)', 'my', None, None, None) + Persian = Language('Persian', 'fa', None, None, None) + Polish = Language('Polish', 'pl', ['pl'], 'pl-PL', False) + Portuguese = Language('Portuguese', 'pt', ['pt'], 'pt-PT', False) + Romanian = Language('Romanian', 'ro', ['ro'], 'ro-RO', True) + Russian = Language('Russian', 'ru', ['ru'], 'ru-RU', False) + Scots_Gaelic = Language('Scots Gaelic', 'gd', None, None, None) + Serbian = Language('Serbian', 'sr', None, None, None) + Slovak = Language('Slovak', 'sk', None, None, None) + Slovenian = Language('Slovenian', 'sl', None, None, None) + Spanish = Language('Spanish', 'es', None, None, None) + Swedish = Language('Swedish', 'sv', ['sv'], 'sv-SE', True) + Telugu = Language('Telugu', 'te', None, None, None) + Thai = Language('Thai', 'th', None, None, None) + Turkish = Language('Turkish', 'tr', ['tr'], 'tr-TR', True) + Ukrainian = Language('Ukrainian', 'uk', None, None, None) + Uzbek = Language('Uzbek', 'uz', None, None, None) + Vietnamese = Language('Vietnamese', 'vi', None, None, None) + Welsh = Language('Welsh', 'cy', ['cy'], 'cy-GB', True) + Zulu = Language('Zulu', 'zu', None, None, None) + Hebrew = Language('Hebrew', 'he', None, None, None) + Chinese_Simplified = Language('Chinese (Simplified)', 'zh-cn', ['zh-cn', 'zh-tw'], 'cmn-CN', True) + Mandarin = Chinese_Simplified + + nb_NO = Language(None, None, ['nb', 'nn'], 'nb-NO', True) + pt_BR = Language(None, None, ['pt-br'], 'pt-BR', False) + Brazilian = pt_BR + es_ES = Language(None, None, ['es'], 'es-ES', False) + es_US = Language(None, None, ['es-419'], 'es-US', False) + + @classmethod + def find(cls, value, by: str = "name", apply_func: Callable = None) -> Language: + return super().find(value, by, apply_func) + + @classmethod + def all_of(cls, attr_name: str = "name", apply_func: Callable = None) -> list: + return super().all_of(attr_name, apply_func) + + +@dataclass(init=True, repr=True) +class TTSVoice: + name: str + gender: str + playback_rate: float | int = 1 + + +class TTSVoices(_EnumWrapper): + alto = TTSVoice("alto", "female") + # female is functionally equal to alto + female = TTSVoice("female", "female") + + tenor = TTSVoice("tenor", "male") + # male is functionally equal to tenor + male = TTSVoice("male", "male") + + squeak = TTSVoice("squeak", "female", 1.19) + giant = TTSVoice("giant", "male", .84) + kitten = TTSVoice("kitten", "female", 1.41) + + @classmethod + def find(cls, value, by: str = "name", apply_func: Callable = None) -> TTSVoice: + return super().find(value, by, apply_func) + + @classmethod + def all_of(cls, attr_name: str = "name", apply_func: Callable = None) -> Iterable: + return super().all_of(attr_name, apply_func) + diff --git a/scratchattach/utils/exceptions.py b/scratchattach/utils/exceptions.py index 10b756e8..82e1514a 100644 --- a/scratchattach/utils/exceptions.py +++ b/scratchattach/utils/exceptions.py @@ -18,7 +18,6 @@ class Unauthenticated(Exception): def __init__(self, message=""): self.message = "No login / session connected.\n\nThe object on which the method was called was created using scratchattach.get_xyz()\nUse session.connect_xyz() instead (xyz is a placeholder for user / project / cloud / ...).\n\nMore information: https://scratchattach.readthedocs.io/en/latest/scratchattach.html#scratchattach.utils.exceptions.Unauthenticated" super().__init__(self.message) - pass class Unauthorized(Exception): @@ -32,7 +31,6 @@ def __init__(self, message=""): self.message = "The user corresponding to the connected login / session is not allowed to perform this action." super().__init__(self.message) - pass class XTokenError(Exception): """ @@ -43,6 +41,7 @@ class XTokenError(Exception): pass + # Not found errors: class UserNotFound(Exception): @@ -60,6 +59,7 @@ class ProjectNotFound(Exception): pass + class ClassroomNotFound(Exception): """ Raised when a non-existent Classroom is requested. @@ -75,15 +75,32 @@ class StudioNotFound(Exception): pass + class ForumContentNotFound(Exception): """ Raised when a non-existent forum topic / post is requested. """ pass + class CommentNotFound(Exception): pass + +# Invalid inputs +class InvalidLanguage(Exception): + """ + Raised when an invalid language/language code/language object is provided, for TTS or Translate + """ + pass + + +class InvalidTTSGender(Exception): + """ + Raised when an invalid TTS gender is provided. + """ + pass + # API errors: class LoginFailure(Exception): @@ -95,6 +112,7 @@ class LoginFailure(Exception): pass + class FetchError(Exception): """ Raised when getting information from the Scratch API fails. This can have various reasons. Make sure all provided arguments are valid. @@ -102,6 +120,7 @@ class FetchError(Exception): pass + class BadRequest(Exception): """ Raised when the Scratch API responds with a "Bad Request" error message. This can have various reasons. Make sure all provided arguments are valid. @@ -117,6 +136,7 @@ class Response429(Exception): pass + class CommentPostFailure(Exception): """ Raised when a comment fails to post. This can have various reasons. @@ -124,12 +144,14 @@ class CommentPostFailure(Exception): pass + class APIError(Exception): """ For API errors that can't be classified into one of the above errors """ pass + class ScrapeError(Exception): """ Raised when something goes wrong while web-scraping a page with bs4. @@ -137,9 +159,10 @@ class ScrapeError(Exception): pass + # Cloud / encoding errors: -class ConnectionError(Exception): +class CloudConnectionError(Exception): """ Raised when connecting to Scratch's cloud server fails. This can have various reasons. """ @@ -172,12 +195,12 @@ class RequestNotFound(Exception): pass + # Websocket server errors: class WebsocketServerError(Exception): - """ Raised when the self-hosted cloud websocket server fails to start. """ - pass \ No newline at end of file + pass diff --git a/scratchattach/utils/supportedlangs.py b/scratchattach/utils/supportedlangs.py deleted file mode 100644 index 2cee9061..00000000 --- a/scratchattach/utils/supportedlangs.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -List of supported languages of scratch's translate and text2speech extensions. -Adapted from https://translate-service.scratch.mit.edu/supported?language=en -""" - -from enum import Enum -from dataclasses import dataclass - -from typing import Callable - - -@dataclass(init=True, repr=True) -class _Language: - name: str = None - code: str = None - locales: list[str] = None - tts_locale: str = None - single_gender: bool = None - - -class Languages(Enum): - Albanian = _Language('Albanian', 'sq', None, None, None) - Amharic = _Language('Amharic', 'am', None, None, None) - Arabic = _Language('Arabic', 'ar', ['ar'], 'arb', True) - Armenian = _Language('Armenian', 'hy', None, None, None) - Azerbaijani = _Language('Azerbaijani', 'az', None, None, None) - Basque = _Language('Basque', 'eu', None, None, None) - Belarusian = _Language('Belarusian', 'be', None, None, None) - Bulgarian = _Language('Bulgarian', 'bg', None, None, None) - Catalan = _Language('Catalan', 'ca', None, None, None) - Chinese_Traditional = _Language('Chinese (Traditional)', 'zh-tw', ['zh-cn', 'zh-tw'], 'cmn-CN', True) - Croatian = _Language('Croatian', 'hr', None, None, None) - Czech = _Language('Czech', 'cs', None, None, None) - Danish = _Language('Danish', 'da', ['da'], 'da-DK', False) - Dutch = _Language('Dutch', 'nl', ['nl'], 'nl-NL', False) - English = _Language('English', 'en', ['en'], 'en-US', False) - Esperanto = _Language('Esperanto', 'eo', None, None, None) - Estonian = _Language('Estonian', 'et', None, None, None) - Finnish = _Language('Finnish', 'fi', None, None, None) - French = _Language('French', 'fr', ['fr'], 'fr-FR', False) - Galician = _Language('Galician', 'gl', None, None, None) - German = _Language('German', 'de', ['de'], 'de-DE', False) - Greek = _Language('Greek', 'el', None, None, None) - Haitian_Creole = _Language('Haitian Creole', 'ht', None, None, None) - Hindi = _Language('Hindi', 'hi', ['hi'], 'hi-IN', True) - Hungarian = _Language('Hungarian', 'hu', None, None, None) - Icelandic = _Language('Icelandic', 'is', ['is'], 'is-IS', False) - Indonesian = _Language('Indonesian', 'id', None, None, None) - Irish = _Language('Irish', 'ga', None, None, None) - Italian = _Language('Italian', 'it', ['it'], 'it-IT', False) - Japanese = _Language('Japanese', 'ja', ['ja', 'ja-hira'], 'ja-JP', False) - Kannada = _Language('Kannada', 'kn', None, None, None) - Korean = _Language('Korean', 'ko', ['ko'], 'ko-KR', True) - Kurdish_Kurmanji = _Language('Kurdish (Kurmanji)', 'ku', None, None, None) - Latin = _Language('Latin', 'la', None, None, None) - Latvian = _Language('Latvian', 'lv', None, None, None) - Lithuanian = _Language('Lithuanian', 'lt', None, None, None) - Macedonian = _Language('Macedonian', 'mk', None, None, None) - Malay = _Language('Malay', 'ms', None, None, None) - Malayalam = _Language('Malayalam', 'ml', None, None, None) - Maltese = _Language('Maltese', 'mt', None, None, None) - Maori = _Language('Maori', 'mi', None, None, None) - Marathi = _Language('Marathi', 'mr', None, None, None) - Mongolian = _Language('Mongolian', 'mn', None, None, None) - Myanmar_Burmese = _Language('Myanmar (Burmese)', 'my', None, None, None) - Persian = _Language('Persian', 'fa', None, None, None) - Polish = _Language('Polish', 'pl', ['pl'], 'pl-PL', False) - Portuguese = _Language('Portuguese', 'pt', ['pt'], 'pt-PT', False) - Romanian = _Language('Romanian', 'ro', ['ro'], 'ro-RO', True) - Russian = _Language('Russian', 'ru', ['ru'], 'ru-RU', False) - Scots_Gaelic = _Language('Scots Gaelic', 'gd', None, None, None) - Serbian = _Language('Serbian', 'sr', None, None, None) - Slovak = _Language('Slovak', 'sk', None, None, None) - Slovenian = _Language('Slovenian', 'sl', None, None, None) - Spanish = _Language('Spanish', 'es', None, None, None) - Swedish = _Language('Swedish', 'sv', ['sv'], 'sv-SE', True) - Telugu = _Language('Telugu', 'te', None, None, None) - Thai = _Language('Thai', 'th', None, None, None) - Turkish = _Language('Turkish', 'tr', ['tr'], 'tr-TR', True) - Ukrainian = _Language('Ukrainian', 'uk', None, None, None) - Uzbek = _Language('Uzbek', 'uz', None, None, None) - Vietnamese = _Language('Vietnamese', 'vi', None, None, None) - Welsh = _Language('Welsh', 'cy', ['cy'], 'cy-GB', True) - Zulu = _Language('Zulu', 'zu', None, None, None) - Hebrew = _Language('Hebrew', 'he', None, None, None) - Chinese_Simplified = _Language('Chinese (Simplified)', 'zh-cn', ['zh-cn', 'zh-tw'], 'cmn-CN', True) - Mandarin = Chinese_Simplified - - nb_NO = _Language(None, None, ['nb', 'nn'], 'nb-NO', True) - pt_BR = _Language(None, None, ['pt-br'], 'pt-BR', False) - Brazilian = pt_BR - es_ES = _Language(None, None, ['es'], 'es-ES', False) - es_US = _Language(None, None, ['es-419'], 'es-US', False) - - @staticmethod - def find(value, by: str = "name", apply_func: Callable = None) -> _Language: - """ - Finds the language with the given attribute that is equal to the given value. - the apply_func will be applied to the attribute of each language object before comparison. - - i.e. Languages.find("ukranian", "name", str.lower) will return the Ukrainian language dataclass object - (even though Ukrainian was spelt lowercase, since str.lower will convert the "Ukrainian" string to lowercase) - """ - if apply_func is None: - def apply_func(x): - return x - - for lang_enum in Languages: - lang = lang_enum.value - - try: - if apply_func(getattr(lang, by)) == value: - return lang - except TypeError: - pass - - @staticmethod - def all_of(attr_name: str = "name", apply_func: Callable = None): - """ - Returns the list of each listed language's specified attribute by "attr_name" - - i.e. Languages.all_of("name") will return a list of names: - ["Albanian", "Amharic", ...] - - The apply_func function will be applied to every list item, - i.e. Languages.all_of("name", str.lower) will return the same except in lowercase: - ["albanian", "amharic", ...] - """ - if apply_func is None: - def apply_func(x): - return x - - for lang_enum in Languages: - lang = lang_enum.value - attr = getattr(lang, attr_name) - try: - yield apply_func(attr) - - except TypeError: - yield attr From 2104d002821d45b9e02d79efa47b228667e099a9 Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Wed, 20 Nov 2024 22:08:47 +0000 Subject: [PATCH 10/11] make sure language has code for translation (cherry picked from commit 7d71d969e5cf1ccc3e1a11ca6cfc330085bd7e45) --- scratchattach/other/other_apis.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scratchattach/other/other_apis.py b/scratchattach/other/other_apis.py index 976910fc..c9519b87 100644 --- a/scratchattach/other/other_apis.py +++ b/scratchattach/other/other_apis.py @@ -1,7 +1,6 @@ """Other Scratch API-related functions""" import json -import warnings from ..utils import commons from ..utils.exceptions import BadRequest, InvalidLanguage, InvalidTTSGender @@ -157,7 +156,10 @@ def translate(language: str | Languages, text: str = "hello"): lang = language if not isinstance(lang, Language): - raise InvalidLanguage(f"{language} is not a supported translate language") + raise InvalidLanguage(f"{language} is not a language") + + if lang.code is None: + raise InvalidLanguage(f"{lang} is not a valid translate language") response_json = requests.get( f"https://translate-service.scratch.mit.edu/translate?language={lang.code}&text={text}").json() From 9ec03bc1af1ac653096f10992cadb8a0ffb41869 Mon Sep 17 00:00:00 2001 From: FA ReTek Date: Thu, 21 Nov 2024 17:06:56 +0000 Subject: [PATCH 11/11] 1 docstring --- scratchattach/utils/enums.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scratchattach/utils/enums.py b/scratchattach/utils/enums.py index 9a0c36eb..0fbc0ffc 100644 --- a/scratchattach/utils/enums.py +++ b/scratchattach/utils/enums.py @@ -68,6 +68,9 @@ def apply_func(x): @classmethod def find_by_attrs(cls, value, bys: list[str], apply_func: Callable = None) -> list: + """ + Calls the EnumWrapper.by function multiple times until a match is found, using the provided 'by' attribute names + """ for by in bys: ret = cls.find(value, by, apply_func) if ret is not None: