Local, offline pronunciation trainer: Kokoro TTS prompts, Wav2Vec2 scoring, local LLM phrase generation.
Project description
Mimora
A local, offline pronunciation trainer. Mimora says a phrase out loud, you repeat it, and it scores how close you were - highlighting the words to work on. Practice the same phrase until you pass, then move on to the next one. Everything runs on your machine: speech synthesis, speech recognition, phrase generation, and acoustic analysis.
| Dark theme | Light theme |
|---|---|
Both themes ship with the app; drop your own <name>_schema.json into config/themes/ to add or replace one.
Why Mimora?
- 🔒 100% offline after install - your voice never leaves your computer. No cloud, no accounts, no API keys.
- ♾️ Unlimited, varied practice - a local LLM writes fresh phrases from your own text, so you are not stuck repeating the same canned sentences.
- 💸 No subscription - free and open source; the only cost is disk space for the models.
- 💻 Runs on a normal PC - works on 16 GB RAM with no graphics card. A GPU is optional and just makes it faster.
- 🎯 Instant, word-level feedback - a 0-100 score plus the exact words to work on, with reference-vs-you replay.
Who is it for?
Anyone working on clearer speech in a new language - language learners, accent reduction, and shadowing practice. You bring your own text (a paragraph, a song, a script) and Mimora turns it into spoken drills and scores how close you get.
Supported languages
The practice language is chosen in the settings window (Language, applies after a restart); each language may offer regional variants (Accent).
| Language | Variants | Scoring engines |
|---|---|---|
| English | American, British | phoneme (default, calibrated), acoustic, none |
| Spanish (Peninsular / Castilian) | Castilian | phoneme (experimental - no Spanish calibration yet, scoring falls back to the English calibration until it lands), none |
Mimora is built for practicing English (American and British, fully calibrated). Under the hood the scoring engine is language-independent, and multilingual support is in development. Spanish is included as an experiment: until a Spanish calibration lands, scoring falls back to the English calibration (usable, not tuned); a proper calibration requires labeled non-native speech data for that language.
The English-only acoustic engine is not offered for Spanish. (The translation panel already renders the practice phrase in 200+ languages - that is the translation shown beside the phrase, not the practice language itself.)
How it works
For each practice phrase Mimora runs a simple loop:
- Prompt - a phrase is generated by the local LLM from your practice text, then spoken aloud by the active language's TTS voice (Kokoro for English, Supertonic for Spanish; this synthesized audio is also the reference for scoring).
- Record - you press
SPACE(or click the mic) and repeat the phrase; the take stops on its own once you fall silent (or pressSPACE/ click the mic again). - Analyze - your recording and the reference are compared in a background thread by the active pronunciation engine - by default the phoneme engine (espeak reference phonemes vs a wav2vec2 phoneme recognizer) - plus prosody (pitch / energy). An alternative acoustic engine (Wav2Vec2 embeddings + DTW) is selectable in the settings window (Engine; settings.json
"engine"). - Feedback - you get a score out of 100, what was recognized, and the words to improve.
- Loop - repeat the same phrase until you are happy with the score, then generate the next phrase.
You can replay the reference and your own recording back-to-back to hear the difference.
No-LLM mode for slow machines. Setting the LLM backend to
offin the settings skips the language model entirely - nothing is loaded or started, and each practice phrase is a sentence of your practice text, taken verbatim and in order. This mode is aimed primarily at low-end machines (no GGUF model, no LLM server subprocess, less RAM/VRAM and a faster start); it also suits drilling a text exactly as written. The phrase-length choice is disabled in this mode - sentences are never shortened. Combined with thenonescoring engine, Mimora runs with no LLM and no recognizer model at all.
Example
Reference: The weather is getting colder.
You said: The weather is getting color.
Score: 82 / 100
Work on: colder
Features
- 🎙️ One-press recording - press once, speak, and it stops by itself when you go quiet (peak normalization, silence-based auto-stop).
- 🗣️ One consistent reference voice - prompts and the scored reference are spoken by the same TTS voice, so you always compare against the same target (no second TTS). Prefer variety? Enable Random voice per phrase in the settings.
- 🧠 Practice your own material - paste a paragraph, song, or sentences into the practice text panel and the local LLM turns it into an endless stream of phrases to drill.
- ⚙️ Settings window (the gear button) - pick the practice language and its accent (both apply after a restart), the TTS voice and playback speed (or let Random voice per phrase speak every new phrase with a different voice of the current language - needs at least two voices), choose the phrase length (full phrase or a few words), and set the translation language shown under the phrase. A user name selects the per-user scoring calibration.
- 📊 Objective scoring with two interchangeable engines, selected in the settings window (Engine; settings.json
"engine"). The default phoneme engine scores espeak reference phonemes against a wav2vec2 phoneme recognizer (feature-weighted edit distance, mapped to a calibrated 0-5 grade). The acoustic engine combines per-step cosine DTW over Wav2Vec2 embeddings (40%) with phoneme (30%) and word (30%) error rates. Both are length-invariant and calibratable to your voice (python pronunciation/<engine>/calibrate.py). - 🔁 Replay reference vs. your recording to hear the difference.
- 😀 Articulation face - a schematic mouth opens and closes with the speech as a reference or your recording plays, and shows a smiley reflecting your score while idle.
- 🧵 Responsive UI - analysis and model loading run in daemon threads; the GUI is updated only via
root.after(). - 💻 Fully local & offline after the models are downloaded.
Requirements
- Hardware - runs on a typical laptop or desktop: 16 GB RAM and no GPU required (CPU-only works; the first few phrases are slower). An NVIDIA GPU is optional and speeds up pronunciation analysis and phrase generation.
- Python 3.11 or 3.12 (developed and tested on 3.11 and 3.12). Python 3.13 and newer are not yet supported (as of June 2026).
- Windows is the primary target (TTS playback uses
winsound); asounddevicefallback exists for other platforms. - A microphone and speakers.
- For GPU acceleration: an NVIDIA GPU with a CUDA-enabled PyTorch build.
- espeak-ng (native binary, required by the phonemizer) - installed separately, see below.
- PortAudio (Linux only) - the native library
sounddevicebinds to for recording and playback (libportaudio2on Debian/Ubuntu). The Windows and macOSsounddevicewheels bundle it; the Linux ones do not. See below.
macOS notes
Apple Silicon Macs run the same pinned stack as Windows and Linux. Intel
Macs (x86_64) are supported too, but with an automatic fallback: PyTorch
publishes no macOS x86_64 wheel newer than torch 2.2.2, and that torch cannot
run transformers >= 5. The requirements files therefore carry environment
markers that, on Intel macOS only, install a relaxed stack automatically -
torch 2.2.2, transformers 4.x, NumPy < 2 - with no manual steps. The
trade-off is that this fallback forgoes the transformers >= 5.3 fix for
CVE-2026-4372 and re-enables torch.load for the pinned models (the
CVE-2025-32434 gate, handled in pronunciation/common/compat.py), which is
acceptable for a local app that loads only these fixed, trusted model repos.
Every other platform keeps the hardened pins.
tkinter is bundled by the python.org installer but not by Homebrew Python.
install.py installs the matching python-tk@<version> formula for the
interpreter it runs in; if you set things up by hand on Homebrew Python, match
your version (e.g. brew install python-tk@3.12 for Homebrew Python 3.12).
Models
install.py pre-downloads all of these (see Quick install).
You do not have to run it: on the first start Mimora checks what is missing and
offers to fetch it, naming the exact volume before anything is downloaded. Each
model also has its own command, listed per row below.
The first-run window asks about two levels separately. The models a session
cannot run without (the active engine's recognizer plus the active language's
TTS) are a notice with Download and Quit; the local chat model
(llama-server plus the GGUF) is a real choice, and declining it writes
"llm_backend": "off" into config/settings.json, where you can turn it back
on later. The translator is fetched lazily and is never part of that question.
Download sizes below are measured, not estimated, and are kept as data in
mimora/models_info.py; re-snap them with
python tools/measure_model_sizes.py.
| Model | Used by | Download | Notes |
|---|---|---|---|
facebook/wav2vec2-xlsr-53-espeak-cv-ft |
pronunciation analysis (phoneme engine, default) | 1264 MB | espeak IPA phoneme recognizer; python -m mimora.model_fetch --hf |
facebook/wav2vec2-large-960h |
pronunciation analysis (acoustic engine) | 1262 MB | python -m mimora.model_fetch --hf |
Kokoro-82M (hexgrad/Kokoro-82M) |
text-to-speech (English) | 363 MB | python -m mimora.model_fetch --hf |
Supertonic 3 (Supertone/supertonic-3) |
text-to-speech (Spanish) | 404 MB | into model_cache/supertonic3/; python -m mimora.model_fetch --supertonic. Weights are OpenRAIL-M licensed (code MIT), so they are downloaded, never bundled |
facebook/nllb-200-distilled-600M |
offline translation (translation panel) | 2483 MB | NLLB-200 200-language translator; fetched on demand when translation is switched on |
A GGUF chat model (e.g. Llama-3.2-3B-Instruct-Q4_K_M) |
phrase generation | 2019 MB | python -m mimora.gguf_fetch. Not needed with "llm_backend": "off" (phrases come verbatim from the practice text) |
| llama-server binary (pinned llama.cpp release) | phrase generation | 641 MB CUDA, 18 MB CPU | most of the CUDA figure is NVIDIA's runtime (391 MB), not llama.cpp; python -m mimora.llama_server_fetch |
Installation
install.py is the recommended way to install - it handles dependencies, GPU
builds, and model downloads in one guided run. The manual steps further below are
an alternative if you prefer to run each command yourself.
Installation is set up to use prebuilt packages (wheels), so no compilation toolchain is needed - everything installs from ready-made binaries except a few small pure-Python packages that build trivially. You do not need a C/C++ compiler or CUDA toolkit for the standard setup.
Quick install (script, recommended)
install.py automates the whole setup: it checks the native pieces pip cannot
supply (espeak-ng, plus tkinter and PortAudio on Linux and the MSVC runtime
on Windows), installs the Python dependencies, auto-detects an NVIDIA GPU and
installs the matching CUDA build of torch, pre-downloads the Hugging Face
models into model_cache/, installs the pinned llama-server binary into
bin/llama/ and downloads the GGUF chat model into models/.
git clone https://github.com/vikonix/Mimora.git Mimora
cd Mimora
# Create and activate a virtual environment, then run the installer INSIDE it
# (the script installs into whatever interpreter runs it):
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
python install.py
The installer prints each step and the exact command, then asks before running
it (answer Y to run, n to abort, s to skip). Anything already installed is
detected and offered as reinstall-or-skip rather than blindly redone. The full
run is logged to logs/install.log.
Expect the full run to take several minutes (mostly downloads, so it depends on your internet speed), and to use roughly 10 GB of disk once all packages and models are in place.
Useful flags:
--yes- run non-interactively (skips already-installed steps; add--reinstallto force them)--dry-run- print the steps and commands without executing anything--cpu- skip the GPU (CUDA) installs--skip-models/--skip-gguf- skip the model / GGUF downloads
espeak-ng (a native binary, see below) is checked but not installed on Windows -
follow the printed instructions if it is missing. On Windows, enabling
Developer Mode lets the model cache use symlinks; without it the installer
falls back to copying files (more disk use, but it always works).
Manual installation (alternative)
# 1. Clone
git clone https://github.com/vikonix/Mimora.git Mimora
cd Mimora
# 2. All dependencies in one step
# The list lives in [project.dependencies] in pyproject.toml and covers both
# pronunciation engines (panphon included, for the default phoneme engine).
# No separate per-engine install is needed.
#
# Editable (-e), because this is a clone: a plain `pip install .` would copy
# the code into site-packages, leaving a second copy that your edits do not
# reach. Editable also keeps Mimora in "source tree" mode, so config/, models/
# and logs/ stay in the project directory rather than moving to the OS
# user-data directory (see mimora/paths.py).
pip install -e .
The offline translator (NLLB-200) needs no extra step - its dependencies
(transformers, sentencepiece) are in that same list.
On Windows with an NVIDIA card, add the CUDA build of torch afterwards (see
GPU support): PyPI's torch is CPU-only there. On
Linux PyPI already serves a CUDA build, and macOS has no CUDA at all.
Install espeak-ng (required for phoneme analysis)
phonemizer needs the native espeak-ng binary on your PATH:
- Windows - download and run the installer from the espeak-ng releases.
- macOS -
brew install espeak-ng - Linux -
sudo apt-get install espeak-ng
Audio on Linux (PortAudio)
sounddevice is a wrapper around the native PortAudio library. Its Windows
and macOS wheels ship that library inside; its Linux wheels do not, so without
the system package every import fails with
OSError: PortAudio library not found - including the one in
mimora/detect_hardware.py, which is why install.py checks for it up front:
sudo apt-get install libportaudio2 # Debian / Ubuntu
sudo dnf install portaudio # Fedora
If the library is installed but the app finds no audio devices (the
installer prints 0 input / 0 output), the usual cause is the backend rather
than the library: Debian and Ubuntu build PortAudio with the ALSA backend only,
while WSL and most desktop setups route audio through PulseAudio. Check it with
ldd "$(ldconfig -p | grep -m1 portaudio | awk '{print $NF}')" | grep pulse
An empty answer means no PulseAudio backend. Two known ways out: install
libasound2-plugins and point ALSA's default device at pulse
(pcm.!default pulse in ~/.asoundrc), or rebuild PortAudio from source with
./configure --with-pulseaudio.
Emoji icons on Linux (mic button shows a blank box)
The mic/record button (mimora/ui.py draw_mic_button) draws its state icons
(🎤 🔴 🔊 ⌛ ⚡) as text on the Tk canvas, using the platform font
(mimora/ui_theme.py, "DejaVu Sans" on Linux). DejaVu Sans covers ⚡/⌛
(older BMP symbols) but not 🎤/🔴/🔊 (astral-plane emoji), so on a fresh
Linux install those three render as a blank/tofu box instead of the icon -
it can look like the mic and speaker icons are simply missing.
Fix: install a monochrome emoji font so Tk can render the glyphs as normal
vector outlines (Tk canvas text cannot render color/bitmap emoji fonts like
fonts-noto-color-emoji, which is the one apt installs by default):
sudo apt install fonts-symbola # in Ubuntu's universe repo; enable it first if missing:
# sudo add-apt-repository universe && sudo apt update
fc-cache -f -v
Then restart Mimora.
GPU support (recommended)
Whether you need this step depends on your platform. PyPI serves CPU-only
torch on Windows and macOS and a CUDA-enabled build on Linux, and macOS has
no CUDA at all - so this section is about Windows with an NVIDIA card, which
is the one combination that gets a CPU wheel it did not want. Mimora says so at
startup if it happens: the app still works, Wav2Vec2 and speech synthesis are
just several times slower.
-
PyTorch - install a CUDA build (other CUDA versions: see pytorch.org):
python -m pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu124 --force-reinstall
Reinstall
torchandtorchaudiotogether: force-reinstallingtorchalone leaves atorchaudiobuilt against the previous torch, which then fails to import (OSError: [WinError 127]) and breaks pronunciation analysis.With uv this is one flag instead - it reads the installed driver and picks the matching PyTorch index itself, falling back to CPU when there is no GPU:
uv tool install mimora --torch-backend auto # or, equivalently: # UV_TORCH_BACKEND=auto uv tool install mimora
Needs uv 0.9.20 or newer. There is no equivalent for
pipx: an index cannot be named in a published package's metadata, so a pipx install gets the CPU wheel and the manual step above. -
The LLM needs no pip package at all: it runs in the official llama.cpp binary, and the fetcher below picks the GPU build for your platform automatically - CUDA on Windows, Vulkan on Linux, where llama.cpp publishes no CUDA binaries at all (see the next section).
Get the llama-server binary
The default LLM backend runs the official llama.cpp server as a subprocess.
install.py installs it, and the app offers to fetch it on the first start; to
do it separately, or to change the build:
python -m mimora.llama_server_fetch
This downloads a pinned llama.cpp release into bin/llama/, verifies the
checksum of every asset, and then confirms that the binary really runs on the
GPU backend it advertises - a CUDA build with missing runtime DLLs otherwise
falls back to the CPU silently and just runs about three times slower.
--list shows the available builds, --variant picks one explicitly, and
--dry-run prints the plan without downloading. If you already manage your own
llama-server, put it on PATH or name it in settings.json
("llama_server_path") instead.
Builds are pinned for Windows x64 (CUDA, falling back to CPU when the driver is too old), Linux x64 (Vulkan, falling back to CPU) and macOS (Metal on Apple Silicon, CPU-only on Intel - llama.cpp builds its Intel asset with Metal switched off).
The macOS builds come with the release's own limits, and neither can be worked around from here. Both carry a minimum macOS version stamped into the binary at build time, read out of the current pin and checked before anything is downloaded: 13.3 for the Intel build (llama.cpp sets that deployment target explicitly) and 26.0 for the Apple Silicon one (llama.cpp sets none, so the build inherits the macOS of the runner it was compiled on). A Mac below its build's minimum is told so and left alone - the installer records it as a manual step and moves on, rather than failing.
The Intel build has a second limit that no header states and nothing can check in advance: it is compiled for the CPU of llama.cpp's CI runner, so an older Intel Mac stops with an illegal instruction the first time the binary runs.
In either case the way out is the same: build llama.cpp on the machine and name
the result in "llama_server_path", or switch "llm_backend" to lm-studio.
The Linux fallback is worth a word. llama.cpp ships no CUDA binary for Linux,
so the GPU build there is the Vulkan one, and whether it can see the GPU cannot
be known before downloading it - it needs a Vulkan loader (libvulkan1), an
ICD manifest published by the driver, and a device reachable through both.
Under WSL2 the NVIDIA driver publishes no Vulkan ICD at all, so the check comes
back empty. The fetcher therefore tries Vulkan, verifies it with
--list-devices, and installs the CPU build instead when no device appears,
saying so in the log. If you fix the Vulkan side later, --force re-runs the
whole selection.
Get a GGUF model
install.py already downloads llama-3.2-3b-instruct-q4_k_m.gguf into models/,
the first-run window offers the same download, and python -m mimora.gguf_fetch
does it on its own (--list shows the target path and whether the file is there).
To use a different model instead, download a small instruct model (e.g. Llama-3.2-3B-Instruct-Q4_K_M.gguf) and place it at the path set by EXTERNAL_MODEL_PATH in mimora/config.py (default: models/llama-3.2-3b-instruct-q4_k_m.gguf).
Usage
Run from the same virtual environment you installed into (so the app uses the interpreter that has all the dependencies):
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
python main.py
On first launch the app loads the TTS and pronunciation (Wav2Vec2) models and starts the LLM server. If you ran install.py (or already launched once), the models are cached and this is just a load that takes a moment; if any model is still missing, it is downloaded first (several GB), which takes a while. Once it shows Ready:
- Edit the Practice text panel (or keep the default).
- Click Next phrase ▶ - Mimora generates a phrase and speaks it.
- Press
SPACE(or click the mic button) and repeat the phrase; the take auto-stops on silence (pressSPACE/ click the mic again to stop manually). - Read your score and verdict on the phrase card: mispronounced words are underlined (click any word to hear it slowly) and the WORK ON badges name the sounds to fix (click one for an example word). Earlier takes stay in the attempt history below.
- Use Reference ▶ (or Slow ▶) / My recording ▶ to compare, then repeat or generate the next phrase.
The first few phrases run noticeably slowly - the models are still warming up and loading their data into memory on their first call. This is normal; speed settles to its steady state after the initial requests.
Press ESC or close the window to quit (the LLM server subprocess is terminated cleanly).
Where Mimora keeps your files
Running from a clone - which is every installation today - everything stays
inside the project directory: config/, models/, model_cache/, bin/llama/
and logs/. Nothing about that changes, and there is nothing to migrate.
Installed as a package (a future option), the same layout moves under the operating system's user-data directory, because a package's own directory belongs to whatever installed it and is rebuilt on the next upgrade:
| OS | Location |
|---|---|
| Windows | %APPDATA%\Mimora\ |
| macOS | ~/Library/Application Support/Mimora/ |
| Linux | $XDG_DATA_HOME/mimora/ (default ~/.local/share/mimora/) |
Set the MIMORA_HOME environment variable to put that directory anywhere else.
It overrides both cases and is the answer to the three situations that need one:
moving several gigabytes of downloads to another drive, escaping a roaming
Windows profile that would carry them across the network at every login, and
running two configurations side by side.
Paths inside config/settings.json resolve against that file's own
directory. Absolute paths work everywhere and are what the settings window
writes; a relative one is read as relative to the settings file you are editing.
Keys you leave out are not affected by the rule at all - their defaults are
resolved for you, and the downloads and the files shipped with the app do not
live in the same place.
GPU / CPU notes
Several torch models (the active engine's Wav2Vec2 - the phoneme recognizer by default, Kokoro, and the NLLB translator) plus llama.cpp can compete for VRAM on a single GPU. Mimora mitigates this two ways:
- The LLM runs in a separate process (llama-server), and the practice loop runs its phases (LLM → Kokoro → Wav2Vec2) sequentially, so they don't synthesize/infer at the same time. The NLLB translator defaults to CPU (
TRANSLATOR_DEVICE). - If VRAM is still tight, set
WAV2VEC2_DEVICE = "cpu"inmimora/config.py- short phrases analyze acceptably on CPU.
Known limitations
- Spanish
phonemescoring is experimental for now. The defaultphonemeengine uses a multilingual IPA recognizer, but its scoring calibration is per-language; a proper Spanish calibration requires labeled non-native speech data and is not available yet, so Spanish scoring falls back to the English calibration (usable, not tuned - the app logs a startup warning and the settings window shows a notice). Theacousticengine is English-only (English ASR model) and is not offered for other languages. (The translation panel already targets many languages - that is the practice phrase's translation, not the practice language itself.) - The transcription-based word errors only surface mistakes the ASR actually "hears"; subtle distortions where the word is still recognized may not appear in the word list (the default phoneme engine's IPA edit distance, or the acoustic engine's DTW, plus prosody partially compensate).
- Scoring is heuristic and depends on your voice and microphone. After a practice session, re-anchor the active engine to your data:
python pronunciation/phoneme/calibrate.py(default engine) orpython pronunciation/acoustic/calibrate.py(acoustic engine);--dry-runpreviews the change. Every attempt's raw components are logged tologs/phoneme_samples.jsonl(orlogs/acoustic_samples.jsonl) andlogs/main.logfor inspection.
Credits
- OpenPronounce (MIT) - the pronunciation-scoring core reused in
pronunciation/acoustic/. - Kokoro-82M - text-to-speech (English variants).
- Supertonic 3 (supertonic-py, code MIT, weights OpenRAIL-M) - text-to-speech (Spanish variant), ONNX runtime.
- wav2vec2-xlsr-53-espeak-cv-ft (Hugging Face Transformers) - espeak-style IPA phoneme recognizer for the default
phonemeengine. - Wav2Vec2 (Hugging Face Transformers) - acoustic embeddings and transcription (
acousticengine). - NLLB-200 (Hugging Face Transformers) - offline translation for the translation panel.
- espeak-ng / phonemizer-fork - reference phonemization (espeak IPA).
- panphon - articulatory feature distance used by the phoneme edit-distance scoring.
- llama.cpp - local LLM inference; the official
llama-serverbinary is run as a subprocess.
License
See LICENSE. The reused OpenPronounce components are MIT-licensed; their attribution is retained in pronunciation/acoustic/speech.py.
Model weights have their own licenses. In particular, the Supertonic 3 TTS
weights are licensed under OpenRAIL-M
(the supertonic package code is MIT). Mimora therefore never bundles these
weights: they are downloaded from Hugging Face by install.py or on the first
online run, into model_cache/supertonic3/.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mimora-1.1.0rc2-py3-none-any.whl.
File metadata
- Download URL: mimora-1.1.0rc2-py3-none-any.whl
- Upload date:
- Size: 348.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0761b0e835d1490c82efbc710177a55bc4119639506c8f9055f04589eff7f269
|
|
| MD5 |
73dd7ceedcf7a8be015b31af47b18b89
|
|
| BLAKE2b-256 |
069d8e3c5e1ae1cebf436a04767da38e985efc210fa7aae379a65e65be876ec6
|