Controlling MM5 from external applications

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

Moderators: jiri, drakinite, Addon Administrators

Barry4679
Posts: 2445
Joined: Fri Sep 11, 2009 8:07 am
Location: Australia
Contact:

Controlling MM5 from external applications

Post by Barry4679 »

I have been wanting to control MM5 from an external (python) app, like I did with MM5, for a long time. I have asked here before, but at the time I was unable to understand the advice I was given. It all seemed broken, and there was zero documentation if one was not working with javascript.

The Developer page in the Wiki has just a fraction of the material that is available for MM4. It has said "More Coming soon (MediaMonkey)" since forever.

So I was very pleased to notice today that Drakinite has posted up something that was genuinely useful. It broke through for me, as prior snippets that I had been given caused MM5 to break.

So many thanks Drakinite. I wonder if I could contribute a sub-page to your effort, to contain a Python specific page to help anyone get started ? .... PM me through the forum if you like.

The sample that Drakinite used as illustration in his wiki page is close to what I had been asking about. If that is not a happy coincidence, thank you for that too Drakinite.

Some comments:
  1. . Why is MM5 so fragile when running code that it received via SDB.runJSCode calls?

    Example:
    PetrCBR wrote: Fri Mar 30, 2018 5:13 am Your example does not work because you didn't returned value from getStringValue. app.getIniFile is asynchronous operation so you want to return value using runJSCode_callback (since MM5 2090).

    Code: Select all

    SDB.runJSCode("(function() { app.getIniFile().then(function(iniFileAccess) { runJSCode_callback(iniFileAccess.getStringValue('System','DBName'))   }); })()", True)
    That call, and all my attempts to fix it, caused MM5 to break ... MM5 panel goes white .. all MM5 tasks need to be individually killed by the Windows Task Manager.

    Why does a syntax error crash MM5?

    And sometimes after it fails, and I have done the above housekeeping, connection is difficult to reestablish. .. (-2147023170, 'The remote procedure call failed.', None, None) ... and this where what was working before now fails

    Can that be improved?
  2. I also see your efforts on this page ... ie. The "supported in MM5?" column for the SDBApplication ActiveX class.
    I looks like you were starting something that you didn't finish.
    Is there any list of what was converted to MM5?
    It seems to me to be a waste of their effort if they don't make any of it discoverable.

    ie. did this happen?
    Ludek wrote: Sun Dec 10, 2017 5:55 am Anyhow looking into our code you seem to be right that SDBPlayList is not fully implemented, most of the code of the corresponding properties/methods is commented out.
    Seems to make sense to add them, going to discuss with other devs and look into it.
    I know that ActiveX is not cross platform, but it very useful for anybody not from a web programming background, ie doesn't know javascript .. which atm seems about as much fun as poking sharp sticks into my eyes
  3. What is the easiest way to test JS functions before sending them to to MM5?
    Example; the function that you ran to generate the playlist contents. what tool did you, when you were a pennyless student :) , use to test a function like that?

    Is testable from the Shift+Ctrl+Alt DevTools Console that opens in the browser? ... Else what do you recommend for casual use?
  4. The script that you used in your Wiki article gets me going, which is good.

    But I am using it against MM5 AutoPlaylists. I have no control over how large the generated playlist may be, and I potentially have many playlists to generate, and it all happens during the close down of my app, so is noticeable if there is a delay. That method sends 55 (!) attributes per track, including lyrics.

    My preference would be something that did something like this:
    1. either trigger SendTo, for the tracks generated by AutoPlaylist 'xxx', to Static Playlist 'yyy' ... ie. all done inside the mm5 db, without getting all the 55 columns, and converting up and down into json
    2. or return the SQL statement which generated the tracklist ... it should be available because I can see it logged into the MM5 debug log. Much of he time I only need the album ids, so I only really need the Where, ORDER and Limit clauses
    My application has attached the MM5.db database, so I could then read what I want from there, directly.
    I am likely to be able to achieve that?

    [UPDATE] re Q.2 ... the bit asking whether SDBPlayList was added to the MM5 COM implementation.

    I see the answer. I have found out how to browse your COM package, and see that it has been implemented. Thanks.
    It looks like I can pull down the tracklist from an AutoPlaylist, just using python and COM.
    So that would appear to address Q.4 also.

    It would help if the objects in your class were arranged in alphabetic order, ie not like this
