
איך לגרום ל-Claude לייצר סרטונים בקול שלכם: המדריך המלא

הקול בסרטון שלמטה לא הוקלט: Claude הפיק אותו בקול המשובט שלי, דרך n8n ו-Gemini TTS של Google. במדריך הזה תקימו את אותה מערכת בעצמכם, שלב אחרי שלב, כך שתוכלו לבקש מ-Claude סרטון ולקבל קובץ מוכן בקול שלכם. מפתח ה-API של Google נשאר ב-n8n ולא מגיע לשיחה, והמדריך שלם גם אם לא צפיתם בסרטון.
מה בונים
כש-Claude רץ בענן, הוא עובד בסביבה סגורה. אין בה את מפתח ה-API שלכם, והיא לא יכולה לפנות ל-Google. לתת ל-Claude את המפתח בתוך השיחה זה רעיון רע, ובענן זה גם לא יעבוד.
הפתרון הוא לשים באמצע את n8n. הוא מחזיק את המפתח אצלו, ו-Claude מדבר רק איתו. הדרך הולכת ככה:
- Claude שולח ל-n8n שורת טקסט.
- n8n פונה ל-Google, מקבל קובץ שמע, ושומר אותו בטבלה.
- n8n מחזיר ל-Claude קישור עם קוד אקראי.
- Claude מוריד את הקובץ מהקישור, וממשיך לבנות את הסרטון.
המפתח לא מגיע לשיחה בשום שלב.
בצד של n8n תקימו שלושה workflows:
- Worker (
Voice TTS · Worker): קורא ל-Google ושומר את השמע בטבלה. - Generate (
Voice TTS · Generate): הדלת ש-Claude מפעיל. - Download (
Voice TTS · Download): מוריד את הקובץ לפי קוד אקראי.
בסוף תוסיפו ל-Claude סקיל קטן בשם voice-narration, שמסביר לו איך להשתמש בכל זה.
מה צריך
- חשבון Google עם גישה ל-AI Studio, ומפתח API בתשלום. שיבוט קול לא עובד עם מפתח חינמי.
- שרת n8n שנגיש מהאינטרנט, בענן או מותקן אצלכם.
- מנוי Claude שתומך ב-connectors וב-skills.
שיבוט קול לא זמין בכל מקום. בדקו בתיעוד של Google על שיבוט קול אם הוא זמין אצלכם.
את כל הקבצים של המערכת תמצאו במאמר עצמו, בפרק "הקבצים" בסוף. השלבים מפנים אליו.
מה מחליפים ואיפה
בקבצים יש כמה ערכי מקום שצריך להחליף. הנה כולם במקום אחד:
YOUR_VOICE_ID | מזהה הקול שלכם (מתחיל ב-voice_) | ה-Worker, צומת Call Gemini TTS |
REPLACE_WITH_voice_tts_files_ID | בוחרים את הטבלה voice_tts_files מהרשימה | Worker: Delete Expired Rows ו-Store Audio. Download: Find Token Row |
REPLACE_WITH_WORKER_ID | בוחרים את ה-Worker שייבאתם מהרשימה | Generate, צומת Call Worker |
https://YOUR-N8N-DOMAIN (כולל ה-https://) | הכתובת המלאה של ה-n8n שלכם, למשל https://n8n.example.com | Generate, צומת Result |
YOUR_GENERATE_WORKFLOW_ID | מזהה ה-workflow Generate | SKILL.md |
YOUR-N8N-DOMAIN (בלי https://) | הדומיין של ה-n8n שלכם, למשל n8n.example.com | SKILL.md |
שלבי ההתקנה
שלב 1: שכפול הקול
- פתחו את דף יצירת הדיבור ב-AI Studio: https://aistudio.google.com/generate-speech
- הסתכלו בפינה השמאלית למטה. שם מוצג המפתח הפעיל, והקול יישמר בפרויקט שלו.
- בשורת הקולות לחצו Create new voice.
- בחרו Voice Replication (ולא Voice Design).

- תנו שם לקול ולחצו Record your voice. הקליטו כעשרים שניות של דיבור טבעי, בחדר שקט, בלי הד ובלי מוזיקה ברקע.
- בשלב Verify it's you לחצו Record verification והקריאו את משפט ההסכמה באנגלית. אפשר לשבט רק קול שהוא שלכם. הקליטו את שתי ההקלטות באותו מיקרופון ובאותו חדר.

- לחצו Replicate voice וחכו.
- כשזה מסתיים, העתיקו את מזהה הקול. הוא מתחיל ב-
voice_. שמרו אותו בצד.

לא מצאתם אותו בדף? ראו "איך מוצאים את מזהה הקול עם curl" למטה.
שלב 2: מפתח API באותו פרויקט
זה החלק הכי חשוב. קול ששמור ב-Google שייך לפרויקט שבו הוא נוצר. מפתח מפרויקט אחר לא רואה אותו, ותקבלו שגיאה 404 שהקול לא נמצא.
- ב-AI Studio לכו ל-Dashboard, ואז ל-API Keys (או ישר אל https://aistudio.google.com/apikey), ולחצו Create API key.
- בחרו את הפרויקט שבו נוצר הקול, תנו שם למפתח ולחצו ליצור.
- העתיקו את המפתח. הוא כמו סיסמה: לא מדביקים אותו בשיחה עם Claude ולא בקוד.

שלב 3: credential ב-n8n
- ב-n8n, תחת Personal, פתחו את הלשונית Credentials וצרו credential חדש.
- חפשו Google Gemini ובחרו את הסוג Google Gemini(PaLM) Api.
- הדביקו את המפתח בשדה API Key ותנו שם ברור, למשל
Gemini Voice. - שמרו. המפתח נשמר מוצפן ב-n8n, והצמתים משתמשים בו בלי לראות אותו.

שלב 4: טבלת הקבצים
- בלשונית Data tables צרו טבלה בשם
voice_tts_files. - הוסיפו שלוש עמודות, בדיוק בשמות ובסוגים האלה:
token, סוג stringaudio_b64, סוג stringcreated_at_ms, סוג number

שלב 5: ייבוא Worker (קודם כל)
- צרו workflow חדש. בתפריט שלוש הנקודות בפינה בחרו Import from File ובחרו את
worker.json. את תוכן הקובץ תמצאו בפרק "הקבצים" בסוף המאמר. - פתחו את הצומת Delete Expired Rows. בשדה Data table עברו מ-By ID ל-From list ובחרו
voice_tts_files. - פתחו את הצומת Store Audio ועשו אותו דבר.

- פתחו את הצומת Call Gemini TTS:
- ב-Credential to connect with בחרו את
Gemini Voice. - בגוף הבקשה (JSON) החליפו את
YOUR_VOICE_IDבמזהה הקול ששמרתם.
- ב-Credential to connect with בחרו את

- שמרו. אל תפרסמו את ה-Worker, אין בכך צורך.
- ההגדרה "לא לשמור נתוני ריצה, לא בהצלחה ולא בכישלון" כבר בקובץ. היא מונעת מהשמע להגיע לנתוני הריצה ולהציף את השיחה של Claude.
אם אתם בונים את ה-Worker ביד כמו בסרטון, הכתובת והביטוי של Call Gemini TTS נמצאים בקובץ worker-http-body.txt (בפרק "הקבצים"). הפעילו שם גם Execute Once בהגדרות הצומת.
שלב 6: ייבוא Generate
- צרו workflow חדש וייבאו את
generate.jsonבאותה דרך. - פתחו את הצומת Call Worker. בשדה Workflow בחרו From list ובחרו את
Voice TTS · Worker. - פתחו את הצומת Result. בשדה
urlהחליפו את כל הטקסטhttps://YOUR-N8N-DOMAIN, כולל ה-https://, בכתובת המלאה של ה-n8n שלכם, למשלhttps://n8n.example.com, בלי סלאש בסוף. השאירו את/webhook/voice-file?token=ואת מה שאחריו כמו שהם.

- שמרו.
- העתיקו את מזהה ה-workflow מסרגל הכתובת. הוא המחרוזת שאחרי
/workflow/. תצטרכו אותו בשלב 11.
אל תפרסמו את Generate. כל עוד הוא לא מפורסם, ה-webhook שלו לא פעיל באינטרנט, ו-Claude מפעיל אותו רק דרך MCP. אם תפרסמו אותו, כל מי שיודע את הכתובת יוכל להפעיל אותו ולבזבז לכם קרדיט של Gemini.
שלב 7: ייבוא Download ופרסום
- צרו workflow חדש וייבאו את
download.json. - פתחו את הצומת Find Token Row. בשדה Data table בחרו From list ובחרו
voice_tts_files. - שמרו ולחצו Publish. זה ה-workflow היחיד שמפרסמים.

מי שמחזיק בקישור יכול להוריד את הקובץ במשך שעה, אז לא מעבירים דרכו טקסט רגיש.
שלב 8: חיבור Claude ל-n8n
- ב-n8n לכו ל-Settings, ואז ל-Instance-level MCP.
- ודאו ש-MCP status הוא Enabled. בשביל זה צריך הרשאת בעלים או מנהל.
- ב-Workflows enabled ודאו ש-Generate ברשימה. אם הוא לא שם, הוסיפו אותו. בלי זה Claude לא יכול להפעיל אותו.

- לחצו Connect, ונפתח חלון Connect a client.
- הישארו בלשונית OAuth, שהיא המומלצת, כי אין בה טוקן להעתיק.
- בחרו Claude.ai ולחצו Add to Claude.ai.
- Claude נפתח עם ה-connector מוכן. אשרו שם, ואשרו גם ב-n8n כשהוא שואל אם לתת גישה.

אם הכפתור לא עובד: העתיקו את ה-Server URL, וב-Claude לכו ל-Customize, ואז Connectors, ואז Add, והדביקו.
שלב 9: אישור הדומיין
החיבור ב-MCP כבר עובד, אבל הורדת הקובץ היא בקשת רשת רגילה מתוך הסביבה של Claude, והיא פונה רק לכתובות מאושרות.
- ב-Settings של Claude, תחת Capabilities, לחצו Manage ליד Allowed websites, והוסיפו את הדומיין של ה-n8n שלכם, למשל
n8n.example.com. - פתחו שיחה חדשה, כדי שההגדרה תיכנס לתוקף.

שלב 10: בדיקה
בשיחה החדשה כתבו ל-Claude:
הפעל את Voice TTS Generate עם המשפט הזה: "שלום, זו בדיקה של הקול שלי." הורד את הקובץ מהקישור, ובדוק כמה הוא ארוך.
אם קיבלתם קובץ תקין באורך הגיוני, הכל מחובר. Claude לא שומע, אז האזינו לקובץ בעצמכם.

שלב 11: הסקיל
- צרו תיקייה בשם
voice-narrationושמרו בתוכה קובץ בשםSKILL.md, עם התוכן שמופיע בפרק "הקבצים" בסוף המאמר. - החליפו
YOUR_GENERATE_WORKFLOW_IDבמזהה ה-workflow Generate משלב 6. - החליפו
YOUR-N8N-DOMAINבדומיין של ה-n8n שלכם. - שמרו, וכווצו את התיקייה
voice-narrationלקובץ zip. ה-zip צריך להכיל את התיקייה עצמה, עםSKILL.mdבתוכה. - ב-Claude לכו ל-Customize, ואז Skills, ואז Add, והעלו את ה-zip.

הסקיל אומר ל-Claude ארבעה דברים:
- לכל שורת קריינות, להפעיל את Generate פעם אחת, עם הפרמטרים הנכונים.
- לחכות שהריצה תסתיים ולקרוא רק את הצומת Result, כדי לא להציף את השיחה.
- להוריד את הקובץ מהקישור ולבדוק שהוא תקין.
- לא לשנות סגנון ולא לכתוב הוראות סגנון בתוך הטקסט.
שלב 12: סרטון ראשון
פתחו שיחה חדשה וכתבו בקשה רגילה, למשל: "תכין לי סרטון קצר על הנושא הזה, בקול שלי." Claude כותב תסריט, מפעיל את n8n לכל שורה, מוריד את הקבצים ובונה את הסרטון.
דבר אחד הוא לא יכול לעשות: הוא לא שומע. תמיד האזינו לתוצאה לפני שאתם מפרסמים.

איך מוצאים את מזהה הקול עם curl
אם לא הצלחתם להעתיק את מזהה הקול מ-AI Studio, אפשר לשלוף אותו עם המפתח שיצרתם בשלב 2. הריצו את הפקודה במסוף במחשב שלכם, לא בשיחה עם Claude.
macOS או Linux:
curl -sS "https://generativelanguage.googleapis.com/v1beta/voices?type=replicated" -H "x-goog-api-key: YOUR_API_KEY"
Windows PowerShell (כותבים curl.exe, כי curl הוא כינוי לפקודה אחרת):
curl.exe -sS "https://generativelanguage.googleapis.com/v1beta/voices?type=replicated" -H "x-goog-api-key: YOUR_API_KEY"
החליפו YOUR_API_KEY במפתח. בתשובה חפשו את הערך שמתחיל ב-voice_ והעתיקו אותו כולו.
כשמשהו לא עובד
| הודעה שהקול לא נמצא (שגיאה 404) | המפתח הוא מפרויקט אחר | צרו מפתח באותו פרויקט של הקול, והחליפו אותו ב-credential |
| הקול נשמע לא כמו שאתם רגילים | מישהו שינה את הסגנון | בדקו שהסגנון נעול בצומת Normalize Input כמחרוזת קבועה |
| Claude מפעיל את ה-workflow, אבל ההורדה נחסמת | הדומיין לא ברשימה המאושרת, או שצריך שיחה חדשה | הוסיפו את הדומיין ב-Settings, Capabilities, Allowed websites, ופתחו שיחה חדשה |
| השיחה מוצפת בטקסט ארוך | השמע זולג לנתוני הריצה | ודאו שהקריאה ל-Google נמצאת ב-Worker ולא ב-Generate, ושקוראים רק את Result |
ועוד שני דברים: קול שמור נשמר שנה, ואחר כך צריך ליצור אותו מחדש. ולמודל יש מכסה יומית (בזמן הכתיבה, 100 בקשות ביום ב-Tier 1, לכל הפרויקט). אם מופיעה השגיאה Rate limit exceeded, ההודעה עצמה אומרת מתי אפשר לנסות שוב.
שלושה דברים לזכור
- המפתח והקול באותו פרויקט.
- הסגנון נעול.
- טקסט רגיש לא עובר בקישור, כי הוא פתוח לכל מי שמחזיק בו במשך שעה.
קישורים
- AI Studio, יצירת דיבור: https://aistudio.google.com/generate-speech
- יצירת API KEY: https://aistudio.google.com/apikey
- התיעוד של Google על שיבוט קול: https://ai.google.dev/gemini-api/docs/voice-replication
- n8n: https://n8n.io
- Claude: https://claude.ai
- Connectors ב-Claude: https://claude.ai/customize/connectors
- Capabilities בהגדרות של Claude: https://claude.ai/settings/capabilities
- Skills ב-Claude: https://claude.ai/customize/skills
- האתר שלי: https://iftah.dev
הקבצים
הנה כל הקבצים של המערכת במלואם. את התוכן של כל בלוק שומרים בקובץ עם השם שבכותרת שלו. הערכים כמו YOUR_VOICE_ID נשארים בקבצים כפי שהם, ואחרי הייבוא מחליפים אותם לפי הטבלה שלמעלה. אפשר גם להעתיק את תוכן ה-JSON של workflow ולהדביק אותו (Ctrl+V) ישר על קנבס ריק ב-n8n, במקום להשתמש ב-Import from File.
worker.json
ה-workflow "Voice TTS · Worker". קורא ל-Google ושומר את השמע בטבלה. מייבאים אותו בשלב 5.
{
"name": "Voice TTS · Worker",
"nodes": [
{
"id": "478dc871-8280-4bd7-91cd-eb1d621aa40f",
"name": "Worker Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.3,
"position": [
240,
300
],
"parameters": {
"inputSource": "workflowInputs",
"workflowInputs": {
"values": [
{
"name": "text",
"type": "string"
},
{
"name": "style",
"type": "string"
},
{
"name": "token",
"type": "string"
}
]
}
}
},
{
"id": "eb6f617a-d608-4cef-ad64-bbd5b294d947",
"name": "Delete Expired Rows",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
480,
300
],
"parameters": {
"resource": "row",
"operation": "deleteRows",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_voice_tts_files_ID"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "created_at_ms",
"condition": "lt",
"keyValue": "={{ $now.toMillis() - 3600000 }}"
}
]
}
},
"alwaysOutputData": true
},
{
"id": "de1f0477-aab0-4f92-b95e-08840a83b7ec",
"name": "Call Gemini TTS",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.5,
"position": [
720,
300
],
"parameters": {
"method": "POST",
"url": "https://generativelanguage.googleapis.com/v1beta/interactions",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googlePalmApi",
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: \"gemini-3.8-flash-tts\", input: [ { type: \"user_input\", content: [ { type: \"text\", text: $(\"Worker Trigger\").first().json.text, annotations: [ { type: \"speech_metadata\", style: $(\"Worker Trigger\").first().json.style } ] } ] } ], response_format: { type: \"audio\" }, generation_config: { speech_config: [ { voice: \"YOUR_VOICE_ID\" } ] } }) }}",
"options": {
"timeout": 120000
}
},
"executeOnce": true,
"onError": "continueErrorOutput"
},
{
"id": "4c38b8aa-be30-47be-90d3-8088eb30d598",
"name": "Return Error",
"type": "n8n-nodes-base.set",
"typeVersion": 3.5,
"position": [
1200,
440
],
"parameters": {
"mode": "manual",
"includeOtherFields": false,
"assignments": {
"assignments": [
{
"id": "w-error",
"name": "error",
"value": "={{ String($json.error?.description || $json.error?.message || $json.error || \"unknown error\").slice(0, 600) }}",
"type": "string"
}
]
}
}
},
{
"id": "12e535ea-19ab-4bc2-8553-259ee1e344d7",
"name": "Store Audio",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
960,
300
],
"parameters": {
"resource": "row",
"operation": "insert",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_voice_tts_files_ID"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"token": "={{ $(\"Worker Trigger\").first().json.token }}",
"audio_b64": "={{ $json.steps[0].content[0].data }}",
"created_at_ms": "={{ $now.toMillis() }}"
},
"schema": [
{
"id": "token",
"displayName": "token",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "audio_b64",
"displayName": "audio_b64",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "created_at_ms",
"displayName": "created_at_ms",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"canBeUsedToMatch": true
}
]
}
},
"onError": "continueErrorOutput"
},
{
"id": "87f0dfd9-54e4-487e-a5f8-1b607a9705b4",
"name": "Return Result",
"type": "n8n-nodes-base.set",
"typeVersion": 3.5,
"position": [
1200,
200
],
"parameters": {
"mode": "manual",
"includeOtherFields": false,
"assignments": {
"assignments": [
{
"id": "w-chars",
"name": "base64_chars",
"value": "={{ $(\"Call Gemini TTS\").first().json.steps[0].content[0].data.length }}",
"type": "number"
}
]
}
}
}
],
"connections": {
"Worker Trigger": {
"main": [
[
{
"node": "Delete Expired Rows",
"type": "main",
"index": 0
}
]
]
},
"Delete Expired Rows": {
"main": [
[
{
"node": "Call Gemini TTS",
"type": "main",
"index": 0
}
]
]
},
"Call Gemini TTS": {
"main": [
[
{
"node": "Store Audio",
"type": "main",
"index": 0
}
],
[
{
"node": "Return Error",
"type": "main",
"index": 0
}
]
]
},
"Store Audio": {
"main": [
[
{
"node": "Return Result",
"type": "main",
"index": 0
}
],
[
{
"node": "Return Error",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"availableInMCP": true,
"saveDataErrorExecution": "none",
"saveDataSuccessExecution": "none"
}
}
generate.json
ה-workflow "Voice TTS · Generate". הדלת ש-Claude מפעיל. מייבאים אותו בשלב 6.
{
"name": "Voice TTS · Generate",
"nodes": [
{
"id": "6496b8c4-10a3-40ba-8139-3c94d4262c50",
"name": "Receive Request",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.2,
"position": [
240,
300
],
"parameters": {
"httpMethod": "POST",
"path": "voice-tts-generate",
"authentication": "none",
"responseMode": "lastNode",
"responseData": "firstEntryJson"
}
},
{
"id": "c68a5f75-3e2e-4d1e-84ac-6587442e882c",
"name": "Normalize Input",
"type": "n8n-nodes-base.set",
"typeVersion": 3.5,
"position": [
480,
300
],
"parameters": {
"mode": "manual",
"includeOtherFields": false,
"assignments": {
"assignments": [
{
"id": "f-text",
"name": "text",
"value": "={{ $(\"Receive Request\").first().json.body?.text ?? \"\" }}",
"type": "string"
},
{
"id": "f-style",
"name": "style",
"value": "Warm, friendly and clear Israeli Hebrew narration, natural conversational pace, like a community member explaining a quick how-to to friends. English words pronounced in English.",
"type": "string"
}
]
}
},
"executeOnce": false
},
{
"id": "0d8248fe-2818-47f9-b17e-86a4cfe6611d",
"name": "Generate Token",
"type": "n8n-nodes-base.crypto",
"typeVersion": 2,
"position": [
720,
300
],
"parameters": {
"action": "generate",
"encodingType": "hex",
"stringLength": 64
}
},
{
"id": "0143c3cb-c767-44d5-bc2e-7432feac002f",
"name": "Result",
"type": "n8n-nodes-base.set",
"typeVersion": 3.5,
"position": [
1680,
200
],
"parameters": {
"mode": "manual",
"includeOtherFields": false,
"assignments": {
"assignments": [
{
"id": "r-token",
"name": "token",
"value": "={{ $(\"Generate Token\").first().json.data }}",
"type": "string"
},
{
"id": "r-url",
"name": "url",
"value": "=https://YOUR-N8N-DOMAIN/webhook/voice-file?token={{ $(\"Generate Token\").first().json.data }}",
"type": "string"
},
{
"id": "r-chars",
"name": "base64_chars",
"value": "={{ $(\"Call Worker\").first().json.base64_chars }}",
"type": "number"
}
]
}
}
},
{
"id": "0a8b2ece-5867-4719-9ba7-a3f251bae033",
"name": "Call Worker",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.4,
"position": [
1200,
300
],
"parameters": {
"source": "database",
"workflowId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_WORKER_ID"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"text": "={{ $json.text }}",
"style": "={{ $json.style }}",
"token": "={{ $json.data }}"
},
"matchingColumns": [],
"schema": [
{
"id": "text",
"displayName": "text",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
},
{
"id": "style",
"displayName": "style",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
},
{
"id": "token",
"displayName": "token",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"mode": "once",
"options": {}
}
},
{
"id": "562c0840-6540-4682-a715-b707dbd07707",
"name": "Worker Succeeded?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1440,
300
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "c-worker-ok",
"leftValue": "={{ !$json.error && Number($json.base64_chars) > 0 }}",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"rightValue": ""
}
],
"combinator": "and"
}
}
},
{
"id": "4b0e28b4-0205-4328-8f0b-37609f246e34",
"name": "Fail With Worker Error",
"type": "n8n-nodes-base.stopAndError",
"typeVersion": 1,
"position": [
1680,
440
],
"parameters": {
"errorType": "errorMessage",
"errorMessage": "={{ \"Voice generation failed: \" + ($json.error ?? \"no result from worker\") }}"
}
}
],
"connections": {
"Normalize Input": {
"main": [
[
{
"node": "Generate Token",
"type": "main",
"index": 0
}
]
]
},
"Receive Request": {
"main": [
[
{
"node": "Normalize Input",
"type": "main",
"index": 0
}
]
]
},
"Generate Token": {
"main": [
[
{
"node": "Call Worker",
"type": "main",
"index": 0
}
]
]
},
"Call Worker": {
"main": [
[
{
"node": "Worker Succeeded?",
"type": "main",
"index": 0
}
]
]
},
"Worker Succeeded?": {
"main": [
[
{
"node": "Result",
"type": "main",
"index": 0
}
],
[
{
"node": "Fail With Worker Error",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"availableInMCP": true
}
}
download.json
ה-workflow "Voice TTS · Download". מוריד את הקובץ לפי קוד אקראי. מייבאים אותו בשלב 7.
{
"name": "Voice TTS · Download",
"nodes": [
{
"id": "b41af513-0e57-42cf-9636-ab4b973d7ef5",
"name": "Receive Download Request",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.2,
"position": [
240,
300
],
"parameters": {
"httpMethod": "GET",
"path": "voice-file",
"authentication": "none",
"responseMode": "responseNode"
}
},
{
"id": "221c92cc-757a-483d-af3c-5b9722e92dc0",
"name": "Token Format Valid?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
480,
300
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "c-token-format",
"leftValue": "={{ $json.query?.token ?? \"\" }}",
"operator": {
"type": "string",
"operation": "regex"
},
"rightValue": "^[0-9a-f]{64}$"
}
],
"combinator": "and"
}
}
},
{
"id": "e4d722a8-4715-4fca-9acd-b494f143a8d2",
"name": "Find Token Row",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1.1,
"position": [
720,
200
],
"parameters": {
"resource": "row",
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "REPLACE_WITH_voice_tts_files_ID"
},
"matchType": "allConditions",
"filters": {
"conditions": [
{
"keyName": "token",
"condition": "eq",
"keyValue": "={{ $json.query.token }}"
}
]
},
"returnAll": false,
"limit": 1
},
"alwaysOutputData": true
},
{
"id": "a9b31e1d-5c9e-405e-8f68-21030ed8d6a8",
"name": "Row Exists And Fresh?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
960,
200
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "c-fresh",
"leftValue": "={{ !!$json.audio_b64 && Number($json.created_at_ms) > $now.toMillis() - 3600000 }}",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"rightValue": ""
}
],
"combinator": "and"
}
}
},
{
"id": "8399da11-530a-4359-a49a-1ccfb93b3da8",
"name": "Base64 To WAV",
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1.2,
"position": [
1200,
100
],
"parameters": {
"operation": "toBinary",
"sourceProperty": "audio_b64",
"options": {
"mimeType": "audio/wav",
"fileName": "voice.wav"
}
}
},
{
"id": "85d91f61-0391-449b-833f-08b1b597a116",
"name": "Respond Audio",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
1440,
100
],
"parameters": {
"respondWith": "binary",
"responseDataSource": "automatically",
"options": {
"responseCode": 200,
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "audio/wav"
}
]
}
}
}
},
{
"id": "d2933d90-205d-4f09-a5cb-27e90f6897f5",
"name": "Respond 404",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
1200,
400
],
"parameters": {
"respondWith": "noData",
"options": {
"responseCode": 404
}
}
}
],
"connections": {
"Receive Download Request": {
"main": [
[
{
"node": "Token Format Valid?",
"type": "main",
"index": 0
}
]
]
},
"Token Format Valid?": {
"main": [
[
{
"node": "Find Token Row",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond 404",
"type": "main",
"index": 0
}
]
]
},
"Find Token Row": {
"main": [
[
{
"node": "Row Exists And Fresh?",
"type": "main",
"index": 0
}
]
]
},
"Row Exists And Fresh?": {
"main": [
[
{
"node": "Base64 To WAV",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond 404",
"type": "main",
"index": 0
}
]
]
},
"Base64 To WAV": {
"main": [
[
{
"node": "Respond Audio",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"availableInMCP": true,
"saveDataSuccessExecution": "none"
}
}
worker-http-body.txt
הכתובת והביטוי של הצומת Call Gemini TTS, למי שבונה את ה-Worker ביד כמו בסרטון.
Call Gemini TTS - the HTTP Request node in "Voice TTS · Worker"
Method: POST
URL:
https://generativelanguage.googleapis.com/v1beta/interactions
Authentication: Predefined Credential Type > Google Gemini(PaLM) Api > your credential
Send Body: on
Body Content Type: JSON
Specify Body: Using JSON
JSON body - paste the single line below into the JSON field. It starts with {{ and ends with }}, without a leading "=". If the field is in Fixed mode, switch it to Expression. Then replace YOUR_VOICE_ID with your voice id (it starts with voice_):
{{ JSON.stringify({ model: "gemini-3.8-flash-tts", input: [ { type: "user_input", content: [ { type: "text", text: $("Worker Trigger").first().json.text, annotations: [ { type: "speech_metadata", style: $("Worker Trigger").first().json.style } ] } ] } ], response_format: { type: "audio" }, generation_config: { speech_config: [ { voice: "YOUR_VOICE_ID" } ] } }) }}
Options > Timeout: 120000
Settings tab > On Error: Continue (using error output)
Settings tab > Execute Once: on
voice-narration/SKILL.md
זה הקובץ של הסקיל. שמרו את התוכן שלמטה בקובץ בשם SKILL.md בתוך תיקייה בשם voice-narration, החליפו את שני הערכים (YOUR_GENERATE_WORKFLOW_ID ו-YOUR-N8N-DOMAIN), כווצו את התיקייה לקובץ zip והעלו אותו ל-Claude, כמו בשלב 11.
---
name: voice-narration
description: Generates narration or voiceover audio in the user's own cloned voice by calling an n8n workflow that holds the Gemini TTS key. Use whenever the user asks for narration, voiceover, a spoken line, or audio in their voice, for a video, reel, presentation or any other project, in any language.
---
# Narration in the user's cloned voice
The Gemini key lives in n8n. This session has no key and cannot reach Google, so every line goes through the n8n connector. Work one narration line at a time.
## Rules
1. **One `execute_workflow` call per narration line**, on the n8n connector:
- `workflowId`: `YOUR_GENERATE_WORKFLOW_ID`
- `executionMode`: `"manual"`
- `triggerNodeName`: `"Receive Request"`
- `inputs`: `{"webhookData":{"method":"POST","body":{"text":"<the line>"}}}`
The call returns only an execution id.
2. **Poll `get_workflow_execution`** with that `workflowId` and `executionId`, `includeData: true` and `nodeNames: ["Result"]`, until `status` is `success` (a few seconds). Read only `Result`: it holds `token`, `url` and `base64_chars`. Never read other nodes, they flood the chat. If the run ends in error, report the message (`Voice generation failed: ...`) and stop. Do not edit the workflows.
3. **Download and verify.** Right away, because the link expires after 1 hour:
`curl -sS -o line_<i>.wav -w "%{http_code}" "<url>"`
Expect `200`, then check the file with `ffprobe` (a valid 24 kHz mono WAV). If either step fails, retry once. If it fails again, tell the user.
4. **Never send a style, and never write style instructions inside the text.** The workflow uses one locked style and ignores any other. Anything in `text` is spoken aloud, so put only the words to be spoken there, with no tone, pace or stage directions.
## Also
- You cannot hear audio. When you deliver, say the lines are not verified and ask the user to listen for misread words. To redo a line, call the workflow again.
- Anyone holding a download link can fetch that audio for 1 hour. Do not send sensitive text.
- If the download is blocked (curl cannot connect or returns 403), the n8n domain `YOUR-N8N-DOMAIN` is missing from Settings → Capabilities → Allowed websites. Ask the user to add it and open a new chat.
לסיום
אז מה בנינו? Claude מבקש, n8n מחזיק את המפתח, Google מייצרת, והקישור פג אחרי שעה.
אם בניתם את זה, ספרו לי מה יצא לכם. ואם בא לכם לחבר AI גם למוצר או לעסק שלכם, אני ב-https://iftah.dev.


