{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "SoTurn Spotify tracks into lyrics posters with Musixmatch and OpenAIng lyrics poster",
  "nodes": [
    {
      "id": "c75dcdee-942c-449b-b185-c5c9e937af29",
      "name": "Match track by metadata in Musixmatch",
      "type": "@musixmatch/n8n-nodes-musixmatch.musixmatchTool",
      "position": [
        448,
        496
      ],
      "parameters": {
        "qTrack": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Track_Name', ``, 'string') }}",
        "qArtist": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Artist_Name', ``, 'string') }}",
        "operation": "matcherTrackGet"
      },
      "credentials": {
        "musixmatchApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0b5ae95f-c49f-404b-be02-b6f7ba5d28e1",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        240,
        496
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.2",
          "cachedResultName": "gpt-5.2"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "f49b7641-3820-47cb-b127-f2b59d660e1f",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -784,
        96
      ],
      "parameters": {
        "width": 400,
        "height": 468,
        "content": "## How it works\nThis workflow generates a printable lyrics poster from any Spotify song. A user submits a Spotify track URL via form, the workflow fetches the song metadata, retrieves lyrics from Musixmatch, selects the most impactful line, and generates a gallery-ready A4 poster using OpenAI image generation.\n\n## Setup steps\n1. **Spotify credentials**: Connect your Spotify app in the \"Get track metadata from Spotify\" node.\n2. **Musixmatch API key**: Add your API key to both Musixmatch tool nodes.\n3. **OpenAI API key**: Connect your OpenAI account in the \"Generate poster with OpenAI\" node.\n4. **Activate**: Click \"Test workflow\" or activate for production use.\n\nThe output is a 1024\u00d71536 portrait image with the selected lyric and song credits."
      },
      "typeVersion": 1
    },
    {
      "id": "20ba9aa3-bcd7-4b80-ba1a-9260816e0b5e",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -272,
        96
      ],
      "parameters": {
        "color": 6,
        "width": 184,
        "height": 144,
        "content": "## Input\nCollects Spotify URL and extracts track ID"
      },
      "typeVersion": 1
    },
    {
      "id": "ac40f1a8-32f9-4891-83f7-d516f66ccb7f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        352,
        96
      ],
      "parameters": {
        "color": 6,
        "width": 296,
        "height": 144,
        "content": "## AI Processing\nMatches track, retrieves lyrics, selects best line, builds image prompt"
      },
      "typeVersion": 1
    },
    {
      "id": "546eb78a-21a4-403b-9443-8599d691b57d",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        96
      ],
      "parameters": {
        "color": 6,
        "width": 188,
        "height": 144,
        "content": "## Output\nGenerates image and returns to user"
      },
      "typeVersion": 1
    },
    {
      "id": "c023bc26-38f7-4ffd-99b9-c9444e11dff8",
      "name": "Collect Spotify URL from form",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -240,
        272
      ],
      "parameters": {
        "options": {},
        "formTitle": "Create your lyrics poster",
        "formFields": {
          "values": [
            {
              "fieldName": "Spotify track URL",
              "fieldLabel": "Spotify track URL",
              "placeholder": "https://open.spotify.com/track/2bwet19EI2xvhAlfMis6mH?si=a9b18878fa4c42be"
            }
          ]
        },
        "responseMode": "lastNode",
        "formDescription": "Paste a valid Spotify song URL to generate a lyrics poster"
      },
      "typeVersion": 2.4
    },
    {
      "id": "7f4ea76a-0710-49ad-824b-25f88d71e27b",
      "name": "Extract Spotify track ID",
      "type": "n8n-nodes-base.code",
      "position": [
        -32,
        272
      ],
      "parameters": {
        "jsCode": "// Template-ready Spotify Track URL -> Track URI converter\n// -------------------------------------------------------\n// What this node does:\n// - Reads a Spotify track link (or a spotify:track:... URI) from the incoming item\n// - Extracts the 22-character track ID\n// - Outputs item.json.track_uri in the format: spotify:track:<trackId>\n//\n// How to use in your own template:\n// 1) Ensure your trigger provides a field containing the Spotify track URL/URI.\n// 2) Set INPUT_FIELD_NAME below to match that field name.\n// 3) Downstream nodes can reference: {{$json.track_uri}}\n//\n// Supported inputs:\n// - https://open.spotify.com/track/<trackId>?...\n// - spotify:track:<trackId>\n//\n// Not supported without an extra HTTP step:\n// - https://spoti.fi/<short>  (short URLs are redirects; you must expand them first)\n\nconst INPUT_FIELD_NAME = 'Spotify track URL'; // Change to your incoming field name if needed\n\nconst items = $input.all();\n\nfor (const item of items) {\n  // Read URL/URI from the expected field, plus a few common fallbacks\n  const source =\n    item.json[INPUT_FIELD_NAME] ??\n    item.json.url ??\n    item.json.spotifyUrl ??\n    item.json.spotify_track_url;\n\n  if (!source) {\n    throw new Error(\n      `Missing Spotify track input. Expected a field named \"${INPUT_FIELD_NAME}\" (or one of the fallbacks: url, spotifyUrl, spotify_track_url).`\n    );\n  }\n\n  // Extract the track ID (22 base62 chars)\n  const match =\n    String(source).match(/open\\.spotify\\.com\\/track\\/([A-Za-z0-9]{22})/) ||\n    String(source).match(/^spotify:track:([A-Za-z0-9]{22})$/);\n\n  if (!match) {\n    if (String(source).includes('spoti.fi/')) {\n      throw new Error(\n        'Short Spotify URL detected (spoti.fi). Add an HTTP Request node to follow redirects and obtain the final open.spotify.com URL before this node.'\n      );\n    }\n    throw new Error('Invalid Spotify track URL/URI. Could not find a 22-character track ID.');\n  }\n\n  const trackId = match[1];\n  item.json.track_uri = `spotify:track:${trackId}`;\n}\n\nreturn items;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ca818415-08d5-423a-b64a-4f40afef695e",
      "name": "Get track metadata from Spotify",
      "type": "n8n-nodes-base.spotify",
      "position": [
        192,
        272
      ],
      "parameters": {
        "id": "={{ $json.track_uri }}",
        "resource": "track",
        "operation": "get"
      },
      "credentials": {
        "spotifyOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f812d8b3-3b07-4571-8889-eac8ebaff7e4",
      "name": "Select lyric and build image prompt",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        384,
        272
      ],
      "parameters": {
        "text": "={{ $json.name }} by {{ $json.album.artists[0].name }}",
        "options": {
          "systemMessage": "=You are a visual artist and creative director agent whose sole responsibility is to produce a complete, detailed image-generation prompt for a song-quote poster.\n\nYou do not generate images.\nYou only return the final image-generation prompt text.\n\nYou have access to:\n\t\u2022\tmatch_track_by_metadata \u2014 identify the correct song\n\t\u2022\tget_lyrics \u2014 retrieve full lyrics\n\nYou must follow the workflow exactly and in order.\nNo partial output. No drafts. No explanations.\n\n\u2e3b\n\nMANDATORY WORKFLOW\n\n1) Identify the track\n\nUse match_track_by_metadata with the provided song title and artist name. Confirm the correct track.\n\n\u2e3b\n\n2) Retrieve lyrics\n\nCall get_lyrics to retrieve the full lyrics.\n\n\u2e3b\n\n3) Select the quote (strict rules)\n\nSelect one single-line lyric excerpt that best represents the emotional or thematic core of the song.\n\nHard constraints (non-negotiable):\n\t\u2022\tExactly one line\n\t\u2022\tNo line breaks\n\t\u2022\tMaximum 90 characters\n\t\u2022\tCopied verbatim from the lyrics\n\t\u2022\tDo NOT wrap the line in quotation marks\n\t\u2022\tDo not default to the chorus unless it is clearly the strongest line\n\nThis line is the only lyric text used in the poster.\n\n\u2e3b\n\n4) Interpret meaning\n\nAnalyze the selected line for:\n\t\u2022\tEmotional subtext\n\t\u2022\tMood and tension\n\t\u2022\tImplied narrative or feeling\n\nThink emotionally and conceptually, not literally.\n\n\u2e3b\n\n5) Construct the image-generation prompt\n\nReturn a single, complete, self-contained, highly detailed image-generation prompt.\n\t\u2022\tAll required sections must be present\n\t\u2022\tTarget length: 250\u2013450 words\n\t\u2022\tIf any section is missing or incomplete, the output is invalid\n\n\u2e3b\n\nREQUIRED IMAGE-GENERATION PROMPT STRUCTURE\n\n(Output exactly this structure, with headings)\n\n\u2e3b\n\nSECTION 1 \u2014 Song Metadata (Reference Only)\n\t\u2022\tSelected lyric (single line, no quotes):\n\t\u2022\tSong title:\n\t\u2022\tArtist name:\n\n\u2e3b\n\nSECTION 2 \u2014 Concept & Emotional Interpretation\n\nDescribe the emotional meaning and atmosphere of the lyric. Focus on mood, implication, and tone rather than literal storytelling.\n\n\u2e3b\n\nSECTION 3 \u2014 Style Choice (Adaptive)\n\nChoose the most appropriate visual style based on the lyric:\n\t\u2022\tAbstract / modern\n\t\u2022\tFigurative / conceptual\n\t\u2022\tGraphic / design-led\n\nThe style must feel modern, witty, and intentional.\nDo not default to abstraction if a clearer figurative idea fits better.\n\n\u2e3b\n\nSECTION 4 \u2014 Visual Direction & Composition\n\t\u2022\tPoster format inspired by high-end contemporary design\n\t\u2022\tStrong focal point and controlled negative space\n\t\u2022\tIntentional composition with visual balance\n\t\u2022\tNo borders, frames, watermarks, or signatures\n\t\u2022\tNo random artifacts or meaningless objects\n\n\u2e3b\n\nSECTION 5 \u2014 Format, Texture & Finish\n\t\u2022\tAspect ratio: A4 paper size, portrait orientation\n\t\u2022\tApply a very subtle, uniform film-like grain across the entire poster\n\t\u2022\tGrain must be fine, understated, and non-destructive\n\t\u2022\tMust not reduce text sharpness or legibility\n\n\u2e3b\n\nSECTION 6 \u2014 Typography & Legibility (Strict)\n\nAll text must be perfectly legible. This is mandatory.\n\nMain lyric text:\n\t\u2022\tIntegrated into the artwork (negative space, shapes, composition)\n\t\u2022\tHigh contrast against background\n\t\u2022\tClean, modern typography (sans-serif or refined serif)\n\t\u2022\tNo quotation marks\n\t\u2022\tNo warping, breaking, masking, or textural interference\n\t\u2022\tNo blur, erosion, or overlapping elements\n\nSong title & artist (bottom-left corner):\n\t\u2022\tSong title in small italic\n\t\u2022\tArtist name directly below in bold, same small size\n\t\u2022\tMust be fully visible, unobstructed, and sharp\n\t\u2022\tNo objects, shapes, gradients, textures, or grain overlays may pass over or under this text\n\t\u2022\tClear margin around metadata to guarantee readability\n\n\u2e3b\n\nSECTION 7 \u2014 Quality Bar & Constraints\n\t\u2022\tContemporary, gallery-ready poster\n\t\u2022\tEmotionally resonant, clever, confident\n\t\u2022\tFeels designed, not auto-generated\n\nNever do the following:\n\t\u2022\tNever place quotation marks around the lyric\n\t\u2022\tNever obscure or partially hide the song title or artist\n\t\u2022\tNever sacrifice legibility for style\n\t\u2022\tNever introduce extra symbols, pseudo-text, or filler objects\n\n\u2e3b\n\nFINAL OUTPUT RULES\n\t\u2022\tOutput only the complete image-generation prompt\n\t\u2022\tInclude all sections\n\t\u2022\tNo meta commentary\n\t\u2022\tNo partial completion"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "b2a94d26-069a-46ab-aeed-2e3af43cf504",
      "name": "Get lyrics from Musixmatch",
      "type": "@musixmatch/n8n-nodes-musixmatch.musixmatchTool",
      "position": [
        592,
        496
      ],
      "parameters": {
        "operation": "trackLyricsGet",
        "commonTrackId": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Common_Track_ID', ``, 'string') }}"
      },
      "credentials": {
        "musixmatchApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6aa0f26c-4585-4896-aca3-6d16435b0831",
      "name": "Generate poster with OpenAI",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        752,
        272
      ],
      "parameters": {
        "model": "gpt-image-1",
        "prompt": "={{ $json.output }}",
        "options": {
          "size": "1024x1536",
          "quality": "high"
        },
        "resource": "image"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "ef650269-cf70-44e3-bf32-f49a4a91007d",
      "name": "Return poster to user",
      "type": "n8n-nodes-base.form",
      "position": [
        960,
        272
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "respondWith": "returnBinary",
        "completionTitle": "Your poster is ready",
        "completionMessage": "Find the poster in your download folder!"
      },
      "typeVersion": 2.4
    }
  ],
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "staticData": null,
  "connections": {
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Select lyric and build image prompt",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract Spotify track ID": {
      "main": [
        [
          {
            "node": "Get track metadata from Spotify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get lyrics from Musixmatch": {
      "ai_tool": [
        [
          {
            "node": "Select lyric and build image prompt",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Generate poster with OpenAI": {
      "main": [
        [
          {
            "node": "Return poster to user",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect Spotify URL from form": {
      "main": [
        [
          {
            "node": "Extract Spotify track ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get track metadata from Spotify": {
      "main": [
        [
          {
            "node": "Select lyric and build image prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select lyric and build image prompt": {
      "main": [
        [
          {
            "node": "Generate poster with OpenAI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match track by metadata in Musixmatch": {
      "ai_tool": [
        [
          {
            "node": "Select lyric and build image prompt",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    }
  },
  "triggerCount": 0
}