Column: CoverArt dimensions

This adds a column to show the dimensions (WxH) of a coverart (or an image).
Supports jpg, png coverart.

As usual, no guarantees and so on, but it works for me.

Suggestions, tips etc is appreciated.

I'm still learning JScript as I've only used it for a few automation scripts before DO11, so the code might be lacking a bit.

  • Fixed an obvious bug.
  • Added the value "---" for no available dimensions in a file.
  • More bugs
  • If there are multiple coverart images, attempt to determine the coverart with the largest area (WxH), and use those dimensions.
  • changed scriptname/name because it became hard to keep track of all the scripts. Prefixing them according to what they do seemed like a nice idea.
  • configurable support for folder images. If the defined files is inside a folder, we'll show the dimensions of that file. Default files is cover.jpg and folder.jpg

Requires Directory Opus 11.5.5 or higher
Uses the new AudioCoverArt properties.
It can handle jpg and png coverart, but not gif and bmp.

  • changed scriptname/name because it became hard to keep track of all the scripts. Prefixing them according to what they do seemed like a nice idea.
  • shouldn't display undefinedxundefined when encountering an unsupported coverart anymore (or so I think).
  • configurable support for folder images. If the defined files is inside a folder, we'll show the dimensions of that file. Default files is cover.jpg and folder.jpg

Downloads:

  • ColCoverArtDim.js.txt (3.6 KB)
  • Download the file, then drag it to the list under Preferences / Toolbars / Scripts.
  • You can then find a new file display column under the Scripts category.

Script Code:

The script code from the download above is reproduced for reference:

// This script is for Directory Opus 11.5.5 or higher
// CoverArtDim column (CoverArt WxH)
// Uses AudioCoverArt's new width and height properties.
// Shows dimensions for a folder if it contains defined files.
var vAllowedFolderImages=null;
function ImageSize(itm){
  if (itm){
    var metatype=""+itm.Metadata; //Make sure we get a string. Without "" we get a object.
    if ((metatype=="audio")&&(itm.Metadata.audio.coverart>0)){
      var cnt=itm.Metadata.audio.coverart;
      var curitm;
      if(cnt>1){
        var enm=[];
        var pos=0;
        var selwidth=0;
        var selheight=0;
        var enu = new Enumerator(itm.Metadata.audio.coverart);
        if (!enu.atEnd()){
          while (!enu.atEnd()){
            curitm=enu.item();
            enm[pos++]={width:curitm.width,height:curitm.height};
            enu.moveNext();
          }
        }
        for (var i=0;i<pos;i++){
          if ((enm[i])&&((enm[i].width*enm[i].height)>(selheight*selwidth))){
            selwidth=enm[i].width;
            selheight=enm[i].height;
          }
        }
        // If width and height is both 0/undefined then we don't have a supported coverart
        if ((!selwidth)&&(!selheight)) return null;

        return {width:selwidth,height:selheight,all:enm};
      } else{
        curitm=itm.Metadata.audio.coverart(0);
        // If width and height is both 0/undefined then we don't have a supported coverart
        if ((!curitm.width)&&(!curitm.height)) return null;
        return {width:curitm.width,height:curitm.height,all:null};
      }
    } else if (metatype=="image"){
      return {width:itm.Metadata.image.picwidth,height:itm.Metadata.image.picheight,all:null};
    }
  }
  return null;
}

// Called by Directory Opus to initialize the script
function OnInit(initData){
  initData.name = "ColCoverArtDimensions2";
  initData.desc = "Add a column to show the dimensions of coverart or image";
  initData.copyright = "myarmor";
  initData.min_version="11.5.5";
  initData.version = "1.03";
  initData.default_enable = false;

  var col;
  col = initData.AddColumn();
  col.name = "CoverArtDim";
  col.method = "OnCoverArtDim";
  col.label = "CoverArt WxH";
  col.autogroup = true;
  col.autorefresh=true;
  col.justify = "right";
  initData.config.allowedFolderImages=DOpus.NewVector('cover.jpg','folder.jpg');
  return false;
}

function AllowItem(item){
  if (!vAllowedFolderImages) return false;
  var len;
  len=vAllowedFolderImages.count;
  if (len==0) return false;
  for (var i=0;i<len;i++){
    if (vAllowedFolderImages(i).toLowerCase()==item.name.toLowerCase()){
      return true;
    }
  }
  return false;
}

