Skip to main content
dparsons85
Braniac
March 13, 2023
Answered

Export XML from After Effects?

  • March 13, 2023
  • 5 replies
  • 17770 views

Is it possible to export an XML from After Effects? The rights team where I work needs these for asset reporting. A list of the assets isn't good enough since they need the data for how long each asset is used and in what order.

 

If exporting an XML from AE isn't possible is there anything equivalent to it that I could use to get the rights team what they need?

 

Thanks!

Correct answer dparsons85

I had a programmer help me write a plugin that will export a .CSV file next to your project with the following info: file names, timecode in, timecode out, and duration (even if they're in precomps). You can import the CSV file into Google Sheets and export a PDF if you need it in that format. You can download it here.

Also, once you get it into Google Sheets you can use the script below to clean up your file list, it'll do things like getting rid of duplicate rows, combine overlapping files, etc... 

Here's how to add the script to your Sheet: go to Extensions > App Scripts, click on the + (Add a file), choose Script, then copy and paste the script below into the code window and save. To install the script go to Extensions > Macros > Import macro > runAllScripts (at the bottom). To run the script go to Extensions > Macros > runAllScripts to run the script. You'll probably get a warning that you have to agree too then after that you have to run the script again for it to actually run. It'll run from the bottom up so scroll down and you can watch it work.

 

function runAllScripts() {
  deleteRowAndColumn();
  removeFileExtensions();
  removeDuplicateRows();
  cleanUpTimecodes();
}

function deleteRowAndColumn() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.deleteRow(1);
  sheet.deleteColumn(5);
}

function removeFileExtensions() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var range = sheet.getDataRange();
  var values = range.getValues();
  var extensionsToRemove = ['.psd', '.jpg', '.mov', '.mp4', '.png', '.jpeg', '.aif'];
  var newValues = values.map(function(row) {
    return row.map(function(cell) {
      var newCell = cell.toString();
      extensionsToRemove.forEach(function(ext) {
        newCell = newCell.replace(ext, '');
      });
      return newCell;
    });
  });
  range.setValues(newValues);
}

function removeDuplicateRows() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var range = sheet.getDataRange();
  var values = range.getValues();
  var numRows = range.getNumRows();
  var uniqueRows = [];
  var duplicateRowsIndexes = [];
  for (var i = 0; i < numRows; i++) {
    var row = values[i];
    var isDuplicate = uniqueRows.some(function(uniqueRow) {
      return row.every(function(cell, index) {
        return cell === uniqueRow[index];
      });
    });
    if (!isDuplicate) {
      uniqueRows.push(row);
    } else {
      duplicateRowsIndexes.push(i + 1);
    }
  }
  duplicateRowsIndexes.reverse().forEach(function(index) {
    sheet.deleteRow(index);
  });
}

function cleanUpTimecodes() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var cleanedData = [];
  var timecodeMap = {};
  data.forEach(function (row) {
    var fileName = row[0];
    var timeIn = row[1];
    var timeOut = row[2];
    var duration = row[3];
    if (timecodeMap.hasOwnProperty(fileName)) {
      var existing = timecodeMap[fileName];
      var existingTimeIn = existing[1];
      var existingTimeOut = existing[2];
      if (isTimecodeLessThanOrEqual(timeIn, existingTimeOut)) {
        if (isTimecodeGreaterThanOrEqual(timeOut, existingTimeOut)) {
          existing[2] = timeOut;
          existing[3] = calculateDuration(existing[1], timeOut);
        }
      } else {
        cleanedData.push(existing);
        timecodeMap[fileName] = row;
      }
    } else {
      timecodeMap[fileName] = row;
    }
  });
  for (var key in timecodeMap) {
    cleanedData.push(timecodeMap[key]);
  }
  sheet.clearContents();
  sheet.getRange(1, 1, cleanedData.length, cleanedData[0].length).setValues(cleanedData);
}

// Helper functions for cleanUpTimecodes
function isTimecodeLessThanOrEqual(time1, time2) {
  return convertTimecodeToFrames(time1) <= convertTimecodeToFrames(time2);
}

function isTimecodeGreaterThanOrEqual(time1, time2) {
  return convertTimecodeToFrames(time1) >= convertTimecodeToFrames(time2);
}

function convertTimecodeToFrames(timecode) {
  var parts = timecode.split(':');
  return parseInt(parts[0]) * 86400 + parseInt(parts[1]) * 3600 + parseInt(parts[2]) * 60 + parseInt(parts[3]);
}

