Media KEGのKLSを自動でタグ付けする(手抜き)

去年KenwoodのMedia Keg HD60GD9ECを買ったのはいいけど、タグ付けが面倒臭くてずっと放置していた。最近いろいろ試してプログラムからタグ付け出来るのが分かったので、ある程度自動で付けられるようにしてみた。

背景

Media Keg HD60GD9ECは高音質が売りのHDD型オーディオプレーヤーだ。それを生かすならやはり非圧縮音源or可逆圧縮で音楽を楽しみたいもの。しかし、付属のツールにはリッピングCDDBとの連携機能はなく、転送ツールでWAV→KLS(Kenwood Lossless)変換を掛けた場合、転送後のKLSファイルに手動でタグ付けする必要があった。


FreeDBTaggerなどのサードパーティ製のツールでタグ付けは出来るものの、CD一枚分ずつCDDBから取得して保存するのも面倒くさい。(それでも随分助かってるけど。本当にありがたい。)


HD60GD9ECは旧世代機(HD30GA9)と違ってファイルが暗号化されているわけでもなく、タグ付けには標準的なID3v2フォーマットが使われている、ということはID3v2タグを読み書き出来るソフトウェアライブラリとか使えば自力でなんとかできそうだ。

方針

Kenwoodの付属ツールにはCDのリッピング機能はないため、リッピングにはiTunesを使用している。その際iTunesCDDBから楽曲情報を取得しているし、それをファイルとして出力している(真・plist形式のxml読み込み用digester-rules(およびクラス) - terazzoの日記参照。) タグ付けのネタ情報としてはこれを利用出来そう。


転送ツールで作成されたKLSファイルは元のWAVファイルの拡張子を.WAVから.KLSに変えたファイル名となるので、ファイル名の拡張子を除いた部分でマッチングしたら対応する楽曲の情報を取れそう。「01 Allegro」とかは沢山ありそうなので、アルバム名ぐらいまではマッチングに使う。


使用するID3タグ付けライブラリはいろいろ試したけど、ID3v2に対応していて日本語も使えるとなるとなかなか見つからない(MP3::ID3Libとか。本当はスクリプト言語から使えるのが嬉しいんだけど……) なんとかそれなりに軽くて日本語が使えるのがJavaで実装されたEntaggedというプロジェクトに含まれるAudioformats(entagged-audioformats-0.15.jar)だったのでこれを使用。

KLSファイルのタグ読み込みクラス

Entagged Audioformatsのオーディオファイルの読み込みは、フォーマット毎にAudioFileReaderクラスのサブクラスとして実装されている。(Mp3FileReader, WavFileReaderなど。) MP3FileReaderはId3v2の読み込みには対応しているが、Mp3の情報(ビットレート)なども読み込もうとしてしまう実装の為、そのままだとKLSの読み込みには使えない。そこで、MP3FileReaderを真似てKlsFileReaderを実装した。

package sample.klstagger;
import java.io.IOException;
import java.io.RandomAccessFile;

import entagged.audioformats.EncodingInfo;
import entagged.audioformats.Tag;
import entagged.audioformats.exceptions.CannotReadException;
import entagged.audioformats.generic.AudioFileReader;
import entagged.audioformats.mp3.Id3v2Tag;
import entagged.audioformats.mp3.util.Id3v2TagReader;

public class KlsFileReader extends AudioFileReader {
    /** TagReader。ID3v2用のものを使用*/
    private Id3v2TagReader idv2tr;

    public KlsFileReader() {
        super();
        idv2tr = new Id3v2TagReader();
    }

    /** Encode情報は取得しない(nullを戻す) */
    @Override
    protected EncodingInfo getEncodingInfo(RandomAccessFile randomaccessfile)
            throws CannotReadException, IOException {
        return null;
    }

    /** Id3v2Tagを読み込んで戻す */
    @Override
    protected Tag getTag(RandomAccessFile randomaccessfile)
            throws CannotReadException, IOException {
        Id3v2Tag id3v2tag = null;
        try {
            id3v2tag = this.idv2tr.read(randomaccessfile);
        } catch(CannotReadException e) {
            // e.printStackTrace();
            id3v2tag = null;
        }
        return id3v2tag;
    }

}

書き込みはMp3FileWriterクラスがそのまま使える。(KlsFileWriterを作っても良いけど、実装一緒だし省略)
ファイルのタグを上書きする処理はこんな感じ。TaggingExceptionは今回作った例外クラス。TrackInfoは楽曲情報を含む只の値クラス。

package sample.klstagger;

import java.io.File;

import org.apache.commons.io.FilenameUtils;

import entagged.audioformats.AudioFile;
import entagged.audioformats.Tag;
import entagged.audioformats.exceptions.CannotReadException;
import entagged.audioformats.exceptions.CannotWriteException;
import entagged.audioformats.mp3.Mp3FileWriter;

