akari-video 1.3.0-beta.5 → 1.3.0-beta.7
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.
- package/package.json +1 -1
- package/src/vendor-sources.mjs +3 -0
- package/vendor/docs/contract-2026-07-13-asset-library.md +1 -1
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/akari-tools/bin/tools.mjs +26 -3
- package/vendor/packages/akari-tools/src/media/whisper-model-candidates.mjs +41 -0
- package/vendor/packages/akari-tools/src/media/whisper-model.mjs +82 -0
- package/vendor/packages/asset-resolver/src/library.mjs +2 -0
- package/vendor/packages/asset-resolver/src/state.mjs +9 -3
- package/vendor/packages/asset-resolver/test/local-library.test.mjs +16 -0
- package/vendor/packages/edit-lint/src/lint/captions.mjs +11 -1
- package/vendor/packages/edit-store/lib/write-gate.d.ts +12 -0
- package/vendor/packages/edit-store/lib/write-gate.js +57 -1
- package/vendor/packages/gpu-export/package.json +1 -1
- package/vendor/packages/i18n/locales/en/lint.json +1 -0
- package/vendor/packages/i18n/locales/en/partner.json +3 -0
- package/vendor/packages/i18n/locales/en/preview.json +2 -0
- package/vendor/packages/i18n/locales/en/surfaces.json +24 -2
- package/vendor/packages/i18n/locales/en/tools.json +18 -1
- package/vendor/packages/i18n/locales/ja/lint.json +1 -0
- package/vendor/packages/i18n/locales/ja/partner.json +3 -0
- package/vendor/packages/i18n/locales/ja/preview.json +2 -0
- package/vendor/packages/i18n/locales/ja/surfaces.json +24 -2
- package/vendor/packages/i18n/locales/ja/tools.json +18 -1
- package/vendor/packages/osr-export/package.json +1 -1
- package/vendor/packages/overlay-runtime/src/three-runtime.js +1 -1
- package/vendor/packages/render-cut/src/i18n.mjs +9 -2
- package/vendor/skills/address-review/SKILL.md +2 -2
- package/vendor/skills/analyze-footage/SKILL.md +5 -5
- package/vendor/skills/analyze-footage/analysis-json.md +3 -3
- package/vendor/skills/analyze-footage/bin/speechanalyzer-helper.swift +21 -4
- package/vendor/skills/analyze-footage/events-and-hooks.md +3 -3
- package/vendor/skills/analyze-footage/keyframes-and-review.md +1 -1
- package/vendor/skills/analyze-footage/media-and-transcript.md +3 -3
- package/vendor/skills/analyze-footage/person-matte.md +1 -1
- package/vendor/skills/analyze-footage/vision-tracks.md +4 -4
- package/vendor/skills/analyze-footage/workflow.md +2 -2
- package/vendor/skills/analyze-project/SKILL.md +3 -3
- package/vendor/skills/analyze-project/interpretation.md +3 -3
- package/vendor/skills/analyze-project/references/interpretation-shape.md +2 -2
- package/vendor/skills/bake-3d/SKILL.md +2 -2
- package/vendor/skills/beat-sync-edit/SKILL.md +1 -1
- package/vendor/skills/compile-review-session/compilation-rules.md +1 -1
- package/vendor/skills/create-project/SKILL.md +2 -2
- package/vendor/skills/critique-cut/SKILL.md +1 -1
- package/vendor/skills/critique-cut/workflow.md +1 -1
- package/vendor/skills/declare-audio/SKILL.md +1 -1
- package/vendor/skills/edit-lint/SKILL.md +11 -11
- package/vendor/skills/edit-plan/SKILL.md +5 -5
- package/vendor/skills/edit-plan/approvals-and-generation.md +1 -1
- package/vendor/skills/edit-plan/avatar-resolution.md +3 -3
- package/vendor/skills/edit-plan/beat-sync.md +3 -3
- package/vendor/skills/edit-plan/beats.md +3 -3
- package/vendor/skills/edit-plan/bin/generated/contract-validators.cjs +179 -153
- package/vendor/skills/edit-plan/emphasis-detection.md +4 -4
- package/vendor/skills/edit-plan/execution.md +5 -4
- package/vendor/skills/edit-plan/expression-selection.md +1 -1
- package/vendor/skills/edit-plan/plan-json.md +2 -2
- package/vendor/skills/edit-plan/recipe.md +2 -2
- package/vendor/skills/edit-plan/report-guide.md +3 -3
- package/vendor/skills/edit-plan/workflow.md +3 -3
- package/vendor/skills/export-nle/SKILL.md +2 -2
- package/vendor/skills/generate-media/SKILL.md +1 -1
- package/vendor/skills/generate-narration/SKILL.md +4 -4
- package/vendor/skills/generate-narration/lang/ja.md +2 -2
- package/vendor/skills/generate-narration/reading-text.md +1 -1
- package/vendor/skills/harvest-asset/SKILL.md +2 -2
- package/vendor/skills/manage-connections/SKILL.md +1 -1
- package/vendor/skills/manage-connections/bin/test/resolve-packages.test.mjs +1 -1
- package/vendor/skills/overlay-authoring/3d.md +2 -2
- package/vendor/skills/overlay-authoring/SKILL.md +4 -4
- package/vendor/skills/overlay-authoring/motion.md +1 -1
- package/vendor/skills/overlay-authoring/table.md +1 -1
- package/vendor/skills/overlay-authoring/telop.md +2 -2
- package/vendor/skills/overlay-authoring/text-behind-person.md +2 -2
- package/vendor/skills/render-cut/SKILL.md +6 -6
- package/vendor/skills/research-plan/SKILL.md +3 -3
- package/vendor/skills/research-plan/lang/ja.md +1 -1
- package/vendor/skills/research-plan/storyboard.md +1 -1
- package/vendor/skills/setup-audio-library/SKILL.md +3 -3
- package/vendor/skills/setup-chat-approval/SKILL.md +3 -3
- package/vendor/skills/setup-chat-approval/setup-token.md +1 -1
- package/vendor/skills/setup-library/SKILL.md +1 -1
- package/vendor/skills/setup-library/fetch-and-validate.md +2 -2
- package/vendor/skills/setup-library/tools-check.md +1 -1
- package/vendor/skills/setup-remote/SKILL.md +3 -3
- package/vendor/skills/verify/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "1.3.0-beta.
|
|
3
|
+
"version": "1.3.0-beta.7",
|
|
4
4
|
"description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/vendor-sources.mjs
CHANGED
|
@@ -40,6 +40,9 @@ export const VENDOR_SOURCES = [
|
|
|
40
40
|
'packages/akari-tools/bin/tools.mjs',
|
|
41
41
|
'packages/akari-tools/src/common/main-module.mjs',
|
|
42
42
|
'packages/akari-tools/src/media/common.mjs',
|
|
43
|
+
// bin/tools.mjs の install / status whisper が引く(akari tools install whisper)。
|
|
44
|
+
'packages/akari-tools/src/media/whisper-model.mjs',
|
|
45
|
+
'packages/akari-tools/src/media/whisper-model-candidates.mjs',
|
|
43
46
|
'packages/akari-tools/src/i18n.mjs',
|
|
44
47
|
'packages/akari-tools/package.json',
|
|
45
48
|
'packages/akari-voice/src',
|
|
@@ -374,7 +374,7 @@ catalog に載せる素材は、取得元のライセンスが CC0 相当(帰
|
|
|
374
374
|
| v1 category | 形の定義 | 判定 | 旧 category |
|
|
375
375
|
|---|---|---|---|
|
|
376
376
|
| `overlay` | 時間を持つ HTML 断片 | `fragment.html` を `overlays[]` から合成。断片ルートに時刻を置かず、素材本来の長さは `data-akari-natural-duration`(秒)に記録 | `telop` / `motion` |
|
|
377
|
-
| `still` |
|
|
377
|
+
| `still` | 時間を持たない静止画(画像 1 枚 + 任意でツマミ用の HTML シート) | **既定は直下の画像(`preview.png` 以外の 1 枚)を `sources[]` の media クリップとして置く**。`fragment.html` は色相などのツマミを使うとき・画像が 1 枚に決まらないときだけ(2026-10-12 改訂)。HTML シートは決定的スクショで画像に焼ける | `thumbnail` |
|
|
378
378
|
| `scene3d` | 3D モデル + 表示断片、またはベイクレシピ | `fragment.html` + glTF、または `scene.py` | `3d` |
|
|
379
379
|
| `audio` | 音声トラックに載るバイナリ | `edit.json` の `audio.bgm` / `sfx` が参照 | `audio`(変更なし) |
|
|
380
380
|
| `broll` | 映像トラックに載る実写バイナリ | `sources[]` が参照 | `broll`(変更なし) |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "1.3.0-beta.
|
|
3
|
+
"version": "1.3.0-beta.7",
|
|
4
4
|
"description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。 [akari-video npm vendor: bin/akari.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Use `akari doctor --json` and run the path reported in `render_cut.path`. Full installations provide it in a monorepo checkout, ~/.akari/app, /Applications/AKARI Video.app/Contents/Resources/packages, or %LOCALAPPDATA%\\Programs\\@akari-videoshell\\resources\\packages.]",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -6,6 +6,10 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import { dirname, isAbsolute, join, posix, resolve } from 'node:path';
|
|
7
7
|
import { gunzipSync } from 'node:zlib';
|
|
8
8
|
import { isMainModule } from '../src/common/main-module.mjs';
|
|
9
|
+
import { whisperModelCandidates, isWhisperModelExcluded } from '../src/media/whisper-model-candidates.mjs';
|
|
10
|
+
import { WHISPER_MODEL, installWhisperModel } from '../src/media/whisper-model.mjs';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
9
13
|
|
|
10
14
|
const modelPins = [
|
|
11
15
|
{ file: 'wespeaker_en_voxceleb_resnet34_LM.onnx', url: 'https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-recongition-models/wespeaker_en_voxceleb_resnet34_LM.onnx', sha256: 'e9848563da86f263117134dfd7ad63c92355b37de492b55e325400c9d9c39012', bytes: 26530550, license: 'CC-BY-4.0' },
|
|
@@ -275,13 +279,14 @@ export async function installVoiceEngine({ voiceHome = resolveVoiceHome(), platf
|
|
|
275
279
|
}
|
|
276
280
|
|
|
277
281
|
export async function runToolsCli(argv, { env = process.env, homeDir = homedir(), stdout = process.stdout, stderr = process.stderr,
|
|
278
|
-
fetchImpl = fetch, statfs = fs.statfs, platform = process.platform, arch = process.arch, pins = VOICE_ENGINE_PINS } = {}) {
|
|
279
|
-
const usage = toolsText('tools.
|
|
282
|
+
fetchImpl = fetch, statfs = fs.statfs, platform = process.platform, arch = process.arch, pins = VOICE_ENGINE_PINS, whisperModel = WHISPER_MODEL } = {}) {
|
|
283
|
+
const usage = toolsText('tools.whisper.cli.usage');
|
|
280
284
|
if (argv.includes('--help')) { stdout.write(`${usage}\n`); return 0; }
|
|
281
285
|
const [verb, subject, ...rest] = argv;
|
|
282
|
-
if (!['install', 'status'].includes(verb) ||
|
|
286
|
+
if (!['install', 'status'].includes(verb) || !['voice', 'whisper'].includes(subject) || rest.some(value => value !== '--json')) {
|
|
283
287
|
stderr.write(`${usage}\n`); return 2;
|
|
284
288
|
}
|
|
289
|
+
if (subject === 'whisper') return runWhisperCli(verb, rest.includes('--json'), { env, homeDir, stdout, stderr, fetchImpl, whisperModel });
|
|
285
290
|
const voiceHome = resolveVoiceHome({ env, homeDir });
|
|
286
291
|
const json = rest.includes('--json');
|
|
287
292
|
if (verb === 'status') {
|
|
@@ -299,6 +304,24 @@ export async function runToolsCli(argv, { env = process.env, homeDir = homedir()
|
|
|
299
304
|
return ok ? 0 : 1;
|
|
300
305
|
}
|
|
301
306
|
|
|
307
|
+
async function runWhisperCli(verb, json, { env, homeDir, stdout, stderr, fetchImpl, whisperModel }) {
|
|
308
|
+
if (verb === 'status') {
|
|
309
|
+
const model = whisperModelCandidates({ env, homeDir, repoRoot: resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..'), bin: '' })
|
|
310
|
+
.find(candidate => existsSync(candidate) && !isWhisperModelExcluded(candidate));
|
|
311
|
+
stdout.write(`${json ? JSON.stringify({ state: model ? 'ready' : 'missing', model: model ?? null }) : model ? toolsText('tools.whisper.cli.statusReady', { path: model }) : toolsText('tools.whisper.cli.statusMissing')}\n`);
|
|
312
|
+
return model ? 0 : 1;
|
|
313
|
+
}
|
|
314
|
+
let lastStep = -1;
|
|
315
|
+
const result = await installWhisperModel({ homeDir, fetchImpl, model: whisperModel, onProgress: ({ downloadedBytes, totalBytes }) => {
|
|
316
|
+
if (!totalBytes) return;
|
|
317
|
+
const step = Math.min(10, Math.floor(downloadedBytes / totalBytes * 10));
|
|
318
|
+
if (step > lastStep) { lastStep = step; stderr.write(`${toolsText('tools.whisper.cli.downloading')} ${step * 10}%\n`); }
|
|
319
|
+
} });
|
|
320
|
+
const ok = result.outcome === 'installed' || result.outcome === 'unchanged';
|
|
321
|
+
stdout.write(`${json ? JSON.stringify({ ok, outcome: result.outcome, message: result.message, path: result.path }) : result.message}\n`);
|
|
322
|
+
return ok ? 0 : 1;
|
|
323
|
+
}
|
|
324
|
+
|
|
302
325
|
if (isMainModule(import.meta.url)) {
|
|
303
326
|
runToolsCli(process.argv.slice(2)).then(code => { process.exitCode = code; }, error => { console.error(error); process.exitCode = 1; });
|
|
304
327
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Ordered search locations shared by the CLI and shell. The override is a file. */
|
|
5
|
+
export function whisperModelLocations({ env, homeDir, repoRoot, bin }) {
|
|
6
|
+
return [
|
|
7
|
+
{ path: env.WHISPER_CPP_MODEL, recursive: false },
|
|
8
|
+
{ path: path.join(homeDir, ".akari", "tools", "models"), recursive: true },
|
|
9
|
+
{ path: path.join(repoRoot, "models"), recursive: true },
|
|
10
|
+
{ path: path.join(repoRoot, "whisper.cpp", "models"), recursive: true },
|
|
11
|
+
{ path: path.join(homeDir, ".cache", "whisper.cpp"), recursive: true },
|
|
12
|
+
{ path: path.join(homeDir, "Library", "Caches", "whisper.cpp"), recursive: true },
|
|
13
|
+
{ path: path.resolve(path.dirname(bin), "..", "share", "whisper-cpp"), recursive: true },
|
|
14
|
+
{ path: "/opt/homebrew/share/whisper-cpp", recursive: true },
|
|
15
|
+
{ path: "/usr/local/share/whisper-cpp", recursive: true },
|
|
16
|
+
{ path: path.join(homeDir, "Library", "Application Support", "com.prakashjoshipax.VoiceInk", "WhisperModels"), recursive: true },
|
|
17
|
+
].filter(location => Boolean(location.path));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isWhisperModelFilename(candidate) {
|
|
21
|
+
return /^ggml-.*\.bin$/i.test(path.basename(candidate));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isWhisperModelExcluded(candidate, lang = "auto") {
|
|
25
|
+
return path.basename(candidate).startsWith("for-tests-") || (lang !== "en" && path.basename(candidate).includes(".en."));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function findModels(root) {
|
|
29
|
+
if (!existsSync(root)) return [];
|
|
30
|
+
try {
|
|
31
|
+
return readdirSync(root, { recursive: true })
|
|
32
|
+
.map(entry => path.join(root, String(entry)))
|
|
33
|
+
.filter(isWhisperModelFilename);
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function whisperModelCandidates(options) {
|
|
40
|
+
return whisperModelLocations(options).flatMap(location => location.recursive ? findModels(location.path) : [location.path]);
|
|
41
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream, createWriteStream, existsSync, statSync } from "node:fs";
|
|
3
|
+
import { mkdir, rename, rm } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
// アプリの backend(esbuild の cjs 出力)にも束ねられるので、top-level await を含む ../i18n.mjs ではなく
|
|
7
|
+
// bin/tools.mjs と同じ i18n の本体から引く。
|
|
8
|
+
import { t as toolsText } from "../../../i18n/index.mjs";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 認識モデルの既定(アプリの初回セットアップ / 設定 → 道具 と同じもの)。
|
|
12
|
+
* アプリ側 (tool-install.ts) もここを参照するので、URL と sha256 の正本はこの 1 か所。
|
|
13
|
+
* HF の `resolve/main` はブランチ追従だが、内容が変われば sha256 不一致で失敗するので版固定と同じ効果になる。
|
|
14
|
+
*/
|
|
15
|
+
export const WHISPER_MODEL = {
|
|
16
|
+
file: "ggml-large-v3-turbo-q5_0.bin",
|
|
17
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q5_0.bin",
|
|
18
|
+
sha256: "394221709cd5ad1f40c46e6031ca61bce88931e6e088c188294c6d5a55ffa7e2",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** `~/.akari/tools/models`(whisper-model-candidates.mjs の探索対象と一致させる)。 */
|
|
22
|
+
export function whisperModelsDir(homeDir = os.homedir()) {
|
|
23
|
+
return path.join(homeDir, ".akari", "tools", "models");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function sha256OfFile(file) {
|
|
27
|
+
const hash = createHash("sha256");
|
|
28
|
+
for await (const chunk of createReadStream(file)) hash.update(chunk);
|
|
29
|
+
return hash.digest("hex");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* モデルを tmp に落として sha256 を検証してから rename する。置き済みで sha256 が合えば何もしない。
|
|
34
|
+
* model / fetchImpl / homeDir は差し替え可能(テストは小さい偽ファイルで回す)。
|
|
35
|
+
*/
|
|
36
|
+
export async function installWhisperModel({ homeDir = os.homedir(), fetchImpl = fetch, model = WHISPER_MODEL, onProgress } = {}) {
|
|
37
|
+
const directory = whisperModelsDir(homeDir);
|
|
38
|
+
const destination = path.join(directory, model.file);
|
|
39
|
+
if (existsSync(destination) && statSync(destination).isFile() && await sha256OfFile(destination) === model.sha256) {
|
|
40
|
+
return { outcome: "unchanged", message: toolsText("tools.whisper.cli.alreadyReady", { path: destination }), path: destination };
|
|
41
|
+
}
|
|
42
|
+
await mkdir(directory, { recursive: true });
|
|
43
|
+
const temporary = `${destination}.tmp-${process.pid}`;
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetchImpl(model.url, { redirect: "follow" });
|
|
46
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
47
|
+
const total = Number(response.headers?.get?.("content-length")) || undefined;
|
|
48
|
+
const hash = createHash("sha256");
|
|
49
|
+
const output = createWriteStream(temporary);
|
|
50
|
+
let downloaded = 0;
|
|
51
|
+
const write = (chunk) => new Promise((resolve, reject) => output.write(chunk, (error) => error ? reject(error) : resolve()));
|
|
52
|
+
const take = async (chunk) => {
|
|
53
|
+
hash.update(chunk);
|
|
54
|
+
await write(chunk);
|
|
55
|
+
downloaded += chunk.length;
|
|
56
|
+
onProgress?.({ downloadedBytes: downloaded, totalBytes: total });
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
if (response.body?.getReader) {
|
|
60
|
+
const reader = response.body.getReader();
|
|
61
|
+
for (;;) {
|
|
62
|
+
const { done, value } = await reader.read();
|
|
63
|
+
if (done) break;
|
|
64
|
+
await take(value);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
await take(Buffer.from(await response.arrayBuffer()));
|
|
68
|
+
}
|
|
69
|
+
} finally {
|
|
70
|
+
await new Promise((resolve) => output.end(resolve));
|
|
71
|
+
}
|
|
72
|
+
if (hash.digest("hex") !== model.sha256) {
|
|
73
|
+
return { outcome: "failed", message: toolsText("tools.whisper.cli.verifyFailed"), path: destination };
|
|
74
|
+
}
|
|
75
|
+
await rename(temporary, destination);
|
|
76
|
+
return { outcome: "installed", message: toolsText("tools.whisper.cli.installed", { path: destination }), path: destination };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return { outcome: "failed", message: toolsText("tools.whisper.cli.downloadFailed", { error: error instanceof Error ? error.message : String(error) }), path: destination };
|
|
79
|
+
} finally {
|
|
80
|
+
await rm(temporary, { force: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -117,5 +117,7 @@ export function readLocalLibraryItem(env, category, id) {
|
|
|
117
117
|
mediaFile: primaryMediaFile(category, files), libraryDir,
|
|
118
118
|
addedAt: new Date(info.birthtimeMs > 0 ? info.birthtimeMs : info.mtimeMs).toISOString(),
|
|
119
119
|
creditText, warnings, state: 'cached',
|
|
120
|
+
// meta.json の無い取り寄せ(AKARI Sounds は音源ファイルだけを配る)を合成側が見分ける。
|
|
121
|
+
metaOk: Object.keys(meta).length > 0,
|
|
120
122
|
};
|
|
121
123
|
}
|
|
@@ -82,11 +82,17 @@ export async function composeState({ env = process.env, fetchImpl = fetch, inten
|
|
|
82
82
|
localItems.delete(key);
|
|
83
83
|
// Preserve remote download descriptors/preview keys for existing consumers.
|
|
84
84
|
// Local-only assets use the actual directory listing assembled above.
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
if (!local) return item;
|
|
86
|
+
// meta.json の無い取り寄せは表示用の title / tags をカタログから引き継ぐ。空の tags で上書きすると
|
|
87
|
+
// 効果音の 'sfx' が消え、ライブラリの BGM 側へ混ざっていた(オーナー指摘 2026-10-12)。
|
|
88
|
+
const { metaOk, ...localFields } = local;
|
|
89
|
+
return { ...item, ...localFields,
|
|
90
|
+
...(metaOk ? {} : { title: item.title ?? localFields.title, tags: item.tags ?? localFields.tags }),
|
|
91
|
+
tier: item.tier ?? assetTier(item), files: item.files ?? local.files, preview: local.libraryDir && local.preview ? local.preview : item.preview ?? local.preview,
|
|
92
|
+
...(item.source === 'installed' ? { source: item.source } : {}) };
|
|
87
93
|
});
|
|
88
94
|
const localOnlyKeys = new Set(localItems.keys());
|
|
89
|
-
merged.push(...[...localItems.values()].filter(Boolean));
|
|
95
|
+
merged.push(...[...localItems.values()].filter(Boolean).map(({ metaOk, ...local }) => local));
|
|
90
96
|
const items = merged.map((item) => {
|
|
91
97
|
const key = `${item.category}/${item.id}`;
|
|
92
98
|
const tier = localOnlyKeys.has(key) && !Object.hasOwn(item, 'tier')
|
|
@@ -185,3 +185,19 @@ test('cached Lab items retain remote preview and file descriptors', async t => {
|
|
|
185
185
|
assert.deepEqual(item.files, remote.files);
|
|
186
186
|
assert.ok(item.libraryDir);
|
|
187
187
|
});
|
|
188
|
+
|
|
189
|
+
test('downloaded catalog audio without meta.json keeps the catalog tags and title (sfx stays out of BGM)', async t => {
|
|
190
|
+
const { env, catalog, catalogPath } = fixture(t);
|
|
191
|
+
catalog.items.push({ id: 'sfx-x', category: 'audio', title: 'ベルの効果音', tags: ['bell', 'sfx'],
|
|
192
|
+
license: { spdx: 'CC0-1.0' }, version: 1, tier: 'free', files: [{ name: 'sfx-x.mp3', key: 'audio/sfx-x/v1/sfx-x.mp3', bytes: 5 }] });
|
|
193
|
+
writeFileSync(catalogPath, `${JSON.stringify(catalog, null, 2)}\n`);
|
|
194
|
+
// AKARI Sounds の取り寄せは音源ファイルだけで meta.json を持たない。
|
|
195
|
+
const dir = path.join(env.AKARI_LIBRARY_ROOT, 'audio', 'sfx-x');
|
|
196
|
+
mkdirSync(dir, { recursive: true });
|
|
197
|
+
writeFileSync(path.join(dir, 'sfx-x.mp3'), 'sound');
|
|
198
|
+
const item = (await composeState({ env })).items.find(row => row.id === 'sfx-x');
|
|
199
|
+
assert.equal(item.state, 'cached');
|
|
200
|
+
assert.ok(item.tags.includes('sfx'));
|
|
201
|
+
assert.equal(item.title, 'ベルの効果音');
|
|
202
|
+
assert.equal(Object.hasOwn(item, 'metaOk'), false);
|
|
203
|
+
});
|
|
@@ -359,7 +359,17 @@ export function validateCaptions(captions, edit, analysis, findings, paths, cuts
|
|
|
359
359
|
if (caption.time_domain !== "output") {
|
|
360
360
|
const kept = keptOverlap(caption.start, caption.end, edit?.cuts, caption.src);
|
|
361
361
|
const ratio = kept / (caption.end - caption.start);
|
|
362
|
-
if (
|
|
362
|
+
if (kept <= EPSILON) {
|
|
363
|
+
// 話した区間ごとカットで消した字幕は出ないのが正しい結果。エラーにすると GUI で
|
|
364
|
+
// カットするたびに誤報になるため記録だけ残す(オーナー裁定 2026-10-12)。
|
|
365
|
+
addFinding(findings, {
|
|
366
|
+
severity: "info",
|
|
367
|
+
check: "captions.cut-removed",
|
|
368
|
+
message: "the caption is entirely inside removed sections and will not be shown",
|
|
369
|
+
path: itemPath,
|
|
370
|
+
range: { start: caption.start, end: caption.end },
|
|
371
|
+
});
|
|
372
|
+
} else if (ratio < 0.5 - EPSILON) {
|
|
363
373
|
addFinding(findings, {
|
|
364
374
|
severity: "error",
|
|
365
375
|
check: "captions.cut-visibility",
|
|
@@ -22,6 +22,12 @@ export interface EditLintFinding {
|
|
|
22
22
|
message?: string;
|
|
23
23
|
check?: string;
|
|
24
24
|
path?: string;
|
|
25
|
+
range?: {
|
|
26
|
+
start?: number;
|
|
27
|
+
end?: number;
|
|
28
|
+
};
|
|
29
|
+
/** 今回の保存より前の lint にも同じ指摘があった(この保存が起こした問題ではない)。 */
|
|
30
|
+
preexisting?: boolean;
|
|
25
31
|
}
|
|
26
32
|
export interface EditLintGateResult {
|
|
27
33
|
pass: boolean;
|
|
@@ -95,6 +101,12 @@ export declare function assertNoCamelCaseTransitionOut(content: string): void;
|
|
|
95
101
|
* これは render-cut が読む PASS ゲートを常に最新に保つ、意図した保存後 lint の副作用。
|
|
96
102
|
*/
|
|
97
103
|
export declare function scheduleProjectLint(projectRoot: string, options?: DeferredLintOptions): void;
|
|
104
|
+
/**
|
|
105
|
+
* 保存前の lint にも有った指摘へ preexisting を付ける。字幕の挿入で配列の番号がずれても
|
|
106
|
+
* 同じ指摘と分かるよう、時刻範囲を持つ指摘は path の番号ではなく範囲で照合する。
|
|
107
|
+
* 基準が無い(初回・lint.json 不在)ときは従来どおり全部を今回の保存の指摘として返す。
|
|
108
|
+
*/
|
|
109
|
+
export declare function markPreexistingLintFindings(result: EditLintGateResult, baseline: readonly EditLintFinding[] | undefined): EditLintGateResult;
|
|
98
110
|
export declare function writeAtomic(destination: string, content: string): Promise<void>;
|
|
99
111
|
/**
|
|
100
112
|
* edit-lint をプロセス内実行する。inputOverrides は候補全文のメモリ差し替え。
|
|
@@ -27,6 +27,7 @@ exports.writeSavedByStamp = writeSavedByStamp;
|
|
|
27
27
|
exports.writeProjectFilesGuarded = writeProjectFilesGuarded;
|
|
28
28
|
exports.assertNoCamelCaseTransitionOut = assertNoCamelCaseTransitionOut;
|
|
29
29
|
exports.scheduleProjectLint = scheduleProjectLint;
|
|
30
|
+
exports.markPreexistingLintFindings = markPreexistingLintFindings;
|
|
30
31
|
exports.writeAtomic = writeAtomic;
|
|
31
32
|
exports.runEditLint = runEditLint;
|
|
32
33
|
exports.findEditLintModulePath = findEditLintModulePath;
|
|
@@ -72,6 +73,10 @@ const DEFAULT_LINT_DEBOUNCE_MS = 400;
|
|
|
72
73
|
const lintTimers = new Map();
|
|
73
74
|
const lintRevisions = new Map();
|
|
74
75
|
const lintRunChains = new Map();
|
|
76
|
+
/** プロジェクトごとの直近の保存後 lint の指摘。次の保存の「保存前から有った指摘」の基準にする。 */
|
|
77
|
+
const lastLintFindings = new Map();
|
|
78
|
+
/** debounce 窓の最初の書き込みの直前に取った基準。窓の lint が走るときに消費する。 */
|
|
79
|
+
const pendingLintBaselines = new Map();
|
|
75
80
|
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
76
81
|
/**
|
|
77
82
|
* 実ファイルは変更せず、候補全文だけを options.inputOverrides で差し替えて検証する。
|
|
@@ -227,6 +232,10 @@ async function writeProjectFilesGuarded(projectRoot, candidates, options = {}) {
|
|
|
227
232
|
if (typeof editCandidate === 'string') {
|
|
228
233
|
assertNoCamelCaseTransitionOut(editCandidate);
|
|
229
234
|
}
|
|
235
|
+
const lintKey = (0, path_1.resolve)(projectRoot);
|
|
236
|
+
if (!pendingLintBaselines.has(lintKey)) {
|
|
237
|
+
pendingLintBaselines.set(lintKey, lastLintFindings.get(lintKey) ?? await readPersistedLintFindings(projectRoot));
|
|
238
|
+
}
|
|
230
239
|
for (const [name, text] of Object.entries(candidates)) {
|
|
231
240
|
if (text === null) {
|
|
232
241
|
continue;
|
|
@@ -288,10 +297,18 @@ function scheduleProjectLint(projectRoot, options = {}) {
|
|
|
288
297
|
}
|
|
289
298
|
const timer = setTimeout(() => {
|
|
290
299
|
lintTimers.delete(key);
|
|
300
|
+
const baseline = pendingLintBaselines.get(key);
|
|
301
|
+
pendingLintBaselines.delete(key);
|
|
291
302
|
const previousRun = lintRunChains.get(key) ?? Promise.resolve({ pass: true, errors: [], findings: [] });
|
|
292
303
|
const currentRun = previousRun
|
|
293
304
|
.catch(() => ({ pass: true, errors: [], findings: [] }))
|
|
294
|
-
.then(() => (options.lintRunner ?? runEditLint)(key))
|
|
305
|
+
.then(() => (options.lintRunner ?? runEditLint)(key))
|
|
306
|
+
.then(result => {
|
|
307
|
+
if (!result.findings.some(finding => finding.check === 'edit-lint.execution')) {
|
|
308
|
+
lastLintFindings.set(key, result.findings);
|
|
309
|
+
}
|
|
310
|
+
return markPreexistingLintFindings(result, baseline);
|
|
311
|
+
});
|
|
295
312
|
lintRunChains.set(key, currentRun);
|
|
296
313
|
void currentRun.then(result => lintRevisions.get(key) === revision ? options.onLintResult?.(result) : undefined, error => {
|
|
297
314
|
console.warn('[edit-store] 保存後 edit-lint の実行に失敗しました(保存は維持します)。', error);
|
|
@@ -303,6 +320,45 @@ function scheduleProjectLint(projectRoot, options = {}) {
|
|
|
303
320
|
}, options.debounceMs ?? DEFAULT_LINT_DEBOUNCE_MS);
|
|
304
321
|
lintTimers.set(key, timer);
|
|
305
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* 保存前の lint にも有った指摘へ preexisting を付ける。字幕の挿入で配列の番号がずれても
|
|
325
|
+
* 同じ指摘と分かるよう、時刻範囲を持つ指摘は path の番号ではなく範囲で照合する。
|
|
326
|
+
* 基準が無い(初回・lint.json 不在)ときは従来どおり全部を今回の保存の指摘として返す。
|
|
327
|
+
*/
|
|
328
|
+
function markPreexistingLintFindings(result, baseline) {
|
|
329
|
+
if (!baseline?.length)
|
|
330
|
+
return result;
|
|
331
|
+
const remaining = new Map();
|
|
332
|
+
for (const finding of baseline) {
|
|
333
|
+
const key = lintFindingIdentity(finding);
|
|
334
|
+
remaining.set(key, (remaining.get(key) ?? 0) + 1);
|
|
335
|
+
}
|
|
336
|
+
const findings = result.findings.map(finding => {
|
|
337
|
+
const key = lintFindingIdentity(finding);
|
|
338
|
+
const count = remaining.get(key) ?? 0;
|
|
339
|
+
if (count === 0)
|
|
340
|
+
return finding;
|
|
341
|
+
remaining.set(key, count - 1);
|
|
342
|
+
return { ...finding, preexisting: true };
|
|
343
|
+
});
|
|
344
|
+
return { ...result, findings };
|
|
345
|
+
}
|
|
346
|
+
function lintFindingIdentity(finding) {
|
|
347
|
+
const file = finding.path?.split('#', 1)[0] ?? '';
|
|
348
|
+
const range = finding.range;
|
|
349
|
+
const where = range && Number.isFinite(range.start) && Number.isFinite(range.end)
|
|
350
|
+
? `${file}@${range.start}-${range.end}` : finding.path ?? '';
|
|
351
|
+
return JSON.stringify([finding.severity ?? '', finding.check ?? '', finding.message ?? '', where]);
|
|
352
|
+
}
|
|
353
|
+
async function readPersistedLintFindings(projectRoot) {
|
|
354
|
+
try {
|
|
355
|
+
const parsed = JSON.parse(await fs_1.promises.readFile((0, path_1.join)(projectRoot, '.akari', 'lint.json'), 'utf8'));
|
|
356
|
+
return Array.isArray(parsed.findings) ? parsed.findings : undefined;
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
306
362
|
// 同じ宛先への書き込みは 1 本ずつ直列化する。
|
|
307
363
|
// 一時ファイル名が PID だけだった頃は、同一プロセス内で書き込みが 2 本重なると同じ
|
|
308
364
|
// 一時ファイルを奪い合い、短い方が truncate した後に長い方の書き込みがその先へ着地して
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"bundle:frame-engine": "node ../../scripts/gen-frame-engine-bundles.mjs",
|
|
9
9
|
"check:frame-engine-drift": "node ../../scripts/gen-frame-engine-bundles.mjs --check",
|
|
10
10
|
"assert-zero-readback": "node scripts/assert-zero-readback.mjs",
|
|
11
|
-
"test": "node --test test/*.test.mjs",
|
|
11
|
+
"test": "node --import ../../scripts/ci/default-test-language.mjs --test test/*.test.mjs",
|
|
12
12
|
"ci:required": "npm test && npm run assert-zero-readback"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"lint.check.cuts.transitionOutNonAdjacent": "There is a gap before the clip after this transition. Close the gap or remove the transition.",
|
|
4
4
|
"lint.check.cuts.transitionOutZeroOverlap": "This transition has no spare source footage to overlap, so it has no effect. Adjust the clip trim or remove the transition.",
|
|
5
5
|
"lint.check.cuts.transitionOutLayerEvacuated": "This clip is routed through PiP, so its transition will not be exported. Resolve the overlap or remove the transition.",
|
|
6
|
+
"lint.check.captions.cutVisibility": "More than half of this caption's display time falls in a cut section, so it barely appears. Move its start and end into the kept section, or delete it.",
|
|
6
7
|
"lint.check.captions.overlap": "Caption display times overlap. Move the caption start or end points on the timeline.",
|
|
7
8
|
"lint.check.captions.outputDomainExceedsDuration": "An output-timed caption extends beyond the video duration. It will be clipped at the end during export.",
|
|
8
9
|
"lint.check.cuts.trackOverlap": "Clips overlap on the same video track. Move them apart.",
|
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
"partner.partnerPanel.extensionUpdater.extensionNotInstalledStatus": "Extension is not installed",
|
|
76
76
|
"partner.partnerPanel.extensionUpdater.extensionUpdateAvailableText": "An extension update is available",
|
|
77
77
|
"partner.partnerPanel.extensionUpdater.extensionUpDateText": "Extension is up to date",
|
|
78
|
+
"partner.partnerPanel.cliUpdate.availableText": "A new version of {value1} is available: {value2} (current {value3})",
|
|
79
|
+
"partner.partnerPanel.cliUpdate.openSettingsAction": "Update in Settings",
|
|
78
80
|
"partner.partnerPanel.extensionUpdater.laterLabel": "Later",
|
|
79
81
|
"partner.partnerPanel.extensionUpdater.newVersionAvailableText": "A new version of {value1} is available: {value2}",
|
|
80
82
|
"partner.partnerPanel.extensionUpdater.reloadAction": "Reload Now",
|
|
@@ -212,6 +214,7 @@
|
|
|
212
214
|
"partner.partnerSetup.bootstrap.fetchingCodexReleaseInformationText": "Fetching Codex release information: {value1}",
|
|
213
215
|
"partner.partnerSetup.bootstrap.foundExistingClaudeText": "Found existing claude: {value1}",
|
|
214
216
|
"partner.partnerSetup.bootstrap.foundExistingCodexText": "Found existing codex: {value1}",
|
|
217
|
+
"partner.partnerSetup.bootstrap.managedCodexPackageIncompleteText": "The codex package installed by AKARI is incomplete; reinstalling: {value1}",
|
|
215
218
|
"partner.partnerSetup.bootstrap.foundExistingStatus": "Found existing {value1}: {value2}",
|
|
216
219
|
"partner.partnerSetup.bootstrap.foundExistingVersionStatus": "Found existing {value1} {value2}: {value3}",
|
|
217
220
|
"partner.partnerSetup.bootstrap.foundLabel": "Found {value1} {value2}: {value3}",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
+
"preview.emphasisWords.validation.expectedArrayWarning": "[akari-preview] Ignored emphasis_words because it is not an array",
|
|
3
|
+
"preview.emphasisWords.validation.invalidItemWarning": "[akari-preview] Ignored invalid emphasis_words[{index}]",
|
|
2
4
|
"preview.audioMeter.command.openAudioMeterAction": "Open audio meter",
|
|
3
5
|
"preview.audioMeter.display.baseAudioUnavailableLegacyPreviewError": "Base audio is unavailable in legacy preview",
|
|
4
6
|
"preview.audioMeter.display.clearClippingIndicatorAction": "Clear clipping indicator",
|
|
@@ -465,6 +465,17 @@
|
|
|
465
465
|
"surfaces.settings.partnerChecking.label": "Checking…",
|
|
466
466
|
"surfaces.settings.partnerInstalled.status": "Installed",
|
|
467
467
|
"surfaces.settings.partnerNotInstalled.status": "Not installed",
|
|
468
|
+
"surfaces.settings.partnerNow.label": "Now {version}",
|
|
469
|
+
"surfaces.settings.partnerLatest.label": "Latest {version}",
|
|
470
|
+
"surfaces.settings.partnerUpdate.button": "Update",
|
|
471
|
+
"surfaces.settings.partnerUpdating.status": "Updating…",
|
|
472
|
+
"surfaces.settings.partnerSelfUpdating.label": "Updates itself automatically",
|
|
473
|
+
"surfaces.settings.partnerExternalUpdate.message": "A newer version is available. This copy was installed outside AKARI (Homebrew, npm, etc.), so update it the same way you installed it.",
|
|
474
|
+
"surfaces.settings.partnerUpdated.message": "Updated ({version})",
|
|
475
|
+
"surfaces.settings.partnerUpdateFailed.message": "Could not update: {reason}",
|
|
476
|
+
"surfaces.settings.partnerUpdateUnsupported.message": "AKARI cannot update this CLI.",
|
|
477
|
+
"surfaces.settings.partnerUpdateRunning.message": "An update is already running.",
|
|
478
|
+
"surfaces.settings.partnerUpdateBusy.message": "It is in use and cannot be replaced. Close the partner panel and try again.",
|
|
468
479
|
"surfaces.settings.partnerInsert.label": "Installation",
|
|
469
480
|
"surfaces.settings.partnerStart.label": "Start",
|
|
470
481
|
"surfaces.settings.partnerClaudeCodeExtension.label": "Claude Code extension",
|
|
@@ -1688,6 +1699,10 @@
|
|
|
1688
1699
|
"surfaces.onboarding.coachExportStandard.title": "Keep Standard selected, then Export",
|
|
1689
1700
|
"surfaces.onboarding.coachChangeImageQualitySaveDestination.description": "<p>Change quality and destination here if needed.</p>",
|
|
1690
1701
|
"surfaces.onboarding.coachExporting.title": "Exporting…",
|
|
1702
|
+
"surfaces.onboarding.coachExportFailed.title": "Export failed",
|
|
1703
|
+
"surfaces.onboarding.coachExportFailed.description": "<p>Check the settings and try exporting again, or skip this step for now.</p>",
|
|
1704
|
+
"surfaces.onboarding.coachExportRetry.label": "Try exporting again",
|
|
1705
|
+
"surfaces.onboarding.coachExportSkip.label": "Skip export and continue",
|
|
1691
1706
|
"surfaces.onboarding.coachProgressDisplayedBottomRightOkayContinueWorking.description": "<p>Progress appears in the bottom right. You can keep working.</p>",
|
|
1692
1707
|
"surfaces.onboarding.coachExportComplete.title": "Export complete",
|
|
1693
1708
|
"surfaces.onboarding.coachCreatedFilesListedCompletedAlsoExportAsking.description": "<p>Exported files appear under “Completed.”</p><p>You can also ask partner to export; it shows the result and asks for confirmation first.</p>",
|
|
@@ -1714,6 +1729,7 @@
|
|
|
1714
1729
|
"surfaces.onboarding.coachMarkupBack.label": "<button class=\"ao-back\" data-ao=\"back\">← Back</button>",
|
|
1715
1730
|
"surfaces.onboarding.prepareDownloadSampleAssetCheckInternetConnection.message": "Could not download the sample asset. Check your internet connection.",
|
|
1716
1731
|
"surfaces.onboarding.prepareSampleAssetCheckDestinationFreeSpace.message": "Could not prepare the sample asset. Check the destination and free space.",
|
|
1732
|
+
"surfaces.onboarding.preparePartnerStopFailed.message": "Could not stop the active partner, so the project was not switched. Stop the partner's work and try again.",
|
|
1717
1733
|
"surfaces.onboarding.pressAssetSeeContents.title": "Select an Asset to preview it",
|
|
1718
1734
|
"surfaces.onboarding.tryClickingIncludedVideo.description": "<p>Select the imported video.</p>",
|
|
1719
1735
|
"surfaces.onboarding.clickCaptionFixSpot.title": "Select a Caption to edit it in place",
|
|
@@ -1735,7 +1751,7 @@
|
|
|
1735
1751
|
"surfaces.onboarding.askConnectionCopyEvenConnectedAiSeeFlowExample.message": "You can watch the example without connecting an AI partner.",
|
|
1736
1752
|
"surfaces.onboarding.askConnectionCopyConnectAiLaterList.message": "You can connect your AI partner from this list later.",
|
|
1737
1753
|
"surfaces.onboarding.preparing.label": "Preparing...",
|
|
1738
|
-
"surfaces.onboarding.inviteMarkupLikeMakeOneTogetherAddTitlesCaptions.message": "<h2>Make your first video together</h2><p>Add a title and captions to the prepared video, then export. It takes about five minutes, and you can stop anytime.</p>{error}<div class=\"ao-actions\"><button class=\"primary\" data-ao=\"start\">{action}</button></div><div class=\"ao-secondary\"><button data-ao=\"later\">Start on my own later</button></div>",
|
|
1754
|
+
"surfaces.onboarding.inviteMarkupLikeMakeOneTogetherAddTitlesCaptions.message": "<h2>Make your first video together</h2><p>Add a title and captions to the prepared video, then export. It takes about five minutes, and you can stop anytime.</p>{error}{switchNotice}<div class=\"ao-actions\"><button class=\"primary\" data-ao=\"start\">{action}</button></div><div class=\"ao-secondary\"><button data-ao=\"later\">Start on my own later</button></div>",
|
|
1739
1755
|
"surfaces.home.homeDialogChannelName.label": "Channel name",
|
|
1740
1756
|
"surfaces.home.welcomeCardOpenFolderAdvanced.button": "Open folder… (advanced)",
|
|
1741
1757
|
"surfaces.home.welcomeCardOpenProjectLauncher.button": "Open project launcher",
|
|
@@ -1927,6 +1943,8 @@
|
|
|
1927
1943
|
"surfaces.tools.detectSpeechAnalyzerCheckSpeechanalyzerTerms.label": "Check the SpeechAnalyzer terms of use",
|
|
1928
1944
|
"surfaces.tools.defaultOpenPathOpenCommandFailed.message": "Could not run the open command.",
|
|
1929
1945
|
"surfaces.onboarding.inviteMarkupOnceAgain.label": "Try again",
|
|
1946
|
+
"surfaces.onboarding.inviteMarkupAgreeAndStart.label": "Agree and start",
|
|
1947
|
+
"surfaces.onboarding.inviteMarkupPartnerSwitchWarning.message": "The guide will switch from your current project, ‘{current}’, to the guide project (the partner's current work will stop).",
|
|
1930
1948
|
"surfaces.onboarding.inviteMarkupTry.label": "Try it",
|
|
1931
1949
|
"surfaces.channelDesign.genre.title": "What do you film?",
|
|
1932
1950
|
"surfaces.channelDesign.genre.hint": "Choose your main topic. You can change it later.",
|
|
@@ -2222,5 +2240,9 @@
|
|
|
2222
2240
|
"surfaces.tools.voice.name.label": "Speaker separation (voice engine)",
|
|
2223
2241
|
"surfaces.tools.voice.provider.label": "k2-fsa (sherpa-onnx)",
|
|
2224
2242
|
"surfaces.tools.voice.purpose.message": "Separates speakers in footage and suggests who spoke. Voices are processed only on this computer.",
|
|
2225
|
-
"surfaces.tools.voice.size.label": "About 160 MB"
|
|
2243
|
+
"surfaces.tools.voice.size.label": "About 160 MB",
|
|
2244
|
+
"surfaces.projects.contextMenuOpenProject.action": "Open project",
|
|
2245
|
+
"surfaces.projects.contextMenuCopyPath.action": "Copy path",
|
|
2246
|
+
"surfaces.projects.pathCopied.message": "Copied the project path.",
|
|
2247
|
+
"surfaces.projects.couldNotCopyPath.message": "Could not copy the path."
|
|
2226
2248
|
}
|
|
@@ -241,5 +241,22 @@
|
|
|
241
241
|
"tools.voice.engine.error.sampleOutsideChannel": "Voice samples outside the channel cannot be used",
|
|
242
242
|
"tools.voice.engine.error.setupRequired": "Speaker separation needs setup (run akari tools install voice or use Create Captions in the app).",
|
|
243
243
|
"tools.voice.engine.error.sidecarMissing": "Voice sidecar file is missing",
|
|
244
|
-
"tools.voice.engine.notice.countFallback": "Could not separate into {requested} speakers; using the automatically detected {used} speakers."
|
|
244
|
+
"tools.voice.engine.notice.countFallback": "Could not separate into {requested} speakers; using the automatically detected {used} speakers.",
|
|
245
|
+
"tools.transcribe.error.whisperFallback.speechFailed": "SpeechAnalyzer could not transcribe this file, and the whisper.cpp fallback is unavailable.",
|
|
246
|
+
"tools.transcribe.error.whisperFallback.localeUnsupported": "SpeechAnalyzer does not support {lang}, and the whisper.cpp fallback is unavailable.",
|
|
247
|
+
"tools.transcribe.error.whisperFallback.noModel": "No whisper.cpp ggml model was found.\nSearched:\n{locations}\nFix (any one):\n - akari tools install whisper (downloads the model into ~/.akari/tools/models/)\n - Install it from Settings -> Tools -> Whisper\n - Put a ggml-*.bin in ~/.akari/tools/models/, or point the WHISPER_CPP_MODEL environment variable at the file",
|
|
248
|
+
"tools.transcribe.error.whisperFallback.noBin": "whisper-cli was not found. You need the one bundled with the app, or one installed with brew install whisper-cpp (also installable from Settings -> Tools -> Whisper). Get the model with akari tools install whisper.",
|
|
249
|
+
"tools.transcribe.error.whisperFallback.reason": "SpeechAnalyzer error: {reason}",
|
|
250
|
+
"tools.media.help.in": " --in <time> start of the range to transcribe (seconds or mm:ss)",
|
|
251
|
+
"tools.media.help.out": " --out <time> end of the range to transcribe (seconds or mm:ss)",
|
|
252
|
+
"tools.media.help.backend": " --backend <speech-analyzer|whisper-cpp|cloud:<id>> pick the engine (default: auto)",
|
|
253
|
+
"tools.media.help.lang": " --lang <code> language code, e.g. ja, en (default: auto)",
|
|
254
|
+
"tools.whisper.cli.alreadyReady": "The Whisper model is already installed: {path}",
|
|
255
|
+
"tools.whisper.cli.installed": "Downloaded the Whisper model: {path}",
|
|
256
|
+
"tools.whisper.cli.verifyFailed": "The model failed verification (the download may be corrupted). Please try again.",
|
|
257
|
+
"tools.whisper.cli.downloadFailed": "Could not download the model ({error}). Please try again.",
|
|
258
|
+
"tools.whisper.cli.downloading": "Downloading the model",
|
|
259
|
+
"tools.whisper.cli.statusReady": "ready: {path}",
|
|
260
|
+
"tools.whisper.cli.statusMissing": "missing: no ggml model found. Run akari tools install whisper",
|
|
261
|
+
"tools.whisper.cli.usage": "Usage: akari tools install voice|whisper [--json] / akari tools status voice|whisper [--json]"
|
|
245
262
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"lint.check.cuts.transitionOutNonAdjacent": "トランジションの次のクリップとの間にすき間があります。すき間を詰めるか、トランジションを削除してください。",
|
|
4
4
|
"lint.check.cuts.transitionOutZeroOverlap": "トランジションを宣言していますが、のりしろにできる素材の余りがないため効きません。素材のトリムを調整するか、トランジションを削除してください。",
|
|
5
5
|
"lint.check.cuts.transitionOutLayerEvacuated": "このクリップは PiP 経路へ退避されているため、宣言したトランジションは書き出されません。重なりを解消するか、トランジションを削除してください。",
|
|
6
|
+
"lint.check.captions.cutVisibility": "字幕の表示時間の半分以上がカットした区間にかかっていて、ほとんど映りません。字幕の開始・終了を残した区間に合わせるか、字幕を消してください。",
|
|
6
7
|
"lint.check.captions.overlap": "字幕の表示時間が重なっています。タイムラインで字幕の開始・終了位置をずらしてください。",
|
|
7
8
|
"lint.check.captions.outputDomainExceedsDuration": "出力時間の字幕が動画総尺を超えています。書き出しでは動画終端までにクランプして表示されます。",
|
|
8
9
|
"lint.check.cuts.trackOverlap": "同じ映像トラック上でクリップが重なっています。クリップを重ならない位置へ移動してください。",
|
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
"partner.partnerPanel.extensionUpdater.extensionNotInstalledStatus": "拡張は未インストールです",
|
|
76
76
|
"partner.partnerPanel.extensionUpdater.extensionUpdateAvailableText": "拡張の更新があります",
|
|
77
77
|
"partner.partnerPanel.extensionUpdater.extensionUpDateText": "拡張は最新です",
|
|
78
|
+
"partner.partnerPanel.cliUpdate.availableText": "{value1} の新しい版 {value2} があります(今は {value3})",
|
|
79
|
+
"partner.partnerPanel.cliUpdate.openSettingsAction": "設定でアップデート",
|
|
78
80
|
"partner.partnerPanel.extensionUpdater.laterLabel": "後で",
|
|
79
81
|
"partner.partnerPanel.extensionUpdater.newVersionAvailableText": "{value1} の新しい版 {value2} があります",
|
|
80
82
|
"partner.partnerPanel.extensionUpdater.reloadAction": "今すぐ再読み込み",
|
|
@@ -212,6 +214,7 @@
|
|
|
212
214
|
"partner.partnerSetup.bootstrap.fetchingCodexReleaseInformationText": "Codex リリース情報を取得しています: {value1}",
|
|
213
215
|
"partner.partnerSetup.bootstrap.foundExistingClaudeText": "既存の claude を検出: {value1}",
|
|
214
216
|
"partner.partnerSetup.bootstrap.foundExistingCodexText": "既存の codex を検出: {value1}",
|
|
217
|
+
"partner.partnerSetup.bootstrap.managedCodexPackageIncompleteText": "AKARI が入れた codex のパッケージが欠けているため入れ直します: {value1}",
|
|
215
218
|
"partner.partnerSetup.bootstrap.foundExistingStatus": "既存の {value1} を検出: {value2}",
|
|
216
219
|
"partner.partnerSetup.bootstrap.foundExistingVersionStatus": "既存の {value1} {value2} を検出: {value3}",
|
|
217
220
|
"partner.partnerSetup.bootstrap.foundLabel": "{value1} {value2} を検出: {value3}",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
+
"preview.emphasisWords.validation.expectedArrayWarning": "[akari-preview] emphasis_words を無視しました(配列ではありません)",
|
|
3
|
+
"preview.emphasisWords.validation.invalidItemWarning": "[akari-preview] emphasis_words[{index}] を無視しました(要素が不正です)",
|
|
2
4
|
"preview.audioMeter.command.openAudioMeterAction": "音声メーターを開く",
|
|
3
5
|
"preview.audioMeter.display.baseAudioUnavailableLegacyPreviewError": "legacy プレビューでは土台の音声は含まれません",
|
|
4
6
|
"preview.audioMeter.display.clearClippingIndicatorAction": "クリップ表示を解除",
|