Skip to content

Instantly share code, notes, and snippets.

@kriNon
Last active May 15, 2024 08:41
Show Gist options
  • Save kriNon/60036376f3c61b69573a042107d481e2 to your computer and use it in GitHub Desktop.
Save kriNon/60036376f3c61b69573a042107d481e2 to your computer and use it in GitHub Desktop.
// Mixamo Animation downloadeer
// The following script make use of mixamo2 API to download all anims for a single character that you choose.
// The animations are saved with descriptive long names instead of the short ones used by default by mixamo UI.
//
// This script has been written by [email protected] and the author is not responsible of its usage
//
// How to use this script
// 1. Browse mixamo.com
// 2. Log in
// 3. Open JS console (F12 on chrome)
// 4. Download an animation and get the character ID from the Network tab
// 5. Then past the character id in the "character" variable at beginning of this script
// 6. Copy and paste the full script in the mixamo.com javascript console
// 7. The script will open a new blank page.. you will start to see animations downloading
// 8. keep the blank page opened and keep on pressing "Allow multiple downlaods"
// NOTE. This doesn't really work for me, but it was supposed too
// Chrome will ask you all the time to allow multiple downloads
// You can disable this as follow:
// chrome://settings/ > Advanced > Content > Automatic downloads > uncheck "Do not allow any site to download multiple file automatically"
// CHANGE THIS VAR TO DOWNLOAD ANIMATIONS FOR A DIFFERENT CHARACTER
// const character = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
const character = '2b3be6e3-a811-4a3d-a44e-fa2fa827bc93'
//=================================================================================================
const bearer = localStorage.access_token
var oldAnimId = ""
const getAnimationList = (page) => {
console.log('getAnimationList page=', page);
const init = {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearer}`,
'X-Api-Key': 'mixamo2'
}
};
const listUrl = `https://www.mixamo.com/api/v1/products?page=${page}&limit=96&order=&type=Motion%2CMotionPack&query=`;
return fetch(listUrl, init).then((res) => res.json()).then((json) => json).catch(() => Promise.reject('Failed to download animation list'))
}
// retrieves json.details.gms_hash
const getProduct = (animId, character) => {
console.log('getProduct animId=', animId, ' character=', character);
const init = {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearer}`,
'X-Api-Key': 'mixamo2'
}
};
const productUrl = `https://www.mixamo.com/api/v1/products/${animId}?similar=0&character_id=${character}`;
return fetch(productUrl, init).then((res) => res.json()).then((json) => json).catch(() => Promise.reject('Failed to download product details'))
}
const mydownload = (animId) => {
var CharacterProm;
CharacterProm = getProduct(animId, character).then((json) => json.name);
CharacterProm.then(function(result) {
downloadAnimation(animId, character, result)
})
}
const downloadAnimation = (animId, character, product_name) => {
console.log('downloadAnimation animId=', animId, ' character=', character, ' prod name=', product_name);
// skip packs
if (product_name.indexOf(',') > -1) {
console.log('Skipping pack ', product_name);
return Promise.resolve('Skip pack!');
} else {
return getProduct(animId, character)
.then((json) => json.details.gms_hash)
.then((gms_hash) => {
const pvals = gms_hash.params.map((param) => param[1]).join(',')
const _gms_hash = Object.assign({}, gms_hash, { params: pvals }) // Anim is baked with default param values
return exportAnimation(character, [_gms_hash], product_name)
})
.then((json) => monitorAnimation(character,animId))
.catch(() => Promise.reject("Unable to download animation " + animId))
}
}
const downloadAnimLoop = (o) => {
console.log('downloadAnimLoop');
if (!o.anims.length) {
return downloadAnimsInPage(o.currentPage + 1, o.totPages, o.character); // no anims left, get a new page
}
const head = o.anims[0];
const tail = o.anims.slice(1);
const oldvalue = o.anims
return downloadAnimation(head.id, o.character, head.name)
.then(() => {
o.anims = tail;
downloadAnimLoop(o) //loop
})
.catch(() => {
if(getProduct(head.id, o.character).then((json) => json.type=="MotionPack")){
o.anims = tail;
console.log("Skipping MotionPack");
}else{
o.anims = oldvalue
console.log("Recovering from animation failed to download");
}
return downloadAnimLoop(o) // keep on looping
})
}
var downloadAnimsInPage = (page, totPages, character) => {
console.log('downloadAnimsInPage page=', page, ' totPages', totPages, ' character=', character);
if (page >= totPages) {
console.log('All pages have been downloaded');
return Promise.resolve('All pages have been downloaded');
}
return getAnimationList(page)
.then((json) => (
{
anims: json.results,
currentPage: json.pagination.page,
totPages: json.pagination.num_pages,
character
}))
.then((o) => downloadAnimLoop(o))
.catch((e) => Promise.reject("Unable to download all animations error ", e))
}
const start = () => {
console.log('start');
if (!character) {
console.error("Please add a valid character ID at the beginnig of the script");
return
}
downloadAnimsInPage(1, 110, character);
}
const exportAnimation = (character_id, gmsHashArray, product_name) => {
console.log('Exporting Anim´:' + character_id + " to file:" + product_name)
const exportUrl = 'https://www.mixamo.com/api/v1/animations/export'
const exportBody = {
character_id,
gms_hash: gmsHashArray, //[{ "model-id": 103120902, "mirror": false, "trim": [0, 100], "overdrive": 0, "params": "0,0,0", "arm-space": 0, "inplace": false }],
preferences: { format: "fbx7", skin: "false", fps: "60", reducekf: "0" }, // To download collada use format: "dae_mixamo"
product_name,
type: "Motion"
};
const exportInit = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearer}`,
'X-Api-Key': 'mixamo2',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(exportBody)
}
return fetch(exportUrl, exportInit)
.then((res) => {
switch (res.status) {
case 429:{
//console.log('ERROR 429, Too many requests, looping');
sleep(250)
return exportAnimation(character_id, gmsHashArray, product_name);
} break;
default:
res.json().then((json) => json)
}
})
}
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
let CharacterName = "";
const monitorAnimation = (characterId,animId) => {
if (true)
{
const monitorUrl = `https://www.mixamo.com/api/v1/characters/${characterId}/monitor`;
const monitorInit = {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearer}`,
'X-Api-Key': 'mixamo2'
}
};
return fetch(monitorUrl, monitorInit)
.then((res) => {
switch (res.status) {
case 404: {
const errorMsg = ('ERROR: Monitor got 404 error: ' + res.error + ' message=' + res.message);
console.error(errorMsg);
throw new Error(errorMsg);
} break
case 429:{
//console.log('ERROR 429, Too many requests, looping');
sleep(250)
return monitorAnimation(characterId,animId);
} break;
case 202:
case 200: {
return res.json()
} break
default:
throw new Error('Response not handled', res);
}
}).then((msg) => {
switch (msg.status) {
case 'completed':
//console.log(getNameFromURL(msg.job_result));
//console.log(oldAnimId+".fbx");
var CharacterProm;
CharacterProm = getProduct(animId, characterId).then((json) => json.name);
CharacterProm.then(function(result) {
CharacterName=result
});
console.log(msg.job_result)
console.log(getNameFromURL(msg.job_result))
console.log(CharacterName)
if (getNameFromURL(msg.job_result) == CharacterName+".fbx")
{
//oldAnimId = msg.job_result
console.log('Downloading: ', msg.job_result);
//downloadingTab.location.href = msg.job_result;
download(msg.job_result)
return msg.job_result;
}
return monitorAnimation(characterId,animId);
break;
case 'processing':
console.log('Animation is processing... looping');
return monitorAnimation(characterId,animId);
break;// loop
case 'failed':
default:
const errorMsg = ('ERROR: Monitor status:' + msg.status + ' message:' + msg.message + 'result:' + JSON.stringify(msg.job_result));
console.error(errorMsg);
throw new Error(errorMsg);
}
}).catch((e) => Promise.reject("Unable to monitor job for character " + characterId + e))
}
}
function getNameFromURL(url){
const regex = /\bresponse\-content\-disposition=.*\.fbx\b/gm;
const found = url.match(regex);
const text = decodeURIComponent(found)
return text.substring(51, text.length)
}
const download = (path) => {
// Create a new link
const anchor = document.createElement('a');
anchor.href = path;
anchor.download = getNameFromURL(path);
// Append to the DOM
document.body.appendChild(anchor);
// Trigger `click` event
anchor.click();
// Remove element from DOM
document.body.removeChild(anchor);
};
// Workaround for downloading files from a promise
// NOTE that chrome will detect you are downloading multiple files in a single TAB. Please allow it!
//const downloadingTab = window.open('', '_blank');
start()
@handcraftedcc
Copy link

But also this script isn't working for me but the older version is: https://gist.github.com/gnuton/ec2c3c2097f7aeaea8bb7d1256e4b212

@razvanab
Copy link

I get some errors when i add: gmsHashArray[0].inplace = true;

before - body: JSON.stringify(exportBody)
VM448:154 Uncaught SyntaxError: Unexpected token '['

after - body: JSON.stringify(exportBody)
VM451:155 Uncaught SyntaxError: Unexpected identifier

@Ozzie6935
Copy link

Ozzie6935 commented Sep 24, 2022

is there a way to also download appropriate gif with each animation? image id is in gms_hash, but I can't figure out the cors, cloudfront is refusing.

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