function calculateDuration(timeIn, timeOut) {
  var framesIn = convertTimecodeToFrames(timeIn);
  var framesOut = convertTimecodeToFrames(timeOut);
  var durationInFrames = framesOut - framesIn;
  return convertFramesToTimecode(durationInFrames);
}

function convertFramesToTimecode(frames) {
  var hours = Math.floor(frames / 86400);
  var minutes = Math.floor((frames % 86400) / 3600);
  var seconds = Math.floor((frames % 3600) / 60);
  var framesLeft = frames % 60;
  return pad(hours) + ':' + pad(minutes) + ':' + pad(seconds) + ':' + pad(framesLeft);
}

function pad(number) {
  return ('00' + number).slice(-2);
}

 

I made the scripts for Google Sheets using Chat GPT so if you need to make any changes to it and aren't a programmer feed the current script into Chat GPT and have it write you an updated one. Things you might want to change in this script: the framerate (it's for 24fps projects), the removal of file extensions, the combining of overlapping files/timecodes into a single row.

 

5 replies

New Participant
May 25, 2025

Smooth bounce effect 

dparsons85
dparsons85AuthorCorrect answer
Braniac
August 25, 2024

I had a programmer help me write a plugin that will export a .CSV file next to your project with the following info: file names, timecode in, timecode out, and duration (even if they're in precomps). You can import the CSV file into Google Sheets and export a PDF if you need it in that format. You can download it here.

Also, once you get it into Google Sheets you can use the script below to clean up your file list, it'll do things like getting rid of duplicate rows, combine overlapping files, etc... 

Here's how to add the script to your Sheet: go to Extensions > App Scripts, click on the + (Add a file), choose Script, then copy and paste the script below into the code window and save. To install the script go to Extensions > Macros > Import macro > runAllScripts (at the bottom). To run the script go to Extensions > Macros > runAllScripts to run the script. You'll probably get a warning that you have to agree too then after that you have to run the script again for it to actually run. It'll run from the bottom up so scroll down and you can watch it work.

 

function runAllScripts() {
  deleteRowAndColumn();
  removeFileExtensions();
  removeDuplicateRows();
  cleanUpTimecodes();
}

function deleteRowAndColumn() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.deleteRow(1);
  sheet.deleteColumn(5);
}

function removeFileExtensions() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var range = sheet.getDataRange();
  var values = range.getValues();
  var extensionsToRemove = ['.psd', '.jpg', '.mov', '.mp4', '.png', '.jpeg', '.aif'];
  var newValues = values.map(function(row) {
    return row.map(function(cell) {
      var newCell = cell.toString();
      extensionsToRemove.forEach(function(ext) {
        newCell = newCell.replace(ext, '');
      });
      return newCell;
    });
  });
  range.setValues(newValues);
}

function removeDuplicateRows() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var range = sheet.getDataRange();
  var values = range.getValues();
  var numRows = range.getNumRows();
  var uniqueRows = [];
  var duplicateRowsIndexes = [];
  for (var i = 0; i < numRows; i++) {
    var row = values[i];
    var isDuplicate = uniqueRows.some(function(uniqueRow) {
      return row.every(function(cell, index) {
        return cell === uniqueRow[index];
      });
    });
    if (!isDuplicate) {
      uniqueRows.push(row);
    } else {
      duplicateRowsIndexes.push(i + 1);
    }
  }
  duplicateRowsIndexes.reverse().forEach(function(index) {
    sheet.deleteRow(index);
  });
}

function cleanUpTimecodes() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var cleanedData = [];
  var timecodeMap = {};
  data.forEach(function (row) {
    var fileName = row[0];
    var timeIn = row[1];
    var timeOut = row[2];
    var duration = row[3];
    if (timecodeMap.hasOwnProperty(fileName)) {
      var existing = timecodeMap[fileName];
      var existingTimeIn = existing[1];
      var existingTimeOut = existing[2];
      if (isTimecodeLessThanOrEqual(timeIn, existingTimeOut)) {
        if (isTimecodeGreaterThanOrEqual(timeOut, existingTimeOut)) {
          existing[2] = timeOut;
          existing[3] = calculateDuration(existing[1], timeOut);
        }
      } else {
        cleanedData.push(existing);
        timecodeMap[fileName] = row;
      }
    } else {
      timecodeMap[fileName] = row;
    }
  });
  for (var key in timecodeMap) {
    cleanedData.push(timecodeMap[key]);
  }
  sheet.clearContents();
  sheet.getRange(1, 1, cleanedData.length, cleanedData[0].length).setValues(cleanedData);
}

