Tuesday, September 27, 2022

#1 2022-06-15 04:11:33 am

MrCee
Member
Registered: 2016-07-01
Posts: 18

Music.app tags: use property and variable in array. Dynamic update

Hey all,

I need help converting a string to a track property that will help my current code.
I have a display dialog showing what I am attempting to do, these are all strings.
Potentially there is a better way to achieve the same result, maybe scripting o's property which I need to learn when to invoke.

In the past, I've been relying on many lines of code to filter out text if the item begins with, contains, or ends with before I use ASOC in a subroutine. I'm attempting to do the filtering process using strings with repeat loops here which will help de-duplicate many lines of code.

I'm only focusing on 3 track tags here, but many more will be added. The idea is to remove domain names through ASOC after I have filtered results by text.

Applescript:


use framework "Foundation"
use scripting additions

tell application "Music"
   
   set trackID to (get persistent ID of every file track of library playlist 1)
   set trackName to (get name of every file track of library playlist 1)
   set trackArtist to (get artist of every file track of library playlist 1)
   set trackComment to (get comment of every file track of library playlist 1)
   
   repeat with a from 1 to count of the trackID
       set listBeginsWith to {" "}
       set listContains to {" ", "www.", "http"}
       set listEndsWith to {".com", ".net", ".org", ".co", ".su", ".xyz", ".club", ".me", ".ro", ".biz", ".info", ".pro", ".us", " ", "-"}
       
       --set trackTag to {{name:item a of trackName}, {artist:item a of trackArtist}, {comment:item a of trackComment}}
       set trackTag to {{"name", item a of trackName}, {"artist", item a of trackArtist}, {"comment", item a of trackComment}}
       
       repeat with thisTag in trackTag
           repeat with thisBeginsWith in listBeginsWith
               if item 2 of thisTag begins with thisBeginsWith then
                   display dialog "PROPERTY: " & item 1 of thisTag & return & "TAG: " & item 2 of thisTag & return & "BEGINS WITH: " & thisBeginsWith
                   set item 1 of thisTag of (some track of library playlist 1 whose persistent ID is item a of trackID) to my cleanUpText(item 2 of thisTag)
               end if
           end repeat
           
           repeat with thisContains in listContains
               if item 2 of thisTag contains thisContains then
                   display dialog "PROPERTY: " & item 1 of thisTag & return & "TAG: " & item 2 of thisTag & return & "CONTAINS: " & thisContains
                   set item 1 of thisTag of (some track of library playlist 1 whose persistent ID is item a of trackID) to my cleanUpText(item 2 of thisTag)
               end if
           end repeat
           
           repeat with thisEndsWith in listEndsWith
               if item 2 of thisTag ends with thisEndsWith then
                   display dialog "PROPERTY: " & item 1 of thisTag & return & "TAG: " & item 2 of thisTag & return & "ENDS WITH: " & thisEndsWith
                   set item 1 of thisTag of (some track of library playlist 1 whose persistent ID is item a of trackID) to my cleanUpText(item 2 of thisTag)
               end if
           end repeat
       end repeat
       
   end repeat
end tell

on cleanUpText(someText)
   set theNSString to current application's NSString's stringWithString:someText
   
   set theOptions to (current application's NSRegularExpressionSearch as integer) + (current application's NSRegularExpressionAnchorsMatchLines as integer) + (current application's NSRegularExpressionCaseInsensitive as integer)
   
   set theNSString to theNSString's stringByReplacingOccurrencesOfString:"(http.*)?(www\\.)?[\\w]+\\.(com|net|org|co|su|xyz|club|me|ro|biz|info|pro|us)" withString:"" options:theOptions range:{location:0, |length|:theNSString's |length|()}
   set theNSString to theNSString's stringByReplacingOccurrencesOfString:"^[\\d]+\\. +" withString:"" options:theOptions range:{location:0, |length|:theNSString's |length|()}
   set theNSString to theNSString's stringByReplacingOccurrencesOfString:"-( +)?$" withString:"" options:theOptions range:{location:0, |length|:theNSString's |length|()}
   set theNSString to theNSString's stringByReplacingOccurrencesOfString:" +" withString:" " options:theOptions range:{location:0, |length|:theNSString's |length|()}
   
   set theWhiteSet to current application's NSCharacterSet's whitespaceAndNewlineCharacterSet()
   set theNSString to theNSString's stringByTrimmingCharactersInSet:theWhiteSet
   
   return theNSString as text
