iTunesからMusic.appへの移行時に失われたトラック情報を取り戻す方法

更新
本稿の内容が必要になったが、大元のPHPが載っていたサイトが消えてしまった。リンク切れの修正など、本稿を使える状態にするための最低限の更新を行う。

主題
iTunes上で各トラックにつけたラブ済み再生回数をMusic.appに引き継ぐ方法について述べる。

背景
macOS 10.15 Catalinaになって、iTunesは3分割され、そのうち音楽部分をMusic.appが引き継ぐことになった。ところが実際引き継いでみると、iTunes上で登録した順番や、各トラックにつけたラブ済み、それに再生回数が引き継がれないことが分かった。これは筆者にとって衝撃だった。
しかし、そのことをTwitterに書いてから、ふと思い立った。AppleScriptがあるじゃないかと…。
早速Music.appの用語辞書を覗いてみると、登録した順番(date added)は読み出し専用で無理なものの、ラブ済み(Loved)、再生回数(played count)は書き込み可能だった。
後は、iTunes側で、これは以前からバックアップの意味を兼ねて、ライブラリを他のアプリと共有するにチェックしていたので、iTunes Library.xmlが残っていた。これを読み出してAppleScriptに渡せば良いのだが、そこからが、へなちょこ技術者の真骨頂、紆余曲折の始まりだった…。

紆余曲折
あまり内情をバラすと、本ブログの信用問題にもなりかねないので、伏せるべきかも知れないが、それだと自分が苦労した道筋も見えなくなるので、恥を忍んで書くことにする。

  • xpath
    iTunes Library.xmlは、xmlと拡張子がつくんだから、xpathで情報を取り出せるのではないかと考えた。しかし、惜しいところまでは行ったものの、artist, album, title, lovedを関係性を維持して取り出すことはできなかった。

  • csvからtsv
    項目感の関係性を維持するということからcsvを思いついた。だがiTunes Library.xmlをcsvにするスクリプトは見つかったものの、AppleScriptでcsvを読み出す方法が見つからなかった。自前でtsv(タブ区切りテキスト)に変換してトライしたが、順番が狂ったりした。

  • 再びcsv
    tsvを諦め、再びcsvを読み出す方法をサーチ。なんとか見つかった。ところが、実際のcsv項目数とAppleScriptに読み込んだcsv、つまりリストの項目数が合わないという問題が起きた。最終的には、それはフィールドセパレータ内、つまり項目を囲むダブルクオートの使い方であることが分かったが、分かるまでは苦労した。
    この段階でAppleScriptの穴を主宰されている@Piyomaruさんに大変お世話になりました。お礼申し上げます。

手順
ようやく手順を説明する。やや手間と時間がかかるが、得るものは大きいので頑張って欲しい。

用意するもの
iTunes Library.xml、テキストエディタ、CSVの保存、または書き出しができる表計算ソフト(Excel、LibreOffice、Apache OpenOffice、Numbersなど)

作業は以下の手順で行う。

  1. iTunes Library.xmlの整形

  2. PHPスクリプトの入手と一部改造

  3. CSVファイルの生成

  4. CSV内のダブルクオートの最適化

  5. AppleScriptスクリプトライブラリの準備

  6. Music.appへの情報転記AppleScriptの入手と実行