Want a dark skin for MM5? This is the one that works best for me .. elegant, compact & clear.
drakinite
Posts: 971
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: Controlling MM5 from external applications

Post by drakinite »

Hi Barry,

I can currently respond to #2 and #3 easily:

2. Yeah, that "supported in MM5?" column was something I started back in 2020 but never finished. I think it's out of date anyways, after Ludek and Petr made some changes to COM to fix support for MonkeyMote. I might be able to go through that list more quickly now that I have access to the source code. (Beforehand, I was attempting to test each function manually, but now I can just browse the code and see which functions are empty & which are not.)

3. MM's integrated devtools (right click -> Inspect Element -> Console, or navigating to localhost:9222 in a web browser, or typing shift+alt+ctrl) is the easiest way to test JS functions. Syntax errors in code entered by hand in the JS console will not crash MM (but you still need to be careful about read/write locks in shared lists, those will still cause a crash)
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.
drakinite
Posts: 971
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: Controlling MM5 from external applications

Post by drakinite »

Btw: If you're writing a new script, I'd recommend using HTTP requests instead of the COM API, because COM is only supported on Windows (which is the reason it's being deprecated, as we're working on a Mac port). There are two methods to do this: One is MM-specific (via a POST request to the Media Sharing port, see SampleScripts/remoteControl), and the other is through the MM devtools protocol: https://www.mediamonkey.com/forum/viewt ... 37#p447737 / https://www.mediamonkey.com/forum/viewt ... 55#p481255
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.
Barry4679
Posts: 2445
Joined: Fri Sep 11, 2009 8:07 am
Location: Australia
Contact:

Re: Controlling MM5 from external applications

Post by Barry4679 »

drakinite wrote: Mon Nov 21, 2022 6:06 pm I might be able to go through that list more quickly now that I have access to the source code. (Beforehand, I was attempting to test each function manually, but now I can just browse the code and see which functions are empty & which are not.)
They would do everybody a favour if they commented out "empty functions", ie. so that they were not visible in a COM browser ... especially since that is all we have got as documentation atm.
drakinite wrote: Mon Nov 21, 2022 6:06 pm 3. MM's integrated devtools (right click -> Inspect Element -> Console, or navigating to localhost:9222 in a web browser, or typing shift+alt+ctrl) is the easiest way to test JS functions. Syntax errors in code entered by hand in the JS console will not crash MM (but you still need to be careful about read/write locks in shared lists, those will still cause a crash)
OK, so if the call I want to test is
SDB.runJSCode("app.playlists.getByTitleAsync('Test Playlist').then(function(playlist) { if (!playlist) runJSCode_callback('Could not find playlist'); else playlist.getTracklist().whenLoaded().then(function (list) { runJSCode_callback(list.asJSON); }); });", True)
How do I test the vs component of that? ... Where does the vs component of start and end?
The following doesn't return your 'Could not find playlist string ... the return is "Promise {"_subscribers": Array(0)} ... meaning?
I get the same thing whether or not I provide a valid playlist name.
app.playlists.getByTitleAsync('xxxxx').then(function(playlist) { if (!playlist) runJSCode_callback('Could not find playlist'); else playlist.getTracklist().whenLoaded().then(function (list) { runJSCode_callback(list.asJSON); }); });
drakinite wrote: Mon Nov 21, 2022 6:31 pm Btw: If you're writing a new script, I'd recommend using HTTP requests instead of the COM API, because COM is only supported on Windows (which is the reason it's being deprecated, as we're working on a Mac port).
I don't know what "HTTP requests instead of the COM API" means.
You mean submitting js calls?

Thanks for the advice, but I don't know js, and am about overfilled with computer languages anyway.
I don't care too much about cross platform, and I don't have any Apple devices to test with.
I am running on Windows and Linux, and that is all that I can contend with.
Maybe I will regret some time around late 2028, when you are finished your Mac port :wink:

Re (my main) point #1 on my prior post, I will submit something for Mantis. MM5 should not hard crash due to what is probably just a syntax error in a submitted js call.
Barry4679 wrote: Sun Nov 20, 2022 4:59 am I wonder if I could contribute a sub-page to your effort, to contain a Python specific page to help anyone get started ? .... PM me through the forum if you like.

