Zie619/n8n-workflows · error · Error

Quote not found

Error message

Quote not found

What it means

Thrown by the 'Prepare Overlay Text (Quote & Author)' Code node that builds an FFmpeg drawtext filter. It reads the quote text from the 'Merge File Selection Data' node via $node[...] and fails when json['Qoute'] is falsy. The misspelled key 'Qoute' is the actual field name used throughout this workflow, so a 'correctly' spelled Quote field will also trigger this error.

Source

Thrown at workflows/Code/1864_Code_Executecommand_Create_Webhook.json:434

        "options": {
          "append": false
        },
        "fileName": "music1.mp3",
        "operation": "write"
      },
      "typeVersion": 1,
      "notes": "This readWriteFile node performs automated tasks as part of the workflow."
    },
    {
      "id": "9fdca64e-79d7-4a58-91dc-4aa9f9b3c4cc",
      "name": "Prepare Overlay Text (Quote & Author)",
      "type": "n8n-nodes-base.code",
      "position": [
        1620,
        20
      ],
      "parameters": {
        "jsCode": "// Define separate configuration for the quote and the author\nconst quoteFont = \"Kanit-Italic.ttf\";      // Font for the quote\nconst quoteFontSize = 70;\nconst authorFont = \"Kanit-Italic.ttf\";         // Font for the author (ensure this supports Thai)\nconst authorFontSize = 50;\nconst fontColor = \"white\";\nconst lineHeightMultiplier = 1.1;\nconst videoWidth = 1080;\nconst margin = 40;  // Gap from left and right edges\n\n// Effective width for the quote text (accounting for left/right margins)\nconst effectiveVideoWidth = videoWidth - 2 * margin;\n\n// Estimate average character width based on quoteFontSize (this is a rough estimate)\nconst avgCharWidth = quoteFontSize * 0.6;\nconst maxCharsPerLine = Math.floor(effectiveVideoWidth / avgCharWidth);\n\n// Retrieve the quote transcript and author from the \"Merge\" node\nconst transcript = $node[\"Merge File Selection Data\"].json[\"Qoute\"];\nif (!transcript) {\n  throw new Error(\"Quote not found\");\n}\nconst author = $node[\"Merge File Selection Data\"].json[\"Author\"];\nif (!author) {\n  throw new Error(\"Author not found\");\n}\n\n// Split the transcript into words and group them into lines based on maxCharsPerLine\nconst words = transcript.split(' ');\nconst lines = [];\nlet currentLine = \"\";\nlet currentCharCount = 0;\n\nwords.forEach(word => {\n  const wordLength = word.length;\n  const additionalSpace = currentLine ? 1 : 0;\n  const potentialLength = currentCharCount + additionalSpace + wordLength;\n  if (potentialLength <= maxCharsPerLine) {\n    currentLine += (currentLine ? \" \" : \"\") + word;\n    currentCharCount = potentialLength;\n  } else {\n    lines.push(currentLine);\n    currentLine = word;\n    currentCharCount = wordLength;\n  }\n});\nif (currentLine) {\n  lines.push(currentLine);\n}\n\n// Calculate layout for the quote block\nconst lineHeight = quoteFontSize * lineHeightMultiplier;\nconst totalHeight = lines.length * lineHeight;\n\n// Build drawtext commands for each quote line (centered horizontally)\n// Each line is positioned so that the entire quote block is vertically centered.\nconst quoteCommands = lines.map((line, index) => {\n  // Escape any single quotes in the line\n  const escapedLine = line.replace(/'/g, \"\\\\'\");\n  return `drawtext=fontfile=${quoteFont}:text='${escapedLine}':fontsize=${quoteFontSize}:fontcolor=${fontColor}:x=(w-text_w)/2:y=((h-${totalHeight})/2)+(${index}*${lineHeight})`;\n});\n\n// Build the drawtext command for the author\n// Place the author text below the quote block with a small gap (e.g. 20 pixels)\n// Align it to the right by setting x = w - text_w - margin.\nconst authorY = `((h-${totalHeight})/2)+(${lines.length}*${lineHeight})+20`;\nconst escapedAuthor = author.replace(/'/g, \"\\\\'\");\nconst authorCommand = `drawtext=fontfile=${authorFont}:text='${escapedAuthor}':fontsize=${authorFontSize}:fontcolor=${fontColor}:x=w-text_w-${margin}:y=${authorY}`;\n\n// Combine all commands (separated by commas) into one drawtext filter string.\nconst fullDrawTextFilter = quoteCommands.concat(authorCommand).join(\", \");\n\n// Return the prepared filter string for insertion into your FFmpeg command.\nreturn {\n  json: {\n    drawText: fullDrawTextFilter\n  }\n};\n"
      },
      "typeVersion": 2,
      "notes": "This code node performs automated tasks as part of the workflow."
    },
    {
      "id": "082cf794-89a9-42cc-b9ee-96792a17893f",
      "name": "Generate Final Video Clip",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        1640,
        340
      ],
      "parameters": {
        "command": "=ffmpeg -i {{ $('Save Video Background Locally').item.json.fileName }} -i {{ $('Save Music Background Locally').item.json.fileName }} -filter_complex \"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[vid]; color=black@0.3:size=1080x1920:d=10[bg]; [vid][bg]overlay=shortest=1[bgvid]; [bgvid]{{ $json.drawText }}[outv]; [1:a]volume=0.8[aout]\" -map \"[outv]\" -map \"[aout]\" -aspect 9:16 -c:v libx264 -c:a aac -shortest output.mp4 -y"
      },
      "typeVersion": 1,
      "notes": "This executeCommand node performs automated tasks as part of the workflow."
    },

