Scripts (JS & VBS) Snippet: Enumerating files and metadata tags

Below are scripts which demonstrate how to use DOpus.FSUtil.ReadDir and the FolderEnum object, for reading directory listings, as well as the Metadata.tags collection for listing any user-defined tags set on files.

The two scripts below do the same thing: Print the metadata tags (if any) for each file in C:. (For my test, I created a "test.txt" and tagged it with "cat" and "dog".)

One script in JScript and the other in VBScript. The behaviour of the two should be identical.

Note that sometimes metadata is not available for a file at all. For example, when I run it this happens with hiberfil.sys and pagefile.sys, because Opus is unable to inspect those files (they are locked by Windows). The files could have tags, but Opus can't access them, so this is distinct from an empty list of tags.

JScript:

function PadSpaceRight(str, len)
{
	str = str + ""; // Force to string.
	if (str.length < len)
		str = str + Array(len + 1 - str.length).join(" ");
	return str;
}

var tagString;

var folderEnum = DOpus.FSUtil.ReadDir("C:\\", false);

while (!folderEnum.complete)
{
	var folderItem = folderEnum.next;

	if (!folderItem.is_dir)
	{
		if (folderItem.metadata == "none")
		{
			tagString = "<no metadata available>";
		}
		else
		{
			tagString = "";

			for (var tagEnum = new Enumerator(folderItem.metadata.tags);
			     !tagEnum.atEnd();
			     tagEnum.moveNext() )
			{
				if (tagString != "")
				{
					tagString += ", ";
					tagString += tagEnum.item();
				}
				else
				{
					tagString = tagEnum.item();
				}	
			}

			if (tagString == "")
			{
				tagString = "<no tags>";
			}
		}

		DOpus.Output(PadSpaceRight(folderItem + ": ",25) + tagString);
	}
}


VBScript:

Option Explicit

Function PadSpaceRight(str, slen)
	str = CStr(Str) ' Force To String.
	If (Len(str) < slen) Then
		str = str & Space(slen - Len(str))
	End If
	PadSpaceRight = str
End Function

Dim folderItem
Dim folderEnum
Dim tag
Dim tagString

Set folderEnum = DOpus.FSUtil.ReadDir("C:\", False)

Do While (Not folderEnum.complete)

	Set folderItem = folderEnum.Next

	If (Not folderItem.is_dir) Then

		If (folderItem.metadata = "none") Then

			tagString = "<no metadata available>"

		Else

			tagString = ""

			For Each tag In folderItem.metadata.tags
				If (tagString <> "") Then
					tagString = tagString & ", "
					tagString = tagString & tag
				Else
					tagString = tag
				End If
			Next

			If (tagString = "") Then
				tagString = "<no tags>"
			End If
		End If

		DOpus.Output(PadSpaceRight(folderItem + ": ",25) & tagString)
	End If
Loop


Other types of metadata tags:

The post above is about the actual "tags" metadata, which is just a free-text collection of strings you can associate with a file (e.g. cat;dog;parrot).

If you were looking for "metadata tags" as in MP3 Album Artist, or EXIF Focal Length, those are actually more simple to access. You can use named properties in the various metadata objects Opus populates for you.

Here is an example script which:

  • Loops through all selected files
  • Skips folders, and files which do not have image metadata
  • For files with a "Focal Length" value but no "Focal Length 35mm" value, the former value is doubled and written into the latter. (Whether this is a valid conversion depends on the camera but is not important for the code example.)

JScript:

function OnClick(clickData)
{
	var cmd = clickData.func.command;
//	cmd.deselect = false; // Prevent automatic deselection

	for (var eSel = new Enumerator(clickData.func.sourcetab.selected); !eSel.atEnd(); eSel.moveNext())
	{
		var fileItem = eSel.item();
		if (!fileItem.is_dir)
		{
			var fileMeta = fileItem.metadata;
			if (fileMeta == "image")
			{
				var imageMeta = fileMeta.image;
				var focalLength = imageMeta["focallength"];
				var focalLength35mm = imageMeta["35mmfocallength"];
				
				if (typeof focalLength == "number"
				&&	typeof focalLength35mm == "undefined")
				{
					focalLength35mm = focalLength * 2;

					var cmdString = "SetAttr META \"35mmfocallength:" + focalLength35mm + "\"";
					cmd.ClearFiles();
					cmd.AddFile(fileItem);
					cmd.RunCommand(cmdString);
				}
			}
		}
	}
}


In the above JScript example, you could use both imageMeta.focallength and imageMeta["focallength"] and either would work. However, imageMeta.35focallength would not work because JScript does not allow property name to start with digits; imageMeta["35focallength"] must be used instead to work around this.

The example above also demonstrates how to check for missing metadata fields using the JScript typeof technique.

1 Like

How to get it run only for the 'Selected' items,

Tried the below Modification, But it didn't work at all

var folderEnum = new Enumerator(clickData.func.sourcetab.selected);

If you want to make use of the code from a button, you need to wrap the example code into a function first, as the example above does not give a context on where it's going to be used (a button/a script command or script column eg.). It's a big snippet basically. Make sure to use a "Script Function" type button and set the script language accordingly. Below demo expects javascript.

function OnClick(clickData){
    //put example code it here
}

You seem to have been on the right track, because you already tried with the correct ClickData object for buttons to get through to the source tab and its selected items.
Now that you'd like to fetch items from a tab, another Enumerator object is required to loop through any selected items. The given example reads in a folder on its own and does make use of the FolderEnum object, which is native to DO. Unfortunately, it has different methods and works slighty different than the standard jscript Enumerator which is required for iterating over items from a tab, so you'd need to make some additional changes to handle the different enumerator types, than just exchanging DOpus.FSUtil.ReadDir("C:\\", false); with new Enumerator(clickData.func.sourcetab.selected);.

I "translated" Leo's example to be used from a button directly - make sure to select files beforehand, as nothing's going to happen if you did not. o)

