-
-
Save gnuton/ec2c3c2097f7aeaea8bb7d1256e4b212 to your computer and use it in GitHub Desktop.
// Mixamo Animation downloadeer | |
// | |
// Author: Antonio Aloisio <[email protected]> | |
// Contributions: kriNon | |
// | |
// 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 = 'ef7eb018-7cf3-4ae1-99ac-bab1c2c5d419' | |
//================================================================================================= | |
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 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); | |
o.anims = tail; | |
return downloadAnimation(head.id, o.character, head.name) | |
.then(() => downloadAnimLoop(o)) //loop | |
.catch(() => { | |
console.log("Recovering from animation failed to downlaod"); | |
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 downlaoded'); | |
} | |
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, 100, 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: "30", 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(500) | |
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); | |
} | |
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(500) | |
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': | |
if (oldAnimId != msg.job_result) | |
{ | |
oldAnimId = msg.job_result | |
console.log('Downloading: ', msg.job_result); | |
downloadingTab.location.href = msg.job_result; | |
return msg.job_result; | |
} | |
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)) | |
} | |
} | |
// 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() |
Hi, I use this script to download from Mixamo website, however I have a strange problem: it downloads arbitrary number of animations and I can't figure out why - for one model it downloads 2280 animations, for another 2150, and sometimes even about 1500. I pretty new to JS and can't figure out what causes this problem, do you have any idea?
Hi,
It's long time I do not run this script. it download all the animations in the page skipping the collections IIRC.
it downloads the animations for the first 100 pages, see "downloadAnimsInPage(1, 100, character);"
you can try to replace 100 with an higher number and see if you get a more close number of anims per character.
let met know if that helps
Hi, actually I changed it to 104 since I saw this is the last page number, and the problem occurs with that number. Another issue I now noticed is that it downloads a lot of duplicates, i.e it sometimes downloads the same animation several times. Thank you for your help!
Hi, after I clean all the duplicates I remain with only 200-300 animations(compare to more than 2400 available in Mixamo). So instead of downgliding unique animations, it downloads the same one over and over again, I try over 2 weeks to fix the problem, but I'm not that expert in JS and I cant solve it, is there any chance you can look to it and see if you know where the problem comes from? it will be very helpful because the difference from 200 to 2400 is very big. Thanks!
Thank you very much. You're a very clever coder. Your code helps me. with the code that you make work easy and not complicated. Hopefully the code will always update again. And always healthy. Greetings from Indonesia.
The script throws a 429 error, API has a rate limit. When rate limit is hit, loop downloads same animation until rate time expires.
@gnuton please can you update the script to include a delay in downloading the next animation? I've tried using this multiple times & while it's great, the duplicate downloads are causing it to bypass some downloads, leaving around 1000 animations unobtainable via this method. Unfortunately I don't know JS enough to add this in.
Yo. I made a few fixes to the original script. Posting it here in case it helps people.
This version prevents the script from downloading multiple copies of a file, and when the API throws a 429 error it will wait 0.5 seconds and try again (until it eventually works).
https://gist.github.com/kriNon/60036376f3c61b69573a042107d481e2
For inplace animations add this before line 155:
gmsHashArray[0].inplace = true;
Hi, actually I changed it to 104 since I saw this is the last page number, and the problem occurs with that number. Another issue I now noticed is that it downloads a lot of duplicates, i.e it sometimes downloads the same animation several times. Thank you for your help!
I think it does that cause there's more than one like "jumping down" for example
If anybody is using this script try it in Microsoft Edge I did it in windows 11 and it worked just fine I got them all in Chrome it was a little hairy
Change the last line before start to
const downloadingTab = window.open('http://google.com', '_blank');
and set google.com to auto download files.. There is no option to enable auto download from about blank