function FindFolderArt(itm){
  var folderEnum=DOpus.FSUtil.ReadDir(itm.realpath);
  var foldername=itm.name.toLowerCase();
  if (!folderEnum.complete){
    var curitm;
    while (!folderEnum.complete){
      curitm=folderEnum.Next();
      if ((!curitm.is_dir)&&(AllowItem(curitm))){
        return curitm;
      }
    }
  }
}
// Handler for the CoverArtDim column
function OnCoverArtDim(scriptColData){
  if (scriptColData.col!="CoverArtDim") return;
  var dims=null;
  var bIsDir;
  if (scriptColData.item.is_dir) {
    bIsDir=true;
    vAllowedFolderImages=Script.config.allowedFolderImages;
    var itm=FindFolderArt(scriptColData.item);
    if (itm){
      dims=ImageSize(itm);
    }
  } else{
    dims=ImageSize(scriptColData.item);
  }
  if (dims){
    scriptColData.value=dims.width+"x"+dims.height;
    scriptColData.sort=dims.width;
  } else if (!bIsDir){
    scriptColData.value="---";
    scriptColData.sort=9999999;
  }
}

Older Version:

This older version does not use the AudioCoverArt object added in Opus 11.5.5 and instead parses the images itself. It supports jpg, png, gif and bmp, but is slower than the newer script (above).

This script reads the image width/height directly from the binary coverart/image and is mainly an experiment using the new File and Blob objects (hence no real errorchecking and so on).

// This script is for Directory Opus 11.5.4 or higher
// CoverArtDim column (CoverArt WxH)
// Extracts the dimensions directly from blob or file.
// Shows dimensions for a folder if it contains defined files.
var vAllowedFolderImages=null;

function MakeLong(lo,hi){
  return (lo|(hi<<16));
}
function MakeWord(lo,hi){
  return (((lo<<16)>>16)|((hi<<16)>>8));
}

function GetByte2(Value,ByteToGet){
  return (Value & (0xFF<<(ByteToGet*8)))>>>(ByteToGet*8);
}

function SwapHiLo(w){
  return MakeWord(GetByte2(w,1),GetByte2(w,0));
}

// Simplifies using a Blob by making it behave more like a file.
var soBeginning=0;
var soCurrent=1;
var soEnd=2;

function BlobStream(blob){
  this.blob=blob;
  this.position=0;
  this.size=0;
  if (this.blob){
    this.size=this.blob.size;
  }
}

BlobStream.prototype.Seek=function(offset,mode){
  switch(mode){
    case soBeginning: {
      this.position=offset;
      return this.position;
    }
    case soCurrent: {
      this.position=this.position+offset;
      return this.position;
    }
    case soEnd:{
      this.position=blob.size-offset;
      return this.position;
    }
    default: return -1;
  }
};

BlobStream.prototype.ReadByte=function(){
  return this.blob(this.position++);
};

BlobStream.prototype.ReadWord=function(){
  return MakeWord(this.ReadByte(),this.ReadByte());
};

BlobStream.prototype.ReadLong=function(){
  return MakeLong(MakeWord(this.ReadByte(),this.ReadByte()),MakeWord(this.ReadByte(),this.ReadByte()));
};

BlobStream.prototype.ReadBytes=function(numbytes){
  var retval=[];
  for (var i=0;i<numbytes;i++){
    retval[i]=this.ReadByte();
  }
  return retval;
};

BlobStream.prototype.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;
};

BlobStream.prototype.CanRead=function(numbytes){
  if ((this.position+numbytes-1)<this.size) return true;
  return false;
};

BlobStream.prototype.WriteByte=function(b){
  this.blob(this.position)=b;
  this.position++;
};

BlobStream.prototype.WriteWord=function(word){
  this.WriteByte(GetByte2(word,0));
  this.WriteByte(GetByte2(word,1));
};

BlobStream.prototype.WriteLong=function(along){
  this.WriteByte(GetByte2(along,0));
  this.WriteByte(GetByte2(along,1));
  this.WriteByte(GetByte2(along,2));
  this.WriteByte(GetByte2(along,3));
};

BlobStream.prototype.WriteString=function(s){
  for (var i=0;i<s.length;i++){
    this.WriteByte(s.charCodeAt(i));
  }
};

BlobStream.prototype.Resize=function(size){
  this.blob.Resize(size);
  this.size=this.blob.size;
};

BlobStream.prototype.ToArray=function(){
  var vbarr=new VBArray(this.blob.ToVBArray());
  if (vbarr){
    return vbarr.toArray();
  }
  return null;
};

