Skip to content

Instantly share code, notes, and snippets.

@gnuton
Last active January 22, 2024 07:29
Show Gist options
  • Star 41 You must be signed in to star a gist
  • Fork 21 You must be signed in to fork a gist
  • Save gnuton/ec2c3c2097f7aeaea8bb7d1256e4b212 to your computer and use it in GitHub Desktop.
Save gnuton/ec2c3c2097f7aeaea8bb7d1256e4b212 to your computer and use it in GitHub Desktop.
Script which downloads all mixamo animations for one character.
// Mixamo Animation downloadeer
//
// Author: Antonio Aloisio <gnuton@gnuton.org>
// 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 gnuton@gnuton.org 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()
@MaximasPrime
Copy link

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

@AdirRahamim
Copy link

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?

@gnuton
Copy link
Author

gnuton commented Sep 10, 2020

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

@AdirRahamim
Copy link

AdirRahamim commented Sep 10, 2020

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!

@gnuton
Copy link
Author

gnuton commented Sep 10, 2020 via email

@AdirRahamim
Copy link

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!

@gnuton
Copy link
Author

gnuton commented Oct 4, 2020 via email

@aliimanario
Copy link

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.

@Skaster87
Copy link

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
Copy link
Author

gnuton commented Nov 24, 2020 via email

@aliimanario
Copy link

aliimanario commented Nov 28, 2020 via email

@GOvEy1nw
Copy link

GOvEy1nw commented Sep 2, 2021

@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.

@kriNon
Copy link

kriNon commented Jan 9, 2022

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

@handcraftedcc
Copy link

For inplace animations add this before line 155:
gmsHashArray[0].inplace = true;

@mjoe67886
Copy link

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

@mjoe67886
Copy link

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

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