LAMEAudioFormat

macOS アプリで MP3 オーディオ出力を利用するために、
AudioToolbox や CoreAudioPublicLibrary などを調べたのですが、
QuickTime SDK が使えない今、MP3 エンコーダーは どうやらMacOS の中には
入っていないことがわかりました。
そこで、JUCEに入っている LAMEAudioFormat が使えるかどうか確認してみましたが、
実装して驚いたのですがなんとコマンドライン版の LAME を呼び出して使うみたいですね。
まあ、一応使えますが、目的 (ID3Tagのフル実装) には力足らずで、
おそらくID3v2 の UTF-16 コードが入力できません。
lame のコマンドライン入力で macOS の Terminal で日本語タイトルを入力するとうまくいくので lame 自体は ID3v2 (UTF-16) を正しく処理できているみたいですが、
LameAudioFormat の 入力metadata (StringArray) に日本語を (UTF-16/UTF-8/WideCharacter) で入力しても JUCE のプロセス処理の中で文字コード変換が発生しているようで lame のコマンドラインに正しい文字コードが到達していないみたいでした。
lame のコマンドラインでのメタデータ入力は貧弱なので、IDTagエディタなどのように
セーブされた mp3 ファイルをreopenしてAttachできる機構があればよいと思いました
まあ、LAME.Framework をスタティックライブラリ化したもの (.a library) をリンクできて使用したほうが一番スマートでベストなのですが。

はじめまして。
私は文字コードの知識などは全くありませんが、ちょうど同じような問題に取り組もうと思っていましたので私の当て勘を書かせていただきます。詳しい方がいらっしゃいましたら是非ご教授いただきたいです。

恐らくAudioFormatクラスが問題ではなくて、Stringクラスがお使いのテキストエディタのエンコーディングに対応しておらず(それは仕方がないことなのですが)、その結果metadata用のStringArrayクラスが保持しているデータも壊れてしまっているのではないかと思われます。

こちらにJulesが英語圏外(と言うべきなのか、charの範囲の外の、と書くべきか分かりませんが)の文字を入力する際の注意点を書いています。

曰く「StringクラスはUTF-8文字を想定しているけれども、コンパイラーはどんなタイプのエンコードを君のテキストエディタが使用しているのか知らないから、ソースファイルをセーブする時予想をするんだけど、それはだいたい間違っている。だからほとんどの場合、エンコードはエディターか、コンパイラか、ライブラリクラスかのどこかでゆがめられてしまう。唯一のクラスプラットフォームなunicodeをC++ソースに埋め込むやり方は ASCII + エスケープ文字にレベルを落とす(dumbing down)ことです。直接書くのはたいへんだけれども、幸運なことにIntrojucer(訳注:Projucerの前身)を開いて"UTF-8 String Literal Helper"を使えば、クソみたいな作業を代わりにやってくれて、unicode文字を安全なC++の式に変換してくれるから、それをコードにコピペすればいいよ。例えば以下のようにね
String textToDisplay = CharPointer_UTF8 ("\xe4\xb8\x80\xe4\xba\x9b\xe6\x96\x87\xe5\xad\x97");
とのことです。

このやり方だと、確かに静的な文字列は扱えますが、Yoshinori_Ogawaさんのおっしゃっているようなユーザーがメタデータを入力するような、入力が動的に変わるような場合に対応できません。
何かいい方法があればご教授いただければ幸いです。

JUCEの日本語ローカライズの一助になれば幸いです。

編集:訳文を付け足しました。最初いらないと思っていたのですが、私自身分かっていなかったことがありましたので、訳しました。

はじめまして。
なるほど、すべてのテキストを UTF-8 にすればいいのですね。
であれば、const char* String(“文字列”).toRawUTF8() で一発変換できるかもしれないですね。
ちょっと試行錯誤してみます。結果がわかったらまた返事します

いえ、問題はそう簡単ではないかもしれません。
Stringのchar*を引数にとるコンストラクターを見てみてください。

例えば

String("あいうえお")

