音楽ファイルのタグを編集する emacs 関数 mf-tag-write / mf-tag-read

# 音楽ファイルのタグを編集する emacs の関数です.
- 音楽ファイルのタグの変更が一括処理できます。
- m4a(mp4) mp3(ID3v2.2, ID3v2.3) oma(Atrac3Plus) flac に対応しています。
- mp3 は同期データには未対応です。
- mp3 は古い規定外の sjis になっているタイトルも正常に読めるようにしてあります(つもり)。その場合編集すると規定のコーディングに変換されて書き込まれます。

よくあるスクリプト支援関数ですが、おそらく atrac 用は無いと思うので公開します。

# インストール
[ここからダウンロード](https://github.com/s-fubuki/wtag.git)展開コピーし init.el で load-path を通し require します。

以下は展開した wtag ディレクトリを "~/.emacs.d/local/" 下にコピーした場合です。

```emacs-lisp:init.el
(setq load-path (cons "~/.emacs.d/local/wtag/" load-path))
(require 'mf-tag-write)
```

# 使い方
以下のようにファイル名と書き換えしたいタグを指定します。
タグは連想リストの形式で指定します。
指定のないタグは素通りしそのまま残ります。

バイナリにパッキングするときに日本語文字コードもそれぞれの規格に合わせて適宜変換するので、特に考えずそのまま書いて大丈夫です。

```emacs-lisp:tag
(mf-tag-write "file.m4a" '(("TALB" . "新アルバム名") ("TIT2" . "新しい曲名")))
```

alist の car が "文字列リテラル" ではなく 'シンボル ならエイリアスになり、
例えば "TALB" ならクォートなしで album 等と書けます。
形式の違うファイルのタグも同じシンボルになるので使い易いと思います。これについては次項で述べます。

```emacs-lisp:alias
(mf-tag-write "file.oma" '((album . "新アルバム名") (title . "新しい曲名")))
```

デフォルトでは元のファイルをバックアップとして残しますが、三番目の引数に `non-nil` を指定するとバックアップを作りません。
文字列を指定するとそのファイル名で新たなファイルが作られバックアップは作りません。


```emacs-lisp:no&nbsp;backup&nbsp;&&nbsp;out&nbsp;other&nbsp;file
(mf-tag-write "file.mp3" '((album . "新アルバム名") (title . "新しい曲名")) t)
(mf-tag-write "file.mp3" '((album . "新アルバム名") (title . "新しい曲名")) "out-file.mp3")
```
<!--
# エイリアス
alias の 各tag との対応は 変数 `mf-*-tag-alias`[^1] に連想リストで設定されています。
てっとり早いのは変数名の末尾にカーソルを置いて C-x C-e するとエコーエリアに表示されるので、C-h C-e 等で広げて見れます。

| mode            | symbol            |
| :--             | :--               |
| mp3(ID3v2.3)    | mf-id33-tag-alias |
| mp3(ID3v2.2)    | mf-id32-tag-alias |
| oma(Atrac3Plus) | mf-oma-tag-alias  |
| m4a(mp4)        | mf-mp4-tag-alias  |
| flac            | mf-flac-tag-alias |


[^1]: `*` がコーデック種になります。 
-->
# アートワークの指定

アートワークの場合手動だとオブジェクトを指定するわけにもいかないのでファイル名で指定できます。

```emacs-lisp:画像指定
(mf-tag-write "file.m4a" '((cover . "~/artwork.jpg")))
```

この場合、拡張子が .jpg か .png ならファイル名文字列だけでも画像の指定と見なします。
括弧がひとつ無くなることに注意してください。

```emacs-lisp:正しい略記
(mf-tag-write "file.mp4" '("~/artwork.jpg"))
```

以下のように間違えると次項で説明する "~/artwork.jpg" というタグを削除する指定になるので、ドットペアで書く方が逆にミスしないかもしれません。

```emacs-lisp:間違い
(mf-tag-write "file.mp4" '(("~/artwork.jpg")))
```

テキストも拡張子 .txt のファイルで同様に指定し iTunes の Lyric 欄等に反映されるデータになります。
これは .oma file 等対応するタグの無いコーデックはしていません。

# タグの削除
タグまたはシンボルだけを指定します。
以下だと cover タグと copy タグのデータが削除されます。

```emacs-lisp:削除
(mf-tag-write "file.m4a" '((cover) (copy)))
```

# 簡単な使用例
今までの例と大差ないですが、 "file.m4a" のアルバム名、ジャンル名とジャケットを差し替えてリリックを削除します。バックアップはせず元のファイル名で書き出されます。

```emacs-lisp:使用例
(mf-tag-write "file.m4a" '((album . "新アルバム名") (genre . "Rock") (cover . "~/image.jpg") (lyric)) 'no-backup)
;; または
(mf-tag-write "file.m4a" '((album . "新アルバム名") (genre . "Rock") "~/image.jpg" (lyric)) 'no-backup)
```

Directory の中の mp4 file をまとめて処理したい等のときは、以下のようなループを組むことが考えられます。 `dolist` は他言語の `foreach` 相当の処理をする引数のリストの要素だけ反復するマクロです。

```emacs-lisp:特定ディレクトリ内すべて
(dolist (f (eshell-extended-glob "~/foo/*.mp4"))
  (mf-tag-write f '((album . "foo album") (artist . "bar artist") (cover . "~/baz.jpg")) t))
```

# flac の 暫定対応につて
mf-lib-mp4 と同じ要領で変更したヘッダと元のヘッダの長さの差分でオフセットをかけ直ししていますが、
Wlakman や foobar2000 なども seek block を参照していないようなので(zero fill された出鱈目な seek block にしても再生に影響がない)、オフセットルーチンが正しく動作しているのかどうかは未確認です。

# ソニーのアプリから作ったデータについて
SonicStageCP や MediaGo, MusicCenter1..2 等のソニーのアプリ(以下 X-アプリも含めてソニーアプリと書きます)でリッピングしたデータの場合、タグ変更を行なうついでに重複登録された画像等をデリートしているのでそれについて述べます。
ちなみに元々はこれが目的で1年前に書いたプログラムでした。

消さずに残すこともできますがその方法は次の項目で述べます。
このデリートに関してはソニーアプリ以外で作ったデータにはほぼ関係無いので、該当しない場合読み飛ばされても大丈夫です。(但しレコチョクのデータには関係あり::[m4a だと Walkman でジャケが表示されない問題](https://qiita.com/s-fubuki/items/0f470b859837927b4e28#m4a-%E3%81%A0%E3%81%A8-walkman-%E3%81%A7%E3%82%B8%E3%83%A3%E3%82%B1%E3%81%8C%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C))

## ひとつのファイルに同じ複数の画像を持っている
ソニーアプリでデータを作ってアートワークがセットされると、mp4(aac) でも oma(atrac) でもひとつのデータファイルに同じ画像を複数枚抱き込みます。
SonicStageCP と MediaGo, MusicCenter1 でしか確認していませんが、SonicStageCP と MusicCenter1 の間の版である X-アプリでもおそらく同じだと思います。

複数の機器への互換性をデータ側で取る対策だと思うのですが、データが大きくなりすぎないようにする為なのか画像を無惨に縮小しています。
Walkman では更に縮小したサムネールを作って、そちらで表示しているのでクオリティダウンされてしまったことに気づきにくいです。

そうした縮小されたまったく同じ画像を atrac では 2枚、 mp4 だと 2～3枚も抱き込んでいます。 参照されるのはそのうちのひとつだけなので、ひとつだけ残して残りを削除しています。
つまりこのツールでタグの書き換えをすると抱え込むアートワークが1枚になります。 (ちなみに MusicCenter2 だと縮小はしなくなったようです?)

## Atrac3Plus (.oma file) で消すもの
いくつか拡張子があるようですが、対応しているのは .oma です。
Atrac3Plus のタグは mp3 の ID3(の4文字のタグのもの)形式をほぼそのまま使っていて、互換性を確保し規格内のディスクリプションを利用して独自拡張をしています。

画像等のバイナリのデータはタグ `GEOB` でディスクプリションが  `OMG_BKLSI` `OMG_FENCA1` `OMG_OLINF` `OMG_TDFCA` と4種類あり、ふたつは画像(同じもの)ですが残りふたつは不明です。
予想ではアーティストリンク等なのでしょうか。
自分はその機能は使っていなかったのですが必ず入っています。

アートワーク画像を差し替えする場合、この 4つある `GEOB` すべてを削除して更めて `GEOB` をひとつ加えてディスクリプション `OMG_TDFCA` で画像をセットしています。
どちらか片方だけ画像のあるデータを作って試したところ、片方だけでも Walkman で表示され違いが判らなかったのでこちらにしましたが特に理由はありません。

画像以外の未知のバイナリタグまで消してしまっていますが、自分の使い方(Walkmanのみ)では今のところ影響は出ていません。

## aac (.mp4 file)で消すもの

クイックタイムの世界ではデータの塊(幹)をアトムと呼ぶようなので以下そう呼びます。

ソニーアプリで作った場合 `udta` `ilst` `meta` `uuid` の各アトムそれぞれにタグ情報が入っていて iTunes 互換であろう処の `ilst` アトム だけ残して アップルタイプでは存在しない `meta` と `uuid` (黄色の箇所)を消しています。(MediaGo だけ `uuid` は無いようです。)
画像を含め曲タイトル等必要なデータは `ilst` にもすべて入っているので問題ありません。
(本文では iTunes の作る "ilst" だけの形式のものをアップルタイプと呼ぶことにします)

![atoms-test-20191219.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239081/fd94274a-7265-f524-249a-fdf5aa8e26fc.png)

`ilst` を覆うようにある `udta` の中にもタイトル情報があり、`ilst` にアートワークを含んだ完全な曲目情報を持っていても、A30 前夜の Walkman だとこちらが存在すると `ilst` の方は参照されません。
この `udta` のデータがソニーのメイン形式のタグだと思われます(この形式を本文ではソニータイプと呼ぶことにします)。mediaGo では `udta` は `ilst` の後にあり図とは位置が違っています。

`udta` 用のアートワーク画像はここではなくこの次の `meta` に入っています。
`meta` はふたつあり `ilst` より後の離れた方の拡張された `meta` です。
そこにはテキストをUTF-16ビッグエンデアンにしたタグがID32形式で入っています。

画像を含んでいてサイズの大きい `meta` の方だけ丸ごと削除しています。 `udta` 側は文字情報のみであまり大きくないしレコチョク等ソニー以外でもデータ自体の持つテキストとして使用していて、MusicCenter でリップした痕跡にもなるのでそのまま残してあります。

そしてこれらの後に音楽データ本体である `mdta` が続き、更にその後 MediaGo 以外だと `uuid` というアトムがあり、 未知の形式でその中にも同じ画像が含まれています。 この `uuid` もソニーのものにしかないので互換のための不要なデータと判断し丸ごと削除しています。
 
あと何故か MP3 のように文字列を ASCIIZ(C言語等のNULL末尾文字列) にしていて、コンバート等でゴミとして出るので末尾の \0 もカットしています。

こちらのデータも1年使ってみて今のところ影響は出ていません。

dired-mp4-atoms-tree を使うとファイルのツリー構造が判りまます。dired から対象ファイルをポイントして `M-x dired-mp4-atoms-tree` してください。

## m4a だと Walkman でジャケが表示されない問題

削除とは別にもうひとつデータを書き換える箇所があるのでそれについて述べます。

m4a ファイルを Walkman で再生すると 音は出るがジャケも表示されなければ曲名以外「不明」と出てしまうファイルがあります。 mf-tag-write はそのようなデータならそれを正常化するパッチもあてます。

A30 前夜の Walkman は AAC (m4a mp4) は Apple タイプでも Sony タイプでも読み込めるのですが、どちらのタグセットを使うのかは、おそらく `udta` アトムに `titl` タグがあるかどうかで判別しています。
`titl` があれば Sony 式として `udta` から、無ければ後方の sony `meta` からジャケ等を絵、 それも無ければ `ilst` からタグを読み込んでいます。

`udta` のこの部分は iTunes で生成したデータにはまったく存在しないのですが、レコチョクのものもおそらく用途は違えどソニーのもの同様 `titl` タグが入っているのです。 レコチョクのデータも勿論基本的に事実上の標準である iTunes 形式なので `ilst` にしかタグデータを持っていないので Sony タイプで読み込むとタグの無い「不明」状態になってしまいます。 ただ `titl` はあるのでそれが曲名として表示されます。

A30 以降だと `ilst` の方しか見ないのでジャケが出ない等の不具合は無いのですが、MusicCenter は今だに A30 以降では参照もしないいくつもの同じ画像をつけた無駄なデータを生成しています。

## ジャケが表示されない問題の対策
この状況を騙すため `udta` に `titl` タグがあれば `Titl` に書き換えて `titl` が無いように見せかけます。 削除までしていません。

このたった1バイトの変更でレコチョクや MORAで購入した m4a も A30前夜の Walkman でもジャケやアー名等のタグ情報が表示されるようになります。

## 削除やパッチを制御する変数

以上の処理はデータがそのようなデータならデフォルトで行なわれますが、データにそのような部分がなければ実行もされないので iTunes でリップしたデータ等にはデフォルトでも何の影響もありません。
が、完全に抑止することも以下のオプション変数でできます。

```emacs-lisp:ソニー拡張部を削除しない
(setq mf-no-mc-delete t)
```
mf-tag-write.el でタグの書き換えとするとき、この変数が `non-nil` 設定されていると、複数の画像が含まれたソニー系アプリの mp4 や oma データの場合でも重複画像の削除を行ないません。デフォルトは nil で削除されます。

```emacs-lisp:`udat'に偽装パッチを当てない
(setq mf-no-one-patch t)
```

この変数が non-nil であるとソニータイプを消す `titl` タグパッチを充てません。 mf-tag-write のタグの書き換えは `ilst` の箇所(アップルタイプ)しかしないので、 ソニータイプ優先の A30 前夜の Walkman 等は書き換えたタグが反映されなくなります。こちらもデフォルトは nil でパッチされます。


## ギャップレスについて
現在のソニーアプリ(MusicCenter)でリップしたデータを Walkman ZX100 や A37 でテストした結果です。

アップルタイプの場合タグでギャップレス情報を持っていて、再生側で繋るよう調整しているようなので、未知の余計な部分をカットしたとき、ギャップレス情報も消されてしまうんではという懸念があったのですが、結論から言って大丈夫でした。mp4(m4a), mp3 共に mf-tag-write を通したものとそうでないものに違いは出ません。
mp4 に限っては atrac 同様データ自体がギャップレスで生成されているようです。
なので逆に CD 1枚通してリップせず曲単位でバラでリップするとブツ切れになる可能性がありますが。

mp3 は 一見ギャプレス風に再生されるのですが、トラックをまたいで連続した曲だと繋ぎ目でリズムが狂います。
クリックが出ないようにクロスフェード風につなげて連続再生しているだけだと思います。
他方 mp4 だと oma のように CD そのままでリズムが狂うこともなくスムースに接続されます。

おそらく FLAC ならそういう問題もなくマスターとしての保存にも適していますが、いかんせんデータが大きすぎるので、大量にお手軽に扱いたい自分にはまだ向きません。

## mp3 でも Walkman でジャケが表示されない問題
ひとつのケースとしてジャケットを収めた APIC フレームの中のファイル名のコーディングが UTF-16-LE だと、 A30 シリーズ前夜のものは元より A30 以降のものでもジャケデータの取得に失敗して表示されません。

これは ascii 範囲文字しか使っていないのにユニコードを使っていて表示できていない場合なら
ascii ファイル名のデータでジャケを再設定するか、
(mf-tag-write file.mp3 nil) などと書き戻しをすると表示できるようになります。

# mf-tag-read

まとめて処理する場合処理に合わせてループを組んだりできるので関数で使えるのがいいのですが、お手軽にちょっとタイトル変えたい等というときは、インタラクティブなインターフェイスがあると便利なので、そういうものを挟み込めるようリード関数とライト関数を分けてあります。

読み出し側関数はタグリストを返すのみでそれを表示用バッファに出力するわけでもないので、インタラクティブ型の関数にはしていません。スクラッチ等で実行すると返されたリストが見れます。

```emacs-lisp:mf-tag-read
(mf-tag-read "01-甘酸っぱい春にサクラサク.oma" 1024 t)
((:tag " *ver" :data "ea3") (:tag "TIT2" :data "甘酸っぱい春にサクラサク") (:tag "TPE1" :data "Berryz工房 x ℃-ute") (:tag "TALB" :data "甘酸っぱい春にサクラサク [Berryz工房盤]") (:tag "TCON" :data "Japanese Pop") (:tag "TXXX" :dsc "OMG_TPE1S" :data "Berryzコウボウ x ℃-ute") (:tag "TXXX" :dsc "OMG_TRACK" :data "1") (:tag "TYER" :data "2011") (:tag "TXXX" :dsc "OMG_AGENR" :data "Japanese Pop") (:tag "TXXX" :dsc "OMG_ALBMS" :data "アマズッパイハルニサクラサク [Berryzコウボウバン]") (:tag "TXXX" :dsc "OMG_ASGTM" :data "875000") (:tag "TXXX" :dsc "OMG_ATP1S" :data "ベリーズコウボ　ｘ　?ｕｔｅ") (:tag "TXXX" :dsc "OMG_ATPE1" :data "Berryz工房 x ℃-ute") (:tag "TXXX" :dsc "OMG_TIT2S" :data "アマズッパイハルニサクラサク") (:tag "TXXX" :dsc "OMG_TRLDA" :data "2011/01/01 00:00:00") (:tag "TCOM" :data "つんく♂") (:tag "TLEN" :data "305000") (:tag "TXXX" :dsc "USR_L2TMDDA" :data "2011/11/08 17:47:14"))
```

引数なしだとファイルを丸ごと読み込みますが、ふたつめの引数で読み込む長さをバイトで指定できます。足らないと必要分読み直します。が、少なすぎても再読み込みすべき大きさが判らずうまくいきません。
FLAC なら最低でも 2048 くらい指定するのがいいです。

みっつめの引数が non-nil なら画像等のバイナリタグを読み込みません。主にデバッグ用です。

lib-utility も load してあれば `mf-tag-list` を使ってバッファ表示できます。
read/write 関数を使ったサンプルを他にも同梱してあるので、それらは別の頁で紹介します。

# 動作 test した環境

以下のバージョンの Emacs でいつも使っている通常の環境(win10)と
-Q 起動して最低限の準備だけした環境で動くことを確認しました。

> GNU Emacs 26.3 (build 1, x86_64-w64-mingw32)
>  of 2019-08-29

[GNU Emacs](https://www.gnu.org/software/emacs/)

```emacs-lisp:emacs&nbsp;-Q&nbsp;環境にて
;; ライブラリを展開したディレクトリに移動
(cd wtag)

;; load-path にカレントディレクトリを追加
(setq load-path (cons nil load-path)) 

;; emacs に load する
(require 'mf-tag-write)
 ```

使ったデータは 90% が SonicStageCP, MediaGoGo, MusicCenter1, 2 でリッピングしたデータで残りが iTunes やレコチョク, dead.net 等のデータです。

emacs "27.0.91" -Q でもテストしてみましたが taged だと画像がボロボロに崩れて表示されてしまいます。svg もライブラリがあるのに使えず emacs の起動画像で png が選択されてしまう等。
27 はグラフィック・ライブラリ周りとの連携がまだ安定していないようで、この辺りが正式公開に至らない理由なのでしょうか。

# 変更履歴
## Sat Sep 26 08:50:14 2020
Bug-fix と アーカイブのロケーションを変更

## Sat Aug  8 09:07:14 2020
lib-flac: 1.2
 - ジャケ無しデータにジャケセットできなかった(かもしれない)を修正.
 - データ再読み込み時、読み込みサイズをヘッダ情報から算出するようにする.
 - PADDING block のデリート.

## Sat Mar 28 12:47:04 2020
flac 暫定対応。

## Fri Jan 17 15:29:24 2020
This Text: MediaGo で作られた mp4アトム の配置が MusicCenter で作られたものとは少し違っていたので、この文章の中のその箇所を修正しました。

mf-tag-write: このドキュメントではアートワークの指定でオブジェクトが指定できるようになっていますが、 実装が保留されていたいたことに気づいて実装しました。 リリックも同様の仕様です。

wtag: kakasi で nkf を不要に/ shell-file-name が CMD.EXE でもちゃんと動くように(Emacs の shell をそうしている人はいないと思いますが...)/ wtag-log-file-name が `NIL' ならログをファイルに書き出さない. 1 file でも動くようになっていたのがいつのまにが駄目になっていたので別途 1ファイル専用の aged を追加.

## Tue Dec 31 12:41:57 2019 1.1
MediaGo の mp4 でソニータイプの判断を誤っていたため修正しました。
MediaGo でリップし MusicCenter に取り込むと MusicCenter 上でジャケやタイトル等のデータの修正ができないので、 MediaGo でリップした曲は MusicCenter で更めてリッピングし直していて勘違いしていました。
このドキュメントの関連する部分も修正しました。 済みませんでした。

# 参考サイト等
- [Wikipedia ID3タグ] (https://ja.wikipedia.org/wiki/ID3%E3%82%BF%E3%82%B0)
- [ID3v2公式(?)] (http://id3.org/)
- [MP3ファイルのタグについて (+MP4)] (http://eleken.y-lab.org/report/other/mp3tags.shtml)
- [Cactus Software サウンド・MIDI マルチメディア アプリケーション] (http://www.cactussoft.co.jp/Sarbo/divMPeg3UnmanageID3v2.html)
- [MP4 - MP4 Basic file structure] (https://github.com/uupaa/H264.js/wiki/MP4)
- [AtomicParsley] (http://atomicparsley.sourceforge.net/)