Texture meta-data

From Second Life Wiki
Jump to navigation Jump to search

On upload, j2c textures will have all CME segments in the main header removed to be replaced by a single CME segment. That segment will be serialized as an unordered key-value url query string with the following keys and associated values.

The agent_id of the resident that uploaded the texture.
The utc time the texture was uploaded in YYYYmmddHHMMSS format.
The original width of the image as reported by the viewer. This value must be interpretable as an integer greater than 0 and less than 1,000,000.
The original height of the image as reported by the viewer. This value must be interpretable as an integer greater than 0 and less than 1,000,000.
The average color of the image as calculated during verification. The value will be RRGGBBAA in hex with 00 being none and ff representing full saturation of that component. If the image only has three components, then alpha is set to ff. If there are more than four components, the extra components are skipped.


The actual encoded comment string will look something like:


tool support

The j2c_comments.py script understands j2c codestream CME markers and will parse for them in the main header. To use it, pass a list of j2c codestream file names on the command line. The script will emit the file name parsed and report on comments found for each. If the example above is in test.texture, the session would look something like:

$ ./j2c_comments.py test.texture
test.texture: {

The tool will emit error messages if the codestream could not be understood, or if the comment is not latin1. Comments which do not conform to the query string format will be treated as a plain, raw latin1 string.


#!/usr/bin/env python
Extract out all of the j2c CME markers

import cgi
import optparse
import struct

class NotJ2C(Exception):

def parse_cme(filename):
    j2c = file(filename, 'rb')
    comments = None
    while True:
        marker_bytes = j2c.read(2)
        marker, marker_type = struct.unpack('BB', marker_bytes)
        if marker != 0xff:
            raise NotJ2C
        if marker_type == 0x90:
            # we found the SOT, so we're done.
            return comments
        elif marker_type == 0x4f:
            # this is SOC, which does not have a lsoc element
        size_bytes = j2c.read(2)
        size = struct.unpack('!H', size_bytes)[0] - 2

        if marker_type == 0x64:
            # found a CME. read it, and interpret it.
            rcme = j2c.read(2)
            rcme = struct.unpack('!H', rcme)[0]
            if rcme == 0:
                new_comment = '<binary>'
            elif rcme == 1:
                raw_comment = j2c.read(size - 2)
                new_comment = cgi.parse_qs(raw_comment)
                if len(new_comment) == 0:
                    new_comment = raw_comment
                new_comment = '<unknown>'

            if comments is None:
                comments = new_comment
            elif type(comments) == 'list':
                comments = [comments, new_comments]

        j2c.seek(size, 1)

def main():
    parser = optparse.OptionParser(
        usage="usage: %prog [options] file_list",
        description="Read through file list and extract the CME field")
    options, args = parser.parse_args()
    if len(args) < 1:
        parser.error("Please specify one or more files to assign.")
    for filename in args:
            comments = parse_cme(filename)
            print filename + ': ' + str(comments)
        except NotJ2C:
            print filename + ': Unable to parse file'

if __name__ == "__main__":