function JPGSizeStream(stream){
  if(!stream) return null;
  var iHeight,iWidth,iBitdepth;
  var n,b,w;
  var pos;
  var newpos;
  n=stream.size-8;
  if (n<=0) return null;
  stream.Seek(0,soBeginning);

  if (stream.ReadWord()!=0xD8FF){
    DOpus.Output('Not a valid JPEG file');
    return null;
  }
  b=stream.ReadByte();
  while ((stream.position<n)&&(b==0xff)){
    b=stream.ReadByte();
    switch(b){
      case 0xc0:
      case 0xc1:
      case 0xc2:
      case 0xc3:
      {
        stream.Seek(3,soCurrent);//skip 3 unneeded bytes
        w=stream.ReadWord();
        iHeight=SwapHiLo(w);
        w=stream.ReadWord();
        iWidth=SwapHiLo(w);
        b=stream.ReadByte();
        iBitdepth=b*8;
        return {width:iWidth,height:iHeight,bitdepth:iBitdepth};
      }
      case 0xff: {
        // Not really tested
        DOpus.Output('Was 0xff');
        b=stream.ReadByte();
        break;
      }
      case 0xD0:
      case 0xD1:
      case 0xD2:
      case 0xD3:
      case 0xD4:
      case 0xD5:
      case 0xD6:
      case 0xD7:
      case 0xD8:
      case 0xD9:
      case 0x01:{
        DOpus.Output("D0..D9,01");
        stream.Seek(1,soCurrent);
        b=stream.ReadByte();
        break;
      }
      default:{
        w=stream.ReadWord();
        pos=stream.Seek(SwapHiLo(w) - 2,soCurrent);
        b=stream.ReadByte();
        break;
      }
    }
  }
  return null;
}

function PNGSizeStream(stream){
  var validsig=[137, 80, 78, 71, 13, 10, 26, 10];
  var sb;
  var iWidth,iHeight;
  if(!stream) return null;
  stream.Seek(0,soBeginning);
  for (var i=0;i<validsig.length;i++){
    sb=stream.ReadByte();
    if (validsig[i]!=sb){
      DOpus.Output('Not a valid PNG file');
      return null;
    }
  }
  stream.Seek(18,soBeginning);
  iWidth=SwapHiLo(stream.ReadWord());
  stream.Seek(22,soBeginning);
  iHeight=SwapHiLo(stream.ReadWord());
  return {width:iWidth,height:iHeight};
}

function GIFSizeStream(stream){
  var iWidth,iHeight;
  var bFound;
  var c;
  if(!stream) return null;
  stream.Seek(0, soBeginning);
  var GIFHeader={
    Sig: stream.ReadString(6),
    ScreenWidth: stream.ReadWord(),
    ScreenHeight: stream.ReadWord(),
    Flags: stream.ReadByte(),
    Background: stream.ReadByte(),
    Aspect: stream.ReadByte()
  };

  if (GIFHeader.Sig.substr(0,3)!="GIF"){
    DOpus.Output('Not a valid GIF file');
    return null;
  }

  if ((GIFHeader.Flags & 0x80)>0){
    var x = 3 * (1 << ((GIFHeader.Flags & 7) + 1));
    stream.Seek(x,soBeginning);
  }
  bFound=false;
  while(stream.position<stream.size){
    c=stream.ReadByte();
    switch(c){
      case 0x2c:{
        var ImageBlock={
          Left:stream.ReadWord(),
          Top:stream.ReadWord(),
          Width:stream.ReadWord(),
          Height:stream.ReadWord(),
          Flags:stream.ReadByte()
        };
        iWidth=ImageBlock.Width;
        iHeight=ImageBlock.Height;
        return {width:iWidth,height:iHeight};
      }
      case 0xff:{
        break;
      }
    }
  }
  return null;
}

function BMPSizeStream(stream){
  var iWidth,iHeight;
  var bFound;
  var c;
  if(!stream) return null;
  stream.Seek(0, soBeginning);
  var BitmapFileHeader={
    bfType: stream.ReadWord(),
    bfSize: stream.ReadLong(),
    bfReserved1: stream.ReadWord(),
    bfReserved2: stream.ReadWord(),
    bfOffBits: stream.ReadLong()
  };
  if (BitmapFileHeader.bfType ==0x4D42){
    // Get the size of the header.
    var hdrsize=stream.ReadLong();
    if (hdrsize<40){
      //We have a BITMAPCOREHEADER
      var BitmapCoreHeader={
        bcWidth: stream.ReadWord(),
        bcHeight: stream.ReadWord(),
        bcPlanes: stream.ReadWord(),
        bcBitCount: stream.ReadWord()
      };
      return {width:BitmapCoreHeader.bcWidth,height:BitmapCoreHeader.bcHeight};
    } else{
      //We have a BITMAPINFOHEADER
      var BitmapInfoHeader={
        biWidth: stream.ReadLong(),
        biHeight: stream.ReadLong(),
        biPlanes: stream.ReadWord(),
        biBitCount: stream.ReadWord()
      };
      return {width:BitmapInfoHeader.biWidth,height:BitmapInfoHeader.biHeight};
    }
  } else DOpus.Output('Not a valid BMP file');
  return null;
}

