-
-
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() |
You have no idea how much your code helped me in my project. I need to download all clips with different combinations of parameters of a animation. Though I don’t really know JavaScript but I managed to do it by modifying your script a little bit. Doing it by hand was a pain. Thank you so much for this script.
Did anyone figure out how to download the animation with the inplace marker checked?
Add this before line 155:
gmsHashArray[0].inplace = true;
But also this script isn't working for me but the older version is: https://gist.github.com/gnuton/ec2c3c2097f7aeaea8bb7d1256e4b212
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
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.
Yeah i only wrote a little JS some years ago. so my JS skills are probably worse then yours :D thanks for the link I'll try it