Column: CoverArt dimensions

Yes, it extracts the image dimensions directly from the first coverart for each audiofile.
It does the same if the file is a gif/jpg/png/bmp.

It is shown as 9999x9999 (iow WidthxHeight), or "---" if it can't fetch dimensions for the file.

Edit: I've changed it to use the dimensions of the coverart image with the largest area if
there are multiple coverarts. It seems to work, although no guarantees.

Added a version that works with the new 11.5.5 properties (CoverArtDim2.osp).

I noticed that the AudioCoverArt only supports jpg and png (gif and jpg will show undefinedxundefined atm), but
it isn't a big difference..I guess. Those don't seem to be very common.

I tested your dimension column, which works for the 11.5.5 and it messes up folder formats somehow.

After adding your column, the "music" folder format sticks to all folders, which do not have the default format, that feels kind of strange and is probably more related to DO than your column.

Do you experience the same?

I've never used those (auto)folder formats. I find this more of a surprise than anything.
Does it happen with both versions of the column?

In theory DO might be using the audio metadata accesses to determine the count of music files and switch format based on it
if the autoformat thingy (no idea what that option is called) is enabled.
But if it did, it shouldn't apply it to folders that don't contain music anyway.

As you see above, the script doesn't do anything that should by itself trigger any folderformat change.
At least as far as I can tell.

Edit: I tried to enable "Preferences, Folders->Folder Behavior->folder content detection", and it didn't seem to enable the music
category unless there was more than a few musicfiles in the folder compared to what other content it had.

I tried the other version now, it does not change the behaviour. Then I tested with some columns of my own and they stick as well! o))
So this surely is not related to your script or somehow, I think added columns just "live on" as long as no folder format is defined for a special path or auto content type detection kicks in switching columns.
Sorry! o)

Nice work on your "js native" version btw, shifting bits and dealing with "real" types is something you see not often in jscript! o)
I see you accurately make use of the prototype thing in js, I find this leads to cluttered code somehow, in case you don't create hundreds of objects, the "this"-way may be an option for you to get rid of all these loose functions, like so:

//constants
var START=0;
var CURRENT=1;
var END=2;

///////////////////////////////////////////////////////////////////////////////
function BlobStream(blob) {
    this.blob = blob;
    this.position = START;
    this.size = 0;
    //////////////////////////////////////////////////////////////////////////
    if (this.blob) {
        this.size = this.blob.size;
    }
    //////////////////////////////////////////////////////////////////////////
    this.Seek = function(offset, mode) {
        switch (mode) {
            case START: {
                this.position = offset;
                return this.position;
            }
            case CURRENT: {
                this.position = this.position + offset;
                return this.position;
            }
            case END: {
                this.position = blob.size - offset;
                return this.position;
            }
            default:
                return -1;
        }
    }
    //////////////////////////////////////////////////////////////////////////
    this.ReadByte = function() {
        return this.blob(this.position++);
    }
    //////////////////////////////////////////////////////////////////////////
    this.ReadWord = function() {
        return MakeWord(this.ReadByte(), this.ReadByte());
    }
    //////////////////////////////////////////////////////////////////////////
    this.ReadLong = function() {
        return MakeLong(MakeWord(this.ReadByte(), this.ReadByte()),
                MakeWord(this.ReadByte(), this.ReadByte()));
    }
    //////////////////////////////////////////////////////////////////////////
    this.ReadBytes = function(numbytes) {
        var retval = [];
        for (var i = 0; i < numbytes; i++) {
            retval[i] = this.ReadByte();
        }
        return retval;
    }
    //////////////////////////////////////////////////////////////////////////
    this.ReadString = function(len) {
        var retval = "";
        var b, i = 0;
        if (len == 0) return retval;
        do {
            b = this.ReadByte();
            retval = retval + String.fromCharCode(b);
            i++;
        } while ((this.position < this.size) && (i < len));
        return retval;
    }
    //////////////////////////////////////////////////////////////////////////
    this.CanRead = function(numbytes) {
        if ((this.position + numbytes - 1) < this.size) return true;
        return false;
    }
    //////////////////////////////////////////////////////////////////////////
    this.WriteByte = function(b) {
        this.blob(this.position) = b;
        this.position++;
    }
    //////////////////////////////////////////////////////////////////////////
    this.WriteWord = function(word) {
        this.WriteByte(GetByte2(word, 0));
        this.WriteByte(GetByte2(word, 1));
    }
    //////////////////////////////////////////////////////////////////////////
    this.WriteLong = function(along) {
        this.WriteByte(GetByte2(along, 0));
        this.WriteByte(GetByte2(along, 1));
        this.WriteByte(GetByte2(along, 2));
        this.WriteByte(GetByte2(along, 3));
    }
    //////////////////////////////////////////////////////////////////////////
    this.WriteString = function(s) {
        for (var i = 0; i < s.length; i++) {
            this.WriteByte(s.charCodeAt(i));
        }
    }
    //////////////////////////////////////////////////////////////////////////
    this.Resize = function(size) {
        this.blob.Resize(size);
        this.size = this.blob.size;
    }
    //////////////////////////////////////////////////////////////////////////
    this.ToArray = function() {
        var vbarr = new VBArray(this.blob.ToVBArray());
        if (vbarr) {
            return vbarr.toArray();
        }
        return null;
    }
}[/code]