View on GitHub (pinned to 94007c1445)

Solutions

  1. Confirm the upstream 'Merge File Selection Data' node output actually contains a first item with a non-empty 'Qoute' field (exact spelling).
  2. If the typo was corrected upstream, update this reader (and the selection node in error 49) to 'Quote' consistently.
  3. Use $('Merge File Selection Data').first().json instead of the legacy $node accessor for predictable item resolution, or read from the current item if the data flows in via connections.
  4. Validate the quote branch earlier (at selection time) so the run fails with error 49's clearer message before FFmpeg setup begins.

Example fix

// before
const transcript = $node["Merge File Selection Data"].json["Qoute"];
if (!transcript) {
  throw new Error("Quote not found");
}

// after
const merged = $('Merge File Selection Data').first().json;
const transcript = (merged["Qoute"] ?? merged["Quote"] ?? '').toString().trim();
if (!transcript) {
  throw new Error(`Quote not found. Merge node payload keys: ${Object.keys(merged).join(', ')}`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

const merged = $('Merge File Selection Data').first().json;
const transcript = (merged["Qoute"] ?? merged["Quote"] ?? '').toString().trim();
const ready = transcript.length > 0;

Type guard

function hasQuote(merged) {
  return Boolean((merged?.["Qoute"] ?? merged?.["Quote"] ?? '').toString().trim());
}

Prevention

When it happens

Trigger: $node['Merge File Selection Data'].json has no 'Qoute' key, or its value is empty string/null/undefined. Causes: the merge node didn't receive quote items (see error 49), the field is spelled 'Quote' in the data, or the value is blank in the source sheet. Note $node[...] returns the node's first output item only — if the quote sat on a later item, it is invisible here.

Common situations: Upstream quote source empty or filtered out; someone 'fixed' the Qoute typo upstream but not in this reader; the merge node emitted the quote on a non-first item; blank cell in the quotes spreadsheet.

Related errors


AI-assisted analysis of Zie619/n8n-workflows@94007c1445 (2026-08-15). Data as JSON: /api/errors/eec3bb933cbe0023. Report an issue: GitHub.