This is under consideration?
Want a dark skin for MM5? This is the one that works best for me .. elegant, compact & clear.
sonos
Posts: 176
Joined: Wed Aug 07, 2013 11:54 am

Re: Controlling MM5 from external applications

Post by sonos »

drakenite wrote:
I think it's out of date anyways, after Ludek and Petr made some changes to COM to fix support for MonkeyMote.
I don't know any details about MonkeyMote. But after I installed MonkeyMote I noticed that the display in landscape mode on different iPad's differ.
So e.g. for iPad Air iOS 12.5.
Image
here are on the right side additional info (e.g. Artist, title) are displayed,
Now compare this with an iPadPro iOS 16
Image
here the additional info is missing.
I contacted the author of MonkeyMote, but even he has no real idea what the reason could be.

Could the changes draknite mentions be a reason?

Carsten
Barry4679
Posts: 2445
Joined: Fri Sep 11, 2009 8:07 am
Location: Australia
Contact:

Re: Controlling MM5 from external applications

Post by Barry4679 »

This post has been moved.
It makes no sense here.
it belongs after this post in tts original thread.
sonos wrote: Sat Dec 03, 2022 12:25 pm
drakenite wrote:
I think it's out of date anyways, after Ludek and Petr made some changes to COM to fix support for MonkeyMote.
I don't know any details about MonkeyMote. But after I installed MonkeyMote I noticed that the display in landscape mode on different iPad's differ.
So e.g. for iPad Air iOS 12.5.
Image
here are on the right side additional info (e.g. Artist, title) are displayed,
Now compare this with an iPadPro iOS 16
Image
here the additional info is missing.
I contacted the author of MonkeyMote, but even he has no real idea what the reason could be.

Could the changes draknite mentions be a reason?

Carsten
Want a dark skin for MM5? This is the one that works best for me .. elegant, compact & clear.
Barry4679
Posts: 2445
Joined: Fri Sep 11, 2009 8:07 am
Location: Australia
Contact:

Re: Controlling MM5 from external applications

Post by Barry4679 »

Earlier in this thread PetrCBR gave me a code snippet that solved the problem that I had at the time.
Here is the snippet working within Python

Code: Select all

mm_pl_id = 1234
pl = SDB.runJSCode(f"app.playlists.getByIDAsync({mm_pl_id}).then(function(playlist) {{ if (!playlist) runJSCode_callback('Could not find playlist'); else playlist.getTracklist().whenLoaded().then(function (list) {{ runJSCode_callback(list.asJSON); }}); }});", True)
I am now working on a task where I want to run a SQL select query with the MM5 context so that I am covered by your IUNICODE collation.

Here is my sample code hacked from Petr's snippet.

Code: Select all

sql = 'Select count(*) from songs;'
pl = SDB.runJSCode(f"executeQueryAsync({sql}).then(function(playlist) {{ if (!playlist) runJSCode_callback('Could not find playlist'); else playlist.getTracklist().whenLoaded().then(function (list) {{ runJSCode_callback(list.asJSON); }}); }});", True)
There is a problem. This call crashes MM5. and control never return to my Python app.
The MM5 exception says "Uncaught SyntaxError. missing ) after argument list"

1. I can't see the error. Can someone assist please? How do I get it to return a json representation list from a query?
2. I also tried a call to app.db.executeQueryAsync, but got the same error. Which call would be better if I was wanting to select some rows|atrributes from the Songs table? What extra does a call to getTracklist offer over a sql call?

3. Is it good enough that an incoming javascript syntax error can crash the whole of MM5? It is an untidy crash. ie. your Restart|Continue|RestartInSafeMode buttons don't seem to function. I have to kill all the MediaMonkey tasks using Windows Task Manager, and the "background processes" are messy to end, because it seems to spawn replacement processes.
Want a dark skin for MM5? This is the one that works best for me .. elegant, compact & clear.
drakinite
Posts: 971
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: Controlling MM5 from external applications

Post by drakinite »

At first glance, it looks like you just forgot to include quotation marks in your JS code string.

I believe that this:

Code: Select all

f"executeQueryAsync({sql})"
is evaluating to this:

Code: Select all

executeQueryAsync(Select count(*) from songs;)
instead of this:

Code: Select all

