[Request] llGetAssetDetails(key UUID, integer asset_type)
Journey Bunny
This function returns public metadata about an asset, and it is an essential component of scripting in Second Life!
How essential?
This functionionalty is not something we've lived without for 20+ years; instead, it is so critical that we have continuously needed to use store extra data and use larger LSL scripts--and more sim memory--to get around this missing function.These are each so essential that we all must constantly jump through data storing and loading hoops constantly to accomplish things like:
Sound:
Trivially, every game might eg, play a game-start sound, then loop the game's tune, then play the game over sound). Game controls etc might be tied to the timing of the music. But you can't do this in Second Life scripting without knowing the length of the sound files. We do silly things like notecards with the length info and we increase the size of a simple music-playing script significantly
by adding dataserver handling. (And we lose a lot of flexibility to swap sounds, since we have to hard-code file sizes)Images:
Re-texturing a surface and getting the aspect ratio and scale correct--again, we need to write down the size (width and height) of the asset. Particularly with PBR's different uv setup, we need to know if we have non-square images!Animations:
Just like sounds, completely essential to know their length in order to string together sequences dynamically. Essential but unknowable, so we again hard-code or write data-loader scripts.Useage:
So, I would like to see llGetAssetDetails return a list of elements related to the asset-type requested. For example, llGetAssetDetails(TEXTURE_PLYWOOD , ASSET_TEXTURE) would return a list of [512,512]
Sound files would have the sound length
Animation files would have play length (and maybe priority?)
Benefit and consideration:
The advantage to this system: if there's ever sufficient justification to add more details for an asset, tack them onto the list, it's pretty backwards-compatible. If there's justification to add another asset that has useful metadata, add an integer type.
This data is needed but even better it's already
not
sensitive. Ironically, anyone and everyone can have it anywhere except Second Life. Eg, it is trivial to use the CDN api to fetch this info. (I just fetched the width and height of the Plywood texture in the example above using curl). The http request functionality in lsl cannot do this: the simulator blocks incoming datatypes that aren't text--eg, all the media files--with a 499 error and empty body. So, the alternative would be to run a proxy service that does this for people but...if the data is publicly available (it is) then why can't these pieces of metadata be used in-world? It would massively simplify dance/music/animesh/gaming etc.Future:
Other assets might be interesting to fetch basic data about, like Notecard size (useful to know how many bytes before you try loading it). I think by establishing this function and having a signature that accepts UUID and which "type" of asset, we establish a flexible future-minded capability where any new asset metadata that can be proven to be both useful
and
safe to be known by all could be added without breaking past content.Technical challenges:
Textures pre-2009 have the w and h written a little differently. The function, under the hood, would invoke 2 different methods. Sounds have the sampling rate and number of samples present, so the length is calculated rather than written in the metadata.
I am unable to fit code for sound and textures (which I already have tested in Python) into this request, so...I'll reply to myself so you can see!
Log In
Journey Bunny
Here's a look at the working, publicly available metadata today (Sorry, I'm on a Python kick)
Code for texture
We only need the header:
``
curl.exe -s --range 0-255 "<CDN ENDPOINT URL>/?texture_id=89556747-24cb-43ed-920b-47caed15465f" -o header.bin
``After fetching header:
import struct
data = open("header.bin", "rb").read()
print("First 52 bytes (SOC + SIZ segment):")
for i in range(0, 52, 16):
chunk = data[i:i+16]
hex_part = " ".join(f"{b:02x}" for b in chunk)
asc_part = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
print(f" {i:04x} {hex_part:<47} |{asc_part}|")
print()
print("Annotated:")
print(f" [0000-0001] {data[0:2].hex()} FF 4F = SOC marker")
print(f" [0002-0003] {data[2:4].hex()} FF 51 = SIZ marker")
print(f" [0004-0005] {data[4:6].hex()} Lsiz = length = {struct.unpack_from('>H', data, 4)[0]}")
print(f" [0006-0007] {data[6:8].hex()} Rsiz = capabilities")
print(f" [0008-000b] {data[8:12].hex()} Xsiz = WIDTH = 0x{struct.unpack_from('>I', data, 8)[0]:08x} = {struct.unpack_from('>I', data, 8)[0]} px")
print(f" [000c-000f] {data[12:16].hex()} Ysiz = HEIGHT = 0x{struct.unpack_from('>I', data, 12)[0]:08x} = {struct.unpack_from('>I', data, 12)[0]} px")
Output:
First 52 bytes (SOC + SIZ segment):
0000 ff 4f ff 51 00 2f 00 00 00 00 02 00 00 00 02 00 |.O.Q./..........|
0010 00 00 00 00 00 00 00 00 00 00 02 00 00 00 02 00 |................|
0020 00 00 00 00 00 00 00 00 00 03 07 01 01 07 01 01 |................|
0030 07 01 01 ff 52 00 0c 00 00 00 05 01 05 04 04 00 |....R...........|
Annotated:
[0000-0001] ff4f FF 4F = SOC marker
[0002-0003] ff51 FF 51 = SIZ marker
[0004-0005] 002f Lsiz = length = 47
[0006-0007] 0000 Rsiz = capabilities
[0008-000b] 00000200 Xsiz = WIDTH = 0x00000200 = 512 px
[000c-000f] 00000200 Ysiz = HEIGHT = 0x00000200 = 512 px
Journey Bunny
Code for Sound
Little trickier, we can't just get the head. So:
import struct, urllib.request
def get_sound_duration(uuid):
url = f"<CDN ENDPOINT URL>/?sound_id={uuid}"
# Request 1 — head bytes, also tells us file size
req = urllib.request.Request(url, headers={"Range": "bytes=0-63"})
with urllib.request.urlopen(req) as r:
head = r.read(64)
content_range = r.headers.get("Content-Range", "")
# "bytes 0-63/198176" → split on "/" → 198176
F = int(content_range.split("/")[1])
num_segs = head[26]
v = 27 + num_segs
sample_rate = struct.unpack_from("<I", head, v + 12)[0]
# Request 2 — tail window, now that we know F
tail_start = F - 4096
tail_end = tail_start + 2047
req = urllib.request.Request(url, headers={"Range": f"bytes={tail_start}-{tail_end}"})
with urllib.request.urlopen(req) as r:
tail = r.read(2048)
pos = tail.rfind(b'OggS')
granule = struct.unpack_from("<q", tail, pos + 6)[0]
duration = granule / sample_rate
return duration, sample_rate, F
print(get_sound_duration("df634a11-f28e-371c-404b-1635aee9aadc"))
Output:
``
29.0, 44100, 198176
``