Need help rewriting MMW4 script for MMW5

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

Moderators: jiri, drakinite, Addon Administrators

pokeefe0001
Posts: 97
Joined: Thu Oct 11, 2018 4:48 pm
Location: USA, Pacific Northwest

Need help rewriting MMW4 script for MMW5

Post by pokeefe0001 »

Note: I am not a "developer". I cobble together bits and pieces from working scripts without necessarily knowing how those original scripts work. That worked in MMW4 but I'm completely lost in MMW5. Luckily this MMW4 script has very little MediaMonkey-specific function in it.

Background:
We have several identical copies of our music library - some on USB flash drives, some on various computer's hard drives, one copy on a NAS. The library is named FDMusic in each case, but the path to the library varies from instance to instance. We have a (portable) copy of MM paired with each library. (Each MM uses just one music library.) We want a playlist created on any of the platforms to be able to be used on any other.

There are some already written MMW4 scripts that import playlists, but that ignore everything but the lowest level part of the file name and find multiple instances of the same named files. We needed an exact match on all of the path below \FDMusic\.

So, my MMW4 script
  1. Asks for a playlist file name. (More about this later.)
  • Determines this MediaMonkey's path to its FDMusic library. (More about this later.)
  • Opens the playlist file and a temporary output file.
  • Reads a playlist record.
  • Changes the high level library path to the local MM's library path
  • Writes the output record and loops back to process the next record.
  • Closes the files.
  • Invokes MM (again) with /ADD to read the temporary file.
This works fine in MMW4, but there are some potential problems.
The most general problem is that I don't know JS (but then, I didn't know VBS, either, so I suspect I can work through that.)

More specific problems:
  1. MMW4 has not way of prompting for a file name (rather than a directory). In fact, Windows provides few ways of doing this, but it can be done in HTA. So my MMW4 script invokes HTA which prompts for a file name and saves the result in a Windows environment variable. The script then extracts the playlist file's path from the environment variable.

    I don't know if HTA can be invoked from MMW5.
    I don't know if the environment variable access functions

    Code: Select all

    Function delENV(ENV)
    '*
    '* Del Environment Variable
    '*
      Dim objWSS
      Set objWSS = CreateObject("WScript.Shell")
      Dim objENV
      Set objENV = objWSS.Environment("Volatile")
      objENV(ENV) = ""
      Set objENV = Nothing
      objWSS.RegDelete "HKCU\Volatile Environment\" & ENV
      Set objWSS = Nothing
      End Function
    
    Function getENV(ENV)
    '*
    '* Get Environment Variable
    '*
      Dim objWSS
      Set objWSS = CreateObject("WScript.Shell")
      Dim objENV
      Set objENV = objWSS.Environment("Volatile")
      getENV = objENV(ENV)
      Set objENV = Nothing
      Set objWSS = Nothing
    End Function
    will work in MMW5 (although I assume they do).
  • My MMW4 technique for determining the local MM's library path is short but (I suspect) very inefficient[

    Code: Select all

      Set TrackData = SDB.Database.QuerySongs(" ")
      If Not TrackData.EOF Then
          'TrackData.Item is a SDBSongData Object
          TrackPath = TrackData.Item.Path
      End If
    Does this work in MMW5? And is there a better way?
  • Does MMW5 have the /ADD option?
Anyone willing to provide some assistance here?
MMV4 (1919) Portable
MMW5 (2606) Portable
Multiple Win11 x64 21H2
Multiple Win7 x32, unknown builds
drakinite
Posts: 965
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: Need help rewriting MMW4 script for MMW5

Post by drakinite »

1. There is a way in MM5 to prompt the user for a file: app.utils.dialogOpenFile. It's on the API docs but I see now that there's a typo (its title is dialogSaveFile) - https://www.mediamonkey.com/docs/api/cl ... ogSaveFile

Code: Select all

app.utils.dialogOpenFile('.', 'm3u', _('Playlist files (*.m3u)'), _('Select playlist file'))
.then(function (path) {
    // This code executes after the user presses "OK", and "path" is the returned file path
    console.log(path);
});
The first parameter (in this case, '.') is the starting path; so if you know where you expect the playlist file to be, you can replace that with the appropriate path.

2. As far as I'm aware, MM5 doesn't have "names" for its scanned folders; so when you say library is named FDMusic, do you simply mean that the folder is named FDMusic? e.g. D:\Documents\Music\FDMusic? If so, then app.filesystem.getLastScannedFolders may help:

Code: Select all

