隠居日録

隠居日録

2016年(世にいう平成28年)、発作的に会社を辞め、隠居生活に入る。日々を読書と散歩に費やす

perlで日本語文字化けが発生し、はまった理由と対策

perlで日本語の文字化けが起きてしまい、はまってしまった。よく考えてみたら、前に一度同じ理由ではまったことがあり(その時はSQLiteに書き込む際の値が文字化けした)、自戒を込めて記録しておく。

よく、perlに関する説明で、「日本語を処理するときは、(内部コードになっていない場合は)decodeで内部コードに変換し、最終的なところでencodeで所望の漢字コードに変換すると文字化けが起きない」というようなことが書かれている。たしかに、この説明は正しい。見た目においては、この方法で問題はほぼ発生しないと思う。ところが、以下のようなperlスクリプトを実行すると、$tagの値は「title」であり、$titleの値は「日本語文字列」なのだが、それらをechoコマンドで表示させると、文字化けする。

use Encode;
use utf8  ;
my $v = '<title>日本語文字列</title>' ;

my ($tag, $title) = $v =~ m{\A<([A-Za-z]+?)>(.*?)</[A-Za-z]+?>};

$title = encode ('utf8', $title);

print "tag = $tag\n";
print "title = $title\n";

my $cmd = "echo $tag $title";
system ($cmd);

なぜなのか?以下の行を追加して、実行してみると、

print "utf8 tag = ", utf8::is_utf8($tag), "\n";
print "utf8 title = ", utf8::is_utf8($title), "\n";

utf8 tagは1であり、utf8 titleはundefだ。つまり、もともとutf8のフラグが付いているスカラー$vから変換した$tagにも中身はアルファベットであるにもかかわらずutf8フラグが付いているのだ。一方、$titleはencodeしたので、utf8フラグはついていない。では、この二つを結合した$cmdはどうなるかというと、utf8フラグが付いてしまうのだ。この問題の凶悪なところは$cmdの内容をperlスクリプト内でprintを用いて出力しても、見た目は正しく見えるのだが、systemに渡すと、その時点で文字化けが発生してしまうことだ。文字化けを避けるためには、$tagを結合する前に、やはりencodeしてutf8フラグを消さなければならないということだ。好きなコードでよいだろうが、アルファベットであることが確実ならば、asciiにでもしておけばよい。

perlとchromecast その4

perlとchromecast その3 - 隠居日録の続き。何とか動画を再生することができた。何が悪かったかというと、2度目のRECEIVER_STATUSがChromecastから送信されてきたら、そのメッセージ中のtransportIdをdestination_idにセットして、もう一度CONNECTを送らなければいけないようだ。この後にLOADをすると、動画が再生された。

LOAD後暫くすると、MEDIA_STATUSが送信されきて、playerStateがLOADINGになる。

$VAR1 = bless( {
                 'protocol_version' => 0,
                 'payload_utf8' => '{"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"IDLE","currentTime":0,"supportedMediaCommands":15,"volume":{"level":1,"muted":false},"media":{"contentId":"http://192.168.0.136/test01.mp4","streamType":"buffered","duration":290,"medadata":{},"contentType":"video/mp4"},"currentItemId":1,"extendedStatus":{"playerState":"LOADING","media":{"contentId":"http://192.168.0.136/test01.mp4","streamType":"buffered","duration":290,"medadata":{},"contentType":"video/mp4"}},"repeatMode":"REPEAT_OFF"}],"requestId":0}',
                 'namespace' => 'urn:x-cast:com.google.cast.media',
                 'payload_type' => 0,
                 'source_id' => '57849d91-2bd4-463c-bcd9-3324ae125401',
                 'destination_id' => '*'
               }, 'CastMessage' );

その後、RECEIVER_STATUSが送信されてきて、statusTextがNow Castingに変化する。

$VAR1 = bless( {
                 'payload_type' => 0,
                 'namespace' => 'urn:x-cast:com.google.cast.receiver',
                 'protocol_version' => 0,
                 'payload_utf8' => '{"requestId":0,"status":{"applications":[{"appId":"CC1AD845","displayName":"Default Media Receiver","isIdleScreen":false,"namespaces":[{"name":"urn:x-cast:com.google.cast.broadcast"},{"name":"urn:x-cast:com.google.cast.media"}],"sessionId":"57849d91-2bd4-463c-bcd9-3324ae125401","statusText":"Now Casting","transportId":"57849d91-2bd4-463c-bcd9-3324ae125401"}],"volume":"controlType":"attenuation","level":1.0,"muted":false,"stepInterval":0.05000000074505806}},"type":"RECEIVER_STATUS"}',
                 'destination_id' => '*',
                 'source_id' => 'receiver-0'
               }, 'CastMessage' );