executeQueryAsync('Select count(*) from songs;')
In this case, I'd recommend putting a backtick (`) since that's a valid JS string, and is less likely to clash with your SQL string and Python strings. So try this:

Code: Select all

sql = 'Select count(*) from songs;'
pl = SDB.runJSCode(f"executeQueryAsync(`{sql}`).then(function(playlist) {{ if (!playlist) runJSCode_callback('Could not find playlist'); else playlist.getTracklist().whenLoaded().then(function (list) {{ runJSCode_callback(list.asJSON); }}); }});", True)

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.
Barry4679
Posts: 2445
Joined: Fri Sep 11, 2009 8:07 am
Location: Australia
Contact:

Re: Controlling MM5 from external applications

Post by Barry4679 »

Thanks Drakinite. ... What would be do without you?

I tried your code snippet.
I haven't tried to understand your code snippet yet, but I tried it.
It fails.
A dump was dispatched to Ventis. See C33D0000
executeQueryAsync is not defined.
see here
Tested with MM 5.1.0.2811
As discussed before this invalid call crashes MM5 .. white screen.
Want a dark skin for MM5? This is the one that works best for me .. elegant, compact & clear.
drakinite
Posts: 971
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: Controlling MM5 from external applications

Post by drakinite »

Sorry, I didn't check the validity of the code itself, I just wrote the reply when I noticed a syntax error. It might work if you change to app.db.executeQueryAsync: https://www.mediamonkey.com/docs/apinew ... QueryAsync
Haven't verified though because I don't currently have access to my computer.

Note to self (since I plan to look through all my replies in this forum to check what I need to update for the documentation): Find a more straightforward way to note the classes that are accessed via the app object, like how DB is app.db, Playlists is app.playlists, etc.
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.
Barry4679
Posts: 2445
Joined: Fri Sep 11, 2009 8:07 am
Location: Australia
Contact:

Re: Controlling MM5 from external applications

Post by Barry4679 »

That didn't work either.
Uncaught Error: URL is not defined
See dump A14A290A, which was dispatched to Ventis, and I marked for your attention.

I went from your original suggestion:

Code: Select all

response = SDB.runJSCode(f"executeQueryAsync(`{sql}`).then(function(playlist) {{ if (!playlist) runJSCode_callback('Could not find playlist'); else playlist.getTracklist().whenLoaded().then(function (list) {{ runJSCode_callback(list.asJSON); }}); }});", True)
to this

Code: Select all

response = SDB.runJSCode(f"app.db.executeQueryAsync(`{sql}`).then(function(playlist) {{ if (!playlist) runJSCode_callback('Could not find playlist'); else playlist.getTracklist().whenLoaded().then(function (list) {{ runJSCode_callback(list.asJSON); }}); }});", True)
Maybe I applied your patch incorrectly. It was unclear to me what I should do.

Previously I have been advised to make all calls via runJSCode to avoid incompleteness in MM5's COM api.
Want a dark skin for MM5? This is the one that works best for me .. elegant, compact & clear.
drakinite
Posts: 971
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: Controlling MM5 from external applications

Post by drakinite »

What is it exactly you're trying to do?

This time, I actually was able to test the code myself. After checking the docs, I realized app.db.executeQueryAsync is only used for update statements, not for select statements. If you want to retrieve data, use app.db.getQueryResultAsync. This will give you a QueryResult object, but unfortunately it seems that the asJSON property for QueryResult doesn't include all its data. The data are tabulated, where you change the current row with next() and prev() and retrieve entries via getValue(column). Check the SQL editor addon to see an example of how it's used in context.

Here's a basic example of app.db.getQueryResultAsync that can be run in the devtools console:

Code: Select all

app.db.getQueryResultAsync('select * from songs where id < 100;').then(qeuryResult => console.log(queryResult))
Is there any specific reason why you're trying to execute SQL in your app instead of using one of the other methods like app.playlists.getByIDAsync?
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.
Barry4679
Posts: 2445
Joined: Fri Sep 11, 2009 8:07 am
Location: Australia
Contact:

Re: Controlling MM5 from external applications

Post by Barry4679 »

drakinite wrote: Mon Aug 28, 2023 4:26 pm What is it exactly you're trying to do?
[snip]
Is there any specific reason why you're trying to execute SQL in your app instead of using one of the other methods like app.playlists.getByIDAsync?
I think that we have had this conversation before.