function ImageSizeStream(stream){
  if (!stream) return null;
  stream.Seek(0,soBeginning);
  var sig=stream.ReadWord();
  switch (sig){
    case 0x5089: return PNGSizeStream(stream);
    case 0xD8FF: return JPGSizeStream(stream);
    case 0x4947: return GIFSizeStream(stream);
    case 0x4D42: return BMPSizeStream(stream);
    default: return null;
  }
}

function ImageSize(filename){
  var stream;
  var itm=DOpus.FSUtil.GetItem(filename);
  if (itm){
    var metatype=""+itm.Metadata; //Make sure we get a string. Without "" we get a object.
    if ((metatype=="audio")&&(itm.Metadata.audio.coverart>0)){
      var cnt=itm.Metadata.audio.coverart;
      if(cnt>1){
        var enm=[];
        var pos=0;
        var selwidth=0;
        var selheight=0;
        var enu = new Enumerator(itm.Metadata.audio.coverart);
        if (!enu.atEnd()){
          while (!enu.atEnd()){
            var curitm=enu.item();
            stream=new BlobStream(curitm.data);
            enm[pos++]=ImageSizeStream(stream);
            enu.moveNext();
          }
        }
        for (var i=0;i<pos;i++){
          if ((enm[i])&&((enm[i].width*enm[i].height)>(selheight*selwidth))){
            selwidth=enm[i].width;
            selheight=enm[i].height;
          }
        }
        return {width:selwidth,height:selheight,all:enm};
      } else {
        var ca=itm.Metadata.audio.coverart(0);
        stream=new BlobStream(ca.data);
        return ImageSizeStream(stream);
      }
    } else{
      var blob;
      if ((".gif;.jpg;.png;.bmp".indexOf(itm.ext.toLowerCase())>=0)){
        var file=DOpus.FSUtil.OpenFile(itm.realpath);
        if (file){
          //Contrary to the documentation, Read don't seem to work unless you specify a size.
          //iow, blob=file.Read() does nothing.
          blob=file.Read(file.size);
          if (blob){
            stream=new BlobStream(blob);
            return ImageSizeStream(stream);
          }
        }
      }
    }
  }
  return null;
}

// Called by Directory Opus to initialize the script
function OnInit(initData){
  initData.name = "ColCoverArtDimensions";
  initData.desc = "Add a column to show the dimensions of coverart or image";
  initData.copyright = "myarmor";
  initData.min_version="11";
  initData.version = "1.03";
  initData.default_enable = false;

  var col;
  col = initData.AddColumn();
  col.name = "CoverArtDim";
  col.method = "OnCoverArtDim";
  col.label = "CoverArt WxH";
  col.autogroup = true;
  col.autorefresh=true;
  col.justify = "right";
  initData.config.allowedFolderImages=DOpus.NewVector('cover.jpg','folder.jpg');
  return false;
}

function AllowItem(item){
  if (!vAllowedFolderImages) return false;
  var len;
  len=vAllowedFolderImages.count;
  if (len==0) return false;
  for (var i=0;i<len;i++){
    if (vAllowedFolderImages(i).toLowerCase()==item.name.toLowerCase()){
      return true;
    }
  }
  return false;
}

function FindFolderArt(itm){
  var folderEnum=DOpus.FSUtil.ReadDir(itm.realpath);
  var foldername=itm.name.toLowerCase();
  if (!folderEnum.complete){
    var curitm;
    while (!folderEnum.complete){
      curitm=folderEnum.Next();
      if ((!curitm.is_dir)&&(AllowItem(curitm))){
        return curitm;
      }
    }
  }
}

// Handler for the CoverArtDim column
function OnCoverArtDim(scriptColData){
  if (scriptColData.col!="CoverArtDim") return;
  var dims=null;
  var bIsDir;
  if (scriptColData.item.is_dir) {
    bIsDir=true;
    vAllowedFolderImages=Script.config.allowedFolderImages;
    var itm=FindFolderArt(scriptColData.item);
    if (itm){
      dims=ImageSize(itm);
    }
  } else{
    dims=ImageSize(scriptColData.item);
  }
  if (dims){
    scriptColData.value=dims.width+"x"+dims.height;
    scriptColData.sort=dims.width;
  } else if (!bIsDir){
    scriptColData.value="---";
    scriptColData.sort=9999999;
  }
}

Hi there! Can you give some more details on this, please? o)
Do these columns show coverart dimensions from mp3 files and similar directly?

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?