そして、MEDIA_STATUSが送られてきて、playerStateがBUFFERINGに変化する。

$VAR1 = bless( {
                 'destination_id' => '*',
                 'source_id' => '57849d91-2bd4-463c-bcd9-3324ae125401',
                 'namespace' => 'urn:x-cast:com.google.cast.media',
                 'payload_type' => 0,
                 'payload_utf8' => '{"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"BUFFERING","currentTime":0,"supportedMediaCommands":15,"volume":"level":1,"muted":false},"videoInfo":{"width":320,"height":240,"hdrType":"sdr"},"currentItemId":1,"repeatMode":"REPEAT_OFF"}],"requestId":0}',
                 'protocol_version' => 0
               }, 'CastMessage' );

更にMEDIA_STATUSが送られてきて、動画の情報が追加されている。

$VAR1 = bless( {
                 'source_id' => '57849d91-2bd4-463c-bcd9-3324ae125401',
                 'destination_id' => '*',
                 'payload_utf8' => '{"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"BUFFERING","currentTime":0,"supportedMediaCommands":15,"volume":{"level":1,"muted":false},"videoInfo":{"width":320,"height":240,"hdrType":"sdr"},"media":{"contentId":"http://192.168.0.136/test01.mp4","streamType":"buffered","duration":290.783,"medadata":{},"contentType":"video/mp4"},"currentItemId":1,"items":{"itemId":1,"media":{"contentId":"http://192.168.0.136/test01.mp4","streamType":"buffered","duration":290.783,"medadata":{},"contentType":"video/mp4"},"autoplay":"true","customData":{}}],"repeatMode":"REPEAT_OFF"}],"requestId":6}',
                 'protocol_version' => 0,
                 'payload_type' => 0,
                 'namespace' => 'urn:x-cast:com.google.cast.media'
               }, 'CastMessage' );

その後playerStateがPLAYINGになり、

                 'protocol_version' => 0,
                 'payload_utf8' => '{"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"PLAYING","currentTime":0.133,"supportedMediaCommands":15,"volume":"level":1,"muted":false},"videoInfo":{"width":320,"height":240,"hdrType":"sdr"},"currentItemId":1,"repeatMode":"REPEAT_OFF"}],"requestId":0}',
                 'payload_type' => 0,
                 'namespace' => 'urn:x-cast:com.google.cast.media',
                 'source_id' => '57849d91-2bd4-463c-bcd9-3324ae125401',
                 'destination_id' => '*'
               }, 'CastMessage' );

動画の再生が終了すると、playStateがIDLEになる。

$VAR1 = bless( {
                 'source_id' => '57849d91-2bd4-463c-bcd9-3324ae125401',
                 'destination_id' => '*',
                 'payload_utf8' => '{"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"IDLE","currentTime":0,"supportedMediaCommands":15,"volume":"level":1,"muted":false},"currentItemId":1,"idleReason":"FINISHED"}],"requestId":0}',
                 'protocol_version' => 0,
                 'namespace' => 'urn:x-cast:com.google.cast.media',
                 'payload_type' => 0
               }, 'CastMessage' );

RECEVIER_STATUSも送信されてきて、statusTextもReady To Castになる。

$VAR1 = bless( {
                 'payload_type' => 0,
                 'namespace' => 'urn:x-cast:com.google.cast.receiver',
                 'protocol_version' => 0,
                 'payload_utf8' => '{"requestId":0,"status":{"applications":[{"appId":"CC1AD845","displayName":"Default Media eceiver","isIdleScreen":false,"namespaces":[{"name":"urn:x-cast:com.google.cast.broadcast"},{"name":"urn:x-cast:com.google.cast.media"}],"sessionId":"57849d91-2bd4-463c-bcd9-3324ae125401","statusText":"Ready To Cast","transportId":"57849d91-2bd4-463c-bcd9-3324ae125401"}],"volume":"controlType":"attenuation","level":1.0,"muted":false,"stepInterval":0.05000000074505806}},"type":"RECEIVER_STATUS"}',
                 'destination_id' => '*',
                 'source_id' => 'receiver-0'
               }, 'CastMessage' );