function OnClick(clickData){
	function PadSpaceRight(str, len)
	{
	   str = str + ""; // Force to string.
	   if (str.length < len)
	      str = str + Array(len + 1 - str.length).join(" ");
	   return str;
	}
	
	var tagString;
	var itemEnum = new Enumerator(clickData.func.sourcetab.selected);

	while (!itemEnum.atEnd())
	{
	   var item = itemEnum.item(); itemEnum.moveNext();
	
	   if (!item.is_dir)
	   {
	      if (item.metadata == "none")
	      {
	         tagString = "<no metadata available>";
	      }
	      else
	      {
	         tagString = "";
	
	         for (var tagEnum = new Enumerator(item.metadata.tags);
	              !tagEnum.atEnd();
	              tagEnum.moveNext() )
	         {
	            if (tagString != "")
	            {
	               tagString += ", ";
	               tagString += tagEnum.item();
	            }
	            else
	            {
	               tagString = tagEnum.item();
	            }   
	         }
	
	         if (tagString == "")
	         {
	            tagString = "<no tags>";
	         }
	      }
	
	      DOpus.Output(PadSpaceRight(item + ": ",25) + tagString);
	   }
	}
}

In case you have trouble getting the button and script together, here is a complete button incoporating the script for easy dowloading. o)
ScriptButtonEnumeration.dcf (2.95 KB)

Thanks a LOT tbone,

As i'm a beginner to programming so it took me sooo much time, in first understanding your and Leo's code and then to modify it.

As i was trying to create a script which can 'Restrict the user' if user tries to MOVE an items having 'Referenced' tag on them, and you told that it was not a straight-forward thing to do (in another thread). I've tried to modify your code a little and was thinking to post the full working code. But there are still some problems.

Code No -1 and Code No - 2 , Purpose -> To generate the list of items which have 'Referenced (for item-referenced in other applications)' tag on them.
Also giving the Total count of Directories and Files in the End.

