{"id":2344,"date":"2026-08-04T09:33:53","date_gmt":"2026-08-04T00:33:53","guid":{"rendered":"https:\/\/rfsec.ddns.net\/db\/?p=2344"},"modified":"2026-08-06T08:50:05","modified_gmt":"2026-08-05T23:50:05","slug":"%e3%83%ad%e3%83%bc%e3%82%ab%e3%83%abllmollama%e3%81%a8voicevox%e3%81%a7%e4%bd%9c%e3%82%8b%e3%80%81%e8%8b%b1%e6%97%a5%e7%bf%bb%e8%a8%b3%ef%bc%86%e8%aa%ad%e3%81%bf%e4%b8%8a%e3%81%92%e3%83%87%e3%82%b9","status":"publish","type":"post","link":"https:\/\/rfsec.ddns.net\/db\/?p=2344","title":{"rendered":"\u30ed\u30fc\u30ab\u30ebLLM(Ollama)\u3068VOICEVOX\u3067\u4f5c\u308b\u3001\u82f1\u65e5\u7ffb\u8a33\uff06\u8aad\u307f\u4e0a\u3052\u30c7\u30b9\u30af\u30c8\u30c3\u30d7\u30a2\u30d7\u30ea"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"885\" src=\"https:\/\/rfsec.ddns.net\/db\/wp-content\/uploads\/2026\/08\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2026-08-06-8.45.35-1024x885.png\" alt=\"\" class=\"wp-image-2353\" srcset=\"https:\/\/rfsec.ddns.net\/db\/wp-content\/uploads\/2026\/08\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2026-08-06-8.45.35-1024x885.png 1024w, https:\/\/rfsec.ddns.net\/db\/wp-content\/uploads\/2026\/08\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2026-08-06-8.45.35-300x259.png 300w, https:\/\/rfsec.ddns.net\/db\/wp-content\/uploads\/2026\/08\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2026-08-06-8.45.35-768x664.png 768w, https:\/\/rfsec.ddns.net\/db\/wp-content\/uploads\/2026\/08\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2026-08-06-8.45.35-624x539.png 624w, https:\/\/rfsec.ddns.net\/db\/wp-content\/uploads\/2026\/08\/\u30b9\u30af\u30ea\u30fc\u30f3\u30b7\u30e7\u30c3\u30c8-2026-08-06-8.45.35.png 1504w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Mac\u3067\u52d5\u304f\u30ed\u30fc\u30ab\u30ebLLM\u74b0\u5883\u3092\u6d3b\u304b\u3057\u3066\u3001\u300c\u82f1\u8a9e\u30c6\u30ad\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u3093\u3067\u65e5\u672c\u8a9e\u306b\u7ffb\u8a33\u3057\u3001\u7d50\u679c\u3092\u8868\u793a\u3057\u306a\u304c\u3089\u97f3\u58f0\u3067\u8aad\u307f\u4e0a\u3052\u308b\u300d\u30c4\u30fc\u30eb\u3092\u4f5c\u3063\u3066\u307f\u307e\u3057\u305f\u3002\u3055\u3089\u306b\u4f1a\u8a71\u3092\u91cd\u306d\u308b\u4e2d\u3067\u3001Ollama\u306e\u30e2\u30c7\u30eb\u4e00\u89a7\u304b\u3089\u306e\u9078\u629e\u3001VOICEVOX\u306e\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u624b\u9806\u78ba\u8a8d\u3001BlackHole\u7d4c\u7531\u306e\u97f3\u58f0\u5165\u529b\u3001\u9332\u97f3\u4e2d\u306e\u9010\u6b21\u6587\u5b57\u8d77\u3053\u3057\u8868\u793a\u307e\u3067\u3001\u6a5f\u80fd\u3092\u6bb5\u968e\u7684\u306b\u62e1\u5f35\u3057\u3066\u3044\u304d\u307e\u3057\u305f\u3002\u3053\u306e\u8a18\u4e8b\u3067\u306f\u305d\u306e\u904e\u7a0b\u3068\u6700\u7d42\u7684\u306a\u30bd\u30fc\u30b9\u30b3\u30fc\u30c9\u5168\u4f53\u3092\u307e\u3068\u3081\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u524d\u63d0\u3068\u306a\u308b\u74b0\u5883<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Mac mini (Apple Silicon)<\/li>\n\n\n\n<li>\u30ed\u30fc\u30ab\u30ebLLM\u5b9f\u884c\u74b0\u5883\u3068\u3057\u3066 <a href=\"https:\/\/ollama.com\/\">Ollama<\/a>\uff0830B\u7a0b\u5ea6\u307e\u3067\u306e\u30e2\u30c7\u30eb\u3092\u60f3\u5b9a\uff09<\/li>\n\n\n\n<li>\u65e5\u672c\u8a9e\u97f3\u58f0\u5408\u6210\u30a8\u30f3\u30b8\u30f3\u3068\u3057\u3066 <a href=\"https:\/\/voicevox.hiroshiba.jp\/\">VOICEVOX<\/a><\/li>\n\n\n\n<li>\u97f3\u58f0\u5165\u529b\u7528\u306e\u4eee\u60f3\u30aa\u30fc\u30c7\u30a3\u30aa\u30c7\u30d0\u30a4\u30b9\u3068\u3057\u3066 <a href=\"https:\/\/existential.audio\/blackhole\/\">BlackHole<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">1. \u307e\u305a\u5168\u4f53\u69cb\u6210\u3092\u6574\u7406\u3059\u308b<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u6700\u521d\u306e\u76f8\u8ac7\u306f\u300cMac\u4e0a\u306e\u30ed\u30fc\u30ab\u30ebLLM\u3092\u4f7f\u3063\u3066\u3001\u82f1\u8a9e\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u3092\u65e5\u672c\u8a9e\u306b\u7ffb\u8a33\u3057\u3001\u7d50\u679c\u3092\u8868\u793a\u3057\u3064\u3064\u97f3\u58f0\u3067\u8aad\u307f\u4e0a\u3052\u308b\u306b\u306f\u4f55\u304c\u5fc5\u8981\u304b\u300d\u3068\u3044\u3046\u3082\u306e\u3067\u3057\u305f\u3002\u6574\u7406\u3059\u308b\u3068\u3001\u5fc5\u8981\u306a\u8981\u7d20\u306f\u6b21\u306e4\u3064\u306b\u5206\u89e3\u3067\u304d\u307e\u3059\u3002<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u7ffb\u8a33\u30a8\u30f3\u30b8\u30f3<\/strong>: Ollama + \u591a\u8a00\u8a9e\u5bfe\u5fdc\u30e2\u30c7\u30eb\uff08Qwen2.5-32B-Instruct\u3001Gemma2-27B-it\u306a\u3069\uff09<\/li>\n\n\n\n<li><strong>\u30d5\u30a1\u30a4\u30eb\u8aad\u307f\u8fbc\u307f\uff06\u30d1\u30a4\u30d7\u30e9\u30a4\u30f3\u5236\u5fa1<\/strong>: Python\u3067\u30c6\u30ad\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307f\u3001Ollama API\u306b\u6295\u3052\u308b<\/li>\n\n\n\n<li><strong>\u7d50\u679c\u8868\u793a<\/strong>: \u30bf\u30fc\u30df\u30ca\u30eb\u3067\u3082\u826f\u3044\u304c\u3001GUI\u306a\u3089Streamlit\/Gradio\u3084PyQt6\u304c\u5019\u88dc<\/li>\n\n\n\n<li><strong>\u97f3\u58f0\u8aad\u307f\u4e0a\u3052<\/strong>: macOS\u6a19\u6e96\u306e<code>say<\/code>\u30b3\u30de\u30f3\u30c9\u3001\u307e\u305f\u306f\u3088\u308a\u9ad8\u54c1\u8cea\u306a\u65e5\u672c\u8a9e\u7279\u5316TTS\u3067\u3042\u308bVOICEVOX<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u5168\u4f53\u306e\u30c7\u30fc\u30bf\u30d5\u30ed\u30fc\u306f\u6b21\u306e\u3088\u3046\u306b\u306a\u308a\u307e\u3059\u3002<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\n   \u2192 Python(\u30c1\u30e3\u30f3\u30af\u5206\u5272)\n   \u2192 Ollama API(\u7ffb\u8a33: Qwen2.5-32B\u7b49)\n   \u2192 \u8868\u793a(\u30bf\u30fc\u30df\u30ca\u30eb or GUI)\n   \u2192 VOICEVOX API(\u97f3\u58f0\u5408\u6210)\n   \u2192 afplay(\u518d\u751f)\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">2. PyQt6\u3067GUI\u30a2\u30d7\u30ea\u306e\u571f\u53f0\u3092\u4f5c\u308b<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u65b9\u5411\u6027\u304c\u56fa\u307e\u3063\u305f\u3068\u3053\u308d\u3067\u3001\u5b9f\u969b\u306bPyQt6\u30d9\u30fc\u30b9\u306eGUI\u30a2\u30d7\u30ea\u3068\u3057\u3066\u5b9f\u88c5\u3059\u308b\u3053\u3068\u306b\u3057\u307e\u3057\u305f\u3002\u8981\u4ef6\u306f\u6b21\u306e4\u70b9\u3067\u3059\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u30d5\u30a1\u30a4\u30eb\u9078\u629e\u30dc\u30bf\u30f3\uff0b\u8aad\u307f\u8fbc\u307f\u30dc\u30bf\u30f3<\/li>\n\n\n\n<li>\u7ffb\u8a33\u30dc\u30bf\u30f3<\/li>\n\n\n\n<li>\u7ffb\u8a33\u7d50\u679c\u306e\u30c6\u30ad\u30b9\u30c8\u8868\u793a<\/li>\n\n\n\n<li>\u8aad\u307f\u4e0a\u3052\u958b\u59cb\u30dc\u30bf\u30f3\uff08VOICEVOX\u5229\u7528\uff09<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">\u7ffb\u8a33\u306fOllama\u306e<code>\/api\/generate<\/code>\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u3092\u3001\u8aad\u307f\u4e0a\u3052\u306fVOICEVOX\u306e<code>\/audio_query<\/code>\u2192<code>\/synthesis<\/code>\u3068\u3044\u30462\u6bb5\u968e\u306eAPI\u3092\u53e9\u304d\u3001\u751f\u6210\u3057\u305fwav\u3092<code>afplay<\/code>\uff08macOS\u6a19\u6e96\u30b3\u30de\u30f3\u30c9\uff09\u3067\u518d\u751f\u3059\u308b\u69cb\u6210\u3067\u3059\u3002GUI\u3092\u30d5\u30ea\u30fc\u30ba\u3055\u305b\u306a\u3044\u3088\u3046\u3001\u7ffb\u8a33\u30fb\u97f3\u58f0\u5408\u6210\u306f\u3044\u305a\u308c\u3082<code>QThread<\/code>\u306e\u30ef\u30fc\u30ab\u30fc\u30b9\u30ec\u30c3\u30c9\u3067\u975e\u540c\u671f\u5b9f\u884c\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Ollama\u306e\u30e2\u30c7\u30eb\u3092<code>ollama list<\/code>\u76f8\u5f53\u3067\u9078\u3079\u308b\u3088\u3046\u306b\u3059\u308b<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u6700\u521d\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u306f\u30e2\u30c7\u30eb\u540d\u3092\u30b3\u30fc\u30c9\u5185\u306b\u56fa\u5b9a\u3057\u3066\u3044\u307e\u3057\u305f\u304c\u3001\u300c<code>ollama list<\/code>\u3067\u53d6\u5f97\u3057\u305f\u4e2d\u304b\u3089\u9078\u629e\u3067\u304d\u308b\u3088\u3046\u306b\u300d\u3068\u3044\u3046\u8981\u671b\u3092\u53d7\u3051\u3066\u3001Ollama\u306e<code>\/api\/tags<\/code>\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\uff08<code>ollama list<\/code>\u3068\u540c\u3058\u60c5\u5831\u6e90\uff09\u3092\u53e9\u304d\u3001\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08\u307f\u30e2\u30c7\u30eb\u3092\u30d7\u30eb\u30c0\u30a6\u30f3\u306b\u52d5\u7684\u8868\u793a\u3059\u308b\u6a5f\u80fd\u3092\u8ffd\u52a0\u3057\u307e\u3057\u305f\u3002\u300c\u4e00\u89a7\u66f4\u65b0\u300d\u30dc\u30bf\u30f3\u3067\u518d\u53d6\u5f97\u3082\u3067\u304d\u308b\u3088\u3046\u306b\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. VOICEVOX\u306e\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u624b\u9806<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">VOICEVOX\u306f\u516c\u5f0f\u30b5\u30a4\u30c8\u304b\u3089Mac\u7248\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3001<code>.dmg<\/code>\u304b\u3089Applications\u30d5\u30a9\u30eb\u30c0\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u307e\u3059\u3002Apple Silicon\u74b0\u5883\u3067\u306f\u521d\u56de\u8d77\u52d5\u6642\u306bRosetta\u306e\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u6c42\u3081\u3089\u308c\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002\u307e\u305f\u3001\u958b\u767a\u5143\u672a\u691c\u8a3c\u306e\u8b66\u544a\u304c\u51fa\u305f\u5834\u5408\u306f\u300c\u30b7\u30b9\u30c6\u30e0\u8a2d\u5b9a\u300d\u2192\u300c\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u3068\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u300d\u304b\u3089\u300c\u3053\u306e\u307e\u307e\u958b\u304f\u300d\u3092\u9078\u629e\u3059\u308b\u3053\u3068\u3067\u8d77\u52d5\u3067\u304d\u307e\u3059\u3002\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u5f8c\u306b\u30a2\u30d7\u30ea\u3092\u8d77\u52d5\u3057\u3066\u304a\u304f\u3068\u3001\u30d0\u30c3\u30af\u30b0\u30e9\u30a6\u30f3\u30c9\u3067API\u30a8\u30f3\u30b8\u30f3\uff08\u30c7\u30d5\u30a9\u30eb\u30c8\u30dd\u30fc\u30c850021\uff09\u304c\u7acb\u3061\u4e0a\u304c\u308a\u3001GUI\u30a2\u30d7\u30ea\u304b\u3089\u306e\u8aad\u307f\u4e0a\u3052\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u53d7\u3051\u4ed8\u3051\u3089\u308c\u308b\u72b6\u614b\u306b\u306a\u308a\u307e\u3059\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u8aad\u307f\u4e0a\u3052\u306b\u4f7f\u308f\u308c\u308b\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u306f\u3001GUI\u4e0a\u306e\u300c\u8a71\u8005\u300d\u30d7\u30eb\u30c0\u30a6\u30f3\u3067\u9078\u629e\u3055\u308c\u305f\u3082\u306e\u3067\u3059\u3002\u8d77\u52d5\u6642\u306bVOICEVOX\u306e<code>\/speakers<\/code>\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u304b\u3089\u5168\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u00d7\u30b9\u30bf\u30a4\u30eb\u306e\u4e00\u89a7\u3092\u53d6\u5f97\u3057\u3066\u8868\u793a\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. BlackHole\u7d4c\u7531\u306e\u97f3\u58f0\u5165\u529b\u6a5f\u80fd\u3092\u8ffd\u52a0\u3059\u308b<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u306e\u8aad\u307f\u8fbc\u307f\u306b\u52a0\u3048\u3066\u3001\u300cMac\u306eBlackHole\u7d4c\u7531\u306e\u97f3\u58f0\u5165\u529b\u304b\u3089\u30c6\u30ad\u30b9\u30c8\u3092\u751f\u6210\u3059\u308b\u6a5f\u80fd\u300d\u3092\u8ffd\u52a0\u3057\u307e\u3057\u305f\u3002\u69cb\u6210\u306f\u6b21\u306e\u901a\u308a\u3067\u3059\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>sounddevice<\/code>\u3067\u5165\u529b\u30c7\u30d0\u30a4\u30b9\u4e00\u89a7\u3092\u53d6\u5f97\u3057\u3001\u540d\u524d\u306b&#8221;BlackHole&#8221;\u3092\u542b\u3080\u30c7\u30d0\u30a4\u30b9\u3092\u81ea\u52d5\u9078\u629e<\/li>\n\n\n\n<li>\u9332\u97f3\u958b\u59cb\/\u505c\u6b62\u3092\u30c8\u30b0\u30eb\u30dc\u30bf\u30f3\u3067\u5236\u5fa1\u3057\u3001<code>sd.InputStream<\/code>\u306e\u30b3\u30fc\u30eb\u30d0\u30c3\u30af\u3067\u97f3\u58f0\u30d5\u30ec\u30fc\u30e0\u3092\u84c4\u7a4d<\/li>\n\n\n\n<li>\u9332\u97f3\u505c\u6b62\u5f8c\u3001\u30ed\u30fc\u30ab\u30eb\u306e<code>faster-whisper<\/code>\uff08<code>small<\/code>\u30e2\u30c7\u30eb\u3001int8\u91cf\u5b50\u5316\u3067CPU\u52d5\u4f5c\uff09\u3092\u4f7f\u3063\u3066\u6587\u5b57\u8d77\u3053\u3057<\/li>\n\n\n\n<li>\u6587\u5b57\u8d77\u3053\u3057\u7d50\u679c\u306f\u300c\u539f\u6587\u300d\u30c6\u30ad\u30b9\u30c8\u30dc\u30c3\u30af\u30b9\u306b\u53cd\u6620\u3055\u308c\u3001\u305d\u306e\u307e\u307e\u7ffb\u8a33\u30dc\u30bf\u30f3\u306b\u7e4b\u304c\u308b<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">\u3053\u308c\u306b\u3088\u308a\u3001\u30b7\u30b9\u30c6\u30e0\u97f3\u58f0\uff08\u52d5\u753b\u3084\u914d\u4fe1\u306e\u82f1\u8a9e\u97f3\u58f0\u306a\u3069\uff09\u3092BlackHole\u7d4c\u7531\u3067\u30ad\u30e3\u30d7\u30c1\u30e3\u3057\u3001\u30c6\u30ad\u30b9\u30c8\u5316\u3057\u3066\u304b\u3089\u7ffb\u8a33\u30fb\u8aad\u307f\u4e0a\u3052\u307e\u3067\u4e00\u6c17\u901a\u8cab\u3067\u884c\u3048\u308b\u3088\u3046\u306b\u306a\u308a\u307e\u3057\u305f\u3002\u3042\u308f\u305b\u3066\u3001\u539f\u6587\u30c6\u30ad\u30b9\u30c8\u30dc\u30c3\u30af\u30b9\u3092\u30d5\u30a1\u30a4\u30eb\u8aad\u307f\u8fbc\u307f\u5c02\u7528\u306e\u8aad\u307f\u53d6\u308a\u5c02\u7528\u304b\u3089\u3001\u6587\u5b57\u8d77\u3053\u3057\u7d50\u679c\u3092\u624b\u76f4\u3057\u3067\u304d\u308b\u7de8\u96c6\u53ef\u80fd\u306a\u4ed5\u69d8\u306b\u5909\u66f4\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. \u9332\u97f3\u4e2d\u306b\u30c6\u30ad\u30b9\u30c8\u3092\u9010\u6b21\u8868\u793a\u3059\u308b<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u6700\u5f8c\u306e\u6539\u5584\u306f\u300c\u9332\u97f3\u4e2d\u306b\u30c6\u30ad\u30b9\u30c8\u3092\u9010\u6b21\u8868\u793a\u3059\u308b\u300d\u3068\u3044\u3046\u3082\u306e\u3067\u3059\u3002faster-whisper\u306f\u771f\u306e\u30b9\u30c8\u30ea\u30fc\u30df\u30f3\u30b0\u8a8d\u8b58\u306b\u306f\u5bfe\u5fdc\u3057\u3066\u3044\u306a\u3044\u305f\u3081\u3001\u9332\u97f3\u30d0\u30c3\u30d5\u30a1\u3092\u4e00\u5b9a\u9593\u9694\uff08\u30c7\u30d5\u30a9\u30eb\u30c85\u79d2\uff09\u3067\u533a\u5207\u308a\u3001\u30c1\u30e3\u30f3\u30af\u3054\u3068\u306b\u500b\u5225\u306b\u6587\u5b57\u8d77\u3053\u3057\u3057\u3066\u7d50\u679c\u3092\u9806\u6b21\u8ffd\u8a18\u8868\u793a\u3059\u308b\u65b9\u5f0f\u3092\u63a1\u7528\u3057\u307e\u3057\u305f\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u9332\u97f3\u4e2d\u3001<code>PARTIAL_CHUNK_INTERVAL_SEC<\/code>\u79d2\u3054\u3068\u306b\u672a\u51e6\u7406\u306e\u97f3\u58f0\u30c1\u30e3\u30f3\u30af\u3092\u53d6\u308a\u51fa\u3057\u3066\u6587\u5b57\u8d77\u3053\u3057<\/li>\n\n\n\n<li>\u7d50\u679c\u3092\u84c4\u7a4d\u3057\u3001<code>partial_result<\/code>\u30b7\u30b0\u30ca\u30eb\u3067\u300c\u3053\u308c\u307e\u3067\u306e\u5168\u6587\u300d\u3092\u539f\u6587\u6b04\u306b\u53cd\u6620\uff08\u30ab\u30fc\u30bd\u30eb\u306f\u81ea\u52d5\u7684\u306b\u672b\u5c3e\u3078\uff09<\/li>\n\n\n\n<li>\u9332\u97f3\u505c\u6b62\u6642\u306b\u306f\u3001\u6b8b\u3063\u3066\u3044\u308b\u672b\u5c3e\u30c1\u30e3\u30f3\u30af\u3082\u51e6\u7406\u3057\u3066\u6700\u7d42\u30c6\u30ad\u30b9\u30c8\u3092\u78ba\u5b9a<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">\u3053\u306e\u65b9\u5f0f\u306b\u306f\u30c1\u30e3\u30f3\u30af\u5883\u754c\u3067\u5358\u8a9e\u304c\u5206\u5272\u3055\u308c\u308b\u3068\u8a8d\u8b58\u7cbe\u5ea6\u304c\u843d\u3061\u308b\u3068\u3044\u3046\u30c8\u30ec\u30fc\u30c9\u30aa\u30d5\u304c\u3042\u308a\u307e\u3059\u304c\u3001\u5f85\u3061\u6642\u9593\u306a\u304f\u8a8d\u8b58\u7d50\u679c\u3092\u78ba\u8a8d\u3067\u304d\u308b\u4f53\u611f\u306e\u826f\u3055\u3092\u512a\u5148\u3057\u307e\u3057\u305f\u3002\u9593\u9694\u3092\u77ed\u304f\u3059\u308c\u3070\u53cd\u5fdc\u306f\u65e9\u304f\u306a\u308b\u4e00\u65b9\u3001\u30e2\u30c7\u30eb\u306e\u63a8\u8ad6\u8ca0\u8377\u304c\u5897\u3048\u308b\u305f\u3081\u3001<code>WHISPER_MODEL_SIZE<\/code>\uff08tiny\/base\/small\/medium\/large-v3\uff09\u3068\u306e\u30d0\u30e9\u30f3\u30b9\u8abf\u6574\u304c\u5fc5\u8981\u3067\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u6700\u7d42\u7684\u306a\u6a5f\u80fd\u4e00\u89a7<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u82f1\u8a9e\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u306e\u9078\u629e\u30fb\u8aad\u307f\u8fbc\u307f<\/li>\n\n\n\n<li>BlackHole\u7d4c\u7531\u306e\u97f3\u58f0\u5165\u529b\u304b\u3089\u306e\u9010\u6b21\u6587\u5b57\u8d77\u3053\u3057\uff08faster-whisper\u4f7f\u7528\uff09<\/li>\n\n\n\n<li>Ollama\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08\u307f\u306e\u30e2\u30c7\u30eb\u4e00\u89a7\u304b\u3089\u306e\u52d5\u7684\u9078\u629e\u3001\u65e5\u672c\u8a9e\u3078\u306e\u7ffb\u8a33<\/li>\n\n\n\n<li>\u7ffb\u8a33\u7d50\u679c\u306e\u8868\u793a\u30fb\u7de8\u96c6<\/li>\n\n\n\n<li>VOICEVOX\u306e\u8a71\u8005\u4e00\u89a7\u304b\u3089\u306e\u9078\u629e\u3001\u97f3\u58f0\u8aad\u307f\u4e0a\u3052<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u5fc5\u8981\u306a\u4f9d\u5b58\u30d1\u30c3\u30b1\u30fc\u30b8<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install PyQt6 requests\npip install sounddevice numpy soundfile faster-whisper   # \u97f3\u58f0\u5165\u529b\u6a5f\u80fd\u7528(\u4efb\u610f)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\u4e8b\u524d\u306b\u4ee5\u4e0b\u3082\u6e96\u5099\u3057\u3066\u304a\u304f\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ollama\u8d77\u52d5\uff0b\u7ffb\u8a33\u7528\u30e2\u30c7\u30eb\u306epull\uff08\u4f8b: <code>ollama pull qwen2.5:32b<\/code>\uff09<\/li>\n\n\n\n<li>VOICEVOX ENGINE\u306e\u8d77\u52d5\uff08\u516c\u5f0fMac\u30a2\u30d7\u30ea\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u30dd\u30fc\u30c850021\uff09<\/li>\n\n\n\n<li>BlackHole (2ch) \u306e\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3068\u3001\u30b7\u30b9\u30c6\u30e0\u97f3\u58f0\u3092BlackHole\u7d4c\u7531\u3067\u30ad\u30e3\u30d7\u30c1\u30e3\u3067\u304d\u308bMulti-Output Device\u306e\u8a2d\u5b9a<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u6700\u7d42\u30bd\u30fc\u30b9\u30b3\u30fc\u30c9\u5168\u4f53<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\n\u82f1\u8a9e\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb \u2192 Ollama(\u30ed\u30fc\u30ab\u30ebLLM)\u3067\u65e5\u672c\u8a9e\u7ffb\u8a33 \u2192 VOICEVOX\u3067\u8aad\u307f\u4e0a\u3052\nPyQt6 GUI\u30a2\u30d7\u30ea\n\n\u524d\u63d0:\n  - Ollama \u304c\u30ed\u30fc\u30ab\u30eb\u3067\u8d77\u52d5\u3057\u3066\u304a\u308a\u3001\u7ffb\u8a33\u7528\u30e2\u30c7\u30eb\u304c1\u3064\u4ee5\u4e0a pull \u6e08\u307f\u3067\u3042\u308b\u3053\u3068\n      $ ollama pull qwen2.5:32b\n      $ ollama serve   (\u901a\u5e38\u306f\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6642\u306b\u5e38\u99d0\u8d77\u52d5\u3055\u308c\u308b)\n    \u8d77\u52d5\u4e2d\u306eOllama\u304c\u6301\u3064\u30e2\u30c7\u30eb\u4e00\u89a7 (`ollama list` \u76f8\u5f53) \u306fGUI\u4e0a\u306e\u30d7\u30eb\u30c0\u30a6\u30f3\u304b\u3089\u9078\u629e\u53ef\u80fd\n  - VOICEVOX ENGINE \u304c\u30ed\u30fc\u30ab\u30eb\u3067\u8d77\u52d5\u3057\u3066\u3044\u308b\u3053\u3068 (\u30c7\u30d5\u30a9\u30eb\u30c8 http:\/\/localhost:50021)\n      \u516c\u5f0f\u30b5\u30a4\u30c8\u304b\u3089macOS\u7248\u30a2\u30d7\u30ea or Docker\u3067\u8d77\u52d5\u3057\u3066\u304a\u304f\n  - (\u4efb\u610f) \u97f3\u58f0\u5165\u529b\u6a5f\u80fd\u3092\u4f7f\u3046\u5834\u5408:\n      BlackHole (2ch) \u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3001\u30b7\u30b9\u30c6\u30e0\u97f3\u58f0\u306e\u51fa\u529b\u5148 or Multi-Output Device \u306b\n      \u542b\u3081\u3066\u304a\u304f (https:\/\/existential.audio\/blackhole\/)\n      \u6587\u5b57\u8d77\u3053\u3057\u306f\u30ed\u30fc\u30ab\u30eb\u306efaster-whisper\u3092\u4f7f\u7528\n  - \u4f9d\u5b58\u30d1\u30c3\u30b1\u30fc\u30b8:\n      pip install PyQt6 requests\n      pip install sounddevice numpy soundfile faster-whisper   # \u97f3\u58f0\u5165\u529b\u6a5f\u80fd\u7528(\u4efb\u610f)\n\n\u5b9f\u884c:\n  python translator_app.py\n\"\"\"\n\nimport sys\nimport os\nimport time\nimport tempfile\nimport subprocess\n\nimport requests\nfrom PyQt6.QtWidgets import (\n    QApplication, QWidget, QVBoxLayout, QHBoxLayout,\n    QPushButton, QTextEdit, QFileDialog, QLabel, QMessageBox,\n    QComboBox, QSpinBox\n)\nfrom PyQt6.QtCore import QThread, pyqtSignal\n\n# \u97f3\u58f0\u5165\u529b(\u9332\u97f3+\u6587\u5b57\u8d77\u3053\u3057)\u95a2\u9023\u306f\u4efb\u610f\u4f9d\u5b58\u3002\u672a\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3067\u3082\u4ed6\u6a5f\u80fd\u306f\u52d5\u4f5c\u3055\u305b\u308b\u3002\ntry:\n    import sounddevice as sd\n    import numpy as np\n    AUDIO_LIBS_AVAILABLE = True\nexcept ImportError:\n    AUDIO_LIBS_AVAILABLE = False\n\n# ===================== \u8a2d\u5b9a =====================\nOLLAMA_BASE_URL = \"http:\/\/localhost:11434\"\nOLLAMA_URL = f\"{OLLAMA_BASE_URL}\/api\/generate\"\nOLLAMA_TAGS_URL = f\"{OLLAMA_BASE_URL}\/api\/tags\"\nOLLAMA_MODEL_FALLBACK = \"qwen2.5:32b\"   # \u30e2\u30c7\u30eb\u4e00\u89a7\u53d6\u5f97\u306b\u5931\u6557\u3057\u305f\u5834\u5408\u306e\u30d5\u30a9\u30fc\u30eb\u30d0\u30c3\u30af\n\nVOICEVOX_BASE_URL = \"http:\/\/localhost:50021\"\nDEFAULT_SPEAKER_ID = 1   # \u56db\u56fd\u3081\u305f\u3093(\u30ce\u30fc\u30de\u30eb)\u3002VOICEVOX\u306e \/speakers \u3067\u4e00\u89a7\u53d6\u5f97\u53ef\u80fd\n\nWHISPER_MODEL_SIZE = \"small\"   # tiny\/base\/small\/medium\/large-v3 (\u7cbe\u5ea6\u3068\u901f\u5ea6\u306e\u30c8\u30ec\u30fc\u30c9\u30aa\u30d5)\nWHISPER_LANGUAGE = \"en\"        # \u5165\u529b\u97f3\u58f0\u306e\u8a00\u8a9e\u3002\u81ea\u52d5\u5224\u5b9a\u3055\u305b\u305f\u3044\u5834\u5408\u306f None \u306b\u5909\u66f4\nPARTIAL_CHUNK_INTERVAL_SEC = 5.0  # \u9332\u97f3\u4e2d\u3001\u4f55\u79d2\u3054\u3068\u306b\u533a\u5207\u3063\u3066\u9010\u6b21\u6587\u5b57\u8d77\u3053\u3057\u3059\u308b\u304b\n# ==================================================\n\n_whisper_model_cache = {}\n\n\ndef get_whisper_model(size: str):\n    \"\"\"faster-whisper\u30e2\u30c7\u30eb\u3092\u30ed\u30fc\u30c9(\u521d\u56de\u306e\u307f)\u3002\u4ee5\u964d\u306f\u30ad\u30e3\u30c3\u30b7\u30e5\u3092\u518d\u5229\u7528\u3059\u308b\u3002\"\"\"\n    from faster_whisper import WhisperModel\n    if size not in _whisper_model_cache:\n        _whisper_model_cache&#91;size] = WhisperModel(size, device=\"cpu\", compute_type=\"int8\")\n    return _whisper_model_cache&#91;size]\n\n\nclass TranslateWorker(QThread):\n    \"\"\"Ollama API\u3067\u7ffb\u8a33\u3092\u5b9f\u884c\u3059\u308b\u30ef\u30fc\u30ab\u30fc\u30b9\u30ec\u30c3\u30c9\"\"\"\n    finished = pyqtSignal(str)\n    error = pyqtSignal(str)\n\n    def __init__(self, text: str, model: str = OLLAMA_MODEL_FALLBACK):\n        super().__init__()\n        self.text = text\n        self.model = model\n\n    def run(self):\n        if not self.model:\n            self.error.emit(\"\u7ffb\u8a33\u306b\u4f7f\u7528\u3059\u308b\u30e2\u30c7\u30eb\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\")\n            return\n        prompt = (\n            \"\u4ee5\u4e0b\u306e\u82f1\u6587\u3092\u81ea\u7136\u306a\u65e5\u672c\u8a9e\u306b\u7ffb\u8a33\u3057\u3066\u304f\u3060\u3055\u3044\u3002\"\n            \"\u8a33\u6587\u306e\u307f\u3092\u51fa\u529b\u3057\u3001\u524d\u7f6e\u304d\u3084\u8aac\u660e\u3001\u6ce8\u91c8\u306f\u4e00\u5207\u4ed8\u3051\u306a\u3044\u3067\u304f\u3060\u3055\u3044\u3002\\n\\n\"\n            f\"{self.text}\"\n        )\n        try:\n            resp = requests.post(\n                OLLAMA_URL,\n                json={\n                    \"model\": self.model,\n                    \"prompt\": prompt,\n                    \"stream\": False,\n                },\n                timeout=600,\n            )\n            resp.raise_for_status()\n            data = resp.json()\n            translated = data.get(\"response\", \"\").strip()\n            if not translated:\n                raise ValueError(\"\u7ffb\u8a33\u7d50\u679c\u304c\u7a7a\u3067\u3057\u305f\u3002\u30e2\u30c7\u30eb\u540d\u3084Ollama\u306e\u8d77\u52d5\u72b6\u614b\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002\")\n            self.finished.emit(translated)\n        except requests.exceptions.ConnectionError:\n            self.error.emit(\n                \"Ollama\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3002`ollama serve` \u304c\u8d77\u52d5\u3057\u3066\u3044\u308b\u304b\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002\"\n            )\n        except Exception as e:\n            self.error.emit(str(e))\n\n\nclass TTSWorker(QThread):\n    \"\"\"VOICEVOX API\u3067\u97f3\u58f0\u5408\u6210\u3057\u3001afplay\u3067\u518d\u751f\u3059\u308b\u30ef\u30fc\u30ab\u30fc\u30b9\u30ec\u30c3\u30c9\"\"\"\n    finished = pyqtSignal()\n    error = pyqtSignal(str)\n\n    def __init__(self, text: str, speaker: int = DEFAULT_SPEAKER_ID):\n        super().__init__()\n        self.text = text\n        self.speaker = speaker\n\n    def run(self):\n        temp_path = None\n        try:\n            # 1. audio_query: \u30c6\u30ad\u30b9\u30c8\u304b\u3089\u97f3\u58f0\u5408\u6210\u7528\u30d1\u30e9\u30e1\u30fc\u30bf\u3092\u751f\u6210\n            query_res = requests.post(\n                f\"{VOICEVOX_BASE_URL}\/audio_query\",\n                params={\"text\": self.text, \"speaker\": self.speaker},\n                timeout=60,\n            )\n            query_res.raise_for_status()\n            query_json = query_res.json()\n\n            # 2. synthesis: \u30d1\u30e9\u30e1\u30fc\u30bf\u304b\u3089wav\u97f3\u58f0\u30c7\u30fc\u30bf\u3092\u751f\u6210\n            synth_res = requests.post(\n                f\"{VOICEVOX_BASE_URL}\/synthesis\",\n                params={\"speaker\": self.speaker},\n                json=query_json,\n                timeout=120,\n            )\n            synth_res.raise_for_status()\n            wav_bytes = synth_res.content\n\n            # 3. \u4e00\u6642\u30d5\u30a1\u30a4\u30eb\u306b\u66f8\u304d\u51fa\u3057\u3066afplay\u3067\u518d\u751f (macOS\u6a19\u6e96\u30b3\u30de\u30f3\u30c9)\n            with tempfile.NamedTemporaryFile(suffix=\".wav\", delete=False) as f:\n                f.write(wav_bytes)\n                temp_path = f.name\n\n            subprocess.run(&#91;\"afplay\", temp_path], check=True)\n            self.finished.emit()\n\n        except requests.exceptions.ConnectionError:\n            self.error.emit(\n                \"VOICEVOX ENGINE\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3002\u30a2\u30d7\u30ea\/Docker\u304c\u8d77\u52d5\u3057\u3066\u3044\u308b\u304b\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002\"\n            )\n        except Exception as e:\n            self.error.emit(str(e))\n        finally:\n            if temp_path and os.path.exists(temp_path):\n                os.remove(temp_path)\n\n\nclass RecordTranscribeWorker(QThread):\n    \"\"\"\n    BlackHole\u7b49\u306e\u5165\u529b\u30c7\u30d0\u30a4\u30b9\u304b\u3089\u97f3\u58f0\u3092\u9332\u97f3\u3057\u3001\u4e00\u5b9a\u9593\u9694\u3054\u3068\u306b\u30c1\u30e3\u30f3\u30af\u5358\u4f4d\u3067\n    faster-whisper\u306b\u3088\u308b\u6587\u5b57\u8d77\u3053\u3057\u3092\u884c\u3063\u3066\u9010\u6b21\u8868\u793a\u3067\u304d\u308b\u30ef\u30fc\u30ab\u30fc\u30b9\u30ec\u30c3\u30c9\u3002\n    \u9332\u97f3\u306e\u505c\u6b62\u306f stop_recording() \u3092\u5916\u90e8(\u30e1\u30a4\u30f3\u30b9\u30ec\u30c3\u30c9)\u304b\u3089\u547c\u3093\u3067\u884c\u3046\u3002\n\n    partial_result: \u30c1\u30e3\u30f3\u30af\u3054\u3068\u306e\u6587\u5b57\u8d77\u3053\u3057\u7d50\u679c\u3092\u53cd\u6620\u3057\u305f\u300c\u3053\u308c\u307e\u3067\u306e\u5168\u6587\u300d\u3092\u901a\u77e5\n    finished: \u505c\u6b62\u5f8c\u3001\u6700\u5f8c\u306e\u30c1\u30e3\u30f3\u30af\u307e\u3067\u51e6\u7406\u3057\u7d42\u3048\u305f\u6700\u7d42\u30c6\u30ad\u30b9\u30c8\u3092\u901a\u77e5\n    \"\"\"\n    partial_result = pyqtSignal(str)\n    finished = pyqtSignal(str)\n    error = pyqtSignal(str)\n\n    def __init__(self, device_index, channels: int = 2, samplerate: int = 44100,\n                 model_size: str = WHISPER_MODEL_SIZE, language=WHISPER_LANGUAGE,\n                 chunk_interval: float = PARTIAL_CHUNK_INTERVAL_SEC):\n        super().__init__()\n        self.device_index = device_index\n        self.channels = channels\n        self.samplerate = samplerate\n        self.model_size = model_size\n        self.language = language\n        self.chunk_interval = chunk_interval\n        self._frames = &#91;]\n        self._processed_index = 0  # self._frames \u306e\u3046\u3061\u3001\u65e2\u306b\u6587\u5b57\u8d77\u3053\u3057\u6e08\u307f\u306e\u4f4d\u7f6e\n        self._text_parts = &#91;]\n        self._stream = None\n        self._stop_flag = False\n\n    def stop_recording(self):\n        self._stop_flag = True\n\n    def run(self):\n        if not AUDIO_LIBS_AVAILABLE:\n            self.error.emit(\n                \"\u97f3\u58f0\u5165\u529b\u306b\u5fc5\u8981\u306a\u30e9\u30a4\u30d6\u30e9\u30ea\u304c\u3042\u308a\u307e\u305b\u3093\u3002\"\n                \"`pip install sounddevice numpy soundfile faster-whisper` \u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002\"\n            )\n            return\n\n        try:\n            def callback(indata, frames, time_info, status):\n                self._frames.append(indata.copy())\n\n            self._stream = sd.InputStream(\n                device=self.device_index,\n                channels=self.channels,\n                samplerate=self.samplerate,\n                callback=callback,\n            )\n            self._stream.start()\n\n            last_chunk_time = time.time()\n            while not self._stop_flag:\n                self.msleep(200)\n                if time.time() - last_chunk_time &gt;= self.chunk_interval:\n                    self._process_pending_chunk()\n                    last_chunk_time = time.time()\n\n            self._stream.stop()\n            self._stream.close()\n\n            # \u505c\u6b62\u5f8c\u3001\u307e\u3060\u51e6\u7406\u3057\u3066\u3044\u306a\u3044\u672b\u5c3e\u306e\u97f3\u58f0\u3092\u6700\u7d42\u30c1\u30e3\u30f3\u30af\u3068\u3057\u3066\u51e6\u7406\n            self._process_pending_chunk()\n\n            final_text = \"\".join(self._text_parts).strip()\n            if not final_text:\n                raise ValueError(\"\u6587\u5b57\u8d77\u3053\u3057\u7d50\u679c\u304c\u7a7a\u3067\u3057\u305f\u3002\u9332\u97f3\u5185\u5bb9\/\u30c7\u30d0\u30a4\u30b9\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002\")\n\n            self.finished.emit(final_text)\n\n        except Exception as e:\n            self.error.emit(str(e))\n\n    def _process_pending_chunk(self):\n        \"\"\"\u524d\u56de\u51e6\u7406\u4f4d\u7f6e\u4ee5\u964d\u306b\u305f\u307e\u3063\u305f\u97f3\u58f0\u30d5\u30ec\u30fc\u30e0\u3092\u307e\u3068\u3081\u3066\u6587\u5b57\u8d77\u3053\u3057\u3057\u3001\u7d50\u679c\u3092\u901a\u77e5\u3059\u308b\"\"\"\n        frames_snapshot = self._frames&#91;self._processed_index:]\n        self._processed_index = len(self._frames)\n        if not frames_snapshot:\n            return\n\n        chunk = np.concatenate(frames_snapshot, axis=0)\n        # \u6975\u7aef\u306b\u77ed\u3044\u30c1\u30e3\u30f3\u30af(\u6570\u767ems\u672a\u6e80)\u306f\u8a8d\u8b58\u7cbe\u5ea6\u304c\u4f4e\u3044\u305f\u3081\u30b9\u30ad\u30c3\u30d7\u3057\u3001\u6b21\u56de\u306b\u56de\u3059\n        if chunk.shape&#91;0] &lt; int(self.samplerate * 0.3):\n            self._processed_index -= len(frames_snapshot)\n            return\n\n        tmp_path = None\n        try:\n            import soundfile as sf\n            with tempfile.NamedTemporaryFile(suffix=\".wav\", delete=False) as f:\n                tmp_path = f.name\n            sf.write(tmp_path, chunk, self.samplerate)\n\n            model = get_whisper_model(self.model_size)\n            segments, _info = model.transcribe(tmp_path, language=self.language)\n            chunk_text = \"\".join(seg.text for seg in segments)\n\n            if chunk_text:\n                self._text_parts.append(chunk_text)\n                self.partial_result.emit(\"\".join(self._text_parts).strip())\n        except Exception:\n            # \u30c1\u30e3\u30f3\u30af\u5358\u4f4d\u306e\u5931\u6557\u3067\u9332\u97f3\u5168\u4f53\u3092\u6b62\u3081\u306a\u3044\u3002\u6700\u7d42\u7d50\u679c\u306b\u306f\u53cd\u6620\u3055\u308c\u306a\u3044\u304c\u7d99\u7d9a\u3059\u308b\u3002\n            pass\n        finally:\n            if tmp_path and os.path.exists(tmp_path):\n                os.remove(tmp_path)\n\n\nclass TranslatorApp(QWidget):\n    def __init__(self):\n        super().__init__()\n        self.setWindowTitle(\"EN\u2192JA \u7ffb\u8a33\uff06\u8aad\u307f\u4e0a\u3052 (Ollama + VOICEVOX)\")\n        self.resize(760, 640)\n        self.selected_path = None\n        self.original_text = \"\"\n        self.speaker_map = {}  # \u8868\u793a\u540d -&gt; speaker_id\n        self.audio_device_map = {}  # \u8868\u793a\u540d -&gt; device_index\n        self.record_worker = None\n        self.is_recording = False\n        self.init_ui()\n        self.load_speakers()\n        self.load_ollama_models()\n        self.load_audio_devices()\n\n    # ---------------- UI\u69cb\u7bc9 ----------------\n    def init_ui(self):\n        layout = QVBoxLayout()\n\n        # \u30d5\u30a1\u30a4\u30eb\u9078\u629e\u884c\n        file_row = QHBoxLayout()\n        self.file_label = QLabel(\"\u30d5\u30a1\u30a4\u30eb\u672a\u9078\u629e\")\n        select_btn = QPushButton(\"\u30d5\u30a1\u30a4\u30eb\u9078\u629e\")\n        select_btn.clicked.connect(self.select_file)\n        self.load_btn = QPushButton(\"\u8aad\u307f\u8fbc\u307f\")\n        self.load_btn.clicked.connect(self.load_file)\n        self.load_btn.setEnabled(False)\n        file_row.addWidget(self.file_label, stretch=1)\n        file_row.addWidget(select_btn)\n        file_row.addWidget(self.load_btn)\n        layout.addLayout(file_row)\n\n        # \u97f3\u58f0\u5165\u529b\u884c (BlackHole\u7b49\u304b\u3089\u9332\u97f3 \u2192 \u6587\u5b57\u8d77\u3053\u3057)\n        audio_row = QHBoxLayout()\n        audio_row.addWidget(QLabel(\"\u97f3\u58f0\u5165\u529b\u30c7\u30d0\u30a4\u30b9:\"))\n        self.audio_device_combo = QComboBox()\n        audio_row.addWidget(self.audio_device_combo, stretch=1)\n        self.refresh_devices_btn = QPushButton(\"\u30c7\u30d0\u30a4\u30b9\u66f4\u65b0\")\n        self.refresh_devices_btn.clicked.connect(self.load_audio_devices)\n        audio_row.addWidget(self.refresh_devices_btn)\n        self.record_btn = QPushButton(\"\u9332\u97f3\u958b\u59cb\")\n        self.record_btn.clicked.connect(self.toggle_recording)\n        audio_row.addWidget(self.record_btn)\n        layout.addLayout(audio_row)\n\n        # \u539f\u6587\u8868\u793a (\u30d5\u30a1\u30a4\u30eb\u8aad\u307f\u8fbc\u307f\/\u97f3\u58f0\u6587\u5b57\u8d77\u3053\u3057\u7d50\u679c\u3092\u8868\u793a\u3002\u7ffb\u8a33\u524d\u306b\u7de8\u96c6\u53ef)\n        layout.addWidget(QLabel(\"\u539f\u6587 (\u82f1\u8a9e \/ \u7de8\u96c6\u53ef):\"))\n        self.original_edit = QTextEdit()\n        layout.addWidget(self.original_edit)\n\n        # \u7ffb\u8a33\u30dc\u30bf\u30f3\u884c (\u30e2\u30c7\u30eb\u9078\u629e\u4ed8\u304d)\n        translate_row = QHBoxLayout()\n        translate_row.addWidget(QLabel(\"\u30e2\u30c7\u30eb:\"))\n        self.model_combo = QComboBox()\n        translate_row.addWidget(self.model_combo, stretch=1)\n        self.refresh_models_btn = QPushButton(\"\u4e00\u89a7\u66f4\u65b0\")\n        self.refresh_models_btn.clicked.connect(self.load_ollama_models)\n        translate_row.addWidget(self.refresh_models_btn)\n        self.translate_btn = QPushButton(\"\u7ffb\u8a33\u958b\u59cb\")\n        self.translate_btn.clicked.connect(self.start_translate)\n        self.translate_btn.setEnabled(False)\n        translate_row.addWidget(self.translate_btn)\n        layout.addLayout(translate_row)\n\n        # \u8a33\u6587\u8868\u793a (\u7de8\u96c6\u53ef\u80fd: \u8aad\u307f\u4e0a\u3052\u524d\u306b\u624b\u76f4\u3057\u3067\u304d\u308b)\n        layout.addWidget(QLabel(\"\u8a33\u6587 (\u65e5\u672c\u8a9e \/ \u8aad\u307f\u4e0a\u3052\u524d\u306b\u7de8\u96c6\u53ef):\"))\n        self.translated_edit = QTextEdit()\n        layout.addWidget(self.translated_edit)\n\n        # \u8aad\u307f\u4e0a\u3052\u8a2d\u5b9a\u884c\n        speak_row = QHBoxLayout()\n        speak_row.addWidget(QLabel(\"\u8a71\u8005:\"))\n        self.speaker_combo = QComboBox()\n        speak_row.addWidget(self.speaker_combo, stretch=1)\n        self.speak_btn = QPushButton(\"\u8aad\u307f\u4e0a\u3052\u958b\u59cb\")\n        self.speak_btn.clicked.connect(self.start_speak)\n        self.speak_btn.setEnabled(False)\n        speak_row.addWidget(self.speak_btn)\n        layout.addLayout(speak_row)\n\n        # \u30b9\u30c6\u30fc\u30bf\u30b9\u8868\u793a\n        self.status_label = QLabel(\"\u6e96\u5099\u5b8c\u4e86\")\n        layout.addWidget(self.status_label)\n\n        self.setLayout(layout)\n\n    # ---------------- VOICEVOX\u8a71\u8005\u4e00\u89a7\u53d6\u5f97 ----------------\n    def load_speakers(self):\n        try:\n            res = requests.get(f\"{VOICEVOX_BASE_URL}\/speakers\", timeout=10)\n            res.raise_for_status()\n            speakers = res.json()\n            for sp in speakers:\n                name = sp.get(\"name\", \"unknown\")\n                for style in sp.get(\"styles\", &#91;]):\n                    label = f\"{name} - {style.get('name')}\"\n                    self.speaker_map&#91;label] = style.get(\"id\")\n                    self.speaker_combo.addItem(label)\n            if self.speaker_combo.count() == 0:\n                self.speaker_combo.addItem(\"\u30c7\u30d5\u30a9\u30eb\u30c8\u8a71\u8005\")\n                self.speaker_map&#91;\"\u30c7\u30d5\u30a9\u30eb\u30c8\u8a71\u8005\"] = DEFAULT_SPEAKER_ID\n        except Exception:\n            # VOICEVOX\u304c\u672a\u8d77\u52d5\u3067\u3082\u30a2\u30d7\u30ea\u81ea\u4f53\u306f\u8d77\u52d5\u3067\u304d\u308b\u3088\u3046\u306b\u3059\u308b\n            self.speaker_combo.addItem(\"\u30c7\u30d5\u30a9\u30eb\u30c8\u8a71\u8005 (VOICEVOX\u672a\u63a5\u7d9a)\")\n            self.speaker_map&#91;\"\u30c7\u30d5\u30a9\u30eb\u30c8\u8a71\u8005 (VOICEVOX\u672a\u63a5\u7d9a)\"] = DEFAULT_SPEAKER_ID\n            self.status_label.setText(\n                \"VOICEVOX\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u8aad\u307f\u4e0a\u3052\u6642\u306b\u518d\u63a5\u7d9a\u3092\u8a66\u307f\u307e\u3059\u3002\"\n            )\n\n    # ---------------- Ollama\u30e2\u30c7\u30eb\u4e00\u89a7\u53d6\u5f97 (ollama list\u76f8\u5f53) ----------------\n    def load_ollama_models(self):\n        self.model_combo.clear()\n        try:\n            res = requests.get(OLLAMA_TAGS_URL, timeout=10)\n            res.raise_for_status()\n            data = res.json()\n            models = &#91;m.get(\"name\") for m in data.get(\"models\", &#91;]) if m.get(\"name\")]\n            if not models:\n                raise ValueError(\"\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08\u307f\u306e\u30e2\u30c7\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002\")\n            self.model_combo.addItems(models)\n            self.status_label.setText(f\"\u30e2\u30c7\u30eb\u4e00\u89a7\u3092\u53d6\u5f97\u3057\u307e\u3057\u305f ({len(models)}\u4ef6)\")\n        except requests.exceptions.ConnectionError:\n            self.model_combo.addItem(OLLAMA_MODEL_FALLBACK)\n            self.status_label.setText(\n                \"Ollama\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3002`ollama serve` \u306e\u8d77\u52d5\u3092\u78ba\u8a8d\u3057\u3066\u300c\u4e00\u89a7\u66f4\u65b0\u300d\u3092\u62bc\u3057\u3066\u304f\u3060\u3055\u3044\u3002\"\n            )\n        except Exception as e:\n            self.model_combo.addItem(OLLAMA_MODEL_FALLBACK)\n            self.status_label.setText(f\"\u30e2\u30c7\u30eb\u4e00\u89a7\u53d6\u5f97\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {e}\")\n\n    # ---------------- \u97f3\u58f0\u5165\u529b\u30c7\u30d0\u30a4\u30b9\u4e00\u89a7\u53d6\u5f97 ----------------\n    def load_audio_devices(self):\n        self.audio_device_combo.clear()\n        self.audio_device_map.clear()\n\n        if not AUDIO_LIBS_AVAILABLE:\n            self.audio_device_combo.addItem(\"(sounddevice\u672a\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb)\")\n            self.record_btn.setEnabled(False)\n            self.status_label.setText(\n                \"\u97f3\u58f0\u5165\u529b\u3092\u4f7f\u3046\u306b\u306f `pip install sounddevice numpy soundfile faster-whisper` \u304c\u5fc5\u8981\u3067\u3059\u3002\"\n            )\n            return\n\n        try:\n            devices = sd.query_devices()\n            preferred_index = None\n            for idx, dev in enumerate(devices):\n                if dev.get(\"max_input_channels\", 0) &gt; 0:\n                    label = f\"&#91;{idx}] {dev.get('name')}\"\n                    self.audio_device_map&#91;label] = idx\n                    self.audio_device_combo.addItem(label)\n                    if \"blackhole\" in dev.get(\"name\", \"\").lower() and preferred_index is None:\n                        preferred_index = self.audio_device_combo.count() - 1\n\n            if self.audio_device_combo.count() == 0:\n                self.audio_device_combo.addItem(\"\u5165\u529b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\")\n                self.record_btn.setEnabled(False)\n            else:\n                self.record_btn.setEnabled(True)\n                if preferred_index is not None:\n                    self.audio_device_combo.setCurrentIndex(preferred_index)\n        except Exception as e:\n            self.audio_device_combo.addItem(\"\u30c7\u30d0\u30a4\u30b9\u53d6\u5f97\u30a8\u30e9\u30fc\")\n            self.record_btn.setEnabled(False)\n            self.status_label.setText(f\"\u97f3\u58f0\u30c7\u30d0\u30a4\u30b9\u4e00\u89a7\u306e\u53d6\u5f97\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {e}\")\n\n    # ---------------- \u9332\u97f3\/\u6587\u5b57\u8d77\u3053\u3057\u51e6\u7406 ----------------\n    def toggle_recording(self):\n        if not self.is_recording:\n            self.start_recording()\n        else:\n            self.stop_recording()\n\n    def start_recording(self):\n        label = self.audio_device_combo.currentText()\n        device_index = self.audio_device_map.get(label)\n        if device_index is None:\n            QMessageBox.warning(self, \"\u8b66\u544a\", \"\u6709\u52b9\u306a\u97f3\u58f0\u5165\u529b\u30c7\u30d0\u30a4\u30b9\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002\")\n            return\n\n        try:\n            dev_info = sd.query_devices(device_index)\n            samplerate = int(dev_info.get(\"default_samplerate\") or 44100)\n            channels = min(2, dev_info.get(\"max_input_channels\", 1)) or 1\n        except Exception:\n            samplerate, channels = 44100, 2\n\n        self.is_recording = True\n        self.record_btn.setText(\"\u9332\u97f3\u505c\u6b62\")\n        self.audio_device_combo.setEnabled(False)\n        self.refresh_devices_btn.setEnabled(False)\n        self.status_label.setText(\"\u9332\u97f3\u4e2d... (\u3082\u3046\u4e00\u5ea6\u30dc\u30bf\u30f3\u3092\u62bc\u3059\u3068\u505c\u6b62\u3057\u3001\u6587\u5b57\u8d77\u3053\u3057\u3092\u958b\u59cb\u3057\u307e\u3059)\")\n\n        self.record_worker = RecordTranscribeWorker(\n            device_index=device_index, channels=channels, samplerate=samplerate\n        )\n        self.record_worker.partial_result.connect(self.on_record_partial)\n        self.record_worker.finished.connect(self.on_record_done)\n        self.record_worker.error.connect(self.on_record_error)\n        self.record_worker.start()\n\n    def stop_recording(self):\n        if self.record_worker:\n            self.record_btn.setEnabled(False)\n            self.status_label.setText(\"\u6700\u5f8c\u306e\u30c1\u30e3\u30f3\u30af\u3092\u6587\u5b57\u8d77\u3053\u3057\u4e2d...\")\n            self.record_worker.stop_recording()\n\n    def on_record_partial(self, text_so_far: str):\n        self.original_edit.setPlainText(text_so_far)\n        # \u30ab\u30fc\u30bd\u30eb\u3092\u672b\u5c3e\u306b\u79fb\u52d5\u3057\u3066\u3001\u8ffd\u8a18\u3055\u308c\u3066\u3044\u304f\u69d8\u5b50\u304c\u898b\u3048\u308b\u3088\u3046\u306b\u3059\u308b\n        cursor = self.original_edit.textCursor()\n        cursor.movePosition(cursor.MoveOperation.End)\n        self.original_edit.setTextCursor(cursor)\n        self.status_label.setText(\"\u9332\u97f3\u4e2d... (\u9010\u6b21\u8a8d\u8b58\u7d50\u679c\u3092\u8868\u793a\u3057\u3066\u3044\u307e\u3059)\")\n\n    def on_record_done(self, text: str):\n        self.original_edit.setPlainText(text)\n        self.translate_btn.setEnabled(bool(text.strip()))\n        self._reset_recording_ui()\n        self.status_label.setText(\"\u6587\u5b57\u8d77\u3053\u3057\u5b8c\u4e86\")\n\n    def on_record_error(self, msg: str):\n        QMessageBox.critical(self, \"\u97f3\u58f0\u5165\u529b\u30a8\u30e9\u30fc\", msg)\n        self._reset_recording_ui()\n        self.status_label.setText(\"\u97f3\u58f0\u5165\u529b\u5931\u6557\")\n\n    def _reset_recording_ui(self):\n        self.is_recording = False\n        self.record_btn.setText(\"\u9332\u97f3\u958b\u59cb\")\n        self.record_btn.setEnabled(True)\n        self.audio_device_combo.setEnabled(True)\n        self.refresh_devices_btn.setEnabled(True)\n\n    # ---------------- \u30d5\u30a1\u30a4\u30eb\u9078\u629e\/\u8aad\u307f\u8fbc\u307f ----------------\n    def select_file(self):\n        path, _ = QFileDialog.getOpenFileName(\n            self, \"\u82f1\u8a9e\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u3092\u9078\u629e\", \"\", \"Text Files (*.txt);;All Files (*)\"\n        )\n        if path:\n            self.selected_path = path\n            self.file_label.setText(os.path.basename(path))\n            self.load_btn.setEnabled(True)\n\n    def load_file(self):\n        if not self.selected_path:\n            QMessageBox.warning(self, \"\u8b66\u544a\", \"\u5148\u306b\u30d5\u30a1\u30a4\u30eb\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\")\n            return\n        try:\n            with open(self.selected_path, \"r\", encoding=\"utf-8\") as f:\n                self.original_text = f.read()\n            self.original_edit.setPlainText(self.original_text)\n            self.translate_btn.setEnabled(bool(self.original_text.strip()))\n            self.status_label.setText(\"\u30d5\u30a1\u30a4\u30eb\u8aad\u307f\u8fbc\u307f\u5b8c\u4e86\")\n        except UnicodeDecodeError:\n            QMessageBox.critical(\n                self, \"\u30a8\u30e9\u30fc\",\n                \"UTF-8\u3068\u3057\u3066\u8aad\u307f\u8fbc\u3081\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30d5\u30a1\u30a4\u30eb\u306e\u6587\u5b57\u30b3\u30fc\u30c9\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002\"\n            )\n        except Exception as e:\n            QMessageBox.critical(self, \"\u30a8\u30e9\u30fc\", f\"\u30d5\u30a1\u30a4\u30eb\u8aad\u307f\u8fbc\u307f\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {e}\")\n\n    # ---------------- \u7ffb\u8a33\u51e6\u7406 ----------------\n    def start_translate(self):\n        text = self.original_edit.toPlainText().strip()\n        if not text:\n            return\n        model = self.model_combo.currentText().strip()\n        if not model:\n            QMessageBox.warning(self, \"\u8b66\u544a\", \"\u30e2\u30c7\u30eb\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u300c\u4e00\u89a7\u66f4\u65b0\u300d\u3092\u62bc\u3057\u3066\u304f\u3060\u3055\u3044\u3002\")\n            return\n        self.translate_btn.setEnabled(False)\n        self.status_label.setText(\n            f\"\u7ffb\u8a33\u4e2d... ({model} \/ \u30e2\u30c7\u30eb\u30b5\u30a4\u30ba\u306b\u3088\u3063\u3066\u306f\u6570\u5206\u304b\u304b\u308a\u307e\u3059)\"\n        )\n        self.translate_worker = TranslateWorker(text, model=model)\n        self.translate_worker.finished.connect(self.on_translate_done)\n        self.translate_worker.error.connect(self.on_translate_error)\n        self.translate_worker.start()\n\n    def on_translate_done(self, translated: str):\n        self.translated_edit.setPlainText(translated)\n        self.translate_btn.setEnabled(True)\n        self.speak_btn.setEnabled(True)\n        self.status_label.setText(\"\u7ffb\u8a33\u5b8c\u4e86\")\n\n    def on_translate_error(self, msg: str):\n        QMessageBox.critical(self, \"\u7ffb\u8a33\u30a8\u30e9\u30fc\", msg)\n        self.translate_btn.setEnabled(True)\n        self.status_label.setText(\"\u7ffb\u8a33\u5931\u6557\")\n\n    # ---------------- \u8aad\u307f\u4e0a\u3052\u51e6\u7406 ----------------\n    def start_speak(self):\n        text = self.translated_edit.toPlainText().strip()\n        if not text:\n            return\n        speaker_label = self.speaker_combo.currentText()\n        speaker_id = self.speaker_map.get(speaker_label, DEFAULT_SPEAKER_ID)\n\n        self.speak_btn.setEnabled(False)\n        self.status_label.setText(\"\u97f3\u58f0\u5408\u6210\u4e2d...\")\n        self.tts_worker = TTSWorker(text, speaker=speaker_id)\n        self.tts_worker.finished.connect(self.on_speak_done)\n        self.tts_worker.error.connect(self.on_speak_error)\n        self.tts_worker.start()\n\n    def on_speak_done(self):\n        self.speak_btn.setEnabled(True)\n        self.status_label.setText(\"\u8aad\u307f\u4e0a\u3052\u5b8c\u4e86\")\n\n    def on_speak_error(self, msg: str):\n        QMessageBox.critical(self, \"\u8aad\u307f\u4e0a\u3052\u30a8\u30e9\u30fc\", msg)\n        self.speak_btn.setEnabled(True)\n        self.status_label.setText(\"\u8aad\u307f\u4e0a\u3052\u5931\u6557\")\n\n\ndef main():\n    app = QApplication(sys.argv)\n    win = TranslatorApp()\n    win.show()\n    sys.exit(app.exec())\n\n\nif __name__ == \"__main__\":\n    main()\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u307e\u3068\u3081<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u300c\u30ed\u30fc\u30ab\u30ebLLM\u3067\u82f1\u65e5\u7ffb\u8a33\uff0b\u8aad\u307f\u4e0a\u3052\u300d\u3068\u3044\u3046\u5358\u7d14\u306a\u8981\u4ef6\u304b\u3089\u51fa\u767a\u3057\u3001\u5bfe\u8a71\u3092\u91cd\u306d\u308b\u4e2d\u3067Ollama\u30e2\u30c7\u30eb\u306e\u52d5\u7684\u9078\u629e\u3001VOICEVOX\u306e\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u9078\u629e\u3001BlackHole\u7d4c\u7531\u306e\u97f3\u58f0\u5165\u529b\u3001\u9010\u6b21\u6587\u5b57\u8d77\u3053\u3057\u8868\u793a\u3068\u3001\u6bb5\u968e\u7684\u306b\u6a5f\u80fd\u3092\u7a4d\u307f\u4e0a\u3052\u3066\u3044\u304d\u307e\u3057\u305f\u3002\u3059\u3079\u3066\u30ed\u30fc\u30ab\u30eb\u5b8c\u7d50\uff08Ollama\uff0bfaster-whisper\uff0bVOICEVOX\uff09\u3067\u52d5\u4f5c\u3059\u308b\u305f\u3081\u3001\u5916\u90e8API\u3078\u306e\u4f9d\u5b58\u3084\u30b3\u30b9\u30c8\u3092\u6c17\u306b\u305b\u305a\u3001\u30b7\u30b9\u30c6\u30e0\u97f3\u58f0\u306e\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u7ffb\u8a33\u30c4\u30fc\u30eb\u3068\u3057\u3066\u62e1\u5f35\u3057\u3066\u3044\u3051\u308b\u571f\u53f0\u304c\u3067\u304d\u305f\u3068\u601d\u3044\u307e\u3059\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mac\u3067\u52d5\u304f\u30ed\u30fc\u30ab\u30ebLLM\u74b0\u5883\u3092\u6d3b\u304b\u3057\u3066\u3001\u300c\u82f1\u8a9e\u30c6\u30ad\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u3093\u3067\u65e5\u672c\u8a9e\u306b\u7ffb\u8a33\u3057\u3001\u7d50\u679c\u3092\u8868\u793a\u3057\u306a\u304c\u3089\u97f3\u58f0\u3067\u8aad\u307f\u4e0a\u3052\u308b\u300d\u30c4\u30fc\u30eb\u3092\u4f5c\u3063\u3066\u307f\u307e\u3057\u305f\u3002\u3055\u3089\u306b\u4f1a\u8a71\u3092\u91cd\u306d\u308b\u4e2d\u3067\u3001Ollama\u306e\u30e2\u30c7\u30eb\u4e00\u89a7\u304b\u3089\u306e\u9078\u629e\u3001VOICEVOX [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[21],"tags":[],"class_list":["post-2344","post","type-post","status-publish","format-standard","hentry","category-llm"],"featured_image_src":null,"author_info":{"display_name":"mars","author_link":"https:\/\/rfsec.ddns.net\/db\/?author=1"},"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=\/wp\/v2\/posts\/2344","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2344"}],"version-history":[{"count":2,"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=\/wp\/v2\/posts\/2344\/revisions"}],"predecessor-version":[{"id":2356,"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=\/wp\/v2\/posts\/2344\/revisions\/2356"}],"wp:attachment":[{"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2344"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2344"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rfsec.ddns.net\/db\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2344"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}