Spaces:
Running on Zero
Running on Zero
Download glyph_ascii_v1.py from voidful/BlueMagpie-TTS-Demo: direct link, hf CLI and curl.
- Browser
- Download file 17 kB
-
https://huggingface.co/spaces/voidful/BlueMagpie-TTS-Demo/resolve/main/glyph_ascii_v1.py
- Command line
-
hf download hf://spaces/voidful/BlueMagpie-TTS-Demo/glyph_ascii_v1.py
-
curl -L -o glyph_ascii_v1.py https://huggingface.co/spaces/voidful/BlueMagpie-TTS-Demo/resolve/main/glyph_ascii_v1.py
17 kB
| """Strict, reversible ASCII identifier rendering for pre-certified glyph audio. | |
| This module deliberately contains no runtime generation, audio, asset loading, | |
| or release-evidence integration. It defines only the fail-closed | |
| ``glyph_ascii_v1`` grammar and the proof object that a later integration layer | |
| can bind to a frozen asset manifest. | |
| The accepted language is intentionally narrower than RFC email/URL syntax: | |
| * every input byte must already be lowercase ASCII (there is no case folding); | |
| * email local parts, DNS labels, and URL path segments contain letters plus a | |
| small set of non-consecutive separators; | |
| * URLs are exactly ``http`` or ``https``, have no authority decorations, and | |
| have no query or fragment. | |
| Every accepted byte maps bijectively to one of 32 expanded, unambiguous | |
| production spoken tokens. Tokens in a complete rendering are separated by | |
| exactly one ASCII space. | |
| """ | |
| from __future__ import annotations | |
| from collections.abc import Mapping | |
| from dataclasses import dataclass | |
| import hashlib | |
| import json | |
| import re | |
| GLYPH_ASCII_V1_GRAMMAR_ID = "glyph_ascii_v1" | |
| GLYPH_ASCII_V1_MAX_IDENTIFIER_BYTES = 512 | |
| GLYPH_ASCII_V1_MAX_ATOMS = 512 | |
| GLYPH_ASCII_V1_MAX_CANONICAL_CHARS = 8_192 | |
| _SHA256_RE = re.compile(r"[0-9a-f]{64}\Z", flags=re.ASCII) | |
| _ALPHA = r"[a-z]+" | |
| _LABEL = rf"{_ALPHA}(?:-{_ALPHA})*" | |
| _DOMAIN = rf"{_LABEL}(?:\.{_LABEL})+" | |
| _LOCAL = rf"{_ALPHA}(?:[._-]{_ALPHA})*" | |
| _PATH_SEGMENT = rf"{_ALPHA}(?:[-_.]{_ALPHA})*" | |
| _EMAIL_RE = re.compile(rf"{_LOCAL}@{_DOMAIN}\Z", flags=re.ASCII) | |
| _URL_RE = re.compile( | |
| rf"(?:http|https)://{_DOMAIN}(?:/{_PATH_SEGMENT}(?:/{_PATH_SEGMENT})*)?\Z", | |
| flags=re.ASCII, | |
| ) | |
| _ZH_ORDINALS = ( | |
| "一", | |
| "二", | |
| "三", | |
| "四", | |
| "五", | |
| "六", | |
| "七", | |
| "八", | |
| "九", | |
| "十", | |
| "十一", | |
| "十二", | |
| "十三", | |
| "十四", | |
| "十五", | |
| "十六", | |
| "十七", | |
| "十八", | |
| "十九", | |
| "二十", | |
| "二十一", | |
| "二十二", | |
| "二十三", | |
| "二十四", | |
| "二十五", | |
| "二十六", | |
| ) | |
| class GlyphAsciiV1Error(ValueError): | |
| """Raised when an identifier, manifest binding, or proof is invalid.""" | |
| class GlyphAsciiV1Symbol: | |
| """One member of the frozen 32-byte canonical alphabet.""" | |
| raw_byte: int | |
| canonical_token: str | |
| asset_id: str | |
| _LETTER_SYMBOLS = tuple( | |
| GlyphAsciiV1Symbol( | |
| raw_byte=ord(character), | |
| canonical_token=f"第{ordinal}個英文字母", | |
| asset_id=f"letter-{character}", | |
| ) | |
| for character, ordinal in zip("abcdefghijklmnopqrstuvwxyz", _ZH_ORDINALS) | |
| ) | |
| _PUNCTUATION_SYMBOLS = ( | |
| GlyphAsciiV1Symbol(ord("@"), "網路符號小老鼠", "symbol-at"), | |
| GlyphAsciiV1Symbol(ord("."), "網路符號點", "symbol-dot"), | |
| GlyphAsciiV1Symbol(ord("-"), "這個符號叫做橫線", "symbol-hyphen"), | |
| GlyphAsciiV1Symbol(ord("_"), "這個符號叫做底線", "symbol-underscore"), | |
| GlyphAsciiV1Symbol(ord(":"), "這個符號叫做冒號", "symbol-colon"), | |
| GlyphAsciiV1Symbol(ord("/"), "網路符號斜線", "symbol-slash"), | |
| ) | |
| GLYPH_ASCII_V1_SYMBOLS = _LETTER_SYMBOLS + _PUNCTUATION_SYMBOLS | |
| _SYMBOL_BY_RAW_BYTE = {symbol.raw_byte: symbol for symbol in GLYPH_ASCII_V1_SYMBOLS} | |
| _SYMBOL_BY_CANONICAL_TOKEN = { | |
| symbol.canonical_token: symbol for symbol in GLYPH_ASCII_V1_SYMBOLS | |
| } | |
| _EXPECTED_ASSET_IDS = tuple(symbol.asset_id for symbol in GLYPH_ASCII_V1_SYMBOLS) | |
| if ( | |
| len(GLYPH_ASCII_V1_SYMBOLS) != 32 | |
| or len(_SYMBOL_BY_RAW_BYTE) != 32 | |
| or len(_SYMBOL_BY_CANONICAL_TOKEN) != 32 | |
| or len(set(_EXPECTED_ASSET_IDS)) != 32 | |
| ): | |
| raise RuntimeError("glyph_ascii_v1 alphabet is not bijective") | |
| class GlyphAsciiV1AtomProof: | |
| """Range- and manifest-bound proof for one exact source byte.""" | |
| ordinal: int | |
| absolute_raw_start: int | |
| absolute_raw_end: int | |
| identifier_raw_start: int | |
| identifier_raw_end: int | |
| canonical_start: int | |
| canonical_end: int | |
| raw_byte: int | |
| canonical_token: str | |
| asset_id: str | |
| manifest_entry_sha256: str | |
| class GlyphAsciiV1Proof: | |
| """Complete canonical rendering proof for one accepted identifier.""" | |
| grammar_id: str | |
| identifier_kind: str | |
| absolute_raw_start: int | |
| absolute_raw_end: int | |
| canonical_start: int | |
| canonical_end: int | |
| raw_sha256: str | |
| canonical_sha256: str | |
| asset_manifest_sha256: str | |
| atoms: tuple[GlyphAsciiV1AtomProof, ...] | |
| def _require_plain_nonnegative_int(value: object, *, field: str) -> int: | |
| if type(value) is not int or value < 0: | |
| raise GlyphAsciiV1Error(f"{field} must be a non-negative integer") | |
| return value | |
| def _require_sha256(value: object, *, field: str) -> str: | |
| if type(value) is not str or _SHA256_RE.fullmatch(value) is None: | |
| raise GlyphAsciiV1Error(f"{field} must be a lowercase SHA-256 digest") | |
| return value | |
| def _validated_manifest( | |
| asset_entry_sha256_by_asset_id: Mapping[str, str], | |
| ) -> tuple[tuple[str, str], ...]: | |
| if not isinstance(asset_entry_sha256_by_asset_id, Mapping): | |
| raise GlyphAsciiV1Error("asset manifest entries must be a mapping") | |
| if set(asset_entry_sha256_by_asset_id) != set(_EXPECTED_ASSET_IDS): | |
| raise GlyphAsciiV1Error( | |
| "asset manifest must contain exactly the 32 glyph_ascii_v1 asset ids" | |
| ) | |
| entries: list[tuple[str, str]] = [] | |
| for asset_id in _EXPECTED_ASSET_IDS: | |
| if type(asset_id) is not str: | |
| raise GlyphAsciiV1Error("asset id has an invalid type") | |
| digest = _require_sha256( | |
| asset_entry_sha256_by_asset_id[asset_id], | |
| field=f"asset manifest entry {asset_id!r}", | |
| ) | |
| entries.append((asset_id, digest)) | |
| return tuple(entries) | |
| def _asset_manifest_sha256_from_entries( | |
| entries: tuple[tuple[str, str], ...], | |
| ) -> str: | |
| payload = json.dumps( | |
| { | |
| "grammar_id": GLYPH_ASCII_V1_GRAMMAR_ID, | |
| "entries": [ | |
| {"asset_id": asset_id, "sha256": digest} | |
| for asset_id, digest in entries | |
| ], | |
| }, | |
| ensure_ascii=True, | |
| separators=(",", ":"), | |
| sort_keys=True, | |
| ).encode("ascii") | |
| return hashlib.sha256(payload).hexdigest() | |
| def glyph_ascii_v1_asset_manifest_sha256( | |
| asset_entry_sha256_by_asset_id: Mapping[str, str], | |
| ) -> str: | |
| """Return the deterministic logical-manifest digest for all 32 assets.""" | |
| return _asset_manifest_sha256_from_entries( | |
| _validated_manifest(asset_entry_sha256_by_asset_id) | |
| ) | |
| def _strict_raw_bytes(raw_identifier: str) -> bytes: | |
| if type(raw_identifier) is not str: | |
| raise GlyphAsciiV1Error("raw identifier must be a string") | |
| # This is intentionally lower(), never casefold(). Eligibility requires | |
| # the source to arrive in its canonical lowercase form. | |
| if raw_identifier != raw_identifier.lower(): | |
| raise GlyphAsciiV1Error("raw identifier is not native lowercase") | |
| try: | |
| raw_bytes = raw_identifier.encode("ascii", errors="strict") | |
| except UnicodeEncodeError as error: | |
| raise GlyphAsciiV1Error("raw identifier is not strict ASCII") from error | |
| if not raw_bytes: | |
| raise GlyphAsciiV1Error("raw identifier is empty") | |
| if len(raw_bytes) > GLYPH_ASCII_V1_MAX_IDENTIFIER_BYTES: | |
| raise GlyphAsciiV1Error("raw identifier exceeds the byte cap") | |
| if len(raw_bytes) > GLYPH_ASCII_V1_MAX_ATOMS: | |
| raise GlyphAsciiV1Error("raw identifier exceeds the atom cap") | |
| return raw_bytes | |
| def glyph_ascii_v1_identifier_kind(raw_identifier: str) -> str: | |
| """Parse the exact v1 byte grammar and return ``email`` or ``url``.""" | |
| raw_bytes = _strict_raw_bytes(raw_identifier) | |
| # Match the decoded ASCII only after the byte-level eligibility checks. | |
| raw = raw_bytes.decode("ascii") | |
| matches = tuple( | |
| kind | |
| for kind, pattern in (("email", _EMAIL_RE), ("url", _URL_RE)) | |
| if pattern.fullmatch(raw) is not None | |
| ) | |
| if len(matches) != 1: | |
| raise GlyphAsciiV1Error("raw identifier is outside the unambiguous v1 grammar") | |
| return matches[0] | |
| def render_glyph_ascii_v1( | |
| raw_identifier: str, | |
| *, | |
| asset_entry_sha256_by_asset_id: Mapping[str, str], | |
| absolute_raw_start: int = 0, | |
| canonical_start: int = 0, | |
| ) -> tuple[str, GlyphAsciiV1Proof]: | |
| """Render one identifier and bind every byte to its frozen asset entry.""" | |
| absolute_raw_start = _require_plain_nonnegative_int( | |
| absolute_raw_start, | |
| field="absolute_raw_start", | |
| ) | |
| canonical_start = _require_plain_nonnegative_int( | |
| canonical_start, | |
| field="canonical_start", | |
| ) | |
| identifier_kind = glyph_ascii_v1_identifier_kind(raw_identifier) | |
| raw_bytes = raw_identifier.encode("ascii") | |
| manifest_entries = _validated_manifest(asset_entry_sha256_by_asset_id) | |
| entry_sha_by_asset_id = dict(manifest_entries) | |
| manifest_sha256 = _asset_manifest_sha256_from_entries(manifest_entries) | |
| tokens: list[str] = [] | |
| atoms: list[GlyphAsciiV1AtomProof] = [] | |
| canonical_cursor = canonical_start | |
| for ordinal, raw_byte in enumerate(raw_bytes): | |
| symbol = _SYMBOL_BY_RAW_BYTE.get(raw_byte) | |
| if symbol is None: | |
| # The grammar and alphabet are intentionally independent checks. | |
| raise GlyphAsciiV1Error("accepted grammar byte lacks a canonical atom") | |
| if ordinal: | |
| canonical_cursor += 1 # exactly one ASCII space between atoms | |
| token_start = canonical_cursor | |
| token_end = token_start + len(symbol.canonical_token) | |
| tokens.append(symbol.canonical_token) | |
| atoms.append( | |
| GlyphAsciiV1AtomProof( | |
| ordinal=ordinal, | |
| absolute_raw_start=absolute_raw_start + ordinal, | |
| absolute_raw_end=absolute_raw_start + ordinal + 1, | |
| identifier_raw_start=ordinal, | |
| identifier_raw_end=ordinal + 1, | |
| canonical_start=token_start, | |
| canonical_end=token_end, | |
| raw_byte=raw_byte, | |
| canonical_token=symbol.canonical_token, | |
| asset_id=symbol.asset_id, | |
| manifest_entry_sha256=entry_sha_by_asset_id[symbol.asset_id], | |
| ) | |
| ) | |
| canonical_cursor = token_end | |
| canonical = " ".join(tokens) | |
| if len(canonical) > GLYPH_ASCII_V1_MAX_CANONICAL_CHARS: | |
| raise GlyphAsciiV1Error("canonical rendering exceeds the character cap") | |
| proof = GlyphAsciiV1Proof( | |
| grammar_id=GLYPH_ASCII_V1_GRAMMAR_ID, | |
| identifier_kind=identifier_kind, | |
| absolute_raw_start=absolute_raw_start, | |
| absolute_raw_end=absolute_raw_start + len(raw_bytes), | |
| canonical_start=canonical_start, | |
| canonical_end=canonical_start + len(canonical), | |
| raw_sha256=hashlib.sha256(raw_bytes).hexdigest(), | |
| canonical_sha256=hashlib.sha256(canonical.encode("utf-8")).hexdigest(), | |
| asset_manifest_sha256=manifest_sha256, | |
| atoms=tuple(atoms), | |
| ) | |
| return canonical, proof | |
| def inverse_glyph_ascii_v1( | |
| proof: GlyphAsciiV1Proof, | |
| *, | |
| asset_entry_sha256_by_asset_id: Mapping[str, str], | |
| ) -> str: | |
| """Validate a proof from first principles and reconstruct its exact bytes.""" | |
| if type(proof) is not GlyphAsciiV1Proof: | |
| raise GlyphAsciiV1Error("glyph proof has an invalid type") | |
| manifest_entries = _validated_manifest(asset_entry_sha256_by_asset_id) | |
| entry_sha_by_asset_id = dict(manifest_entries) | |
| manifest_sha256 = _asset_manifest_sha256_from_entries(manifest_entries) | |
| if ( | |
| type(proof.grammar_id) is not str | |
| or proof.grammar_id != GLYPH_ASCII_V1_GRAMMAR_ID | |
| or type(proof.identifier_kind) is not str | |
| or proof.identifier_kind not in {"email", "url"} | |
| or _require_plain_nonnegative_int( | |
| proof.absolute_raw_start, | |
| field="proof.absolute_raw_start", | |
| ) | |
| != proof.absolute_raw_start | |
| or _require_plain_nonnegative_int( | |
| proof.absolute_raw_end, | |
| field="proof.absolute_raw_end", | |
| ) | |
| != proof.absolute_raw_end | |
| or _require_plain_nonnegative_int( | |
| proof.canonical_start, | |
| field="proof.canonical_start", | |
| ) | |
| != proof.canonical_start | |
| or _require_plain_nonnegative_int( | |
| proof.canonical_end, | |
| field="proof.canonical_end", | |
| ) | |
| != proof.canonical_end | |
| or proof.absolute_raw_end <= proof.absolute_raw_start | |
| or proof.canonical_end <= proof.canonical_start | |
| or _require_sha256(proof.raw_sha256, field="proof.raw_sha256") | |
| != proof.raw_sha256 | |
| or _require_sha256(proof.canonical_sha256, field="proof.canonical_sha256") | |
| != proof.canonical_sha256 | |
| or _require_sha256( | |
| proof.asset_manifest_sha256, | |
| field="proof.asset_manifest_sha256", | |
| ) | |
| != proof.asset_manifest_sha256 | |
| or proof.asset_manifest_sha256 != manifest_sha256 | |
| or type(proof.atoms) is not tuple | |
| or not proof.atoms | |
| or len(proof.atoms) > GLYPH_ASCII_V1_MAX_ATOMS | |
| ): | |
| raise GlyphAsciiV1Error("glyph proof header is inconsistent") | |
| raw_bytes = bytearray() | |
| canonical_tokens: list[str] = [] | |
| raw_absolute_cursor = proof.absolute_raw_start | |
| raw_identifier_cursor = 0 | |
| canonical_cursor = proof.canonical_start | |
| for ordinal, atom in enumerate(proof.atoms): | |
| if type(atom) is not GlyphAsciiV1AtomProof: | |
| raise GlyphAsciiV1Error("glyph atom proof has an invalid type") | |
| if ( | |
| type(atom.canonical_token) is not str | |
| or type(atom.asset_id) is not str | |
| ): | |
| raise GlyphAsciiV1Error("glyph atom text field has an invalid type") | |
| integer_fields = ( | |
| atom.ordinal, | |
| atom.absolute_raw_start, | |
| atom.absolute_raw_end, | |
| atom.identifier_raw_start, | |
| atom.identifier_raw_end, | |
| atom.canonical_start, | |
| atom.canonical_end, | |
| atom.raw_byte, | |
| ) | |
| if any(type(value) is not int for value in integer_fields): | |
| raise GlyphAsciiV1Error("glyph atom integer field has an invalid type") | |
| entry_sha256 = _require_sha256( | |
| atom.manifest_entry_sha256, | |
| field="atom.manifest_entry_sha256", | |
| ) | |
| symbol = _SYMBOL_BY_CANONICAL_TOKEN.get(atom.canonical_token) | |
| expected_canonical_start = canonical_cursor + (1 if ordinal else 0) | |
| if ( | |
| atom.ordinal != ordinal | |
| or atom.absolute_raw_start != raw_absolute_cursor | |
| or atom.absolute_raw_end != raw_absolute_cursor + 1 | |
| or atom.identifier_raw_start != raw_identifier_cursor | |
| or atom.identifier_raw_end != raw_identifier_cursor + 1 | |
| or atom.canonical_start != expected_canonical_start | |
| or atom.canonical_end | |
| != expected_canonical_start + len(atom.canonical_token) | |
| or symbol is None | |
| or symbol.raw_byte != atom.raw_byte | |
| or symbol.asset_id != atom.asset_id | |
| or entry_sha256 != atom.manifest_entry_sha256 | |
| or entry_sha_by_asset_id.get(atom.asset_id) | |
| != atom.manifest_entry_sha256 | |
| ): | |
| raise GlyphAsciiV1Error("glyph atom proof is inconsistent") | |
| raw_bytes.append(atom.raw_byte) | |
| canonical_tokens.append(atom.canonical_token) | |
| raw_absolute_cursor = atom.absolute_raw_end | |
| raw_identifier_cursor = atom.identifier_raw_end | |
| canonical_cursor = atom.canonical_end | |
| canonical = " ".join(canonical_tokens) | |
| try: | |
| raw = bytes(raw_bytes).decode("ascii", errors="strict") | |
| except UnicodeDecodeError as error: | |
| raise GlyphAsciiV1Error("glyph proof does not reconstruct ASCII") from error | |
| if ( | |
| raw_absolute_cursor != proof.absolute_raw_end | |
| or raw_identifier_cursor != len(raw_bytes) | |
| or canonical_cursor != proof.canonical_end | |
| or proof.absolute_raw_end | |
| != proof.absolute_raw_start + len(raw_bytes) | |
| or proof.canonical_end != proof.canonical_start + len(canonical) | |
| or hashlib.sha256(bytes(raw_bytes)).hexdigest() != proof.raw_sha256 | |
| or hashlib.sha256(canonical.encode("utf-8")).hexdigest() | |
| != proof.canonical_sha256 | |
| ): | |
| raise GlyphAsciiV1Error("glyph proof does not cover its declared ranges") | |
| # Re-parse the reconstructed bytes, then rebuild the only valid proof. | |
| kind = glyph_ascii_v1_identifier_kind(raw) | |
| expected_canonical, expected_proof = render_glyph_ascii_v1( | |
| raw, | |
| asset_entry_sha256_by_asset_id=entry_sha_by_asset_id, | |
| absolute_raw_start=proof.absolute_raw_start, | |
| canonical_start=proof.canonical_start, | |
| ) | |
| if ( | |
| kind != proof.identifier_kind | |
| or expected_canonical != canonical | |
| or type(expected_proof) is not type(proof) | |
| or expected_proof != proof | |
| ): | |
| raise GlyphAsciiV1Error("glyph proof is not the canonical expected proof") | |
| return raw | |