Problems 1 -> If i just 'deleted' the 'Dir checking' if statement from your code, Opus crashed if trying to run the code on a Folder
Problem 2 -> Code NO - 1 (modified in your code) does not run fine, while the Code NO - 2 (modified in Leo's code) works fine,
Problem 3 -> How to invoke this code on MOVE event, without writing this code in separate MOVE events
Problem 4 -> can there be a way to invoke the script on 'SELECT' event (ie, on single click on an item, just like there appears a '->' (MOVE) button in Beyond-Compare on selecting an item)
Problem 5 -> Using Leo's and your guidance i tried to check the 'LABEL' for an item but it does not work, can we not check the Label of an item using metadata (excluding the loop)

Code NO - 1

function OnClick(clickData){
      
   var tagString;
   var itemEnum = new Enumerator(clickData.func.sourcetab.selected);

   while (!itemEnum.atEnd())
   {
      var item = itemEnum.item(); itemEnum.moveNext();
   
      if (!item.is_dir)
      {
         if (item.metadata == "none")
         {
            tagString = "<no metadata available>";
         }
         else
         {
            tagString = "";
   
            for (var tagEnum = new Enumerator(item.metadata.tags);
                 !tagEnum.atEnd();
                 tagEnum.moveNext() )
            {
               if (tagEnum.item() == "Referenced")
	      {

	       	if (folderItem.is_dir)
		{
			countDirectory = countDirectory + 1;
			tagString = "\nReferenced Directory ++++++++++ : ";
			
		}
		else
		{
			countFile = countFile + 1;
			tagString = "\nReferenced File                 : ";
		}
		
	       writeToFile(tagString + PadSpaceRight(folderItem));
	       break;
	     }
          }
       }
    }
 }

       writeToFile("\n_____________________________________________________");
       writeToFile("Total No of Referenced DIRECTORIES ++++++++ :" + countDirectory);
       writeToFile("Total No of Referenced Files                :" + countFile);
       writeToFile("_____________________________________________________");
}


function PadSpaceRight(str, len)
   {
      str = str + ""; // Force to string.
      if (str.length < len)
         str = str + Array(len + 1 - str.length).join(" ");
      return str;
   }

function writeToFile(data)
{
      var fso = new ActiveXObject("Scripting.FileSystemObject");
      var fh = fso.OpenTextFile("D:\\data1111.txt", 8, true, 0);
      fh.WriteLine(data);
      fh.Close(); 
}

Code NO - 2

// main script entry point. clickData is a ClickData object
function OnClick(clickData) {
 
var tagString;
var countDirectory = 0;
var countFile = 0;

var folderEnum = DOpus.FSUtil.ReadDir("D:\\New Folder (2)", true);

while (!folderEnum.complete)
{
   
   var folderItem = folderEnum.next;

    if (folderItem.metadata != "none")
      {
      
         tagString = "";
	 	 
         for (var tagEnum = new Enumerator(folderItem.metadata.tags);
              !tagEnum.atEnd();
              tagEnum.moveNext() )
         {

  	    if (tagEnum.item() == "Referenced")
	    {

	       	if (folderItem.is_dir)
		{
			countDirectory = countDirectory + 1;
			tagString = "\nReferenced Directory ++++++++++ : ";
			
		}
		else
		{
			countFile = countFile + 1;
			tagString = "\nReferenced File                 : ";
		}
		
	       writeToFile(tagString + PadSpaceRight(folderItem));
	       break;
	    }
         }
       }
       
}
       writeToFile("\n_____________________________________________________");
       writeToFile("Total No of Referenced DIRECTORIES ++++++++ :" + countDirectory);
       writeToFile("Total No of Referenced Files                :" + countFile);
       writeToFile("_____________________________________________________");
       
}

function writeToFile(data)
{
      var fso = new ActiveXObject("Scripting.FileSystemObject");
      var fh = fso.OpenTextFile("D:\\data00000.txt", 8, true, 0);
      fh.WriteLine(data);
      fh.Close(); 
} 

function PadSpaceRight(str, len)
{
   str = str + ""; // Force to string.
   if (str.length < len)
      str = str + Array(len + 1 - str.length).join(" ");
   return str;
}

@tbone:
The button code you provided doesn't really work for me. It works at the first time but if I select the same file a second time the result is always . I have to refresh the current tab to get it working again.

Confirmed, I wouldn't know what to change to make it work different though.
Maybe it's some kind of malfunction, as I expect a button press to always yield the same result, it's stateless as far as I know.

This will be fixed in the next update.

Many thanks for fixing this. :smiley:

I have been playing with T Bone's java script code to enumerate tags over the lat few days trying to turn it into a VB script. I am no great coder, and it is no surprise that it has defeated me with an endless list of parse errors.

I was wondering if anyone had this script as a VB script?

I do not recommend turning things from js into vbs. o) Try the jscript route, it's not really any harder than vbs, but much more powerful and flexible and the internet is full of examples (and I'd think so is the script addin section here).

Thanks for the advice Tbone, but I am struggling hard with VB and learning another scripting language would make the situation worse. The real problems is that I want to use the code snippet in a VB script. I tried to dismantle and re-assemble Leo's script to do what I want, but to no avail.

I think the line

var itemEnum = new Enumerator(clickData.func.sourcetab.selected);

is what is giving me the real problem, so I will concentrate on that first.

But thanks for the advice, once again.

One thing VB does nicer than JS is enumeration; JS requires a special Enumerator object whereas VB has it built in. E.g. to enumerate through the selected files in the source tab, in VB you can do this:

For Each f In clickData.func.sourcetab.selected
    ' Do something with f
