mirumoji 2.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mirumoji-2.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 svdc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: mirumoji
3
+ Version: 2.0.1
4
+ Summary: CLI launcher for the Mirumoji project, an open-source, self-hostable Japanese language immersion tool.
5
+ Author-email: svdc <svdc1mail@gmail.com>
6
+ Maintainer-email: svdc <svdc1mail@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/svdC1/mirumoji
9
+ Project-URL: Issues, https://github.com/svdC1/mirumoji/issues
10
+ Keywords: japanese,video-player,language,cli,language-learning
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: click
17
+ Requires-Dist: python-dotenv
18
+ Dynamic: license-file
19
+
20
+ # Overview
21
+
22
+ > Mirumoji is an open-source, self-hosted Japanese immersion toolkit. Drop in any video, anime episode, drama or audio clip and it gives you: clickable tokenized subtitles with dictionary pop-ups, Whisper-powered transcription, instant SRT/clip extraction, and one-click Anki deck export — all in Docker, all running on your own machine.
23
+
24
+ > Optional [`OpenAI`](https://platform.openai.com/docs/overview) Integration - Customizable GPT Breakdowns of Subtitles -
25
+
26
+ > Optional [`Modal`](https://modal.com) Integration - Install a CPU only version and run all GPU tasks on the cloud -
27
+
28
+ ---
29
+
30
+ # Preview
31
+
32
+ > You can see a preview of the application's frontend _(No backend running)_ [`Here`](https://svdc1.github.io/mirumoji)
33
+
34
+ # Features
35
+
36
+ ## **Interactive Video Player**
37
+
38
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/player.png?raw=true)
39
+
40
+ > Upload your local anime/J-Drama espisodes or any Japanese video and `.SRT` subtitles.
41
+
42
+ ## **Clickable Japanese Subtitles**
43
+
44
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/word_dialog.png?raw=true)
45
+
46
+ > Subtitles are tokenized ([`kuromoji.js`](https://github.com/takuyaa/kuromoji.js)), allowing you to click individual words with
47
+ > integrated offline JMDict for definitions.
48
+
49
+ ## **OpenAI Integration**
50
+
51
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/gpt_template.png?raw=true)
52
+
53
+ > Modify System Message and User Prompt with `{sentence}` and `{word}` variables.
54
+
55
+ ## **Local Media Processing**
56
+
57
+ ### **Video Conversion**
58
+
59
+ > Upload videos in various formats; they can be converted to MP4 for optimal playback.
60
+
61
+ ### **SRT Generation**
62
+
63
+ > Generate subtitles for your videos. Runs [`FasterWhisper`](https://github.com/SYSTRAN/faster-whisper) with modified parameters to increase accuracy for longer media such as Anime/ J-Drama episodes.
64
+
65
+ ## **Audio Transcription**
66
+
67
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/transcription.png?raw=true)
68
+
69
+ > Transcribe Japanese audio from recordings or uploaded files.
70
+
71
+ ## **Profile-Based Data Management:**
72
+
73
+ ### **Persistent Storage**
74
+
75
+ ![alt-img](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/profile.png?raw=true)
76
+
77
+ > Profile configurations and all other profile-related data is stored and managed via SQLite database by the backend.
78
+
79
+ ### **Clip Saving**
80
+
81
+ ![alt-img](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/clips.png?raw=true)
82
+
83
+ > Save important video segments with their associated word breakdowns and export as an Anki Deck
84
+
85
+ ---
86
+
87
+ # Setup
88
+
89
+ > Both the [`frontend`](https://github.com/svdC1/mirumoji/tree/main/apps/frontend) and [`backend`](https://github.com/svdC1/mirumoji/tree/main/apps/backend) have pre-built [`Docker`](https://www.docker.com/) images and are set up to work with Docker Compose.
90
+
91
+ > The easiest setup is to run the [`launcher`](https://github.com/svdC1/mirumoji/tree/main/apps/cli) for your platform. **For detailed instructions please refer to the [`Setup Guide`](https://github.com/svdC1/mirumoji/wiki/Setup-Guide)**
92
+
93
+ > You can also choose to [`build`](https://docs.docker.com/build/) the images locally with the Dockerfiles provided.
94
+
95
+ ---
@@ -0,0 +1,76 @@
1
+ # Overview
2
+
3
+ > Mirumoji is an open-source, self-hosted Japanese immersion toolkit. Drop in any video, anime episode, drama or audio clip and it gives you: clickable tokenized subtitles with dictionary pop-ups, Whisper-powered transcription, instant SRT/clip extraction, and one-click Anki deck export — all in Docker, all running on your own machine.
4
+
5
+ > Optional [`OpenAI`](https://platform.openai.com/docs/overview) Integration - Customizable GPT Breakdowns of Subtitles -
6
+
7
+ > Optional [`Modal`](https://modal.com) Integration - Install a CPU only version and run all GPU tasks on the cloud -
8
+
9
+ ---
10
+
11
+ # Preview
12
+
13
+ > You can see a preview of the application's frontend _(No backend running)_ [`Here`](https://svdc1.github.io/mirumoji)
14
+
15
+ # Features
16
+
17
+ ## **Interactive Video Player**
18
+
19
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/player.png?raw=true)
20
+
21
+ > Upload your local anime/J-Drama espisodes or any Japanese video and `.SRT` subtitles.
22
+
23
+ ## **Clickable Japanese Subtitles**
24
+
25
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/word_dialog.png?raw=true)
26
+
27
+ > Subtitles are tokenized ([`kuromoji.js`](https://github.com/takuyaa/kuromoji.js)), allowing you to click individual words with
28
+ > integrated offline JMDict for definitions.
29
+
30
+ ## **OpenAI Integration**
31
+
32
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/gpt_template.png?raw=true)
33
+
34
+ > Modify System Message and User Prompt with `{sentence}` and `{word}` variables.
35
+
36
+ ## **Local Media Processing**
37
+
38
+ ### **Video Conversion**
39
+
40
+ > Upload videos in various formats; they can be converted to MP4 for optimal playback.
41
+
42
+ ### **SRT Generation**
43
+
44
+ > Generate subtitles for your videos. Runs [`FasterWhisper`](https://github.com/SYSTRAN/faster-whisper) with modified parameters to increase accuracy for longer media such as Anime/ J-Drama episodes.
45
+
46
+ ## **Audio Transcription**
47
+
48
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/transcription.png?raw=true)
49
+
50
+ > Transcribe Japanese audio from recordings or uploaded files.
51
+
52
+ ## **Profile-Based Data Management:**
53
+
54
+ ### **Persistent Storage**
55
+
56
+ ![alt-img](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/profile.png?raw=true)
57
+
58
+ > Profile configurations and all other profile-related data is stored and managed via SQLite database by the backend.
59
+
60
+ ### **Clip Saving**
61
+
62
+ ![alt-img](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/clips.png?raw=true)
63
+
64
+ > Save important video segments with their associated word breakdowns and export as an Anki Deck
65
+
66
+ ---
67
+
68
+ # Setup
69
+
70
+ > Both the [`frontend`](https://github.com/svdC1/mirumoji/tree/main/apps/frontend) and [`backend`](https://github.com/svdC1/mirumoji/tree/main/apps/backend) have pre-built [`Docker`](https://www.docker.com/) images and are set up to work with Docker Compose.
71
+
72
+ > The easiest setup is to run the [`launcher`](https://github.com/svdC1/mirumoji/tree/main/apps/cli) for your platform. **For detailed instructions please refer to the [`Setup Guide`](https://github.com/svdC1/mirumoji/wiki/Setup-Guide)**
73
+
74
+ > You can also choose to [`build`](https://docs.docker.com/build/) the images locally with the Dockerfiles provided.
75
+
76
+ ---
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mirumoji"
7
+ version = "2.0.1"
8
+ authors = [{ name = "svdc", email = "svdc1mail@gmail.com" }]
9
+ maintainers = [{ name = "svdc", email = "svdc1mail@gmail.com" }]
10
+ description = "CLI launcher for the Mirumoji project, an open-source, self-hostable Japanese language immersion tool."
11
+ keywords = ["japanese", "video-player", "language", "cli", "language-learning"]
12
+ readme = "README.md"
13
+ requires-python = ">=3.9"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ license = "MIT"
19
+ license-files = ["LICEN[CS]E*"]
20
+ dependencies = ["click", "python-dotenv"]
21
+
22
+ [project.scripts]
23
+ mirumoji = "mirumoji.launcher:cli"
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/svdC1/mirumoji"
27
+ Issues = "https://github.com/svdC1/mirumoji/issues"
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,605 @@
1
+ import click
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ import socket
6
+ from textwrap import dedent
7
+ from typing import Optional, List
8
+ from pathlib import Path
9
+ from dotenv import dotenv_values, load_dotenv
10
+
11
+ MAIN_REPO_SUBDIR = Path("mirumoji_workspace")
12
+ MAIN_REPO_URL = "https://github.com/svdC1/mirumoji.git"
13
+ # Docker image names for local builds
14
+ FRONTEND_LOCAL_IMAGE_NAME = "mirumoji_frontend_local:latest"
15
+ BACKEND_GPU_LOCAL_IMAGE_NAME = "mirumoji_backend_gpu_local:latest"
16
+ BACKEND_CPU_LOCAL_IMAGE_NAME = "mirumoji_backend_cpu_local:latest"
17
+
18
+ # Relative paths within MAIN_REPO
19
+ FRONTEND_DOCKERFILE_RELPATH = Path("apps/frontend/Dockerfile")
20
+ FRONTEND_BUILD_CONTEXT_RELPATH = Path("apps/frontend")
21
+ BACKEND_GPU_DOCKERFILE_RELPATH = Path("apps/backend/Dockerfile")
22
+ BACKEND_CPU_DOCKERFILE_RELPATH = Path("apps/backend/Dockerfile.cpu")
23
+ BACKEND_BUILD_CONTEXT_RELPATH = Path("apps/backend")
24
+
25
+ COMPOSE_PREBUILT_CPU_RELPATH = Path("compose/docker-compose.cpu.yaml")
26
+ COMPOSE_PREBUILT_GPU_RELPATH = Path("compose/docker-compose.gpu.yaml")
27
+ COMPOSE_PREBUILT_DOCKER_GPU_RELPATH = Path(
28
+ "compose/docker-compose.gpu.dockerpull.yaml")
29
+ COMPOSE_PREBUILT_DOCKER_CPU_RELPATH = Path(
30
+ "compose/docker-compose.cpu.dockerpull.yaml")
31
+ COMPOSE_LOCAL_CPU_RELPATH = Path("compose/docker-compose.local.cpu.yaml")
32
+ COMPOSE_LOCAL_GPU_RELPATH = Path("compose/docker-compose.local.gpu.yaml")
33
+ ENV_FILE_NAME = ".env"
34
+
35
+ # --- Help Messages ---
36
+ BUILD_HELP = "Build Docker images locally (--build) or pull pre-built \
37
+ images from registry. (--pull)"
38
+
39
+ GPU_HELP = "Use GPU Version of Backend (--gpu) or CPU version (--cpu)"
40
+ REG_HELP = "Pull Images from GitHub Registry (--github-pull) or \
41
+ from Docker Hub (--docker-pull)"
42
+ # --- Helper Functions ---
43
+
44
+
45
+ def get_host_lan_ip():
46
+ """
47
+ Returns the primary LAN IPv4 address of the host machine
48
+ and loads it as an environment variable
49
+ """
50
+ click.secho("\n--- Getting HOST IPv4 ---", fg="blue")
51
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
52
+ try:
53
+ s.connect(('8.8.8.8', 80))
54
+ ip = s.getsockname()[0]
55
+ click.secho(f"HOST IPv4 Acquired : {ip}", fg="bright_green")
56
+ os.environ["HOST_LAN_IP"] = ip
57
+ return ip
58
+ except Exception as e:
59
+ click.secho(
60
+ f"Error Getting Host IPv4 Address: {e}",
61
+ fg="red",
62
+ err=True
63
+ )
64
+ sys.exit(1)
65
+ finally:
66
+ s.close()
67
+
68
+
69
+ def run_command(command_list: List,
70
+ cwd: Optional[Path] = None,
71
+ check: bool = True,
72
+ shell: bool = False
73
+ ):
74
+ """
75
+ Runs a command, streams output, and handles errors.
76
+ """
77
+
78
+ if not shell and not isinstance(command_list, list):
79
+ click.secho(
80
+ message="'command_list' must be a list for non-shell commands.",
81
+ fg='red',
82
+ err=True
83
+ )
84
+ sys.exit(1)
85
+ if shell:
86
+ cmd_str = command_list
87
+ else:
88
+ cmd_str = " ".join(map(str, command_list))
89
+ if not cwd:
90
+ cwd = Path.cwd()
91
+
92
+ click.secho(message=f"CWD: {cwd}",
93
+ fg='cyan')
94
+ click.secho(message=f"Running command: '{cmd_str}'",
95
+ fg='cyan')
96
+
97
+ try:
98
+ process = subprocess.Popen(
99
+ args=command_list,
100
+ cwd=cwd,
101
+ stdout=subprocess.PIPE,
102
+ stderr=subprocess.STDOUT,
103
+ text=True,
104
+ shell=shell,
105
+ bufsize=1,
106
+ universal_newlines=True,
107
+ encoding='utf-8',
108
+ errors='replace'
109
+ )
110
+
111
+ # Stream output
112
+ if process.stdout:
113
+ for line in iter(process.stdout.readline, ''):
114
+ line_content = line.rstrip('\r\n')
115
+ if line_content:
116
+ click.secho(message=f" ↪ {line_content}",
117
+ fg="cyan"
118
+ )
119
+ else:
120
+ click.echo("")
121
+ process.stdout.close()
122
+
123
+ return_code = process.wait()
124
+ if check and return_code != 0:
125
+ # Error message printed after all output has been streamed
126
+ raise subprocess.CalledProcessError(return_code, cmd_str)
127
+ return process
128
+ except subprocess.CalledProcessError as e:
129
+ message = dedent(f"""\
130
+ Error: Command '{e.cmd}' returned non-zero exit status "
131
+ {e.returncode}.
132
+ """)
133
+ click.secho(message=message, fg="red", err=True)
134
+ sys.exit(e.returncode or 1)
135
+
136
+ except FileNotFoundError as e:
137
+ message = dedent(f"""\
138
+ Error: Command not found: {e.filename}.
139
+ Please ensure it's installed and in your PATH.
140
+ """)
141
+ click.secho(message=message, fg='red', err=True)
142
+ sys.exit(1)
143
+ except Exception as e:
144
+ message = dedent(f"""\
145
+ An unexpected error occurred while trying to run command '{cmd_str}':
146
+ {e}
147
+ """)
148
+ click.secho(message=message, fg='red', err=True)
149
+ sys.exit(1)
150
+
151
+
152
+ def ensure_repo(repo_url: str,
153
+ repo_path: Path):
154
+ """
155
+ Ensures the repository is cloned or updated.
156
+ """
157
+ if not repo_path.is_dir():
158
+ click.secho(
159
+ message=f"Cloning repository: {repo_url} into {repo_path}...",
160
+ fg="green"
161
+ )
162
+ run_command(["git", "clone", repo_url, str(repo_path)])
163
+ else:
164
+ click.secho(
165
+ message=f"Repo {repo_path} already exists. Fetching updates...",
166
+ fg="green"
167
+ )
168
+ run_command(["git", "fetch", "--all"],
169
+ cwd=repo_path)
170
+
171
+ try:
172
+ git_rev_parse_cmd = ["git", "rev-parse", "--abbrev-ref", "HEAD"]
173
+ result = subprocess.run(
174
+ git_rev_parse_cmd,
175
+ cwd=repo_path,
176
+ text=True,
177
+ capture_output=True,
178
+ check=True
179
+ )
180
+ current_branch = result.stdout.strip()
181
+ except subprocess.CalledProcessError:
182
+ current_branch = "HEAD"
183
+
184
+ if current_branch == "HEAD":
185
+ click.secho(
186
+ message="Currently in a detached HEAD state.",
187
+ fg="yellow"
188
+ )
189
+ click.secho(
190
+ message="Attempting to checkout default branch (main)...",
191
+ fg="yellow"
192
+ )
193
+ # Try checking out 'main'
194
+ checkout_main_cmd = ["git", "checkout", "main"]
195
+ run_command(checkout_main_cmd,
196
+ cwd=repo_path,
197
+ check=True)
198
+
199
+ result = subprocess.run(
200
+ git_rev_parse_cmd,
201
+ cwd=repo_path,
202
+ text=True,
203
+ capture_output=True,
204
+ check=True
205
+ )
206
+ current_branch = result.stdout.strip()
207
+
208
+ click.secho(
209
+ message=f"Pulling latest changes for branch '{current_branch}'...",
210
+ fg="green"
211
+ )
212
+ run_command(["git", "pull", "origin", current_branch], cwd=repo_path)
213
+ click.secho("Repository setup complete.", fg="bright_green")
214
+
215
+
216
+ def check_env_file(expected_vars: List,
217
+ env_file_path: Path):
218
+ """
219
+ Checks for .env file and required variables.
220
+ """
221
+ click.secho(
222
+ message=f"Checking for {ENV_FILE_NAME} file at: {env_file_path}",
223
+ fg="green"
224
+ )
225
+ if not env_file_path.is_file():
226
+ message = dedent(f"""\
227
+ Error: {ENV_FILE_NAME} file not found at {env_file_path}.
228
+ Please create it with the variables: {', '.join(expected_vars)}
229
+ """)
230
+ click.secho(message=message, fg="red", err=True)
231
+ sys.exit(1)
232
+
233
+ click.secho(f"Loading variables from {ENV_FILE_NAME}...", fg="green")
234
+ env_config = dotenv_values(env_file_path)
235
+ missing_vars = [var for var in expected_vars if not env_config.get(var)]
236
+
237
+ if missing_vars:
238
+ message = dedent(f"""\
239
+ Error: Missing or empty variables in {env_file_path}:
240
+ {', '.join(missing_vars)}.
241
+ Please ensure all required variables are set:
242
+ {', '.join(expected_vars)}
243
+ """)
244
+ click.secho(message=message, fg="red", err=True)
245
+ sys.exit(1)
246
+ load_dotenv(dotenv_path=env_file_path)
247
+ click.secho(message="Variable Configuration Passed", fg="bright_green")
248
+
249
+
250
+ def get_build_locally():
251
+ """
252
+ Asks for user input on whether to build images locally or pull
253
+ pre-built images.
254
+ """
255
+ try:
256
+ click.secho("\n--- Build Configuration ---", fg="blue")
257
+ build_locally = click.confirm(
258
+ text="Build Docker images locally?",
259
+ default=False
260
+ )
261
+ click.secho(
262
+ f"\nSelected:\nBuild Locally: {build_locally}",
263
+ fg="blue"
264
+ )
265
+ return build_locally
266
+
267
+ except Exception as e:
268
+ click.secho(
269
+ f"Error while selecting configuration options: {e}",
270
+ fg="red",
271
+ err=True
272
+ )
273
+ sys.exit(1)
274
+
275
+
276
+ def get_gpu_cpu():
277
+ """
278
+ Asks for user input on whether to use CPU or GPU version.
279
+ """
280
+ try:
281
+ click.secho("\n--- Backend Configuration ---", fg="blue")
282
+ use_gpu = click.confirm(
283
+ text="Run Local GPU version of the backend (NVIDIA GPU required)?",
284
+ default=False
285
+ )
286
+
287
+ click.secho(
288
+ f"\nSelected:\nUse GPU: {use_gpu}",
289
+ fg="blue"
290
+ )
291
+ return use_gpu
292
+
293
+ except Exception as e:
294
+ click.secho(
295
+ f"Error while selecting configuration options: {e}",
296
+ fg="red",
297
+ err=True
298
+ )
299
+ sys.exit(1)
300
+
301
+
302
+ def get_registry():
303
+ try:
304
+ click.secho("\n--- Registry Configuration ---", fg="blue")
305
+ pull_registry = click.confirm(
306
+ text="Pull from GitHub Registry ? (N = Pull from DockerHub)",
307
+ default=False
308
+ )
309
+ if pull_registry:
310
+ reg = "GitHub"
311
+ else:
312
+ reg = "DockerHub"
313
+ click.secho(
314
+ f"\nSelected:\nRegistry:{reg}",
315
+ fg="blue"
316
+ )
317
+ return reg
318
+
319
+ except Exception as e:
320
+ click.secho(
321
+ f"Error while selecting registry configuration options: {e}",
322
+ fg="red",
323
+ err=True
324
+ )
325
+ sys.exit(1)
326
+
327
+
328
+ def build_imgs_locally(use_gpu: bool):
329
+ """
330
+ Runs the docker build command for the frontend and backend
331
+ images based on whether CPU or GPU version is chosen.
332
+ """
333
+ try:
334
+ click.secho("\n--- Building Docker Images ---", fg="blue")
335
+ click.secho("\nBuilding frontend image...", fg="green")
336
+ frontend_build_cmd = [
337
+ "docker",
338
+ "build",
339
+ "-t",
340
+ FRONTEND_LOCAL_IMAGE_NAME,
341
+ "-f",
342
+ str(FRONTEND_DOCKERFILE_RELPATH),
343
+ str(FRONTEND_BUILD_CONTEXT_RELPATH)
344
+ ]
345
+ run_command(frontend_build_cmd)
346
+ click.secho("Frontend image build complete.", fg="bright_green")
347
+
348
+ if use_gpu:
349
+ click.secho("Building GPU backend image...", fg="green")
350
+ backend_image_name = BACKEND_GPU_LOCAL_IMAGE_NAME
351
+ backend_dockerfile_relpath = BACKEND_GPU_DOCKERFILE_RELPATH
352
+ else:
353
+ click.secho("Building CPU backend image...", fg="green")
354
+ backend_image_name = BACKEND_CPU_LOCAL_IMAGE_NAME
355
+ backend_dockerfile_relpath = BACKEND_CPU_DOCKERFILE_RELPATH
356
+
357
+ backend_build_cmd = [
358
+ "docker",
359
+ "build",
360
+ "-t",
361
+ backend_image_name,
362
+ "-f",
363
+ str(backend_dockerfile_relpath),
364
+ str(BACKEND_BUILD_CONTEXT_RELPATH)
365
+ ]
366
+ run_command(backend_build_cmd)
367
+ click.secho("\nBackend image build complete.", fg="bright_green")
368
+
369
+ except Exception as e:
370
+ click.secho(
371
+ f"Error while building local images: {e}",
372
+ fg="red",
373
+ err=True
374
+ )
375
+ sys.exit(1)
376
+
377
+
378
+ def configure_repo():
379
+ """
380
+ Displays a header, sets up the local repository path, clones or
381
+ updates the repository, sets the working directory to the local repository
382
+ path and returns both the local repository path and original working
383
+ directory.
384
+ """
385
+ try:
386
+ click.secho("--- Mirumoji Launcher ---", fg="magenta")
387
+ current_user_cwd = Path.cwd()
388
+ repo_path = current_user_cwd / MAIN_REPO_SUBDIR
389
+ ensure_repo(MAIN_REPO_URL, repo_path)
390
+ original_cwd = current_user_cwd
391
+ # All subsequent paths are relative to repo_path
392
+ os.chdir(repo_path)
393
+ click.secho(message=f"Changed working directory to: {repo_path}",
394
+ fg="blue")
395
+ return (repo_path, original_cwd)
396
+
397
+ except Exception as e:
398
+ click.secho(
399
+ f"Error while configuring repository: {e}",
400
+ fg="red",
401
+ err=True
402
+ )
403
+ sys.exit(1)
404
+
405
+
406
+ # --- Click CLI structure ---
407
+ @click.group()
408
+ def cli():
409
+ """Mirumoji Launcher: Setup and run Mirumoji with Docker."""
410
+ pass
411
+
412
+
413
+ @cli.command()
414
+ @click.option('--build/--pull',
415
+ default=None,
416
+ help=BUILD_HELP)
417
+ @click.option("--gpu/--cpu",
418
+ default=None,
419
+ help=GPU_HELP)
420
+ @click.option("--github-pull/--docker-pull",
421
+ default=None,
422
+ help=REG_HELP)
423
+ @click.option('--no-clear',
424
+ is_flag=True,
425
+ default=False,
426
+ help="Do not clear the terminal after each step")
427
+ def launch(build, gpu, github_pull, no_clear):
428
+ """
429
+ Guides through setup, image building (optional), and running Mirumoji.
430
+ """
431
+ # --- Option Config ---
432
+ try:
433
+ if build is None:
434
+ build_locally = get_build_locally()
435
+ if not no_clear:
436
+ click.clear()
437
+ else:
438
+ build_locally = build
439
+ if gpu is None:
440
+ use_gpu = get_gpu_cpu()
441
+ if not no_clear:
442
+ click.clear()
443
+ else:
444
+ use_gpu = gpu
445
+ except Exception as e:
446
+ click.secho(
447
+ f"\nError while configuring options: {e}",
448
+ fg="red",
449
+ err=True
450
+ )
451
+ sys.exit(1)
452
+
453
+ repo_path, original_cwd = configure_repo()
454
+ if not no_clear:
455
+ click.clear()
456
+ try:
457
+ if build_locally:
458
+ build_imgs_locally(use_gpu=use_gpu)
459
+ if not no_clear:
460
+ click.clear()
461
+ else:
462
+ if github_pull is None:
463
+ registry = get_registry()
464
+ if not no_clear:
465
+ click.clear()
466
+ else:
467
+ if github_pull:
468
+ reg = "GitHub"
469
+ else:
470
+ reg = "DockerHub"
471
+ registry = reg
472
+ click.secho("\nUsing pre-built images.", fg='green')
473
+
474
+ click.secho(f"\n--- Checking {ENV_FILE_NAME} File ---", fg="blue")
475
+ env_file_abs_path = original_cwd / ENV_FILE_NAME
476
+ required_env_vars = ["OPENAI_API_KEY"]
477
+
478
+ if not use_gpu:
479
+ # CPU version requires Modal keys
480
+ required_env_vars.extend(["MODAL_TOKEN_ID", "MODAL_TOKEN_SECRET"])
481
+ check_env_file(required_env_vars,
482
+ env_file_abs_path)
483
+ if not no_clear:
484
+ click.clear()
485
+ # Get Host IPv4
486
+ HOST_LAN_IP = get_host_lan_ip()
487
+ if not no_clear:
488
+ click.clear()
489
+
490
+ if build_locally:
491
+ if use_gpu:
492
+ compose_file_relpath = COMPOSE_LOCAL_GPU_RELPATH
493
+ else:
494
+ compose_file_relpath = COMPOSE_LOCAL_CPU_RELPATH
495
+ else:
496
+ if use_gpu:
497
+ if registry == "DockerHub":
498
+ compose_file_relpath = COMPOSE_PREBUILT_DOCKER_GPU_RELPATH
499
+ else:
500
+ compose_file_relpath = COMPOSE_PREBUILT_GPU_RELPATH
501
+ else:
502
+ if registry == "DockerHub":
503
+ compose_file_relpath = COMPOSE_PREBUILT_DOCKER_CPU_RELPATH
504
+ else:
505
+ compose_file_relpath = COMPOSE_PREBUILT_CPU_RELPATH
506
+
507
+ click.secho("\n--- Running Docker Compose ---", fg="blue")
508
+ click.secho(f"Using compose file: {compose_file_relpath}",
509
+ fg="bright_magenta")
510
+ docker_compose_cmd = [
511
+ "docker",
512
+ "compose",
513
+ "-f",
514
+ str(compose_file_relpath),
515
+ "-p",
516
+ "mirumoji",
517
+ "up",
518
+ "-d"
519
+ ]
520
+ run_command(docker_compose_cmd)
521
+ if not no_clear:
522
+ click.clear()
523
+ stop_instructions = dedent(f"""\
524
+
525
+ --- Accessible at ---
526
+
527
+ Local: 'https://localhost'
528
+
529
+ LAN: 'https://{HOST_LAN_IP}'
530
+
531
+ --- Launcher Stop Command ---
532
+
533
+ launcher shutdown
534
+
535
+ --- Docker Stop Command ---
536
+
537
+ docker compose -p mirumoji down
538
+ """)
539
+ click.secho(message=stop_instructions, fg="bright_green")
540
+
541
+ except Exception as e:
542
+ click.secho(
543
+ f"An unexpected error occurred during the launch process: {e}",
544
+ fg="red",
545
+ err=True
546
+ )
547
+ sys.exit(1)
548
+ finally:
549
+ os.chdir(original_cwd)
550
+ click.secho(f"Returned to original working directory: {original_cwd}",
551
+ fg="blue")
552
+
553
+
554
+ @cli.command()
555
+ @click.option("--clean/--no-clean",
556
+ default=None,
557
+ help="Delete Docker Volumes")
558
+ @click.option('--no-clear',
559
+ is_flag=True,
560
+ default=False,
561
+ help="Do not clear the terminal after each step")
562
+ def shutdown(clean, no_clear):
563
+ """
564
+ Runs docker compose down on application.
565
+ """
566
+ repo_path, original_cwd = configure_repo()
567
+ if not no_clear:
568
+ click.clear()
569
+ if clean is None:
570
+ delete_volumes = click.confirm(
571
+ text="Delete Data (Docker Volumes) ?",
572
+ default=False
573
+ )
574
+ if not no_clear:
575
+ click.clear()
576
+ else:
577
+ delete_volumes = clean
578
+ try:
579
+ cmd = ['docker',
580
+ 'compose',
581
+ '-p',
582
+ 'mirumoji',
583
+ "down"
584
+ ]
585
+ if delete_volumes:
586
+ cmd.append("-v")
587
+ run_command(cmd, cwd=repo_path)
588
+ if not no_clear:
589
+ click.clear()
590
+ click.secho(message="All Services Stopped.", fg="bright_green")
591
+ except Exception as e:
592
+ click.secho(
593
+ f"An unexpected error occurred during shutdown: {e}",
594
+ fg="red",
595
+ err=True
596
+ )
597
+ sys.exit(1)
598
+ finally:
599
+ os.chdir(original_cwd)
600
+ click.secho(f"Returned to original working directory: {original_cwd}",
601
+ fg="blue")
602
+
603
+
604
+ if __name__ == '__main__':
605
+ cli()
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: mirumoji
3
+ Version: 2.0.1
4
+ Summary: CLI launcher for the Mirumoji project, an open-source, self-hostable Japanese language immersion tool.
5
+ Author-email: svdc <svdc1mail@gmail.com>
6
+ Maintainer-email: svdc <svdc1mail@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/svdC1/mirumoji
9
+ Project-URL: Issues, https://github.com/svdC1/mirumoji/issues
10
+ Keywords: japanese,video-player,language,cli,language-learning
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: click
17
+ Requires-Dist: python-dotenv
18
+ Dynamic: license-file
19
+
20
+ # Overview
21
+
22
+ > Mirumoji is an open-source, self-hosted Japanese immersion toolkit. Drop in any video, anime episode, drama or audio clip and it gives you: clickable tokenized subtitles with dictionary pop-ups, Whisper-powered transcription, instant SRT/clip extraction, and one-click Anki deck export — all in Docker, all running on your own machine.
23
+
24
+ > Optional [`OpenAI`](https://platform.openai.com/docs/overview) Integration - Customizable GPT Breakdowns of Subtitles -
25
+
26
+ > Optional [`Modal`](https://modal.com) Integration - Install a CPU only version and run all GPU tasks on the cloud -
27
+
28
+ ---
29
+
30
+ # Preview
31
+
32
+ > You can see a preview of the application's frontend _(No backend running)_ [`Here`](https://svdc1.github.io/mirumoji)
33
+
34
+ # Features
35
+
36
+ ## **Interactive Video Player**
37
+
38
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/player.png?raw=true)
39
+
40
+ > Upload your local anime/J-Drama espisodes or any Japanese video and `.SRT` subtitles.
41
+
42
+ ## **Clickable Japanese Subtitles**
43
+
44
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/word_dialog.png?raw=true)
45
+
46
+ > Subtitles are tokenized ([`kuromoji.js`](https://github.com/takuyaa/kuromoji.js)), allowing you to click individual words with
47
+ > integrated offline JMDict for definitions.
48
+
49
+ ## **OpenAI Integration**
50
+
51
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/gpt_template.png?raw=true)
52
+
53
+ > Modify System Message and User Prompt with `{sentence}` and `{word}` variables.
54
+
55
+ ## **Local Media Processing**
56
+
57
+ ### **Video Conversion**
58
+
59
+ > Upload videos in various formats; they can be converted to MP4 for optimal playback.
60
+
61
+ ### **SRT Generation**
62
+
63
+ > Generate subtitles for your videos. Runs [`FasterWhisper`](https://github.com/SYSTRAN/faster-whisper) with modified parameters to increase accuracy for longer media such as Anime/ J-Drama episodes.
64
+
65
+ ## **Audio Transcription**
66
+
67
+ ![alt-text](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/transcription.png?raw=true)
68
+
69
+ > Transcribe Japanese audio from recordings or uploaded files.
70
+
71
+ ## **Profile-Based Data Management:**
72
+
73
+ ### **Persistent Storage**
74
+
75
+ ![alt-img](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/profile.png?raw=true)
76
+
77
+ > Profile configurations and all other profile-related data is stored and managed via SQLite database by the backend.
78
+
79
+ ### **Clip Saving**
80
+
81
+ ![alt-img](https://github.com/svdC1/mirumoji/blob/main/.github/example_imgs/clips.png?raw=true)
82
+
83
+ > Save important video segments with their associated word breakdowns and export as an Anki Deck
84
+
85
+ ---
86
+
87
+ # Setup
88
+
89
+ > Both the [`frontend`](https://github.com/svdC1/mirumoji/tree/main/apps/frontend) and [`backend`](https://github.com/svdC1/mirumoji/tree/main/apps/backend) have pre-built [`Docker`](https://www.docker.com/) images and are set up to work with Docker Compose.
90
+
91
+ > The easiest setup is to run the [`launcher`](https://github.com/svdC1/mirumoji/tree/main/apps/cli) for your platform. **For detailed instructions please refer to the [`Setup Guide`](https://github.com/svdC1/mirumoji/wiki/Setup-Guide)**
92
+
93
+ > You can also choose to [`build`](https://docs.docker.com/build/) the images locally with the Dockerfiles provided.
94
+
95
+ ---
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/mirumoji/__init__.py
5
+ src/mirumoji/launcher.py
6
+ src/mirumoji.egg-info/PKG-INFO
7
+ src/mirumoji.egg-info/SOURCES.txt
8
+ src/mirumoji.egg-info/dependency_links.txt
9
+ src/mirumoji.egg-info/entry_points.txt
10
+ src/mirumoji.egg-info/requires.txt
11
+ src/mirumoji.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mirumoji = mirumoji.launcher:cli
@@ -0,0 +1,2 @@
1
+ click
2
+ python-dotenv
@@ -0,0 +1 @@
1
+ mirumoji