end cleanUpText

Last edited by MrCee (2022-06-15 05:57:39 am)

Offline

 

#2 2022-06-15 05:55:22 am

KniazidisR
Member
From:: Greece
Registered: 2019-03-03
Posts: 2524

Re: Music.app tags: use property and variable in array. Dynamic update

Properties of tracks in Music.app is not array of keys and values, but a record. Also, AppleScript has lists and not arrays.

You can't do repeat loop with keys in the record. Because the key-value pairs are not indexed in the record. For example, following attempt should throw error:

Applescript:


set theCount to (count {aKey:"aValue", cKey:"cValue"})

repeat with i from 1 to theCount
   item i of {aKey:"aValue", cKey:"cValue"}
end repeat

The proper way to change values in the record doesn't use repeat loops, but key names:

Applescript:


tell {aKey:"aValue", cKey:"cValue"} -- source record
   set aKey of it to "cleanedKeyValue"
   set cKey of it to "cleanedKeyValue2"
   return it -- changed record
end tell

Last edited by KniazidisR (2022-06-15 06:18:05 am)


Model: MacBook Pro
OS X: Catalina 10.15.7
Web Browser: Safari 14.1
Ram: 4 GB

Offline

 

#3 2022-06-17 05:20:34 am

technomorph
Member
Registered: 2017-12-14
Posts: 279

Re: Music.app tags: use property and variable in array. Dynamic update

Could you give some examples of what your wanting to replace?
Have you checked and tested your RegEx Patterns?

I see that you already have RegEx Patterns.
I've got a bunch of Extensions on NSString and NSRegularExpression and NSDictionary
that I've trimmed down to a Framework that you can import and use.

Many of them help simply the creation of items.
For Example:

Applescript:

NSString
-(NSRegularExpression*)stringAsRegExPattern;
-(NSRange)fullRange;
-(NSString*)cleanAllWhiteSpace;

-(NSRegularExpression*)stringAsRegExPattern {
NSString* pattern = self;
NSRegularExpressionOptions regexOptions = NSRegularExpressionCaseInsensitive | NSRegularExpressionUseUnicodeWordBoundaries;
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:pattern
options:regexOptions
error:&error];
if (error) {
NSLog(@"%s --------------- ERROR CREATING THE REGEX", __PRETTY_FUNCTION__);
return nil;
}
return regex;
}

-(NSRange)fullRange {
return NSMakeRange(0, self.length);
}

-(NSString*)cleanAllWhiteSpace {
NSString* dirtyString = [NSString stringWithString:self];
NSRegularExpressionOptions regexOptions = NSRegularExpressionCaseInsensitive;
NSString* pattern = @"[\\s]+";
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:regexOptions error:&error];
if (error) {
NSLog(@"Couldn't create regex with given string and options");
}

NSString* cleanSpaceString = [regex stringByReplacingMatchesInString:dirtyString options:0 range:dirtyString.fullRange withTemplate:@" "];

NSString* cleanEndSpace = [cleanSpaceString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return cleanEndSpace;
}

Applescript:

NSRegularExpression
-(BOOL)containsMatchesForString:(NSString*)aString;
-(NSUInteger)countOfMatchesInString:(NSString*)aString;
-(NSString*)replaceMatchesInString:(NSString*)aString
withTemplate:(NSString*)replaceString;

-(BOOL)containsMatchesForString:(NSString*)aString {
BOOL hasMatches = ([self countOfMatchesInString:aString] > 0);
return hasMatches;
}

-(NSUInteger)countOfMatchesInString:(NSString*)aString {
NSUInteger aCount = 0;
if (aString.ok) {
aCount = [self numberOfMatchesInString:aString
options:0 range:aString.fullRange];
}
return aCount;
}

-(NSString*)replaceMatchesInString:(NSString*)aString
withTemplate:(NSString*)replaceString {
NSString* newString;
NSString* source = aString;
if (source.ok) {
newString = [self
stringByReplacingMatchesInString:source
options:0
range:source.fullRange
withTemplate:replaceString];
newString = [newString cleanAllWhiteSpace];
}
return newString;
}