Next

Yes, the enumerations in JS are a bit more verbose, but hey, you can rewind them and see if you're near the end! o)
And you can break a loop anytime or continue and skip over to the next iteration in JS, which really compensates for this.

While we are at it, in most cases you and Leo use the For-Each and enums to step through selected items and things. But what does actually speak against a regular for-loop (which might be the easiest way for beginners?). They help avoiding the enums in case people are not so familiar with them.

Is there a special reason for using for-each and enums, or should index based looping work just as well for all the provided collections (might be different for ReadFolder() I guess)? I tend to make use of the index based loops and could not find any drawbacks until now.

//jscript
for(var i=0; i<clickData.func.sourcetab.selected.count; i++){
    var selectedItem = clickData.func.sourcetab.selected(i);
}
' vbscript (is that index starting at 0 or 1? cannot remember)
For i=0 To clickData.func.sourcetab.selected.count
    dim selectedItem : selectedItem = clickData.func.sourcetab.selected(i);
Next

Thank you Jon and T-Bone, with your invaluable help I have been able to finish my script and gained a better insight into the nuts and bolts of VB.
Once again, Many thanks.

Only some types of collection can be indexed by number.

Hi leo,

Your sample has helped once again. I was looking for a way to test if my files contain someText in one of their tags. I think I got it down but have two questions that are still bugging me...

1 - Is the example you provide the best way to test for a certain word in its tags? I can't help something like this (If file.metadata.tag = "test" Then) would be simpler but could not get anything like it to work.

2 - The script below only work and provides results on the first file select even if I select multiple. What could I be missing?

Thanks!

Option Explicit
Function OnClick(ByRef clickData)
	
	Dim file, files
	Set files = clickData.func.sourcetab.selected_files 
	
	For Each file in files
		Dim tag, tagString, regex0
		tagString = ""

	 	For Each tag In file.metadata.tags
			If tagString <> "" Then
				tagString = tagString & ", "
				tagString = tagString & tag
			Else
				tagString = tag
			End If
		Next
	
		If (tagString = "") Then
			tagString = "<no tags>"
		End If

		Set regex0 = new RegExp
		regex0.Pattern = ".*(test).*"
		regex0.IgnoreCase = True
		If (regex0.Test(tagString)) Then
			DOpus.OutputString tagString & vbCrLf & "YES, this file contains ""test"" in one of its tags!"
		End If

   Next
End Function
1 Like

1 - file.metadata.tags is a collection of tags and not a string so you have to enumerate the tags. Since you also have to enumerate the collection of all selected files there is no easier way to do the job.
If file.metadata.tag = "test" Then won't work because a "tag" property doesn't exist. You could only test if the first tag equals "test" with If file.metadata.tags(0) = "test" Then.

2 - Your code is working fine for all selected files here. To keep it simple I'd prefer to use If InStr(tagString,"test") <> 0 Then instead of a RegExp.

If file.metadata.tags is a collection of string (as stated in the manual) and it assumingly is a StringSet type of object,
then file.metadata.tags.exists("MyTag") should work?!

Oh sweet!! Looks like I can use the suggested InStr argument to test each individual tag without having to write all to a string field then testing that string. Although I may end up using regex anyway to be able to make it case insensitive or expand on criteria like "someWord: followedByAny 10 digits", it's still pretty nifty for times when I may not need regex. Now that I think about it, this way is a bit nicer because I can test how many tags the criteria is met in rather than in the whole string, but I think I can use original script to get to that as well. Anyway, thanks for your inputs kundal and tbone, I definitely have enough info to keep me busy for the rest of the day.

Option Explicit
Function OnClick(ByRef clickData)
	
	Dim file, files
	Set files = clickData.func.sourcetab.selected_files 
	
	For Each file in files
		Dim countAllTags, countRegexTags, regex0, tag
		countAllTags = 0
		countRegexTags = 0
		
		For Each tag In file.metadata.tags
			countAllTags = countAllTags + 1
			If InStr(tag,"test") <> 0 Then
				countRegexTags = countRegexTags + 1
				DOpus.OutputString  tag
			End If
		Next
		
		DOpus.OutputString "All Tags: " & countAllTags & ", Tags with regex: " & countRegexTags	& vbCrLf & "--------"
		
   Next

End Function

BTW, I did try "file.metadata.tags.exists("MyTag")" but that did not seem to work.

To make it case insensitive you could convert the string "tag" to lowercase: If InStr(LCase(tag),"test") <> 0 Then.