public class KlsTagger {
    /**
     * ファイルに指定されたタグを付ける
     * @param targetFile オーディオファイル
     * @param trackInfo 楽曲情報を含む値クラスTrackInfoのインスタンス
     * @throws TaggingException タグ付け時の例外
     */
    public void tagFile(File targetFile, TrackInfo trackInfo) throws TaggingException {
        AudioFile audioFile;
        try {
            // 読み込み
            audioFile = new KlsFileReader().read(targetFile);
            Tag tag = audioFile.getTag();
            
            // タグ情報を更新
            updateTag(tag, trackInfo);
            
            // 書き込み(Mp3用のクラスを借用)
            Mp3FileWriter mp3FileWriter = new Mp3FileWriter();
            mp3FileWriter.write(audioFile);
        } catch (CannotReadException e) {
            e.printStackTrace();
            throw new TaggingException("Failed to read Tag." + e.getMessage(), e);
        } catch (CannotWriteException e) {
            e.printStackTrace();
            throw new TaggingException("Failed to write Tag." + e.getMessage(), e);
        }
    }
    /**Tagの内容をTrackInfoの内容で上書き */
    private void updateTag(Tag tag, TrackInfo trackInfo) {
        tag.setAlbum(trackInfo.getAlbum());
        tag.setArtist(trackInfo.getArtist());
        tag.setGenre(trackInfo.getGenre());
        tag.setTitle(trackInfo.getTitle());
        tag.setTrack(trackInfo.getTrack());
        if (trackInfo.getYear() != null) {
            tag.setYear(trackInfo.getYear());
        }
    }

    private static final String KLS_EXTENSION = "KLS";
    /** このクラスが処理出来るファイルかを判定 */
    public boolean canTag(File targetFile) {
        String path = targetFile.getPath();
        String extension = FilenameUtils.getExtension(path);
        return extension.equalsIgnoreCase(KLS_EXTENSION);
    }
}


TrackInfoの値は、前回のiTunes Music Library.xmlを読むコードを利用して取得し、楽曲単位でファイル名をキーにHashMapで保持(ソースコードは略。)


また、コマンドラインから呼び出せるように、パラメータとしてiTunes Music Library.xmlとタグ付けの対象となるディレクトリのパスを指定するとディレクトリ内を階層的にタグ付けしていくクラスMainを作成した(ソースコードは略)

使用法

Kenwoodの転送ツールはWindows上で動作するので、タグ付けもWindows上でおこなっている。次のようなバッチファイルを書いておく。
KLSTagger.bat

@echo off
set BASE_DIR=%~dp0
set CLASSPATH=%BASE_DIR%
set CLASSPATH=%CLASSPATH%;%BASE_DIR%lib\klstagger.jar
set CLASSPATH=%CLASSPATH%;%BASE_DIR%lib\entagged-audioformats-0.15.jar
set CLASSPATH=%CLASSPATH%;%BASE_DIR%lib\commons-beanutils-core-1.7.0.jar
set CLASSPATH=%CLASSPATH%;%BASE_DIR%lib\commons-digester-1.8.jar
set CLASSPATH=%CLASSPATH%;%BASE_DIR%lib\commons-io-1.4.jar
set CLASSPATH=%CLASSPATH%;%BASE_DIR%lib\commons-logging-1.1.jar
set CLASSPATH=%CLASSPATH%;%BASE_DIR%lib\log4j-1.2.13.jar

set ITUNES_LIBRARY_PATH="C:\Documents and Settings\USER\My Documents\My Music\iTunes\iTunes Music Library.xml"

@echo on
java -classpath "%CLASSPATH%" -DiTunesLibraryFilePath=%ITUNES_LIBRARY_PATH% sample.klstagger.Main %1

正直BATファイルの書き方がよく分からない……
klstagger.jarは今回作成したクラスを含むjarファイル。
%0にバッチファイル自体のパスがくるので、そこからの相対的な位置で必要なjarファイルをクラスパスを追加してjavaを起動している。
%1にはバッチファイルへの引数が来る。Explorerなどでバッチファイル上にファイルをドロップした場合、%1にドロップしたファイルのパスを渡しつつ起動してくれるので、普段はこのバッチのショートカットをデスクトップに置いておき、KEG内のKLSを含むディレクトリをドラッグ&ドロップすれば、ドロップしたファイルのパスを引数として起動(そしてタグ付け)できる。


日本語やアクセント記号付きのアーティスト名なども大丈夫


これでかなり快適になった。でも最後に付属ツールから「ライブラリ更新」が必要だけど。

課題

  • 複数CDに分かれるものも、プレイヤー側では1フォルダにしたい
    • ファイル名が被らなければタグ付け出来るけど、トラック番号がCD単位になる
      • iTunes Music Library.xmlのDisc Number/Disc Countなども使って独自にトラック番号を生成するとか
  • ドロップしたファイルが「Frédéric François Chopin」などのように装飾記号入りだとうまく動かない
    • Javaのmain()で拾った時には既にASCIIになっている(対処方法が分からない)
    • そもそもパラメータ部分の文字コードは何なんだろう。file.encodingはMS932だけど。
    • java.util.Fileで取得したサブディレクトリ名が「Frédéric François Chopin」の場合は問題なく動く
  • 既にタグ付けされている場合にはスキップしても良いかも