Skip to content

Instantly share code, notes, and snippets.

@dansleboby
Last active June 22, 2025 15:26
Show Gist options
  • Save dansleboby/b8dacd07ed09dfcd851f7f42f6594136 to your computer and use it in GitHub Desktop.
Save dansleboby/b8dacd07ed09dfcd851f7f42f6594136 to your computer and use it in GitHub Desktop.
Suno.com Aligned Words Fetcher to SRT or LRC file
// ==UserScript==
// @name Suno Aligned Words Fetcher with Auth
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Fetch aligned words with auth and add a button under the image on Suno pages.
// @author Your Name
// @match https://suno.com/song/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const file_type = "lrc"; // lrc ou srt
// Helper function to get the value of a cookie by name
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// Helper function to fetch aligned words data with Bearer token
async function fetchAlignedWords(songId, token) {
const apiUrl = `https://studio-api.prod.suno.com/api/gen/${songId}/aligned_lyrics/v2/`;
try {
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data && data.aligned_words) {
console.log('Aligned words:', data.aligned_words);
return data.aligned_words;
} else {
console.error('No aligned words found.');
}
} catch (error) {
console.error('Error fetching aligned words:', error);
}
}
// Function to add a button under the image
function addButton(imageSrc, alignedWords) {
const imageElements = document.querySelectorAll(`img[src*="${imageSrc}"].w-full.h-full`);
console.log(imageSrc, imageElements);
imageElements.forEach(function(imageElement, k) {
console.log(k, imageElement);
if (imageElement) {
const button = document.createElement('button');
button.innerText = 'Download '+file_type;
button.style.marginTop = '10px';
button.style.zIndex = '9999';
button.style.position = 'absolute';
button.style.bottom = '0';
button.style.left = '0';
button.style.right = '0';
button.style.background = 'gray';
button.style.borderRadius = '5px';
button.style.padding = '10px 6px';
button.addEventListener('click', () => {
const srtContent = file_type === 'srt' ? convertToSRT(alignedWords) : convertToLRC(alignedWords);
const blob = new Blob([srtContent], { type: 'text/'+file_type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'aligned_words.'+file_type;
a.click();
URL.revokeObjectURL(url); // Clean up the URL object
});
imageElement.parentNode.appendChild(button);
} else {
console.error('Image not found.');
}
});
}
// Function to convert aligned words to SRT format
function convertToSRT(alignedWords) {
let srtContent = '';
alignedWords.forEach((wordObj, index) => {
const startTime = formatTime(wordObj.start_s);
const endTime = formatTime(wordObj.end_s);
srtContent += `${index + 1}\n`;
srtContent += `${startTime} --> ${endTime}\n`;
srtContent += `${wordObj.word}\n\n`;
});
return srtContent;
}
// Helper function to format time into SRT format (HH:MM:SS,MS)
function formatTime(seconds) {
const date = new Date(0);
date.setMilliseconds(seconds * 1000); // Convert seconds to milliseconds
const hours = String(date.getUTCHours()).padStart(2, '0');
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
const secs = String(date.getUTCSeconds()).padStart(2, '0');
const milliseconds = String(date.getUTCMilliseconds()).padStart(3, '0');
return `${hours}:${minutes}:${secs},${milliseconds}`;
}
// Function to convert aligned words to LRC format
function convertToLRC(alignedWords) {
let lrcContent = '';
alignedWords.forEach(wordObj => {
const time = formatLrcTime(wordObj.start_s);
lrcContent += `${time}${wordObj.word}\n`;
});
return lrcContent;
}
// Helper function to format time into LRC format [mm:ss.xx]
function formatLrcTime(seconds) {
const date = new Date(0);
date.setMilliseconds(seconds * 1000); // Convert seconds to milliseconds
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
const secs = String(date.getUTCSeconds()).padStart(2, '0');
const hundredths = String(Math.floor(date.getUTCMilliseconds() / 10)).padStart(2, '0'); // Convert milliseconds to hundredths of a second
return `[${minutes}:${secs}.${hundredths}]`;
}
// Main function to run the script
function main() {
const urlParts = window.location.href.split('/');
const songId = urlParts[urlParts.length - 1]; // Get song ID from URL
const imageSrcPattern = songId;
// Get the token from the cookie
const sessionToken = getCookie('__session');
if (!sessionToken) {
console.error('Session token not found in cookies.');
return;
}
// Fetch aligned words and add the button
fetchAlignedWords(songId, sessionToken).then((alignedWords) => {
if (alignedWords) {
addButton(imageSrcPattern, alignedWords);
}
});
}
setTimeout(function() { main(); }, 5000);
})();
@dansleboby
Copy link
Author

Go on https://suno.com/song/ with the song you want and after 5 sec you will see a button to download the file.

@bhkangw
Copy link

bhkangw commented Dec 10, 2024

This is sick! But is it still working? I don't see the download button showing up.

