iTunesLibrary.framework経由でiTunes/Music.appのライブラリのジャンルごとの再生回数を集計するAppleScriptです。
7,000曲程度入っているライブラリで、ジャンル名の名寄せも含め、開発環境(MacBook Pro Retina 2012, Core i7 2.66GHz@macOS 10.14.6)で0.7秒程度で終了します。2,500曲程度入っているMac mini 2014 Core i5 2.6GHz@macOS 10.15.1で0.6秒程度です。
macOS 10.14まで(iTunes.app)と、macOS 10.15以降(Music.app)でも同様にiTunesLibrary.framework経由でライブラリへのアクセスが行えます。
AppleScript名:iTunes Libraryの再生回数をジャンルごとに集計、ジャンル名名寄せ付き |
— Created 2019-11-10 by Takaaki Naganoya — 2019 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "iTunesLibrary" –https://developer.apple.com/reference/ituneslibrary/itlibmediaitem?language=objc property NSArray : a reference to current application’s NSArray property NSString : a reference to current application’s NSString property NSScanner : a reference to current application’s NSScanner property NSPredicate : a reference to current application’s NSPredicate property NSDictionary : a reference to current application’s NSDictionary property NSCountedSet : a reference to current application’s NSCountedSet property NSMutableArray : a reference to current application’s NSMutableArray property NSSortDescriptor : a reference to current application’s NSSortDescriptor property NSMutableCharacterSet : a reference to current application’s NSMutableCharacterSet –ジャンル名寄せリスト(2要素から構成される2D List) set genreNayoseList to {{"World", "ワールド"}, {"Anime", "アニメ"}, {"Electronic", "エレクトロニック"}, {"R&B/ソウル", "R&B/ソウル"}, {"Kayokyoku", "歌謡曲"}, {"Electronic", "エレクトロニック"}, {"Vocal", "ヴォーカル"}, {"Classical", "クラシック"}, {"Dance", "ダンス"}, {"Soundtrack", "サウンドトラック"}, {"Rock", "ロック"}} set library to current application’s ITLibrary’s libraryWithAPIVersion:"1.0" |error|:(missing value) if library is equal to missing value then return set aRes1 to (library’s applicationVersion()) as string –> "12.10.1.37" @ macOS 10.15 set aRes2 to (library’s apiMinorVersion()) –> 1 set aRes3 to (library’s apiMajorVersion()) –> 1 set playLists to library’s allPlaylists() set gArray to library’s allMediaItems()’s genre set aRes to countItemsByItsAppearance(gArray) of me set bRes to genreNayoseAndUnify(aRes, genreNayoseList) of me –> {{genreName:"サウンドトラック", numberOfTimes:1965}, {numberOfTimes:1209, genreName:"Podcast"}, {genreName:"ロック", numberOfTimes:1128}, {genreName:"クラシック", numberOfTimes:705}, {numberOfTimes:517, genreName:"ポップ"}, {genreName:"アニメ", numberOfTimes:533}, {numberOfTimes:383, genreName:"J-Pop"}, {numberOfTimes:292, genreName:"Pop"}, {numberOfTimes:279, genreName:"社会/文化"}, {numberOfTimes:252, genreName:missing value}, {genreName:"ワールド", numberOfTimes:246}, {numberOfTimes:187, genreName:"ジャズ"}, {genreName:"エレクトロニック", numberOfTimes:168}, {numberOfTimes:125, genreName:"R&B"}, {numberOfTimes:104, genreName:"ニューエイジ"}, {numberOfTimes:81, genreName:"Unclassifiable"}, {genreName:"歌謡曲", numberOfTimes:60}, {numberOfTimes:57, genreName:"Children’s"}, {numberOfTimes:54, genreName:"オルタナティブ"}, {numberOfTimes:38, genreName:"Holiday"}, {numberOfTimes:32, genreName:"Data"}, {numberOfTimes:31, genreName:"イージーリスニング"}, {genreName:"ヴォーカル", numberOfTimes:31}, {numberOfTimes:19, genreName:"iTunes U"}, {numberOfTimes:17, genreName:"フォーク"}, {numberOfTimes:15, genreName:"ブルース"}, {numberOfTimes:15, genreName:"ディズニー"}, {numberOfTimes:15, genreName:"シンガーソングライター"}, {numberOfTimes:14, genreName:"Easy Listening"}, {numberOfTimes:14, genreName:"ラテン"}, {numberOfTimes:14, genreName:"Electronica/Dance"}, {numberOfTimes:14, genreName:"個人ジャーナル"}, {genreName:"ダンス", numberOfTimes:12}, {numberOfTimes:10, genreName:"アクション/アドベンチャー"}, {numberOfTimes:9, genreName:"J-POP"}, {numberOfTimes:9, genreName:"New Age"}, {numberOfTimes:7, genreName:"演歌"}, {numberOfTimes:6, genreName:"少年"}, {numberOfTimes:6, genreName:"青年"}, {numberOfTimes:6, genreName:"キッズ/ファミリー"}, {numberOfTimes:5, genreName:"Video"}, {numberOfTimes:5, genreName:"プログラミング"}, {numberOfTimes:4, genreName:"ホリデー"}, {numberOfTimes:4, genreName:"カントリー"}, {numberOfTimes:4, genreName:"科学/医学"}, {numberOfTimes:3, genreName:"ビジネス"}, {numberOfTimes:3, genreName:"コメディ"}, {numberOfTimes:3, genreName:"Game Music"}, {numberOfTimes:3, genreName:"Latin"}, {genreName:"R&B/ソウル", numberOfTimes:5}, {numberOfTimes:2, genreName:"#NIPPONSEI @ IRC.MIRCX.COM"}, {numberOfTimes:2, genreName:"Technology"}, {numberOfTimes:2, genreName:"ヒップホップ/ ラップ"}, {numberOfTimes:2, genreName:"ヒップホップ/ラップ"}, {numberOfTimes:2, genreName:"日本"}, {numberOfTimes:2, genreName:"ドラマ"}, {numberOfTimes:1, genreName:"社会科学"}, {numberOfTimes:1, genreName:"コンピュータ/テクノロジー"}, {numberOfTimes:1, genreName:"Tech ニュース"}, {numberOfTimes:1, genreName:"科学/自然"}, {numberOfTimes:1, genreName:"その他"}, {numberOfTimes:1, genreName:"児童書フィクション"}, {numberOfTimes:1, genreName:"レゲエ"}, {numberOfTimes:1, genreName:"Lifestyle & Home"}, {numberOfTimes:1, genreName:"ホリデーミュージック"}, {numberOfTimes:1, genreName:"マネジメント/リーダーシップ"}, {numberOfTimes:1, genreName:"インストゥルメンタル"}, {numberOfTimes:1, genreName:"SF/ファンタジー"}, {numberOfTimes:1, genreName:"146"}, {numberOfTimes:1, genreName:"健康/フィットネス"}, {numberOfTimes:1, genreName:"148"}, {numberOfTimes:1, genreName:"NHK FM(東京)"}, {numberOfTimes:1, genreName:"Seattle Pacific University – Latin"}, {numberOfTimes:1, genreName:"チルドレン・ミュージック"}, {numberOfTimes:1, genreName:"名作"}, {numberOfTimes:1, genreName:"Folk"}} –ジャンルのリストを出現回数で集計 on countItemsByItsAppearance(aList) set aSet to NSCountedSet’s alloc()’s initWithArray:aList set bArray to NSMutableArray’s array() set theEnumerator to aSet’s objectEnumerator() repeat set aValue to theEnumerator’s nextObject() if aValue is missing value then exit repeat bArray’s addObject:(NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{"genreName", "numberOfTimes"}) end repeat –出現回数(numberOfTimes)で降順ソート set theDesc to NSSortDescriptor’s sortDescriptorWithKey:"numberOfTimes" ascending:false bArray’s sortUsingDescriptors:{theDesc} return bArray as list end countItemsByItsAppearance on genreNayoseAndUnify(aList as list, genreNayoseList as list) set gList to FlattenList(genreNayoseList) of me set didProc to {} set a2List to {} repeat with i in aList set aGenre to genreName of i if (aGenre is in gList) and (aGenre is not in didProc) then repeat with ii in genreNayoseList set jj to contents of ii if aGenre is in jj then copy jj to {g1, g2} if chkAlphabet(g1) of me = true then set targG to g2 set targG2 to g1 else set targG to g1 set targG2 to g2 end if set s1Res to searchByGenreName(aList, targG) of me set s2Res to searchByGenreName(aList, targG2) of me set s3Res to addMutipleLists({s1Res, s2Res}) of me set tmpClass to class of s3Res if tmpClass = list then set s3Res to contents of first item of s3Res end if set outRec to {genreName:targG, numberOfTimes:s3Res} set the end of didProc to g1 set the end of didProc to g2 exit repeat end if end repeat set the end of a2List to outRec else if (aGenre is not in didProc) then set the end of a2List to contents of i end if end if end repeat return a2List end genreNayoseAndUnify on searchByGenreName(aList as list, aGenreName as string) set predicatesStr to "genreName == ’" & aGenreName & "’" set anArray to (NSArray’s arrayWithArray:aList) set aPred to (NSPredicate’s predicateWithFormat:predicatesStr) set bRes to (anArray’s filteredArrayUsingPredicate:aPred) if (bRes as list) = {} then return {} set bbRes to first item of bRes return (numberOfTimes of bbRes) as list end searchByGenreName on addMutipleLists(s1List) script spdAdd property s1List : {} property s3List : {} end script copy s1List to (s1List of spdAdd) –init set s1Len to length of first item of (s1List of spdAdd) set (s3List of spdAdd) to makeZero1DList(s1Len, 0) of me repeat with i in (s1List of spdAdd) set tmpLen to length of i if tmpLen is not equal to s1Len then return false repeat with ii from 1 to s1Len set tmp1 to contents of item ii of (s3List of spdAdd) set tmp2 to contents of item ii of i set item ii of (s3List of spdAdd) to (tmp1 + tmp2) end repeat end repeat return (s3List of spdAdd) end addMutipleLists –指定要素を指定回数追加したリストを作成する on makeZero1DList(itemMax, itemElem) set allData to {} repeat itemMax times set the end of allData to itemElem end repeat return allData end makeZero1DList –By Paul Berkowitz –2009年1月27日 2:24:08:JST –Re: Flattening Nested Lists on FlattenList(aList) set oldDelims to AppleScript’s text item delimiters set AppleScript’s text item delimiters to {"????"} set aString to aList as text set aList to text items of aString set AppleScript’s text item delimiters to oldDelims return aList end FlattenList — アルファベットのみか調べて返す on chkAlphabet(checkString) set aStr to NSString’s stringWithString:checkString set allCharSet to NSMutableCharacterSet’s alloc()’s init() allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of "a", 26)) allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of "A", 26)) set aBool to my chkCompareString:aStr baseString:allCharSet return aBool as boolean end chkAlphabet on chkCompareString:checkString baseString:baseString set aScanner to NSScanner’s localizedScannerWithString:checkString aScanner’s setCharactersToBeSkipped:(missing value) aScanner’s scanCharactersFromSet:baseString intoString:(missing value) return (aScanner’s isAtEnd()) as boolean end chkCompareString:baseString: |