# -*- Perl -*- # Copyright (c) 2007-2009 Kazuhiro Ito # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # require 5.005; use English; use FreePWING::Sound; use FreePWING::FPWUtils::FPWUtils; use Getopt::Long; use warnings; use FileHandle; use Compress::Raw::Zlib; use vars qw ($name_pos $name_size $content_pos $content_size); use vars qw ($unzipped $zipped $last_unzipped $last_zipped); use vars qw ($contents); use vars qw ($content_handle $content_index_handle); use vars qw ($index_handle $name_handle); use vars qw (%fpwoald7_conf); use vars qw ($silent_wav); $silent_wav = pack("C*", 0x52, 0x49, 0x46, 0x46, 0x25, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x11, 0x2b, 0x00, 0x00, 0x11, 0x2b, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x64, 0x61, 0x74, 0x61, 0x01, 0x00, 0x00, 0x00, 0x7f); MAIN: { my %tag_table; # # コマンド行を解析する。 # my ($srcdir, $conf_file); if (!GetOptions('workdir=s' => \$work_directory, 'srcdir=s' => \$srcdir, 'conf=s' => \$conf_file)) { exit 1; } require $conf_file; # # fpwutils を初期化する。 # initialize_fpwutils(); # # これから出力するファイルがすでにあれば、削除する。 # unlink($sound_file_name); unlink($sound_tag_file_name); unlink($sound_fmt_file_name); $sound = FreePWING::Sound->new(); # # 生成側ファイルを開く。 # if (!$sound->open($sound_file_name, $sound_tag_file_name, $sound_fmt_file_name)) { die "$PROGRAM_NAME: " . $sound->error_message() . "\n"; } if($fpwoald7_conf{'sound_type'} == 0) { goto FINISH; } # # 読み込みファイルを開く # $srcdir =~ s/([^\/])$/$1\//; my $tmp; my ($content, $content_name); my $output_handle; my $tag_name; my $region; foreach $region ('us', 'uk') { initialize_content_reader($srcdir.$region.'/'); while (1) { ($content_name, $content) = get_next_content(); if (!length($content_name)) { last; } if ($content_name =~ /(.+)\.MP3$/) { $tag_name = $region.'_'.$1; $tag_name =~ s/\./_/g; # # Convert sound if needed. # if ($fpwoald7_conf{'sound_type'} == 2) { $content = get_converted_sound($content); } else { $content = mp3_to_wav($content); } # # 音声データを追加する。 # if (!$sound->add_binary($tag_name, $content)) { die "$PROGRAM_NAME: " . $sound->error_message() . "\n"; } if(verbose_mode()) { print "$tag_name -> $content_name\n"; } } } finalize_content_reader(); print uc($region)." pronunciation sounds are registerd.\n"; } # # 音声の生成側ファイルを閉じる。 # printf ("%6d sounds are registered.\n", $sound->entry_count()); FINISH: if (!$sound->close()) { die "$PROGRAM_NAME: " . $sound->error_message() . "\n"; } # # fpwutils の後始末をする。 # finalize_fpwutils(); exit 0; } sub initialize_content_reader { my $srcdir = $_[0]; my $content_filename = $srcdir.'CONTENT.tda'; my $content_index_filename = $srcdir.'CONTENT.tda.tdz'; my $index_filename = $srcdir.'files.dat'; my $name_filename = $srcdir.'NAME.tda'; my $tmp; $content_handle = new FileHandle; if (!$content_handle->open("$content_filename", 'r')) { die "$PROGRAM_NAME: Failed to open the file, $ERRNO: $content_filename\n"; } binmode $content_handle; $content_index_handle = new FileHandle; if (!$content_index_handle->open("$content_index_filename", 'r')) { die "$PROGRAM_NAME: Failed to open the file, $ERRNO: $content_index_filename\n"; } binmode $content_index_handle; $index_handle = new FileHandle; if (!$index_handle->open("$index_filename", 'r')) { die "$PROGRAM_NAME: Failed to open the file, $ERRNO: $index_filename\n"; } binmode $index_handle; $name_handle = new FileHandle; if (!$name_handle->open("$name_filename", 'r')) { die "$PROGRAM_NAME: Failed to open the file, $ERRNO: $name_filename\n"; } binmode $name_handle; ($name_size, $content_size) = (0, 0); ($unzipped, $zipped) = (0, 0); ($last_zipped, $last_unzipped) = (0, 0); ($name_pos, $content_pos) = get_next_index(); if($name_pos == -1) { die "$PROGRAM_NAME: Unexpected index end.\n"; } return 0; } sub finalize_content_reader { $content_handle->close(); $content_index_handle->close(); $index_handle->close(); $name_handle->close(); return 0; } sub get_next_index { my ($name, $content, $i, $tmp); if (read($index_handle, $tmp, 12) == 12) { ($name, $i, $content) = unpack("vCxxV", $tmp); $name += ($i << 16); } else { # 最終エントリ ($name, $content) = (-1, -1); } return ($name, $content); } sub get_next_content { my $tmp; my ($inflater, $status); my ($content_name, $content); if($name_size == -1) { return ('', ''); } $name_pos += $name_size; $content_pos += $content_size; ($name_size, $content_size) = get_next_index(); if ($name_size != -1) { $name_size -= $name_pos; $content_size -= $content_pos; } if ($unzipped + $last_unzipped <= $content_pos) { if (read($content_index_handle, $tmp, 8) != 8) { die "$PROGRAM_NAME: Unexpected index structure.\n"; } $unzipped += $last_unzipped; $zipped += $last_zipped; ($last_unzipped, $last_zipped) = unpack("VV", $tmp); if (read($content_handle, $tmp, $last_zipped) != $last_zipped) { die "$PROGRAM_NAME: failed to read file\n"; } ($inflater, $status) = new Compress::Raw::Zlib::Inflate(); if ($status != Z_OK) { die "$PROGRAM_NAME: Failed to initialize inflater\n"; } $status = $inflater->inflate($tmp, $contents); if ($status != Z_OK && $status != Z_STREAM_END) { # Do not die, because original data is incorrect. print "$PROGRAM_NAME: warning: failed to inflate, $status\n"; } } # ファイル名を得る。 if ($name_size != -1) { if (read($name_handle, $content_name, $name_size) != $name_size) { die "$PROGRAM_NAME: failed to read file\n"; } } else { if (read($name_handle, $content_name, 1024) == 0) { die "$PROGRAM_NAME: failed to read file\n"; } } $content_name = substr($content_name, 0, -1); # コンテントを得る。 if ($content_size != -1) { $content = substr($contents, $content_pos - $unzipped, $content_size - 1); } else { $content = substr($contents, $content_pos - $unzipped, -1); } return ($content_name, $content); } sub get_converted_sound { my ($sound) = @_; my $tmp_wav_name = './oald7-fpw-temp.wav'; my $handle = new FileHandle; unlink($tmp_wav_name); if (!$handle->open("|$fpwoald7_conf{'sound_lame'} --silent --mp3input --decode - $tmp_wav_name")) { die "$PROGRAM_NAME: failed to start lame, $ERRNO\n"; } binmode($handle); $handle->print($sound); $handle->close(); $handle->new(); if (!$handle->open("$fpwoald7_conf{'sound_sox'} -V1 -t wav $tmp_wav_name $fpwoald7_conf{'sound_sox_options'} -t wav - vol 0.9|")) { die "$PROGRAM_NAME: failed to start sox, $ERRNO\n"; } $sound =''; while ($_ = $handle->getline()) { $sound .= $_; } $handle->close(); if (length($sound) < 45) { print "$PROGRAM_NAME: warning: failed to convert sound.\n"; $sound = $silent_wav; } else { # Set correct wave form length if ($sound =~ /RIFF....(.*?)data....(.*$)/) { $sound = 'RIFF' . pack("V", length($1) + length($2) + 8) . $1 . 'data' . pack("V", length($2)).$2; } else { print "$PROGRAM_NAME: warning: missed RIFF/WAVE header.\n"; $sound = $silent_wav; } } return $sound; } sub mp3_to_wav { my ($mp3) = @_; my %info = (); my $wave = ''; Profiler: { my $count = 0; my @tmp; my @BRateTab1 = # MPEG I - Layer III,II,I (0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,0, 0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,0, 0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,0); my @BRateTab2 = # MPEG II - Layer III, II ,I (0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0, 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0, 0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,0); my @FreqTab = (11025, 12000, 8000, 0, # MPEG 2.5 0, 0, 0, 0, 22050, 24000, 16000, 0, # MPEG II 44100, 48000, 32000, 0 # MPEG I ); my @SmpsPerFrame = ( 576, 1152, 384, # MPEG 2.5 0, 0, 0, # Reserved 576, 1152, 384, # MPEG 2 1152, 1152, 384 # MPEG 1 ); my ($mpeg_verion, $layer); $info{'data_size'} = length($mp3); $info{'data_offset'} = 0; # check ID3v2 tag if (substr($mp3, 0, 3) eq 'ID3') { @tmp = unpack('x6C4', $mp3); my $tag_size = ($tmp[0] << 21) + ($tmp[1] << 14) + ($tmp[2] << 7) + $tmp[3] + 10; $info{'data_size'} -= $tag_size; $info{'data_offset'} += $tag_size; } #check ID3v1 tag if (substr($mp3, length($mp3) - 128, 3) eq 'TAG') { $info{'data_size'} -= 128; } $count = $info{'data_offset'}; @tmp = unpack("C4", substr($mp3, $count, 4)); if ($tmp[0] == 0xff && ($tmp[1] & 0xe0) == 0xe0) { $mpeg_version = ($tmp[1] >> 3) & 0x03; $layer = ($tmp[1] >> 1) & 0x03; } else { print "$PROGRAM_NAME: warning: failed to find first frame.\n"; return $silent_wav; } # $info{'i_bit_rate'} = $tmp[2] >> 4; my $i_bit_rate = $tmp[2] >> 4; if ($mpeg_version == 3) { # MPEG 1 $info{'bit_rate'} = $BRateTab1[($layer - 1) * 16 + $i_bit_rate]; } elsif ($mpeg_version == 2 || $mpeg_version == 0) { # MPEG 2 or MPEG 2.5 $info{'bit_rate'} = $BRateTab2[($layer - 1) * 16 + $i_bit_rate]; } else { $info{'bit_rate'} = 0; } # $info{'i_sampling_rate'} = (($tmp[2] >> 2) & 0x03); $info{'sampling_rate'} = $FreqTab[$mpeg_version * 4 + (($tmp[2] >> 2) & 0x03)]; # $info{'i_padding_bit'} = ($tmp[2] >> 1) & 0x01; my $i_padding_bit = ($tmp[2] >> 1) & 0x01; # $info{'i_channel_mode'} = ($tmp[3] >> 6) & 0x03; $info{'sample_per_frame'} = $SmpsPerFrame[$mpeg_version * 3 + $layer - 1]; $info{'frame_size'} = int($info{'sample_per_frame'} / 8 * $info{'bit_rate'} * 1000 / $info{'sampling_rate'} + $i_padding_bit + 0.5); $info{'frame_length'} = int(($info{'data_size'} / $info{'frame_size'}) + 0.5); $info{'total_sec'} = ($info{'frame_length'} * $info{'sample_per_frame'}) / $info{'sampling_rate'}; $info{'sample_length'} = $info{'frame_length'} * $info{'sample_per_frame'}; $info{'channel_mode'} = ((($tmp[3] >> 6) & 0x03) < 3) ? 2 : 1; $info{'padding'} = ($i_padding_bit == 1) ? 1 : 2; } if ($info{'sample_per_frame'} == 0 || $info{'sampling_rate'} == 0 || $info{'bit_rate'} == 0) { print "$PROGRAM_NAME: warning: RIFF/WAVE header generation was failed .\n"; return $silent_wav; } $wave .= 'RIFF'; $wave .= pack('V', $info{'data_size'} + 70 - 8); $wave .= 'WAVEfmt '; $wave .= pack('V', 30); $wave .= pack('v', 0x55); $wave .= pack('v', $info{'channel_mode'}); $wave .= pack('V', $info{'sampling_rate'}); $wave .= pack('V', $info{'bit_rate'} * 1000 / 8); $wave .= pack('v', 1); $wave .= pack('v', 0); $wave .= pack('v', 0x0c); $wave .= pack('v', 0x01); $wave .= pack('V', $info{'padding'}); $wave .= pack('v', $info{'frame_size'}); $wave .= pack('v', 1); $wave .= pack('v', 0x571); $wave .= 'fact'; $wave .= pack('V', 4); $wave .= pack('V', $info{'sample_length'}); $wave .= "data"; $wave .= pack("V", $info{'data_size'}); $wave .= substr($mp3, $info{'data_offset'}, $info{'data_size'}); return $wave; }