以下、具体的に説明していく。

  1. iTunes Library.xmlの整形
    iTunes Library.xmlをcsvにするスクリプトの大元を書かれている[PHP]iTunesライブラリのXMLファイルから必要な項目だけをCSVにしたいの記事にもあるように、iTunes Library.xmlにおける各トラック(楽曲)が持つ属性値は、項目にあわせてデータ型が指定してある。これを統一してやる必要がある。

    以下のコマンドを実行する。

                sed -e 's/string>/value>/g' iTunes\ Library.xml |\
                sed -e 's/integer>/value>/g' |\
                sed -e 's/date>/value>/g' |\
                sed -e 's/<true\/>/<value>true<\/value>/g' \
                > iTunes_Library_valued.xml
            
  2. PHPスクリプトの入手と一部改造
    [PHP]iTunesライブラリのXMLファイルから必要な項目だけをCSVにしたいの記事に掲載されているPHPスクリプトをファイルに保存し、一部改造する。

    改造箇所は以下の通り。

                --- a/iTunes2csv.php	2020-04-08 10:16:16.000000000 +0900
                +++ b/iTunes2csv.php	2020-04-08 10:37:10.000000000 +0900
                @@ -1,7 +1,9 @@
                +#!/usr/local/bin/php
                +
                 <?php
    
                 //XMLファイルの場所
                -$xml_path = 'iTunes.xml';
                +$xml_path = 'iTunes_Library_valued.xml';
    
                 //XMLファイルを読み込む
                 $data = simplexml_load_file($xml_path);
                @@ -16,7 +18,7 @@
                 $csvSepa = '","';
    
                 //CSVのヘッダー項目を設定。不要ならばコメントアウト
                -$tracks = '"TrackID'.$csvSepa.'Artist'.$csvSepa.'Name'.$csvSepa.'Genre'.$csvSepa.'Grouping'.$csvSepa.'Rating'.$csvSepa.'Composer'.$csvSepa.'Comments"'."\n";
                +//$tracks = '"TrackID'.$csvSepa.'Artist'.$csvSepa.'Name'.$csvSepa.'Genre'.$csvSepa.'Grouping'.$csvSepa.'Rating'.$csvSepa.'Composer'.$csvSepa.'Comments"'."\n";
    
                 //データ数の分だけループ処理させる
                 for ($i=0; $i < $dataCnt; $i++){
                @@ -31,25 +33,19 @@
                   $combineArray = array_combine((array)$keyArray, (array)$valueArray);
    
                   //CSVに入れたい項目を配列から取得する。
                -  $TrackID = $combineArray['Track ID'] ?? '';
                   $Artist = $combineArray['Artist'] ?? '';
                   $Name = $combineArray['Name'] ?? '';
                -  $Genre = $combineArray['Genre'] ?? '';
                -  $Grouping = $combineArray['Grouping'] ?? '';
                -  $Rating = $combineArray['Rating'] ?? '';
                -  $Composer = $combineArray['Composer'] ?? '';
                -  $Comments = $combineArray['Comments'] ?? '';
                +  $Album = $combineArray['Album'] ?? '';
                +  $PCount = $combineArray['Play Count'] ?? '';
                +  $Loved = $combineArray['Loved'] ?? '';
    
                   //CSVの行データを作成する
                   $tracks .= '"'
                -  . $TrackID . $csvSepa
                   . $Artist . $csvSepa
                   . $Name . $csvSepa
                -  . $Genre . $csvSepa
                -  . $Grouping . $csvSepa
                -  . $Rating . $csvSepa
                -  . $Composer . $csvSepa
                -  . $Comments . "\"\n";
                +  . $Album . $csvSepa
                +  . $PCount . $csvSepa
                +  . $Loved .  "\"\n";
                 }
    
                 //CSVファイルを吐き出して終了
            

    変更箇所の適用
    記事に掲載されているスクリプトをコピペでファイルに保存、diffファイルと同じ場所に置き、以下を実行する。
    筆者は、スクリプト及び差分ファイルにiTunes2csvという名前を使った。

                patch -p1 < iTunes2csv.diff
                chmod +x iTunes2csv.php
            
  3. CSVファイルの生成
    上記により、iTunes Library.xmlのCSV生成準備が整った。
    iTunes_Library_valued.xmlとPHPスクリプトを同じ場所に置き、以下のコマンドを実行して、CSVを生成する。

                ./iTunes2csv.php
            
  4. CSV内のダブルクオートの最適化
    紆余曲折の項でも書いたが、CSVをAppleScriptに読み込むにあたり、ダブルクオートが適切な場所にある状態にする必要がある。これはコマンドで一括処理できるものではない。Excel、LibreOffice、Apache OpenOfficeあるいはNumbersなどの表計算ソフトの助けを借りる。

    Excelの場合
    LibraryData.csvをテキストエディタで開き、全項目を選択、コピーする。Excelに貼り付け、データメニューの区切り位置を設定する。その後、ファイルを保存する。

    LibreOffice、Apache OpenOfficeの場合
    LibraryData.csvを開き、一つのフィールドだけ選択、カット&ペーストで戻す。その後、ファイルを保存する。

    Numbersの場合
    LibraryData.csvを開き、一つのフィールドだけ選択、カット&ペーストで戻す。その後、ファイル > 書き出す メニューからCSVを選んで保存する。開いた元のファイルは保存する必要はない。

  5. AppleScriptスクリプトライブラリの準備
    今回、CSVをパース(AppleScript内のリストに変換)する部分は、スクリプトライブラリを用いた。その準備をする。

    1. CSVのParse 5(ASOC)にあるAppleScriptを保存する。
      筆者の場合は、csvParse5という名前を使った。

    2. ホームフォルダ直下のLibraryフォルダにScript Librariesという名前のフォルダを作る。

    3. Script LibrariesフォルダにcsvParse5を入れる。

    4. なお、MacScripterのアカウントがあれば、Nigel Garvey氏作のスクリプトも、同じ手順でライブラリ化しても良い。

  6. Music.appへの情報転記AppleScriptの入手と実行
    トラックの特定をアーティスト、アルバム、曲名のすべてが一致するまでループを回して行うため非常に長い時間(Core i7 2.6GHzのMacでCSV1000行で3時間くらい)がかかる。実行する際は注意されたい。
    2022.04.17に実施した実績では、1,000行で4,602秒、1,737行で7,579秒かかった。csv1行当り4秒ちょっとかかる計算だ。

                --スクリプトライブラリを呼び出すtellブロック。
                tell script "csvParse5"
                
                    --CSVをUTF-8文字列として読み込む。
                    set csvText to read "/Users/roushi/Desktop/csv/LibraryData.csv" as «class utf8»
                    
                    --スクリプトライブラリを呼び出す。
                    set theList to its makeListsFromCSV:theString commaIs:","
                    
                end tell
                
                (* Nigel Garvey氏作のスクリプトを用いる場合
                --スクリプトライブラリを呼び出すtellブロック。
                tell script "csvToList"
                
                    --CSVをUTF-8文字列として読み込む。
                    set csvText to read (choose file) as «class utf8»
    
                    --スクリプトライブラリを呼び出す。
                    set theList to csvToList(csvText, {})
    
                end tell
                *)
    
                --Music.appを処理するtellブロック
                tell application "Music"
                
                    --Music.appに登録されている全てのファイルトラックを取得。
                    set totalTracks to (every file track)
                    
                    --iTunesライブラリをCSV化したもの(リスト)を1行づつリピート
                    repeat with theOneLine in theList
                    
                        --リストの各フィールド値を読み込む。
                        set theArtist to item 1 of theOneLine as string
                        set theName to item 2 of theOneLine as string
                        set theAlbum to item 3 of theOneLine as string
                        set thePlCount to item 4 of theOneLine as string
                        set theLoved to item 5 of theOneLine as string
    
                        --Music.appの全トラックのループ。
                        repeat with aTrack in totalTracks
                        
                            --アーティスト、アルバム、曲名が一致したら…。
                            if (artist of aTrack) is theArtist then
                                if (album of aTrack) is theAlbum then
                                    if (name of aTrack) is theName then
                                    
                                        --再生回数をiTunesとMusic.app双方の和にする。
                                        set played count of aTrack to ((played count of aTrack) + thePlCount)
                                        
                                        --トラックがラブ済みだったら…。
                                        if theLoved is "true" then
                                        
                                            --ラブ済みにする。
                                            set loved of aTrack to true
                                            
                                        end if
                                        
                                        --一致したトラックの処理が済んだらループを抜ける。
                                        exit repeat
                                        
                                    end if
                                    
                                end if
                                
                            end if
                            
                        end repeat
                        
                    end repeat
                    
                end tell
            

    上記スクリプトを
    ScriptEditorで開く(注)

    (注)リンクを開くと、2段階の確認ダイアログが表示される。(下図)

    ScriptEditorを開く確認ダイアログ


    Script作成確認ダイアログ

    最初に開くのは、ScriptEditorを開くかどうかを確認するダイアログ。次に開くのはスクリプトの開発元が不明であることを警告するダイアログである。後者のダイアログについて、筆者を信頼するかしないかはお任せするが、このAppleScriptが危険なものか判断できない人は使わないで戴きたい。

以上。

この投稿へのコメント

コメントはありません。

コメントを残す

メールアドレスが公開されることはありません。

この投稿へのトラックバック

トラックバックはありません。

トラックバック URL