@dansleboby
Copy link
Author

You need to go on a detail page of a song https://suno.com/song/1a042042-b5d5-43e3-8b50-965d1ec0e303 and hard refresh ( I don't support the fake loading) it was also a glitch with the orginal script I just update it. You should see:
image

@mbeach23
Copy link

mbeach23 commented Jan 28, 2025

@dansleboby
I am not experienced in any of this, but have downloaded Tampermonkey and added the above code. When I do a hard reset nothing is happening. Can someone please help me figure this out?

@mbeach23
Copy link

@dansleboby I was able to get it to work, It is downloading the SRT file, but one word per line, Is there a way to change this so that it will export a line of the song at a time?

@dschibait
Copy link

dschibait commented May 25, 2025

love this code, just build in a switch that you can also go for lines (not only words) when using .srt format.
When using lines (lyrics) commands like [Intro] [Outro] etc are removed.
You can go with this .srt file directly on youtube subtitles!

// ==UserScript==
// @name         Suno Aligned Words Fetcher with Auth
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Fetch aligned words with auth and add a button under the image on Suno pages.
// @author       dansleboby + Dschi
// @match        https://suno.com/song/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    const file_type = "srt"; // lrc ou srt
    const use_lyrics = true;

    // Helper function to get the value of a cookie by name
    function getCookie(name) {
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) return parts.pop().split(';').shift();
    }

    // Helper function to fetch aligned words data with Bearer token
    async function fetchAlignedWords(songId, token) {
        const apiUrl = `https://studio-api.prod.suno.com/api/gen/${songId}/aligned_lyrics/v2/`;
        try {
            const response = await fetch(apiUrl, {
                method: 'GET',
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/json'
                }
            });
            const data = await response.json();
            console.log("response json", data);
            if (data && data.aligned_words) {
                if(use_lyrics && file_type == "srt"){
                    console.log('aligned lyrics:', data.aligned_lyrics);
                    return data.aligned_lyrics;
                }
                else{
                    console.log('Aligned words:', data.aligned_words);
                    return data.aligned_words;
                }
            } else {
                console.error('No aligned words found.');
            }
        } catch (error) {
            console.error('Error fetching aligned words:', error);
        }
    }

    // Function to add a button under the image
    function addButton(imageSrc, alignedWords) {
        const imageElements = document.querySelectorAll(`img[src*="${imageSrc}"].w-full.h-full`);
        console.log(imageSrc, imageElements);

        imageElements.forEach(function(imageElement, k) {
            console.log(k, imageElement);
            if (imageElement) {
                const button = document.createElement('button');
                button.innerText = 'Download '+file_type;
                button.style.marginTop = '10px';
                button.style.zIndex = '9999';
                button.style.position = 'absolute';
                button.style.bottom = '0';
                button.style.left = '0';
                button.style.right = '0';
                button.style.background = 'gray';
                button.style.borderRadius = '5px';
                button.style.padding = '10px 6px';

                button.addEventListener('click', () => {
                    const srtContent = file_type === 'srt' ? convertToSRT(alignedWords) : convertToLRC(alignedWords);
                    const blob = new Blob([srtContent], { type: 'text/'+file_type });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = 'aligned_words.'+file_type;
                    a.click();
                    URL.revokeObjectURL(url); // Clean up the URL object
                });

                imageElement.parentNode.appendChild(button);
            } else {
                console.error('Image not found.');
            }
        });
    }

    // Function to convert aligned words to SRT format
    function convertToSRT(alignedWords) {
        let srtContent = '';
        alignedWords.forEach((wordObj, index) => {
            const startTime = formatTime(wordObj.start_s);
            const endTime = formatTime(wordObj.end_s);
            srtContent += `${index + 1}\n`;
            srtContent += `${startTime} --> ${endTime}\n`;
            if(use_lyrics){
                srtContent += `${wordObj.text.replace(/\[.*?\]/g, '')}\n\n`;
            }
            else{
                srtContent += `${wordObj.word}\n\n`;
            }
        });
        return srtContent;
    }

    // Helper function to format time into SRT format (HH:MM:SS,MS)
    function formatTime(seconds) {
        const date = new Date(0);
        date.setMilliseconds(seconds * 1000); // Convert seconds to milliseconds
        const hours = String(date.getUTCHours()).padStart(2, '0');
        const minutes = String(date.getUTCMinutes()).padStart(2, '0');
        const secs = String(date.getUTCSeconds()).padStart(2, '0');
        const milliseconds = String(date.getUTCMilliseconds()).padStart(3, '0');
        return `${hours}:${minutes}:${secs},${milliseconds}`;
    }

     // Function to convert aligned words to LRC format
    function convertToLRC(alignedWords) {
        let lrcContent = '';
        alignedWords.forEach(wordObj => {
            const time = formatLrcTime(wordObj.start_s);
            lrcContent += `${time}${wordObj.word}\n`;
        });
        return lrcContent;
    }

    // Helper function to format time into LRC format [mm:ss.xx]
    function formatLrcTime(seconds) {
        const date = new Date(0);
        date.setMilliseconds(seconds * 1000); // Convert seconds to milliseconds
        const minutes = String(date.getUTCMinutes()).padStart(2, '0');
        const secs = String(date.getUTCSeconds()).padStart(2, '0');
        const hundredths = String(Math.floor(date.getUTCMilliseconds() / 10)).padStart(2, '0'); // Convert milliseconds to hundredths of a second
        return `[${minutes}:${secs}.${hundredths}]`;
    }


    // Main function to run the script
    function main() {
        const urlParts = window.location.href.split('/');
        const songId = urlParts[urlParts.length - 1]; // Get song ID from URL
        const imageSrcPattern = songId;

        // Get the token from the cookie
        const sessionToken = getCookie('__session');
        if (!sessionToken) {
            console.error('Session token not found in cookies.');
            return;
        }

        console.log("fetching song", songId);

        // Fetch aligned words and add the button
        fetchAlignedWords(songId, sessionToken).then((alignedWords) => {
            if (alignedWords) {
                addButton(imageSrcPattern, alignedWords);
            }
        });
    }

    console.log("start suno.ai checking");
    setTimeout(function() { main(); }, 5000);
})();

