Liedtext-Transkription_Vorlage
Experte
Dies ist ein Automatisierungsworkflow mit 18 Nodes. Hauptsächlich werden If, Code, Wait, FormTrigger, HttpRequest und andere Nodes verwendet. .SRT-Untertitel- und .LRC-Liedtext-Dateien aus Audiodateien mit Whisper AI und GPT-5-nano erstellen
Voraussetzungen
- •Möglicherweise sind Ziel-API-Anmeldedaten erforderlich
- •OpenAI API Key
Verwendete Nodes (18)
Kategorie
-
Workflow-Vorschau
Visualisierung der Node-Verbindungen, mit Zoom und Pan
Workflow exportieren
Kopieren Sie die folgende JSON-Konfiguration und importieren Sie sie in n8n
{
"id": "ym5RZpXRcp7ZnW8X",
"meta": {
"instanceId": "b1699e1d8ef82aaaaf2eed0ed67f215d7574a625e2d012a1bcd013054b0defdf",
"templateCredsSetupCompleted": true
},
"name": "LyricsTranscribe_TEMPLATE",
"tags": [
{
"id": "5WzUYUnG7iVDJG7q",
"name": "TEMPLATE",
"createdAt": "2025-10-13T19:43:42.665Z",
"updatedAt": "2025-10-13T19:43:42.665Z"
}
],
"nodes": [
{
"id": "d3f1c98a-1ff5-47ae-a68c-432355827779",
"name": "OpenAI-Chat-Modell",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-544,
176
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-5-nano",
"cachedResultName": "gpt-5-nano"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "SejrVHsogrtvT4yC",
"name": "TEMPLATE"
}
},
"typeVersion": 1.2
},
{
"id": "805bc1ef-55d7-4f4c-b82f-8921d7645f4e",
"name": "WhisperTranscribe",
"type": "n8n-nodes-base.httpRequest",
"position": [
-704,
0
],
"parameters": {
"url": "https://api.openai.com/v1/audio/transcriptions",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "file",
"parameterType": "formBinaryData",
"inputDataFieldName": "Audio_File"
},
{
"name": "model",
"value": "whisper-1"
},
{
"name": "response_format",
"value": "verbose_json"
},
{
"name": "timestamp_granularities[]",
"value": "word"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "YOUR API KEY"
},
{
"name": "Content-Type",
"value": "multipart/form-data"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "a4760f60-c22e-4325-abf1-4e34665b4154",
"name": "AudioInput",
"type": "n8n-nodes-base.formTrigger",
"position": [
-864,
0
],
"webhookId": "9ad0442a-b661-487c-8d8a-09a54400de62",
"parameters": {
"options": {},
"formTitle": "Upload audio file (max 25mb)",
"formFields": {
"values": [
{
"fieldType": "file",
"fieldLabel": "Audio File",
"multipleFiles": false,
"requiredField": true,
"acceptFileTypes": ".mp3"
},
{
"fieldType": "radio",
"fieldLabel": "QualityCheck",
"fieldOptions": {
"values": [
{
"option": "YES"
},
{
"option": "NO"
}
]
},
"requiredField": true
}
]
},
"responseMode": "lastNode",
"formDescription": "Here you can upload your audio file and you get subtitles file back."
},
"typeVersion": 2.3
},
{
"id": "3b4b5409-5cbf-41ec-9be0-bdf4eee690d0",
"name": "TimestampMatching",
"type": "n8n-nodes-base.code",
"position": [
-64,
176
],
"parameters": {
"jsCode": "const WhisperTranscribe = $('WhisperTranscribe').first().json;\nconst words = WhisperTranscribe[\"words\"];\nconst lyrics = $json[\"text\"];\n\nconst minWordMatchRatio = 0.7; // tolerance (0-1) for fuzzy matching per line\nconst segments = lyrics.split(/\\r?\\n/).filter(l => l.trim().length > 0);\nconst normalize = s => s.toLowerCase().replace(/[^a-z0-9']/g, \" \").trim();\nconst whisperWords = words.map(w => ({\n ...w,\n norm: normalize(w.word)\n}));\n\nconst result = [];\n\nlet currentIndex = 0;\nfor (const line of segments) {\n const lineWords = normalize(line).split(/\\s+/).filter(Boolean);\n\n // Try to match this line to consecutive words from Whisper\n let startIndex = -1;\n let endIndex = -1;\n let matchCount = 0;\n\n for (let i = currentIndex; i < whisperWords.length; i++) {\n if (lineWords.includes(whisperWords[i].norm)) {\n if (startIndex === -1) startIndex = i;\n endIndex = i;\n matchCount++;\n if (matchCount / lineWords.length >= minWordMatchRatio) break;\n }\n }\n\n if (startIndex !== -1 && endIndex !== -1) {\n const start = whisperWords[startIndex].start;\n const end = whisperWords[endIndex].end;\n result.push({ start, end, text: line });\n currentIndex = endIndex + 1;\n } else {\n // fallback: if not found, approximate based on previous\n const prevEnd = result.length ? result[result.length - 1].end : words[0].start;\n const approxEnd = prevEnd + 2.5; // arbitrary 2.5s window\n result.push({ start: prevEnd, end: approxEnd, text: line });\n }\n}\n\nreturn [{ json: { timedSegments: result } }];"
},
"typeVersion": 2
},
{
"id": "3bdb855d-0dbb-496c-8d44-9050981a344a",
"name": "SubtitlesPreparation",
"type": "n8n-nodes-base.code",
"position": [
272,
176
],
"parameters": {
"jsCode": "const PostProcessedLyrics = $('RoutingQualityCheck').first().json;\nconst plainText = PostProcessedLyrics[\"text\"];\nconst segments = $json[\"timedSegments\"];\n\nfunction toSrtTime(sec) {\n const h = Math.floor(sec / 3600);\n const m = Math.floor((sec % 3600) / 60);\n const s = Math.floor(sec % 60);\n const ms = Math.floor((sec * 1000) % 1000);\n return `${h.toString().padStart(2, \"0\")}:${m\n .toString()\n .padStart(2, \"0\")}:${s.toString().padStart(2, \"0\")},${ms\n .toString()\n .padStart(3, \"0\")}`;\n}\n\nfunction toLrcTime(sec) {\n const m = Math.floor(sec / 60);\n const s = Math.floor(sec % 60);\n const cs = Math.floor((sec % 1) * 100); // centiseconds\n return `[${m.toString().padStart(2, \"0\")}:${s\n .toString()\n .padStart(2, \"0\")}.${cs.toString().padStart(2, \"0\")}]`;\n}\n\n// --- generate SRT ---\nlet srt = \"\";\nsegments.forEach((seg, i) => {\n const start = toSrtTime(seg.start);\n const end = toSrtTime(seg.end);\n srt += `${i + 1}\\n${start} --> ${end}\\n${seg.text.trim()}\\n\\n`;\n});\n\n// --- generate LRC ---\nlet lrc = \"\";\nsegments.forEach(seg => {\n const time = toLrcTime(seg.start);\n lrc += `${time}${seg.text.trim()}\\n`;\n});\n\n// --- return both ---\nreturn [\n {\n json: {\n srtContent: srt.trim(),\n lrcContent: lrc.trim(),\n segmentCount: segments.length,\n plainText\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "5c72a572-a67b-423c-92f4-fedbb0c0dbd6",
"name": "QualityCheck",
"type": "n8n-nodes-base.wait",
"position": [
80,
-16
],
"webhookId": "779090da-886d-406d-8a0e-daa0d91bc74b",
"parameters": {
"resume": "form",
"options": {},
"formTitle": "Lyrics Review",
"formFields": {
"values": [
{
"fieldType": "file",
"fieldLabel": "Corrected lyrics",
"multipleFiles": false,
"acceptFileTypes": ".txt"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "b1ac8f7e-bcb1-4732-b549-b910ec605784",
"name": "RoutingQualityCheck",
"type": "n8n-nodes-base.if",
"position": [
-240,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "38864488-adc7-429d-b65f-ed254d2eeacf",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('AudioInput').item.json.QualityCheck }}",
"rightValue": "YES"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "404c19e5-9351-4abb-aef3-900dbb7a4587",
"name": "DiffMatch + SrcPrep",
"type": "n8n-nodes-base.code",
"position": [
272,
-16
],
"parameters": {
"jsCode": "// Načtení dat\nconst WhisperTranscribe = $('WhisperTranscribe').first().json;\nconst words = WhisperTranscribe[\"words\"];\n\n// Načtení upravených lyrics\nlet lyricsText = items[0].json.lyricsText;\nif (!lyricsText && items[0].binary && items[0].binary.Corrected_lyrics) {\n lyricsText = Buffer.from(items[0].binary.Corrected_lyrics.data, \"base64\").toString(\"utf8\");\n}\n\n// CLEANUP: Odstranění garbage na konci textu\nlyricsText = lyricsText.replace(/\\t\\d+\\s*$/, '').trim();\n\n// Funkce pro normalizaci textu\nfunction normalizeWord(word) {\n return word\n .toLowerCase()\n .replace(/[.,!?;:\"\"\"''—-]/g, '')\n .trim();\n}\n\n// Funkce pro tokenizaci textu na slova\nfunction tokenize(text) {\n return text\n .replace(/\\\\n/g, ' ')\n .replace(/\\n/g, ' ')\n .split(/\\s+/)\n .filter(w => w.length > 0);\n}\n\n// Získání původního textu z words\nconst originalText = words.map(w => w.word).join(' ');\nconst originalWords = tokenize(originalText);\nconst correctedWords = tokenize(lyricsText);\n\n// Levenshtein distance\nfunction levenshtein(a, b) {\n const matrix = [];\n for (let i = 0; i <= b.length; i++) {\n matrix[i] = [i];\n }\n for (let j = 0; j <= a.length; j++) {\n matrix[0][j] = j;\n }\n for (let i = 1; i <= b.length; i++) {\n for (let j = 1; j <= a.length; j++) {\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\n matrix[i][j] = matrix[i - 1][j - 1];\n } else {\n matrix[i][j] = Math.min(\n matrix[i - 1][j - 1] + 1,\n matrix[i][j - 1] + 1,\n matrix[i - 1][j] + 1\n );\n }\n }\n }\n return matrix[b.length][a.length];\n}\n\n// Alignment\nfunction alignWords(original, corrected, timestamps) {\n const m = original.length;\n const n = corrected.length;\n \n const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));\n const backtrack = Array(m + 1).fill(null).map(() => Array(n + 1).fill(null));\n \n for (let i = 0; i <= m; i++) dp[i][0] = i * -1;\n for (let j = 0; j <= n; j++) dp[0][j] = j * -1;\n \n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const origNorm = normalizeWord(original[i - 1]);\n const corrNorm = normalizeWord(corrected[j - 1]);\n \n let matchScore = 0;\n if (origNorm === corrNorm) {\n matchScore = 2;\n } else {\n const dist = levenshtein(origNorm, corrNorm);\n const maxLen = Math.max(origNorm.length, corrNorm.length);\n if (maxLen > 0 && dist / maxLen < 0.3) {\n matchScore = 1;\n } else {\n matchScore = -1;\n }\n }\n \n const match = dp[i - 1][j - 1] + matchScore;\n const del = dp[i - 1][j] - 1;\n const ins = dp[i][j - 1] - 1;\n \n dp[i][j] = Math.max(match, del, ins);\n \n if (dp[i][j] === match) backtrack[i][j] = 'match';\n else if (dp[i][j] === del) backtrack[i][j] = 'delete';\n else backtrack[i][j] = 'insert';\n }\n }\n \n const alignment = [];\n let i = m, j = n;\n \n while (i > 0 || j > 0) {\n if (i === 0) {\n alignment.unshift({\n correctedWord: corrected[j - 1],\n originalIndex: null,\n timestamp: null,\n type: 'inserted'\n });\n j--;\n } else if (j === 0) {\n i--;\n } else {\n const action = backtrack[i][j];\n \n if (action === 'match') {\n alignment.unshift({\n correctedWord: corrected[j - 1],\n originalIndex: i - 1,\n timestamp: timestamps[i - 1],\n type: normalizeWord(original[i - 1]) === normalizeWord(corrected[j - 1]) ? 'match' : 'modified'\n });\n i--; j--;\n } else if (action === 'delete') {\n i--;\n } else {\n let interpolatedTimestamp = null;\n if (i > 0 && i < m) {\n const prevTimestamp = timestamps[i - 1];\n const nextTimestamp = timestamps[i];\n interpolatedTimestamp = {\n start: prevTimestamp.end,\n end: nextTimestamp.start\n };\n }\n \n alignment.unshift({\n correctedWord: corrected[j - 1],\n originalIndex: null,\n timestamp: interpolatedTimestamp,\n type: 'inserted'\n });\n j--;\n }\n }\n }\n \n return alignment;\n}\n\nconst alignment = alignWords(originalWords, correctedWords, words);\n\nconst alignedWords = alignment.map((item, index) => {\n return {\n word: item.correctedWord,\n start: item.timestamp?.start || null,\n end: item.timestamp?.end || null,\n type: item.type,\n originalIndex: item.originalIndex\n };\n});\n\n// ============================================\n// GENEROVÁNÍ .LRC SOUBORU\n// ============================================\n\nfunction formatLRCTime(seconds) {\n if (seconds === null || isNaN(seconds)) return '[00:00.00]';\n const minutes = Math.floor(seconds / 60);\n const secs = (seconds % 60);\n const secsStr = secs.toFixed(2).padStart(5, '0');\n return `[${String(minutes).padStart(2, '0')}:${secsStr}]`;\n}\n\nconst lyricsLines = lyricsText\n .replace(/\\\\n/g, '\\n')\n .split('\\n')\n .map(line => line.trim())\n .filter(line => line.length > 0);\n\nconst lrcLines = [];\nlet wordIndex = 0;\n\nfor (const line of lyricsLines) {\n const lineWords = tokenize(line);\n \n if (lineWords.length === 0) continue;\n \n let lineStart = null;\n let matchedWords = 0;\n const startWordIndex = wordIndex;\n \n for (let i = wordIndex; i < alignedWords.length && matchedWords < lineWords.length; i++) {\n const alignedWord = alignedWords[i];\n const normalizedAligned = normalizeWord(alignedWord.word);\n const normalizedLine = normalizeWord(lineWords[matchedWords]);\n \n if (normalizedAligned === normalizedLine) {\n if (lineStart === null && alignedWord.start !== null) {\n lineStart = alignedWord.start;\n }\n matchedWords++;\n wordIndex = i + 1;\n }\n }\n \n // Fallback pro missing timestamps\n if (lineStart === null) {\n if (lrcLines.length > 0) {\n const lastTime = lrcLines[lrcLines.length - 1].time;\n lineStart = lastTime + 2;\n } else {\n lineStart = 0;\n }\n }\n \n lrcLines.push({\n time: lineStart,\n text: line\n });\n}\n\n// Deduplikace timestampů v LRC\nconst lrcLinesDeduped = [];\nconst usedTimestamps = new Set();\n\nfor (const line of lrcLines) {\n let adjustedTime = line.time;\n let offset = 0;\n \n while (usedTimestamps.has(adjustedTime.toFixed(2))) {\n offset += 0.01;\n adjustedTime = line.time + offset;\n }\n \n usedTimestamps.add(adjustedTime.toFixed(2));\n lrcLinesDeduped.push({\n time: adjustedTime,\n text: line.text\n });\n}\n\nlrcLinesDeduped.sort((a, b) => a.time - b.time);\nconst lrcContent = lrcLinesDeduped\n .map(line => `${formatLRCTime(line.time)}${line.text}`)\n .join('\\n');\n\n// ============================================\n// GENEROVÁNÍ .SRT SOUBORU (VYLEPŠENO)\n// ============================================\n\nfunction formatSRTTime(seconds) {\n if (seconds === null || isNaN(seconds)) return '00:00:00,000';\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const secs = Math.floor(seconds % 60);\n const ms = Math.floor((seconds % 1) * 1000);\n \n return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')},${String(ms).padStart(3, '0')}`;\n}\n\nconst srtEntries = [];\nconst MIN_DURATION = 0.8; // Minimální délka titulku\nconst MAX_DURATION = 5.0; // Maximální délka titulku\nconst CHARS_PER_SECOND = 20; // Rychlost čtení\n\nwordIndex = 0;\n\nfor (let lineIdx = 0; lineIdx < lyricsLines.length; lineIdx++) {\n const line = lyricsLines[lineIdx];\n const lineWords = tokenize(line);\n \n if (lineWords.length === 0) continue;\n \n let lineStart = null;\n let lineEnd = null;\n let matchedWords = 0;\n let firstMatchIdx = null;\n let lastMatchIdx = null;\n \n // Najdeme všechna slova pro tento řádek\n for (let i = wordIndex; i < alignedWords.length && matchedWords < lineWords.length; i++) {\n const alignedWord = alignedWords[i];\n const normalizedAligned = normalizeWord(alignedWord.word);\n const normalizedLine = normalizeWord(lineWords[matchedWords]);\n \n if (normalizedAligned === normalizedLine) {\n if (firstMatchIdx === null) {\n firstMatchIdx = i;\n }\n lastMatchIdx = i;\n \n if (lineStart === null && alignedWord.start !== null) {\n lineStart = alignedWord.start;\n }\n if (alignedWord.end !== null) {\n lineEnd = alignedWord.end;\n }\n matchedWords++;\n }\n }\n \n // Posuneme wordIndex na konec matchů\n if (lastMatchIdx !== null) {\n wordIndex = lastMatchIdx + 1;\n }\n \n // Validace a úprava časů\n if (lineStart !== null && lineEnd !== null) {\n // Zajistíme že end > start\n if (lineEnd <= lineStart) {\n lineEnd = lineStart + MIN_DURATION;\n }\n \n let duration = lineEnd - lineStart;\n const textLength = line.length;\n \n // Výpočet ideální délky podle textu\n const idealDuration = Math.max(MIN_DURATION, textLength / CHARS_PER_SECOND);\n \n // Pokud je duration příliš krátká, prodloužíme\n if (duration < idealDuration) {\n lineEnd = lineStart + idealDuration;\n duration = idealDuration;\n }\n \n // Pokud je příliš dlouhá, zkrátíme\n if (duration > MAX_DURATION) {\n lineEnd = lineStart + MAX_DURATION;\n duration = MAX_DURATION;\n }\n \n // Kontrola překryvu s následujícím titulkem\n if (lineIdx < lyricsLines.length - 1) {\n // Najdeme start dalšího řádku\n let nextStart = null;\n const nextLine = lyricsLines[lineIdx + 1];\n const nextLineWords = tokenize(nextLine);\n let tempMatched = 0;\n \n for (let i = wordIndex; i < alignedWords.length && tempMatched < nextLineWords.length; i++) {\n const alignedWord = alignedWords[i];\n const normalizedAligned = normalizeWord(alignedWord.word);\n const normalizedNext = normalizeWord(nextLineWords[tempMatched]);\n \n if (normalizedAligned === normalizedNext) {\n if (nextStart === null && alignedWord.start !== null) {\n nextStart = alignedWord.start;\n break;\n }\n }\n }\n \n // Pokud by se překrýval s dalším, zkrátíme s malou mezerou\n if (nextStart !== null && lineEnd > nextStart - 0.1) {\n lineEnd = Math.max(lineStart + MIN_DURATION, nextStart - 0.1);\n }\n }\n \n // Finální validace\n if (lineEnd > lineStart && (lineEnd - lineStart) >= 0.1) {\n srtEntries.push({\n start: lineStart,\n end: lineEnd,\n text: line\n });\n }\n }\n}\n\n// Vytvoření SRT formátu\nconst srtContent = srtEntries\n .map((entry, index) => {\n return `${index + 1}\\n${formatSRTTime(entry.start)} --> ${formatSRTTime(entry.end)}\\n${entry.text}\\n`;\n })\n .join('\\n');\n\n// ============================================\n// STATISTIKY\n// ============================================\n\nconst stats = {\n totalOriginal: originalWords.length,\n totalCorrected: correctedWords.length,\n matched: alignment.filter(a => a.type === 'match').length,\n modified: alignment.filter(a => a.type === 'modified').length,\n inserted: alignment.filter(a => a.type === 'inserted').length,\n lrcLinesGenerated: lrcLinesDeduped.length,\n srtEntriesGenerated: srtEntries.length\n};\n\nreturn [{\n json: {\n alignedWords: alignedWords,\n stats: stats,\n correctedLyrics: lyricsText,\n lrcContent: lrcContent,\n srtContent: srtContent\n }\n}];"
},
"typeVersion": 2
},
{
"id": "fcb24b3f-17c1-43a9-9966-bf0e542d1c4c",
"name": "SRT",
"type": "n8n-nodes-base.convertToFile",
"position": [
464,
-16
],
"parameters": {
"options": {},
"operation": "toText",
"sourceProperty": "srtContent",
"binaryPropertyName": "=SrtFile_ {{ $('AudioInput').item.json['Audio File'].filename }}"
},
"typeVersion": 1.1
},
{
"id": "aa743fae-91a0-4275-a940-874399341e98",
"name": "LRC",
"type": "n8n-nodes-base.convertToFile",
"position": [
464,
176
],
"parameters": {
"options": {},
"operation": "toText",
"sourceProperty": "lrcContent",
"binaryPropertyName": "=LRC_FILE_ {{ $('AudioInput').item.json['Audio File'].filename }}"
},
"typeVersion": 1.1
},
{
"id": "6dad3945-e25a-40b9-bac0-fd2d17a1dba7",
"name": "TranscribedLyrics",
"type": "n8n-nodes-base.convertToFile",
"position": [
-64,
-16
],
"parameters": {
"options": {},
"operation": "toText",
"sourceProperty": "text",
"binaryPropertyName": "=TRANSCRIBED_{{ $('AudioInput').item.json['Audio File'].filename }}"
},
"typeVersion": 1.1
},
{
"id": "201a2931-ae46-43bb-85d2-0abc47ca2c98",
"name": "PostProcessing",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
-544,
0
],
"parameters": {
"text": "={{ $json.text }}",
"batching": {},
"messages": {
"messageValues": [
{
"message": "You are helping with preparing song lyrics for musicians. Take the following transcription and split it into lyric-like lines. Keep lines short (2–8 words), natural for singing/rap phrasing, and do not change the wording."
}
]
},
"promptType": "define"
},
"typeVersion": 1.7
},
{
"id": "6a1a5512-f2c3-4b98-b89f-4f328e42422c",
"name": "Notizzettel",
"type": "n8n-nodes-base.stickyNote",
"position": [
-240,
-256
],
"parameters": {
"color": 5,
"width": 416,
"height": 192,
"content": "## QUALITY CONTROL CHECKPOINT\nChoose your path:\n- Auto: Skip to file generation\n- Manual: Download TXT, make corrections, re-upload for timestamp matching"
},
"typeVersion": 1
},
{
"id": "a7725722-9e75-439e-b4c8-bb0bb16520bb",
"name": "Notizzettel1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-544,
-256
],
"parameters": {
"color": 5,
"width": 224,
"height": 224,
"content": "## AI LYRICS SEGMENTATION\nGPT-5-nano formats raw transcription into natural lyric lines (2-8 words per line) while preserving original wording."
},
"typeVersion": 1
},
{
"id": "81fb4fa3-41fe-4750-bdaf-b8518e16acdf",
"name": "Notizzettel2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-848,
-256
],
"parameters": {
"color": 5,
"height": 224,
"content": "## AUDIO INPUT & TRANSCRIPTION\nUpload your vocal track (MP3) and let Whisper AI transcribe it with precise timestamps. Works best with clean vocal recordings."
},
"typeVersion": 1
},
{
"id": "16538472-625d-425a-b374-2ad750a0ac3e",
"name": "Notizzettel3",
"type": "n8n-nodes-base.stickyNote",
"position": [
512,
-272
],
"parameters": {
"color": 5,
"width": 288,
"height": 224,
"content": "## EXPORT READY FILES\nGenerate professional subtitle files:\n- .SRT for YouTube & video platforms\n- .LRC for Musixmatch & streaming services"
},
"typeVersion": 1
},
{
"id": "0169bbce-6ab2-4ff6-9799-9204a3ba0400",
"name": "Notizzettel4",
"type": "n8n-nodes-base.stickyNote",
"position": [
208,
-256
],
"parameters": {
"color": 5,
"width": 272,
"height": 192,
"content": "## SMART TIMESTAMP ALIGNMENT\nAdvanced diff & matching algorithm aligns your corrections with original Whisper timestamps using Levenshtein distance."
},
"typeVersion": 1
},
{
"id": "1bb72b32-f5f0-4a98-801c-ba751f3db0c3",
"name": "Notizzettel5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1328,
-368
],
"parameters": {
"color": 5,
"width": 400,
"height": 592,
"content": "## 🎵 SUBTITLE & LYRICS GENERATOR WITH WHISPER AI\n\nTransform vocal tracks into professional subtitle and lyrics files with AI-powered transcription and intelligent segmentation.\n\nKEY FEATURES:\n- Whisper AI transcription with word-level timestamps\n- GPT-5-nano intelligent lyrics segmentation (2-8 words/line)\n- Optional quality check with manual correction workflow\n- Smart timestamp alignment using Levenshtein distance\n- Dual output: .SRT (YouTube/video) + .LRC (streaming/Musixmatch)\n- No disk storage - download files directly\n- Supports multiple languages via ISO codes\n\nPERFECT FOR:\nMusicians • Record Labels • Content Creators • Video Editors\n\n⚡ Upload MP3 → AI processes → Download professional subtitle files"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "25d9495b-ed66-4b9d-b118-4192b37e8f79",
"connections": {
"a4760f60-c22e-4325-abf1-4e34665b4154": {
"main": [
[
{
"node": "805bc1ef-55d7-4f4c-b82f-8921d7645f4e",
"type": "main",
"index": 0
}
]
]
},
"5c72a572-a67b-423c-92f4-fedbb0c0dbd6": {
"main": [
[
{
"node": "404c19e5-9351-4abb-aef3-900dbb7a4587",
"type": "main",
"index": 0
}
]
]
},
"201a2931-ae46-43bb-85d2-0abc47ca2c98": {
"main": [
[
{
"node": "b1ac8f7e-bcb1-4732-b549-b910ec605784",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "201a2931-ae46-43bb-85d2-0abc47ca2c98",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"3b4b5409-5cbf-41ec-9be0-bdf4eee690d0": {
"main": [
[
{
"node": "3bdb855d-0dbb-496c-8d44-9050981a344a",
"type": "main",
"index": 0
}
]
]
},
"6dad3945-e25a-40b9-bac0-fd2d17a1dba7": {
"main": [
[
{
"node": "5c72a572-a67b-423c-92f4-fedbb0c0dbd6",
"type": "main",
"index": 0
}
]
]
},
"805bc1ef-55d7-4f4c-b82f-8921d7645f4e": {
"main": [
[
{
"node": "201a2931-ae46-43bb-85d2-0abc47ca2c98",
"type": "main",
"index": 0
}
]
]
},
"404c19e5-9351-4abb-aef3-900dbb7a4587": {
"main": [
[
{
"node": "fcb24b3f-17c1-43a9-9966-bf0e542d1c4c",
"type": "main",
"index": 0
},
{
"node": "aa743fae-91a0-4275-a940-874399341e98",
"type": "main",
"index": 0
}
]
]
},
"b1ac8f7e-bcb1-4732-b549-b910ec605784": {
"main": [
[
{
"node": "6dad3945-e25a-40b9-bac0-fd2d17a1dba7",
"type": "main",
"index": 0
}
],
[
{
"node": "3b4b5409-5cbf-41ec-9be0-bdf4eee690d0",
"type": "main",
"index": 0
}
]
]
},
"3bdb855d-0dbb-496c-8d44-9050981a344a": {
"main": [
[
{
"node": "fcb24b3f-17c1-43a9-9966-bf0e542d1c4c",
"type": "main",
"index": 0
},
{
"node": "aa743fae-91a0-4275-a940-874399341e98",
"type": "main",
"index": 0
}
]
]
}
}
}Häufig gestellte Fragen
Wie verwende ich diesen Workflow?
Kopieren Sie den obigen JSON-Code, erstellen Sie einen neuen Workflow in Ihrer n8n-Instanz und wählen Sie "Aus JSON importieren". Fügen Sie die Konfiguration ein und passen Sie die Anmeldedaten nach Bedarf an.
Für welche Szenarien ist dieser Workflow geeignet?
Experte
Ist es kostenpflichtig?
Dieser Workflow ist völlig kostenlos. Beachten Sie jedoch, dass Drittanbieterdienste (wie OpenAI API), die im Workflow verwendet werden, möglicherweise kostenpflichtig sind.
Verwandte Workflows
WordPress-Blog-Automatisierung Professional Edition (Deep Research) v2.1 Markt
Automatisierung der Erstellung von SEO-optimierten Blogs mit GPT-4o, Perplexity AI und mehrsprachiger Unterstützung
If
Set
Xml
+
If
Set
Xml
125 NodesDaniel Ng
Content-Erstellung
KI-generierte Meta-Werbeaktionen aus Produkt-URLs mit OpenAI und Firecrawl erstellen
Erstelle KI-generierte Meta-Werbekampagnen mit OpenAI und Firecrawl aus Produkt-URLs
If
Set
Code
+
If
Set
Code
40 NodesAdam Crafts
Content-Erstellung
n8n-Knoten in der visuellen Referenzbibliothek erkunden
Erkundung von n8n-Knoten in der visuellen Referenzbibliothek
If
Ftp
Set
+
If
Ftp
Set
113 NodesI versus AI
Sonstiges
Erstellung und Veröffentlichung von Instagram-Carousels mit OpenAI gpt-image-1 und KI-Untertiteln
Automatisiertes Erstellen von Instagram-Karussells aus Google Sheets mit GPT-Image-1 und AI-Captions
If
Set
Code
+
If
Set
Code
32 NodesJorge Martínez
Multimodales KI
Apollo-Daten-Scraping und Kontakt-Workflow 1 ✅
Automatisierte Lead-Generierung mit Apollo, AI-Analyse und zeitgesteuerter E-Mail-Nachfassaktion
If
Code
Wait
+
If
Code
Wait
39 NodesDeniz
Content-Erstellung
Haustierladen 4
🐥 KI-Terminassistent für Tierbedarfsgeschäfte
If
Set
Code
+
If
Set
Code
187 NodesBruno Dias
Künstliche Intelligenz
Workflow-Informationen
Schwierigkeitsgrad
Experte
Anzahl der Nodes18
Kategorie-
Node-Typen9
Autor
Václav Čikl
@vencaExterne Links
Auf n8n.io ansehen →
Diesen Workflow teilen