// Helper functions for cleanUpTimecodes
function isTimecodeLessThanOrEqual(time1, time2) {
  return convertTimecodeToFrames(time1) <= convertTimecodeToFrames(time2);
}

function isTimecodeGreaterThanOrEqual(time1, time2) {
  return convertTimecodeToFrames(time1) >= convertTimecodeToFrames(time2);
}

function convertTimecodeToFrames(timecode) {
  var parts = timecode.split(':');
  return parseInt(parts[0]) * 86400 + parseInt(parts[1]) * 3600 + parseInt(parts[2]) * 60 + parseInt(parts[3]);
}

function calculateDuration(timeIn, timeOut) {
  var framesIn = convertTimecodeToFrames(timeIn);
  var framesOut = convertTimecodeToFrames(timeOut);
  var durationInFrames = framesOut - framesIn;
  return convertFramesToTimecode(durationInFrames);
}

function convertFramesToTimecode(frames) {
  var hours = Math.floor(frames / 86400);
  var minutes = Math.floor((frames % 86400) / 3600);
  var seconds = Math.floor((frames % 3600) / 60);
  var framesLeft = frames % 60;
  return pad(hours) + ':' + pad(minutes) + ':' + pad(seconds) + ':' + pad(framesLeft);
}

function pad(number) {
  return ('00' + number).slice(-2);
}

 

I made the scripts for Google Sheets using Chat GPT so if you need to make any changes to it and aren't a programmer feed the current script into Chat GPT and have it write you an updated one. Things you might want to change in this script: the framerate (it's for 24fps projects), the removal of file extensions, the combining of overlapping files/timecodes into a single row.

 

New Participant
March 24, 2025

Hi dparsons85. Thank you for this script. Unfortuantely I can't get it to work - it only exports the header information, with zero actual information about the clips (name, timecode in/out, duration, path), as far as I can tell. I imported the resulting file into both a Google Sheet doc and Apple Numbers with the same empty result. Any ideas what i might be doing wrong? I got the same result using AE 2023 and AE2024. I reduced the project down to the single compsition I wanted to get the info on, and then selected that comp in the project window before running the script. Many thanks for any time you spend considering this.

dparsons85
Braniac
March 25, 2025

Hmmmm, not sure where things are going wrong for you. Did you follow these steps?

 

  • Download the AE Generate Asset List.jsxbin
  • Put the file in the Scripts UI Panels folder found in Applications/Adobe After Effects 2024/Scripts/ScriptUI Panels 
  • In After Effects, in the title bar menu under Window you'll find the AE Generate Asset List.jsxbin (near the bottom). Open it 
  • Select your timeline and hit Export - this will generate a .csv file next to your project in the Finder 
  • Open a Google Sheet
  • Go to File > Import > Upload and upload the .csv file
New Participant
February 1, 2024

You can use the Bodymovin plugin and from the setting of file> AVD format (.XML). Hope this helps

dparsons85
Braniac
June 18, 2024

This does not work. There's no timcode in info, no timecode out, no duration given.

New Participant
August 24, 2024

And this is why we're out...

OussK
Participating Frequently
March 14, 2023

I don't think that's possible, you may ask some developer to create a script for you that can do this specific request 

Braniac
March 14, 2023

You can use the File/Save As/Save A Copy As XML.

 

This will give you an AEPX file that has readable data about footage and maybe even some other information. You should also save the file as a standard AEP file. APEX files have been problematic in the past.

 

I'm not sure if any of that data would help your billing department, but if they have been working with After Effects artists in the past, they probably have a workflow already setup for bookkeeping. 

dparsons85
Braniac
March 14, 2023

Thanks! Hopefully they can work with the AEPX file. 

 

"but if they have been working with After Effects artists in the past, they probably have a workflow already setup for bookkeeping. "

 

They don't. Everyone else in the department just uses Premiere so we're trying to figure this out.

 

dparsons85
Braniac
March 21, 2023

Unfortunately the .aepx files does not work with Sequence Clip Reporter (the software they use to extract the file names, when they appear on the timeline, and for how long).