@CellularDoor
Copy link

CellularDoor commented Jun 3, 2025

[SOLVED]: Any chance to get the short guide on how to use it for newbies? It is not clear where should I get the bearer token. Suno doesn't have an official API afaik, however there are plenty of third party solutions online with questionable credibility. Probably community can share some services with good reputation though. The script that author shared looks amazing and the idea of sharing such tool for free is invaluable but there are some issues for beginners to solve.

Solution: Apologies for my ignorance. It turned out there is no need to use any third party services to obtain API keys, all you need to use is a browser extension called tampermonkey (you can find a link in the upmost part of of a code) and youhave to use the code inside this extension. Doublecheck if your browser allows the developer mode for extensions, tampermonkey provides comprehensive instructions on this matter.

Many thanks to the author - dansleboby for this gem he shared with us.

@dschibait
Copy link

dschibait commented Jun 3, 2025

Install Tampermonkey

  1. If you haven’t already, install the Tampermonkey browser extension for your browser:
    Chrome: https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo
    Firefox: https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/
    Edge: https://microsoftedge.microsoft.com/addons/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo

  2. Open Tampermonkey Dashboard
    Click the Tampermonkey icon in your browser and choose "Dashboard".

  3. Create a new script
    In the dashboard, click the "+" (Create a new script) button in the top-left.

  4. Paste the code
    Delete the default code and paste the full script from this Gist.
    Save the script
    Click File → Save, or press Ctrl + S.

Visit suno.ai and test
Open https://app.suno.ai/ (reload if already open). You should now see additional button to download lyrics — either word by word or per line, depending on your version.

The "bearer token" is used from your cookies see the part:

    // Get the token from the cookie
    const sessionToken = getCookie('__session');
    if (!sessionToken) {
        console.error('Session token not found in cookies.');
        return;
    }

so no need that you find that out, script will auto detect this. Also i didnt found any API documentation, just look into the response from the call and work with it :)

@CellularDoor
Copy link

Install Tampermonkey

  1. If you haven’t already, install the Tampermonkey browser extension for your browser:
    Chrome: https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo
    Firefox: https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/
    Edge: https://microsoftedge.microsoft.com/addons/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo
  2. Open Tampermonkey Dashboard
    Click the Tampermonkey icon in your browser and choose "Dashboard".
  3. Create a new script
    In the dashboard, click the "+" (Create a new script) button in the top-left.
  4. Paste the code
    Delete the default code and paste the full script from this Gist.
    Save the script
    Click File → Save, or press Ctrl + S.

Visit suno.ai and test Open https://app.suno.ai/ (reload if already open). You should now see additional button to download lyrics — either word by word or per line, depending on your version.

The "bearer token" is used from your cookies see the part:

    // Get the token from the cookie
    const sessionToken = getCookie('__session');
    if (!sessionToken) {
        console.error('Session token not found in cookies.');
        return;
    }

so no need that you find that out, script will auto detect this. Also i didnt found any API documentation, just look into the response from the call and work with it :)

@dschibait, thanks for the prompt clarification and for your modified script which can export SRT for lines, I should confess, I have it in use already, great tool!

@AliBassam
Copy link

Where exactly should I be able to see the button?

@dschibait
Copy link

dschibait commented Jun 18, 2025

Where exactly should I be able to see the button?

on a song overview page ... so when clicking on the name of a song, where you see the cover and lyrics (default), the button is grey and inside the cover-image.

Bildschirmfoto 2025-06-18 um 09 03 45

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment