fixAlbumArt

To discuss development of addons / skins / customization of MediaMonkey.

Moderators: jiri, drakinite, Addon Administrators

MPG
Posts: 418
Joined: Tue May 13, 2008 11:22 pm

fixAlbumArt

Post by MPG »

Hi,
I'm back to the drawing board for my add-on "fixAlbumArt". The idea of this utility is to cycle through all selected songs and find the album art that is the front cover and remove the other art. When I run the following code on my machine I receive the following error:
Application throw an exception Exception EAccessViolation in module libcef.dll at 0231FD0D. Access violation at address 5AD2FD0D in module 'libcef.dll', Read of address 64340454.

Code: Select all

/*
    title: Fix Album Art
    description: Provides options to fix album art
	Original: trixmoto
	Author: MPG IT Consulting
	Date:2021-11-30
*/

var trackList;
var breakException = {};

function removeAll() {
	
	var cvr;
	var cvrList;
	
	// Create a background task so that the user can see the progress in the bottom bar
	var taskProgress = app.backgroundTasks.createNew();
	taskProgress.leadingText = ('Fixing art: ');
	var trackCount = trackList.count;
	var trackIdx = 0;
	
	listAsyncForEach(trackList, 
		// callback for each track
		(track, next) => {

			// Update the background task text
			trackIdx++; 
			taskProgress.text = sprintf('%d of %d', trackIdx, trackCount);
			
			cvrList = track.loadCoverListAsync();
			cvrList.whenLoaded().then(() => {
				cvrList.modifyAsync(function () {
					
                    var blnFrontFound = false;
                    
					//find first cover identified as the front (if any)
					for(var intCnt = 0; intCnt < cvrList.count; intCnt++){
						cvr = cvrList.getValue(intCnt);
						if (blnFrontFound == true){
							cvr.coverTypeDesc = "Not specified";
						} else {
							if( cvr.coverTypeDesc == "Cover (front)"){
								blnFrontFound = true;
							} else {
								cvr.coverTypeDesc = "Not specified";
							}
						}
					}
	
					//if none found make the first embedded cover the front
					if (blnFrontFound == false){
						for(var intCnt = 0; intCnt < cvrList.count; intCnt++){
							if(cvrList.getValue(intCnt).coverStorage == 0){
								cvr = cvrList.getValue(intCnt);						
								cvr.coverTypeDesc = "Cover (front)";
								blnFrontFound = true;
								break;
							}
						}
					}
	
					//if none found, make first image the front
					if (blnFrontFound == false){
						cvr = cvrList.getValue(0);
						cvr.coverTypeDesc = "Cover (front)";
					}
	
					//select all covers except front cover
					for(var intCnt = 0; intCnt < cvrList.count; intCnt++){
						cvr = cvrList.getValue(intCnt);
						if( cvr.coverTypeDesc == "Cover (front)"){
							cvrList.setSelected(intCnt, false);
						} else{
							cvrList.setSelected(intCnt, true);
						}
					};
	
					cvrList.deleteSelected();
					var firstCover = cvrList.getValue(0);

					// The if (firstCover &&) part is just a sanity check to avoid a crash 
					if (firstCover && firstCover.coverStorage != 0){ 
						firstCover.coverStorage = 0;
					}

					// Commit track, then AFTER the commit is done, process the next track
                    cvrList.modified = true;
					track.commitAsync().then(next);
				});
			});
		}, 
		// callback when done
		() => {
			// finish the background task
			return taskProgress.terminate();
			trackList.commitAsync();
		}
	);
}

function btnOkClick() {
	var ddOptions = qid('ddOptions').controlClass.value;

	if (ddOptions == ""){
        messageDlg('You must select a value from the drop down.', 'Error', ['btnOk'], {
        }, function (result) {
			return;
        });
	}
	switch(ddOptions.substring(0,2)){
		case "01":
			removeAll();
			break;
		case "02":
			alert("02");
			break;
		case "03":
			alert("03");
			break;
		case "04":
			alert("04");
			break;
		case "05":
			alert("05");
			break;
		case "06":
			alert("06");
			break;
		case "07":
			alert("07");
			break;
		case "08":
			alert("08");
			break;
		case "09":
			alert("09");
			break;
		case "10":
			alert("10");
			break;
		case "11":
			alert("11");
			break;
		case "12":
			alert("12");
			break;
		case "13":
			alert("13");
			break;
		case "14":
			alert("14");
			break;
		case "15":
			alert("15");
			break;
		case "16":
			alert("16");
			break;
		case "17":
			alert("17");
			break;
	}

	app.setValue('fixAlbumArt', {
		ddOptions: qid('ddOptions').controlClass.value
	});
	trackList.commitAsync();
	closeWindow();
}

