-
-
Save dansleboby/b8dacd07ed09dfcd851f7f42f6594136 to your computer and use it in GitHub Desktop.
// ==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); | |
})(); |
This is sick! But is it still working? I don't see the download button showing up.
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:
@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?
@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?
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);
})();
[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.
Install Tampermonkey
-
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 -
Open Tampermonkey Dashboard
Click the Tampermonkey icon in your browser and choose "Dashboard". -
Create a new script
In the dashboard, click the "+" (Create a new script) button in the top-left. -
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 :)
Install Tampermonkey
- 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- Open Tampermonkey Dashboard
Click the Tampermonkey icon in your browser and choose "Dashboard".- Create a new script
In the dashboard, click the "+" (Create a new script) button in the top-left.- 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!
Where exactly should I be able to see the button?
Go on https://suno.com/song/ with the song you want and after 5 sec you will see a button to download the file.