The short answer is that I am using data in MM database for purposes that aren't covered by the MM5 application.
There aren't methods or functions for what I done.
And I am populating the MM database with data that it hasn't collected; ie:
  • database-wide columns of nominated Custom columns with dynamic data
  • uploading historic or current play history which occurred outside of MM, eg. plays using Sonos players, or plays from mobile devices or at external locations that were captured by an Last.fm account
  • fuzzy logic queries to facilitate tag matching where LFM have "corrected" an User's scrobble tags, or the User has retagged MM since they scrobbled the play

    Basically I have outsourced the whole areas of track play acquisition, MM track play stats & MM play history & scrobbling to my own application. I do this to overcome weaknesses and deficiencies in what MM offers, from the POV of people who want
    • MM to have coverage of ALL of their plays
    • or who want access to their track play history details, not just the date last played & play count ... for autoplaylist purposes
    • or who are album, not mixed track, focused .. meaning that they want to create playlists and MM Collections based on whole-album level criteria such as date last play, play count, average rating, predominant genre etc
    I am doing this from Python.
    I don't think that I have any options to achieve this other than SQL queries and updates. Do you?

    During the MM5 Alpha it was discussed that the COM interface was going to dumbed down, but the ability to run queries, run updates, to start transactions, to commit transactions was going to be retained.
    I know that you trying to help me, but to be frank I asked for support about this issue a month ago in this thread

    To date I have worked around by attaching the MM database to my own database, and doing everything without involving MM5.
    This is OK, but I do prefer to do it via MM if possible, just I did for accessing autoplaylist contents, using the code snippet that Petr helped me with.

    My preference is because that way, I will be covered by the MM tokenizer for updates to any field covered by FTS coverage, ie. the Custom columns. My current workaround is to delete the SQL Update triggers for the specified Custom columns. I do this each time my application opens, to guard against any MM database rebuilds. ... nb. I am unable to use your dll because you don't ship a 64bit version.

    Is the advice to leave my current approach as is? That you are not going to enable sql access to external applications?
    If so, that is surprising and disappointing.

    nb. I may delete the SQL triggers for the nominated Custom columns any way. Most of the data that I store is numeric. I do some global updates, and to have each affected row, trigger overhead just to bloat FTS files with something that will never be searched upon, seems counterproductive. I have requested a option to turn off FTS coverage for nominated Custom columns, but this request has either been rejected or deferred.
Want a dark skin for MM5? This is the one that works best for me .. elegant, compact & clear.
Barry4679
Posts: 2445
Joined: Fri Sep 11, 2009 8:07 am
Location: Australia
Contact:

Re: Controlling MM5 from external applications

Post by Barry4679 »

drakinite wrote: Mon Aug 28, 2023 4:26 pm Here's a basic example of app.db.getQueryResultAsync that can be run in the devtools console:

Code: Select all

app.db.getQueryResultAsync('select * from songs where id < 100;').then(qeuryResult => console.log(queryResult))
I tried that. It gave this crash. See Dump A14A290A. See here
drakinite wrote: Mon Aug 28, 2023 4:26 pm This time, I actually was able to test the code myself. After checking the docs, I realized app.db.executeQueryAsync is only used for update statements, not for select statements. If you want to retrieve data, use app.db.getQueryResultAsync. This will give you a QueryResult object, but unfortunately it seems that the asJSON property for QueryResult doesn't include all its data. The data are tabulated, where you change the current row with next() and prev() and retrieve entries via getValue(column). Check the SQL editor addon to see an example of how it's used in context.
I am not sure sure how to wrap this in the appropriate js, so that I can call from Python, and then receive fully assembled response.

I looked at the dlgSQLEditor.js file.
I see the loop there.
But how do I package that up so that I can submit a query string, and wait until complete, and then return the assembled json result?

BTW .. I now see the cause of the 1000 row limitation discussed elsewhere in the forum.
Consequences of removing that are .. ?

UPDATE: OK, I now see that you have a typo in the snippet that you sent.
I corrected, and it doesn't crash MM any more.
I now know how to test a JS command validity against MM5 ... thanks.
But I still don't know to use this.
Want a dark skin for MM5? This is the one that works best for me .. elegant, compact & clear.
Post Reply