function btnCancelClick() {}

function init(params) {
	this.title = params.title;
	trackList = params.tracks;

	var obj = app.getValue('fixAlbumArt', {
		ddOptions: qid('ddOptions').controlClass.value
	});

	qid('ddOptions').controlClass.value = obj.ddOptions;

	if (trackList === undefined) {
		messageDlg(_("Select tracks to be updated"), 'Error', ['btnOK'], {
			defaultButton: 'btnOK'
		}, function (result) {
			modalResult = 0;
		});
		return;
	};

	if (trackList.count === 0) {
		messageDlg(_("Select tracks to be updated"), 'Error', ['btnOK'], {
			defaultButton: 'btnOK'
		}, function (result) {
			modalResult = 0;
		});
		return;
	};

    window.localListen(qid('btnOK'), 'click', btnOkClick);
    window.localListen(qid('btnCancel'), 'click', btnCancelClick, true);
}
I've been working on this add-on for a couple of months now, and feel that I'm really close. If anyone can help me, I'd really appreciate it.
TIA
MPG
Triumph - Hold On: Music holds the secret, to know it can make you whole.
Ludek
Posts: 4959
Joined: Fri Mar 09, 2007 9:00 am

Re: fixAlbumArt

Post by Ludek »

Hi,
did you submit the crash report or do you have the *.EL file to see callstack of the crash/AV ?
See item 4a here: viewtopic.php?f=30&t=86643

By a brief look at the code, isn't this line causing the AV:

Code: Select all

cvr = cvrList.getValue(0);
?

Especially when the cvrList.count == 0 ?
drakinite
Posts: 965
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: fixAlbumArt

Post by drakinite »

Also, this bit might be problematic?

Code: Select all