var list = app.filesystem.getLastScannedFolders();
var libraryName = 'FDMusic';
var libraryRegex = new RegExp(`${libraryName}(\\\\|\\/)?$`, 'g');

list.locked(function () {
    for (var i = 0; i < list.count; i++) {
        let item = list.getValue(i);
        let path = item.toString();

        if (path.match(libraryRegex) && list.isChecked(i)) {
            // This code block will execute for any scanned path that ends in libraryName AND is enabled
            console.log(path);
        }
    }
});
It returns a native StringList object which you have to iterate through. That regular expression at the top checks to see if the path ends in FDMusic, with an optional forward/backward slash. (on Windows, it's most likely going to end in a forward slash, but I figured it's best to cover one's bases.)

3+. Moving forward from there, you could probably use app.filesystem.loadTextFromFileAsync and app.filesystem.saveTextToFileAsync -

Code: Select all

app.filesystem.loadTextFromFileAsync('/path/to/playlist.m3u').then(function(text) {
    console.log(text);
    // do what you want to the string
    saveTextToFileAsync('/path/to/playlist_modified.m3u', text).then(function () {
       // this code executes after the file has been saved
    });
});
https://www.w3schools.com/jsref/jsref_replace.asp Here's some info on how to replace strings in JS. Might also be helpful to use another regular expression to get the library base path inside the playlist. The ^ character represents the start of the string/line, and the $ character represents the end of the string/line. (except in the case of ${} - That's something built into Javascript for formatting strings).

Code: Select all

var basePathRegex = new RegExp(`^.*${libraryName}(\\\\|\\/)?$`, 'gm');
I don't know the way to programmatically load playlists/files to MM5. I'll ask the others and one of us will get back to you.
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.
pokeefe0001
Posts: 97
Joined: Thu Oct 11, 2018 4:48 pm
Location: USA, Pacific Northwest

Re: Need help rewriting MMW4 script for MMW5

Post by pokeefe0001 »

drakinite wrote: Mon Feb 14, 2022 3:14 pm 1. There is a way in MM5 to prompt the user for a file: app.utils.dialogOpenFile. It's on the API docs but I see now that there's a typo (its title is dialogSaveFile) - https://www.mediamonkey.com/docs/api/cl ... ogSaveFile

Code: Select all

app.utils.dialogOpenFile('.', 'm3u', _('Playlist files (*.m3u)'), _('Select playlist file'))
.then(function (path) {
    // This code executes after the user presses "OK", and "path" is the returned file path
    console.log(path);
});
The first parameter (in this case, '.') is the starting path; so if you know where you expect the playlist file to be, you can replace that with the appropriate path.
I'll give that a try when I have some time.
drakinite wrote: Mon Feb 14, 2022 3:14 pm 2. As far as I'm aware, MM5 doesn't have "names" for its scanned folders; so when you say library is named FDMusic, do you simply mean that the folder is named FDMusic? e.g. D:\Documents\Music\FDMusic?
Yes, each instance of our music library is a folder named FDMusic (with many sub-folders) so a specific track could be (for instance)
\\OF-Pub-NAS1\Public\Seattle Balkan Dancers Library\FDMusic\Atanas\AK008\A4 Draganovo.mp3
or
H:\FDMusic\Atanas\AK008\A4 Draganovo.mp3
or
D:\MMV4\FDMusic\Atanas\AK008\A4 Draganovo.mp3
etc.

All of the music in each instance of MM all comes from one library, so obtaining the path information for any track in the database will provide the high level path. If my TrackData query return a path of
I:\FDMusic\Yuli\1\Loveshko Daichavo.mp3
I know I have to change the playlist record to
I:\FDMusic\Atanas\AK008\A4 Draganovo.mp3

I see that app.filesystem.getLastScannedFolders is a Chrome function. I know nothing about Chrome and don't know when a file is "scanned". (I gather than is not the same as a MediaMonkey scan.) That may give me what I want, but I know that the database inquiry works (in MMW4). I'm just not sure whether it immediately returns the first track data it finds or reads the whole database before returning anything.
drakinite wrote: Mon Feb 14, 2022 3:14 pm 3+. Moving forward from there, you could probably use app.filesystem.loadTextFromFileAsync and app.filesystem.saveTextToFileAsync -

Code: Select all

fs.loadTextFromFileAsync('/path/to/playlist.m3u').then(function(text) {
    console.log(text);
    // do what you want to the string
    saveTextToFileAsync('/path/to/playlist_modified.m3u', text).then(function () {
       // this code executes after the file has been saved
    });
});
https://www.w3schools.com/jsref/jsref_replace.asp Here's some info on how to replace strings in JS. Might also be helpful to use another regular expression to get the library base path inside the playlist. The ^ character represents the start of the string/line, and the $ character represents the end of the string/line. (except in the case of ${} - That's something built into Javascript for formatting strings).

Code: Select all

var basePathRegex = new RegExp(`^.*${libraryName}(\\\\|\\/)?$`, 'gm');
If fs.loadTextFromFileAsync is a Chrome function then I probably do want to use it since I'd like this to be platform-independant. And I know I have learn about regular expressions and JS find and replace functions. It didn't take too long for me to figure it out in VBS so I don't expect too much trouble there.
drakinite wrote: Mon Feb 14, 2022 3:14 pm I don't know the way to programmatically load playlists/files to MM5. I'll ask the others and one of us will get back to you.
I have experimented and found that MMW5 does have the /ADD command-line parameter.

Code: Select all

D:\MMV5\MediaMonkey\MediaMonkey.exe /ADD "D:\Temp\Playlist\D Drive Playlist.m3u"
will read that MU3 playlist and add it to the Playing list. However, I think it would be better to do that with from within the script. I believe there was a way in MMW4 to search the database for a track, get its track index, and add that to Now Playing. I had some problem with that and found it easier to just invoke MediaMonkey with /ADD. I should probably readdress this.
MMV4 (1919) Portable
MMW5 (2606) Portable
Multiple Win11 x64 21H2
Multiple Win7 x32, unknown builds
drakinite
Posts: 965
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: Need help rewriting MMW4 script for MMW5

Post by drakinite »

pokeefe0001 wrote: Mon Feb 14, 2022 5:45 pm I see that app.filesystem.getLastScannedFolders is a Chrome function. I know nothing about Chrome and don't know when a file is "scanned". (I gather than is not the same as a MediaMonkey scan.) That may give me what I want, but I know that the database inquiry works (in MMW4). I'm just not sure whether it immediately returns the first track data it finds or reads the whole database before returning anything.
It's actually not a Chrome function. Anything starting with app is native Delphi code built specifically for MediaMonkey. It's essentially the "interface" between JavaScript and MM's core. The getLastScannedFolders() function returns the media folders that are part of your library. The "File > Add/Rescan files to the Library..." dialog uses that method to get the list you see.
Image

Most native methods are documented on our API documentation here: https://www.mediamonkey.com/docs/api/classes/App.html
pokeefe0001 wrote: Mon Feb 14, 2022 5:45 pm If fs.loadTextFromFileAsync is a Chrome function then I probably do want to use it since I'd like this to be platform-independant. And I know I have learn about regular expressions and JS find and replace functions. It didn't take too long for me to figure it out in VBS so I don't expect too much trouble there.
Sorry, that "fs" bit was a mistake. While running test code in the devtools console, I stored app.filesystem as a local variable "fs" to make it easier to type. I'll edit the original reply to fix that. app.filesystem.loadTextFromFileAsync is another MediaMonkey-specific function, as described above. That method is documented here: https://www.mediamonkey.com/docs/api/cl ... mFileAsync
Also, yes, making things platform-independent was the goal :slight_smile: Still only Windows-based at the moment, but that will hopefully change in the future.
pokeefe0001 wrote: Mon Feb 14, 2022 5:45 pm However, I think it would be better to do that with from within the script. I believe there was a way in MMW4 to search the database for a track, get its track index, and add that to Now Playing. I had some problem with that and found it easier to just invoke MediaMonkey with /ADD. I should probably readdress this.
Yes, there is a way to do it from a script but I don't know at the moment. Will get back to you soon on that. In the meantime, if you want to get started with packing up your script into an easy-to-install addon (considering you have several different installs that you have to manage), you can take a look at the wiki page here: (the Actions & Hotkeys would be especially useful, I believe) https://www.mediamonkey.com/wiki/Gettin ... d_(Addons)

I haven't yet documented how to add actions to submenus, but it's pretty simple. After creating an action in actions_add.js, add it to one of the "submenus" in the window._menuItems object. For example, if you want it to show up in "Tools":

Code: Select all

window._menuItems.tools.submenu.push({
   action: actions.myCustomAction,
   order: 10, // change this to determine where in the group it appears in
   grouporder: 50 // change this to determine which group it appears in
});
EDIT: Added info to the wiki https://www.mediamonkey.com/wiki/Gettin ... s_to_menus
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.
Post Reply