If you like the prototype way more, there's  a way to get both (the non-cluttered look and the correct approach with ".prototype=":
(change WSH.Echo() to DOpus.Output() if you run this within DO only, I test things like that with cscript.exe directly on command prompt, where WSH is your main object.)
[code]///////////////////////////////////////////////////////////////////////////////
function Streamer(stream){
    this.pos = 0;
    ///////////////////////////////////////////////////////////////////////////
    //if prototypes not exist, add them now and once only for all objects
    if(!Streamer.prototype.Seek){
        WSH.Echo("Should see me once..");
        //////////////////////////////////////////////////////////////////////
        Streamer.prototype.Seek = function(){
            WSH.Echo("Seeking().. " + this.pos);
        }
        //////////////////////////////////////////////////////////////////////
        Streamer.prototype.ReadByte = function(){
            WSH.Echo("ReadByte().. " + ++this.pos);
        }
    }
}

var s = new Streamer();
s.Seek();
var s2 = new Streamer();
s2.ReadByte();

I hope you don't mind me writing slighty offtopic jscript stuff here, but you said you were new to JS, so I thought it may be of interest to you. Keep it on! o)

The folder format "sticking" is probably something like the mechanism described in Folder Formats: Detailed Guide part 8: Changes to the Default Formats.

No problem.. Now I kind of get what you meant by "stick". I got the impression that you meant that it switched to music format, not that the
column itself made an appearance in other folders after you removed it.
I've noticed that myself on occation. It seems to happen by enabling the column in a folder, go to other path(s), then back and remove the column..
which then seems to stay enabled in (some of) the folders you went through while it was enabled.

Well, it wouldn't be possible to do so without using an object that supported binaries (blob), although it would be nice if they implemented
some of the functions I used (read*, seek, write*,position) in the blob object itself so you had both direct and stream/file-like access to the data.
I don't think blob autoconvert to number/string etc , so those would have to return proper types if they were to add them.

I normally use C++ and Delphi, and the stream approach is from the latter.

I wasn't sure which approach I should use. I went with non-prototype first, which worked quite nicely.. however, after reading a bit I found that
this creates the functions on each intantiation of the "object", which might be kind of bad with a *stream kind of "object" as it might be instantiated
more than a few times in some scripts.
I didn't think of simply declaring the prototype functions in a similar way I would have if I didn't used prototype, so thanks for that.

The shifting around, as you said, was harder than it looks. In regular languages you can do much of it by casting, but there is no such thing in JS or rather, there
is, but not for the types involved (byte/word/long, etc). Hence, I had to experiment a bit to get it somewhat right. It seems to work, so..

I never got the blob.ToArray() to work (it was always null or undefined), which is why I did the vbarray workaround in that script.

@leo
Exactly! Thanks for pointing to that guide again. That's always a good read, should print myself a booklet with that. o) Still struggle with layouts and formats saved within, but that's another story.

@myarmor

Yes, that's why I mentioned "in case you don't create hundreds of objects". From a performance, memory consumption and coding point of view, the "this"-way should be avoided then.
But very often you have something that you're going to instantiate only once, maybe twice, then using just "this." to get the methods in there is a bit more convenient and totally sufficient I think.

That VBArray object is new to me, learned something new here, thanks! o))

Before this post ends, I'd like to suggest a feature for your cover art dimension column. It does work for music files right now, I personally do not act on files regularly, I just look onto directories (albums) with music files and a cover.jpg/folder.jpg file within (so that's the coverart I'm interested in, not what's in the files). So the column could show the dimension of that cover.jpg/folder.jpg for music-folders if it exists, what do you think? o)

It was new for me too until I "had to" find a way to convert a blob to an array, and blob.ToArray() wouldn't work in JS.