という文を実行した場合、
createFromCharPointer(const CharPointer text)はまず必要なバイトを確保し、引数textを書き込みますが、私の環境(Visual Studio2019)では88行目のwriteが終わった時点でdestは文字化けした状態です。
これは、1バイト1文字とString型が認識してしまい、日本語(恐らく4バイト)テキスト1文字につき4文字の意味のない文字列が生成されているようです。
Stringのコンストラクターの引数にまずエスケープ付のASCIIが渡されていることが重要なようです。

こういった場合どのように文字列をマネージメントするべきなのか、私は経験が無いので存じません…
完全に素人の考えなのですが、メタデータを入力する側のUIに"UTF-8 String Literal Helper"のようなものを実装し、ユーザーが「決定」ボタンを押した時点で入力データをエスケープシーケンス付ASCIIに変換することは可能でしょうか?

DicklessGreat さん、ありがとうございます。
古い JUCE のソースセット (JUCE3?) から introjucerのソースの
juce_CodeHelper.cpp ,
namespace CodeHelpers{:
String stringLiteral (const String& text, int maxLineLength);
}
という文字列変換の道具を見つけました。
これと同じ機能のコンバータを作ることを考えます。

Projucerのソースコードからコードをトレースしてみました。
この関数が、"UTF-8 String Literal Helper"に文字を入力するたびに実行されていました。

CodeHelpers::stringLiteral(const String&, int)addEscapeChars(const String&)などが参考になりそうです。

ちょっと考えましたが、これは不可能ですね、よく考えたらLameのコマンドラインargumentに渡す直前にC言語のエスケープをほどいてあげる人が必要です。アプリケーションの実行系にC言語を解釈する人はいませんので。
であれば、juce_core のプロセス周辺をいじったほうが近道ですね

あ、そうですね。
LameがCのエスケープ文字付ASCIIを読めなければどうしようもないですよね笑
ちなみに、MP3AudioFormatWriterクラスは使えないでしょうか?

覗くとわかりますがMP3Audioformat の Witer は実装が空です。Reader しか定義されていませんね。たいていの codecs がReader だけの実装ですよ

確認してみました。空でした笑

これはおっしゃる通り自分でjuce_coreを書きかえるなどしなければなりませんねぇ。
もしJUCEかもしくはLameに日本語対応をプッシュしようと思ったらどう言ったらいいんですかね…。

すみません、僕はここまでしかわからないです。
お力になれたか分かりませんが、勉強になりました。ありがとうございました。

原因がわかりました。locale を設定しないと、lame.app の入力はASCII文字以外は受け付けてくれませんね。当然といえば当然ですね。
juce_core/native/juce_posix_SharedCode.h
class ChildProcess::ActiveProcess::ActiveProcess (const StringArray& arguments, int streamFlags)
の中で、execvp -> execve に変更したらlameのコマンドラインが日本語を受け付けました。

 #if 1 // ogawa, quick Hack
                       Array<char*> envp;
                        envp.add ("LANG=ja_JP.UTF-8");
                        envp.add (NULL);
                        execve (exe.toRawUTF8(), argv.getRawDataPointer(), envp.getRawDataPointer());
    #else
                          execvp (exe.toRawUTF8(), argv.getRawDataPointer());
    #endif
                             exit (-1);

アプリは 多言語アプリでLANG=“C” で組み立てていますので、今更locallize すると文字変換が誤動作しそうで変えられないです。局所的に locale をセットするか、execve を使用するかちょっと考えてみます。
LANG も正確には MacOSの場合
defaults -read -g AppleLocale
の値を持って来ないといけないですね。

とりあえず、以下のコードで運用してみます。
どうもお騒がせしました。

#if 1 // modify, check only MacOS
                    String envstr = String("LANG="+SystemStats::getUserLanguage()+"_"+SystemStats::getUserRegion()+".UTF-8");
                    Array<char*> envp;
                    envp.add (envstr.getCharPointer().getAddress());
                    envp.add (NULL);
                    execve (exe.toRawUTF8(), argv.getRawDataPointer(), envp.getRawDataPointer());
     #else // JUCE original
                 execvp (exe.toRawUTF8(), argv.getRawDataPointer());
     #endif
1 Like