Applescript:

NDDictionary
+(NSDictionary*)dictionaryWithDictionary:(NSDictionary*)aDict
withOnlyKeys:(NSArray*)keepKeys;
+(NSDictionary*)dictionaryWithDictionary:(NSDictionary*)aDict
withOnlyKeys:(NSArray*)keepKeys
replaceMatchesInString:(NSString*)aFlagString
withString:(NSString*)aReplaceString;


+(NSDictionary*)dictionaryWithDictionary:(NSDictionary*)aDict
withOnlyKeys:(NSArray*)keepKeys {
NSMutableDictionary* trimDict = [NSMutableDictionary dictionaryWithDictionary:aDict];
NSArray* allKeys = trimDict.allKeys;
for (NSString* aKey in allKeys) {
if (![keepKeys containsObject:aKey]) {
[trimDict removeObjectForKey:aKey];
}
}
return trimDict;
}

+(NSDictionary*)dictionaryWithDictionary:(NSDictionary*)aDict
withOnlyKeys:(NSArray*)keepKeys
replaceMatchesInString:(NSString*)aFlagString
withString:(NSString*)aReplaceString {
NSMutableDictionary* aCleanedDict = [NSMutableDictionary new];
NSDictionary* aSourceDict = [self dictionaryWithDictionary:aDict
withOnlyKeys:keepKeys];
NSRegularExpression* aRegEx = [aFlagString stringAsRegExPatternForMatchWordType:RxMatch_Any];
NSArray* allKeys = aSourceDict.allKeys;
for (NSString* aKey in allKeys) {
NSString* aValue = [aSourceDict valueForKey:aKey];
if ([aRegEx containsMatchesForString:aValue]) {
NSString* aCleanString = [aRegEx replaceMatchesInString:aValue
withTemplate:aReplaceString];
[aCleanedDict setValue:aCleanString
forKey:aKey];
}
}
return aCleanedDict;
}

I think you could use the NSDictionary Extension (may need to change it a bit
but you get the idea)

Applescript:

set keepKeys to currentApplication's NSArray's arrayWithArray:{"name", "artist", "comment"}
set aFlagString to currentApplication's NSString's stringWithString:".com .net .org .co"

which will create aPattern "\\b(\\.com|\\.new|\\.org|\\.co)\\b"
you could alway change it so you supply a pattern to the function
set aPattern to urrent application's NSString's stringWithString:"(http.*)?(www\\.)?[\\w]+\\.(com|net|org|co|su|xyz|club|me|ro|biz|info|pro|us)"


Applescript:


repeat with aTrack in allLibraryTracks
set aCleanedDict to currentApplication's NSDictionary's dictionaryWithDictionary:(aTrack's properties)
withOnlyKeys:keepKeys
replaceMatchesInString:aFlagString
withString:""

set cleanedKeys to aCleanedDict's allKeys()
repeat with aKey in cleanedKeys
aTracks's setValue:(aCleanedDict's valueForKey:aKey) forKey:aKey
end repeat
end repeat

basically you go thru each track
get a dictionary from the properties of the tracks that contains on the keys you want
go thru that dictionary and check the values, clean if needed
if they are cleaned add that info to aCleanedDict
get back a cleaned dictionary
update the track with values from that dictionary


actually I just updated and added another function on NSDictionary

Applescript:

+(NSDictionary*)dictionaryWithDictionary:(NSDictionary*)aDict
withOnlyKeys:(NSArray*)keepKeys
replaceMatchesInPattern:(NSString*)aPattern
withString:(NSString*)aReplaceString;

you can DL the framework here:
https://drive.google.com/drive/folders/ … sp=sharing

Offline

 

#4 2022-06-17 09:22:53 am

MrCee
Member
Registered: 2016-07-01
Posts: 18

Re: Music.app tags: use property and variable in array. Dynamic update

Thanks all for the great suggestions. I'm still yet to experiment with the above suggestions but for now, here are some explanations for my code so far...

The reason for so many repeat loops is that I have found as per Nigel's suggestion some time ago it is more efficient to qualify text first before passing it to a subroutine that invokes ASOC clean-up.