I've added new versions of the scripts. They should work, and both have configurable support for that kind of folder images.
cover.jpg and folder.jpg is the default. You can add/change which ones you want using Configure.

If a folder don't contain them the column will be empty.
The reason for that is that a folder usually don't have "folderart" as opposed to audiofiles, and I didn't feel like watching "---" all the time.

Thank you, that is sweet and helpful! o)) What I did not expect:
Your js-native version, to parse the blob for it's dimensions, is much faster than the lately builtin properties for width and height of itm.Metadata.audio.coverart.
The js-native version takes 10 seconds and the other takes 24 seconds to get the dimensions for these 800+ folders. Scripting magic! o)

Thanks again!


Then it wasn't just my imagination. That was the first thing I noticed, but I didn't really bother to measure it as it probably "was only in my head".

When I think of it, it's logical. The "native" version doesn't bother reading more metadata than it needs while the metadata object
most likely fills every single bit of data it can get it's "hands" on (when it is accessed) for every single file. The properties are beneficial
because they make scripting for metadata very accessible for just about everyone, but they obviously has their downside too.

I noticed another thing though. It isn't possible to access coverart metadata for anything but jpg/png, not just in scripting, but also
in the metadata editor (it can't delete a non-jpg/png coverart).
According to the id3v2.3.0 specification there isn't really a limitation on which imagetypes you can use as coverart, but jpg/png is recommended
for portability or whatever they called it.

Use some tool to add a .gif/.bmp as coverart for an .mp3 file and DO will happily display it in the tooltip, but you can't delete it using DO metadata editor.

I reuploaded the files after a tiny change.
The AllowItem function returned true when it shouldn't. It wouldn't really matter because ImageSize would handle it, but it would
cause some unneeded metadata accesses if allowedFolderImages was set to be empty (you'd have to delete the default for it to happen).
If it were, it would allow every single file into ImageSize, causing metadata/blob accesses.

That doesn't really make sense, since by the time the coverart object has been returned to the script, the width and height properties are already part of it. Reading these properties doesn't cause any additional work to be done.

I assume DO don't just read the width/height of the coverart(s) when the metadata object is accessed, but fills the metadata object with
its subobjects, and that might be what is creating difference if there really is one.

I still haven't compared them myself (I couldn't really devise a fair comparison because much seems to happen offscreen), but the
metadata version felt slower when I initially switched over from the other one, but as I said that could be "in my head".
But if it really is so, it goes to show that the implementation of the blob object is rather fast.
I mean, javascript itself is one of the limiting factors when comparing speed against compiled c/c++ code.

Looking at the code and recalling that the speed comparison was done for regular images within folders (no coverart of mp3), the speed increase may come from not using itm.Metadata.image.picwidth/picheight? The faster column does DOpus.FSUtil.OpenFile(itm.realpath).Read() and then parses for image dimensions itself, using itm.Metadata just once before in a string compare to make sure it is no "audio" file (in that case it would get the blob out of itm.Metadata.audio.coverart).

If that's true then accessing itm.Metadata.image is slower for some reason. On the other hand, itm.Metadata is used in both versions and I guess if the item is an image, it's dimensions are already available in itm.Metadata.image? Doing an additional OpenFile().Read() and parsing for dimensions with jscode then should be slower.

I may be wrong of course, but nonetheless interested to why this is! My tests are reproducable in case you question things. o) I added each column separately and sorted by it (forcing column values for all items without scrolling manually). Then pressing F5 and waiting for the busy-circle to go away.

Oh, thats true, the js version also uses the metadata object. I didn't remember it when I wrote the previous message.
I think the only real difference is that it doesn't use coverart(x).width/height and image.picwidth/picheight.

When a script accesses Item.metadata Opus will read in any metadata there is and initialise the sub-objects, however it doesn't read the audio cover art into memory at that point - it merely records how many cover art images there are.

As soon as a script enumerates or accesses an index of Item.metadata.audio.coverart then Opus goes back to the file and reads in all the cover art data.

Is that valid for accessing Item.metadata.image and its properties as well? Or will image dimension be already there, when item.metadata is at hand?

I made one change to the scripts that were different in them to begin with.
The js version accesses .Metadata default property only once, but the other did it twice (first for "audio" then for "image").
Now it stores that value the first time, so both access it once.
Not sure if the old one would reduce performance or not, but I could as well just fetch it once.

No it only applies to the cover art (because in normal use, Opus doesn't use the cover art images except in the metadata panel, so it would be wasteful to store them in memory all the time).