if(cvrList.getValue(intCnt).coverStorage == 0){
      cvr = cvrList.getValue(intCnt);	
Depends on whether cvrList.getValue(idx) can ever return null or undefined. Ludek, can it?
Image
Student electrical-computer engineer, web programmer, part-time MediaMonkey developer, full-time MediaMonkey enthusiast
I uploaded many addons to MM's addon page, but not all of those were created by me. "By drakinite, Submitted by drakinite" means I made it on my own time. "By Ventis Media, Inc., Submitted by drakinite" means it may have been made by me or another MediaMonkey developer, so instead of crediting/thanking me, please thank the team. You can still ask me for support on any of our addons.
MPG
Posts: 418
Joined: Tue May 13, 2008 11:22 pm

Re: fixAlbumArt

Post by MPG »

Okay, so I completely redesigned it to set it up to be async. The code removes all of the art except one, sets it as Cover (front), and doesn't fail...yea me. Unfortunately, the results are not being saved. If I double click the image after running the script, MM freezes. If I just close MM and reopen it, the images are not saved.

Code: Select all

// A simple script that replaces a specified string in Song Title, Artist, Album and Album Artist

function removeAll( trackList) {
	
	var cvr;
	var cvrList;
	
	// Create a background task so that the user can see the progress in the bottom bar
	var taskProgress = app.backgroundTasks.createNew();
	taskProgress.leadingText = ('Fixing art: ');
	var trackCount = trackList.count;
	var trackIdx = 0;

	listAsyncForEach(trackList, 
		// callback for each track
		(track, next) => {

			// Update the background task text
			trackIdx++; 
			taskProgress.text = sprintf('%d of %d', trackIdx, trackCount);
			
			cvrList = track.loadCoverListAsync();
			cvrList.whenLoaded().then(() => {
				cvrList.modifyAsync(function () {
					if(cvrList.count > 0) {
						
						//loop through all covers to find if any cover identified as the front
						//any cover not identified as a front cover is set as "Not Specified"
						var blnFrontFound = false;
						for(var intCnt = 0; intCnt < cvrList.count; intCnt++){
							cvr = cvrList.getValue(intCnt);
							if (blnFrontFound == true){
								cvr.coverTypeDesc = "Not specified";
							} else {
								if( cvr.coverTypeDesc == "Cover (front)"){
									blnFrontFound = true;
								} else {
									cvr.coverTypeDesc = "Not specified";
								}
							}
						}
						alert("1: " + blnFrontFound);
						//if none found make the first embedded cover the front
						if (blnFrontFound == false){
							for(var intCnt = 0; intCnt < cvrList.count; intCnt++){
								if(cvrList.getValue(intCnt).coverStorage == 0){
									cvr = cvrList.getValue(intCnt);						
									cvr.coverTypeDesc = "Cover (front)";
									blnFrontFound = true;
									intCnt = cvrList.count + 1;
								}
							}
						}
						alert("2: " + blnFrontFound);
						//if none found, make first image the front
						if (blnFrontFound == false){
							cvr = cvrList.getValue(0);
							cvr.coverTypeDesc = "Cover (front)";
						}
		
						//select all covers except front cover
						for(var intCnt = 0; intCnt < cvrList.count; intCnt++){
							cvr = cvrList.getValue(intCnt);
							if( cvr.coverTypeDesc == "Cover (front)"){
								cvrList.setSelected(intCnt, false);
								alert("3: " + blnFrontFound);
							} else{
								cvrList.setSelected(intCnt, true);
							}
						};
					}
					cvrList.deleteSelected();
					if(cvrList.count > 0){
						var firstCover = cvrList.getValue(0);

						// The if (firstCover &&) part is just a sanity check to avoid a crash 
						if (firstCover && firstCover.coverStorage != 0){ 
							firstCover.coverStorage = 0;
							alert("4: " + blnFrontFound);							
						}
					}
					// Commit track, then AFTER the commit is done, process the next track
                    cvrList.modified = true;
					alert("track commit")
					track.commitAsync().then(next);
				});
			});
		}, 
		// callback when done
		() => {
			// finish the background task
			trackList.commitAsync();
			alert("tracklist Commit")
			return taskProgress.terminate();
		}
	);

	messageDlg(_('Album Art is fixed!'), 'Information', ['btnOK'], {
		defaultButton: 'btnOK'
	}, undefined);

}
Disregard the alerts....that's me debugging. :cry:
TIA
MPG
Triumph - Hold On: Music holds the secret, to know it can make you whole.
MPG
Posts: 418
Joined: Tue May 13, 2008 11:22 pm

Re: fixAlbumArt

Post by MPG »

I've noticed that if I close MM after running the script, there is a prompt asking if I wish to terminate background scripts. It closes before I ever get to say yes or no.
TIA
MPG
Triumph - Hold On: Music holds the secret, to know it can make you whole.
Ludek
Posts: 4959
Joined: Fri Mar 09, 2007 9:00 am

Re: fixAlbumArt

Post by Ludek »

Not tested, but by a quick look maybe

Code: Select all

track.commitAsync().then(next);
inside of the cvrList.modifyAsync could somehow cause it as it is still in the cvrList write lock?

So try to replace

Code: Select all

track.commitAsync().then(next);
just by

Code: Select all

next();
as subsequent calling of
trackList.commitAsync();
commits all the tracks anyhow.

EDIT: Also ensure that cvrList.modified is TRUE, which is done automatically whenever write lock is acquired, e.g. by modifyAsync (based on our internal code review), so just to ensure it is true (for it to save during commitAsync)

EDIT2: Alternativelly you can use method

Code: Select all

track.saveCoverListAsync();
(if you want to save just the coverList)
Ludek
Posts: 4959
Joined: Fri Mar 09, 2007 9:00 am

Re: fixAlbumArt

Post by Ludek »

BTW: Just tested you code and if I remove the alerts then it is working just fine here.

The only problem is that the message is shown too early, I guess you might want to move it to the end callback after the tracklist.commitAsync like this:

Code: Select all

...
                        () => {
                            // finish the background task
                            trackList.commitAsync().then(() => {
                                taskProgress.terminate();
                                messageDlg(_('Album Art is fixed!'), 'Information', ['btnOK'], {
                                    defaultButton: 'btnOK'
                                }, undefined);
                            });
                        }
MPG
Posts: 418
Joined: Tue May 13, 2008 11:22 pm

Re: fixAlbumArt

Post by MPG »

Hi Ludek...Great news that it worked for you.

Can you double check your results by doing the following test...
After running the script on a song that has several links to images (none that are embedded), click on the image to open it. There is when the freeze is happening on my machine. If I close and reopen MM, the song doesn't have any images.

Edit:
I'm asking if you can do this test, as MM shows the expected result, but then freezes when I double click the image. If I just close MM after running the script and then check the song, it has no images.
Last edited by MPG on Wed Feb 09, 2022 4:16 pm, edited 1 time in total.
TIA
MPG
Triumph - Hold On: Music holds the secret, to know it can make you whole.
MPG
Posts: 418
Joined: Tue May 13, 2008 11:22 pm

Re: fixAlbumArt

Post by MPG »

One other item...I get a failure that track.saveCoverListAsync is not a function. :roll:
TIA
MPG
Triumph - Hold On: Music holds the secret, to know it can make you whole.
drakinite
Posts: 965
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: fixAlbumArt

Post by drakinite »

Btw: If the alerts are giving you trouble, considering the fact that they are synchronous & blocking, try just doing console.log. You can open the dev tools either by going to localhost:9222 on any browser (even on non debug builds) or by right click > inspect element on a debug build.
Image
Student electrical-computer engineer, web programmer, part-time MediaMonkey developer, full-time MediaMonkey enthusiast
I uploaded many addons to MM's addon page, but not all of those were created by me. "By drakinite, Submitted by drakinite" means I made it on my own time. "By Ventis Media, Inc., Submitted by drakinite" means it may have been made by me or another MediaMonkey developer, so instead of crediting/thanking me, please thank the team. You can still ask me for support on any of our addons.
MPG
Posts: 418
Joined: Tue May 13, 2008 11:22 pm

Re: fixAlbumArt

Post by MPG »

sorry, one more item I just noticed...
When you open the song(s) that you run the script against, the image label says "Save to Tag", whereas a song with the embedded image says "Stored In Tag"...

Not sure what the difference is or how to get it changed...Any suggestions?
TIA
MPG
Triumph - Hold On: Music holds the secret, to know it can make you whole.
Ludek
Posts: 4959
Joined: Fri Mar 09, 2007 9:00 am

Re: fixAlbumArt

Post by Ludek »

This is the code that works fine for me:

Code: Select all

function removeAll(trackList) {

                    var cvr;
                    var cvrList;

                    // Create a background task so that the user can see the progress in the bottom bar
                    var taskProgress = app.backgroundTasks.createNew();
                    taskProgress.leadingText = ('Fixing art: ');
                    var trackCount = trackList.count;
                    var trackIdx = 0;

                    listAsyncForEach(trackList,
                        // callback for each track
                        (track, next) => {

                            // Update the background task text
                            trackIdx++;
                            taskProgress.text = sprintf('%d of %d', trackIdx, trackCount);

                            cvrList = track.loadCoverListAsync();
                            cvrList.whenLoaded().then(() => {
                                cvrList.modifyAsync(function () {
                                    if (cvrList.count > 0) {

                                        //loop through all covers to find if any cover identified as the front
                                        //any cover not identified as a front cover is set as "Not Specified"
                                        var blnFrontFound = false;
                                        for (var intCnt = 0; intCnt < cvrList.count; intCnt++) {
                                            cvr = cvrList.getValue(intCnt);
                                            if (blnFrontFound == true) {
                                                cvr.coverTypeDesc = "Not specified";
                                            } else {
                                                if (cvr.coverTypeDesc == "Cover (front)") {
                                                    blnFrontFound = true;
                                                } else {
                                                    cvr.coverTypeDesc = "Not specified";
                                                }
                                            }
                                        }
                                        
                                        //if none found make the first embedded cover the front
                                        if (blnFrontFound == false) {
                                            for (var intCnt = 0; intCnt < cvrList.count; intCnt++) {
                                                if (cvrList.getValue(intCnt).coverStorage == 0) {
                                                    cvr = cvrList.getValue(intCnt);
                                                    cvr.coverTypeDesc = "Cover (front)";
                                                    blnFrontFound = true;
                                                    intCnt = cvrList.count + 1;
                                                }
                                            }
                                        }
                                        
                                        //if none found, make first image the front
                                        if (blnFrontFound == false) {
                                            cvr = cvrList.getValue(0);
                                            cvr.coverTypeDesc = "Cover (front)";
                                        }

                                        //select all covers except front cover
                                        for (var intCnt = 0; intCnt < cvrList.count; intCnt++) {
                                            cvr = cvrList.getValue(intCnt);
                                            if (cvr.coverTypeDesc == "Cover (front)") {
                                                cvrList.setSelected(intCnt, false);                                                
                                            } else {
                                                cvrList.setSelected(intCnt, true);
                                            }
                                        };
                                    }
                                    cvrList.deleteSelected();
                                    if (cvrList.count > 0) {
                                        var firstCover = cvrList.getValue(0);

                                        // The if (firstCover &&) part is just a sanity check to avoid a crash 
                                        if (firstCover && firstCover.coverStorage != 0) {
                                            firstCover.coverStorage = 0;                                            
                                        }
                                    }
                                    // Commit track, then AFTER the commit is done, process the next track
                                    cvrList.modified = true;                                    
                                    next();
                                });
                            });
                        },
                        // callback when done
                        () => {
                            // finish the background task
                            trackList.commitAsync().then(() => {
                                taskProgress.terminate();
                                messageDlg(_('Album Art is fixed!'), 'Information', ['btnOK'], {
                                    defaultButton: 'btnOK'
                                }, undefined);
                            });
                        }
                    );                   
                }

without a freeze and leaving only single artwork front cover / embedded for each track.

Re tracks.saveCoverListAsync : Sorry, just realized that this method is public, but not published, which means that isn't accessible from JS (only from our Delphi internal code). So use track(list).commitAsync() instead.
MPG
Posts: 418
Joined: Tue May 13, 2008 11:22 pm

Re: fixAlbumArt

Post by MPG »

Hi Ludek,
Thanks again for trying to help me. I copied your function into my code and ran the script against my test songs and it still doesn't work for me. My test songs have no embedded images. All images are linked to individual images within a folder (no thm file)
The code removes all of the images except one.
The last image has a label on it that says: "Save to tag"
If I click on the image, MM freezes. If I close and reopen MM without clicking on the image, the image isn't saved to the song.

When I run the code against a song that has an embedded image and other linked images, it works just fine.

Would it be possible for me to share the song and it's images with you so you can test on your machine?
TIA
MPG
Triumph - Hold On: Music holds the secret, to know it can make you whole.
Ludek
Posts: 4959
Joined: Fri Mar 09, 2007 9:00 am

Re: fixAlbumArt

Post by Ludek »

Ahh, I see, isn't it casued by this code:

Code: Select all

  if (firstCover && firstCover.coverStorage != 0) {
                                            firstCover.coverStorage = 0;                                            
                                        }
?

You seem to force the storage from csFile, csNotSaved to csTag this way, but I guess it fails whenever the "imageData" are not loaded thus subsequent writing to the file tag fails.

Is this a purpose to change the storage to csTag ? I am searching for a way how to force the "imageData" to load for the cover item (from JS) if there is one already...

EDIT: Just checking our code and the "imageData" are kept only for the new unsaved images, but once the images are saved then they are internally loaded only on purpose and then freed to not occupy memory needlessly. So I will need to add a function to load the data from JS or rather a new method to change the coverStorage (or add it to the coverStorage setter)
Last edited by Ludek on Thu Feb 10, 2022 5:05 pm, edited 1 time in total.
MPG
Posts: 418
Joined: Tue May 13, 2008 11:22 pm

Re: fixAlbumArt

Post by MPG »

Yes, the purpose is to make one image csTag by using a series of criteria and remove the other images.

Thanks I appreciate your assistance.
TIA
MPG
Triumph - Hold On: Music holds the secret, to know it can make you whole.
Post Reply