I started using repeat loops because I was searching for a better way of writing repetitive code.

Has anyone ever used the 'eval' command in Linux (bash/zsh)? What I wouldn't give to concatenate a line of applescript code with simple text in my OP where (item 1 of thisTag) could be converted from string {"title", "artist", "comment"} to property of record as it is reflected in the 'display dialogue'.  So there has to be a better way.

Because I am dealing with so many records, and have never had a problem with scanning/updating every file track of Library Playlist 1 (and I would prefer to do this rather than selecting tracks), I found that text filtering needed something similar to this on every tag I was checking before invoking my cleanUpText(someText)
This is just 1 tag I'm checking before invoking ASOC and it is so much faster by qualifying the text first... although looking forward to checking out @technomorph dictionary framework.

Applescript:


                   if item a of trackComment begins with " " or ¬
                       item a of trackComment contains " " or ¬
                       item a of trackComment contains "www." or ¬
                       item a of trackComment contains "http" or ¬
                       item a of trackComment ends with ".com" or ¬
                       item a of trackComment ends with ".net" or ¬
                       item a of trackComment ends with ".org" or ¬
                       item a of trackComment ends with ".co" or ¬
                       item a of trackComment ends with ".su" or ¬
                       item a of trackComment ends with ".xyz" or ¬
                       item a of trackComment ends with ".club" or ¬
                       item a of trackComment ends with ".me" or ¬
                       item a of trackComment ends with ".ro" or ¬
                       item a of trackComment ends with ".su" or ¬
                       item a of trackComment ends with ".biz" or ¬
                       item a of trackComment ends with ".info" or ¬
                       item a of trackComment ends with ".pro" or ¬
                       item a of trackComment ends with ".us" or ¬
                       item a of trackComment ends with " " or ¬
                       item a of trackComment ends with "-" then
                       set comment of (some track of library playlist 1 whose persistent ID is item a of trackID) to my cleanUpText(item a of trackComment)
                   end if


on cleanUpText(someText)
   set theNSString to current application's NSString's stringWithString:someText
   set theOptions to (current application's NSRegularExpressionSearch as integer) + (current application's NSRegularExpressionAnchorsMatchLines as integer) + (current application's NSRegularExpressionCaseInsensitive as integer)
   
   -- remove a domain that optionally may start with http or www.
   set theNSString to theNSString's stringByReplacingOccurrencesOfString:"(http.*)?(www\\.)?[\\w]+\\.(com|net|org|co|su|xyz|club|me|ro|biz|info|pro|us)" withString:"" options:theOptions range:{location:0, |length|:theNSString's |length|()}
   
   -- remove specific digits pattern e.g. "01. ", "02. ", "03. " when track numbers appear at the start of any tag
   set theNSString to theNSString's stringByReplacingOccurrencesOfString:"^[\\d]+\\. +" withString:"" options:theOptions range:{location:0, |length|:theNSString's |length|()}
   
   -- remove trailing hyphens and spaces after domain is removed
   set theNSString to theNSString's stringByReplacingOccurrencesOfString:"-( +)?$" withString:"" options:theOptions range:{location:0, |length|:theNSString's |length|()}
   
   -- replace double spaces with a single space
   set theNSString to theNSString's stringByReplacingOccurrencesOfString:" +" withString:" " options:theOptions range:{location:0, |length|:theNSString's |length|()}
   
   -- trim beginning and trailing whitespace
   set theWhiteSet to current application's NSCharacterSet's whitespaceAndNewlineCharacterSet()
   set theNSString to theNSString's stringByTrimmingCharactersInSet:theWhiteSet
   
   return theNSString as text
end cleanUpText

Last edited by MrCee (2022-06-17 10:08:15 am)

Offline

 

#5 2022-06-17 09:37:03 pm

technomorph
Member
Registered: 2017-12-14
Posts: 279

Re: Music.app tags: use property and variable in array. Dynamic update

This is where RegEx is your friend .
Once you build the right pattern you can check for
All of your tests and replace them as needed.

Apple events are pretty taxing that’s why I
Suggest getting all the properties in one request.

Then checking for pattern matches and cleaning.
Only setting new changed values
On the track. (Again reducing calls)

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)