問題の声明
- 上記の要件を満たす必要があるforvoクライアントをできるだけ単純にします。
- シンプルなコマンドラインインターフェイスが必要です。
$say hello world # "hello world"
$say -lng=ru # ( en, ru, tt, etc...)
ツール選択
もちろん、この問題を解決するには多くのアプローチがあります。 最良の選択は、 bash + awk + curl + mpg123 (または他のプレイヤー)を使用することだと思いました。 そのため、最初に必要なパッケージを配置します。たとえば、Debianベースのシステムの場合:
$sudo apt-get install gawk curl mpg123
解決策
将来を見据えて-私はforvo-apiを使用しませんでした。記事の最後で理由を説明します。
forvo検索ページを調べると、フォームがこのPOSTリクエストによって送信されていることがわかります。
パラメータ: id_lang = $LANGUAGE_ID。LANGUAGE_IDは発音言語識別子です。 word_search = $ WORD、ここでWORDは検索ワードです ポストURL: http://www.forvo.com/search/-送信アドレスのリクエスト
したがって、以下を呼び出すことでこのリクエストを実装できます。
#!/bin/bash LANGUAGE_ID=39 #id ( id_lang) WORD="hello world" curl -d "id_lang=$LANGUAGE_ID&word_search=$WORD" -L 'http://www.forvo.com/search/'
このリクエストに対する答えは、検索された単語の発音のオーディオストリームのURLを含むリンク(最初に必要です)を含むhtmlの形式で私たちに届きます。 したがって、オーディオストリームのURLを抽出するパーサーを実装する必要があります。 Awkの実装:
# # parser.awk # /var (_SERVER_HOST|_AUDIO_HTTP_HOST)/{ if(match($0, /var[ \t]+(_SERVER_HOST|_AUDIO_HTTP_HOST)[ \t]*=[ \t]*'?([^']+)'?/, arr)){ if(arr[1] == "_SERVER_HOST"){ srv_host = arr[2]; } else if(arr[1] == "_AUDIO_HTTP_HOST") { audio_http_host = arr[2]; } } } /<a href.+onclick="Play\(/{ if(match($0, /onclick="Play\([^,]+,'([^,]+)'.+\)/, arr)){ mp3Path = arr[1]; if (srv_host == audio_http_host){ mp3Path = ("http://" srv_host "/player-mp3Handler.php?path=" mp3Path); } else { mp3Path = ("http://" audio_http_host "/mp3/" base64_decode(mp3Path)); } } exit; } function base64_decode(val){ command = ("echo '" val "' | base64 -d"); command | getline ret; close(command); return ret; } END{ if(mp3Path) print mp3Path; }
オーディオストリームのurlを受け取ったら、 mpg123マイクロプレーヤーを使用してそれを再現します。 これは合理的な質問を提起するかもしれません:なぜmpg123であり、他のプレイヤーではないのですか? うーん...プレーヤーを選ぶとき、ストリーミングオーディオを再生できる最もミニマルなプレーヤーを探していました。
したがって、メインスクリプトは次のようになります。
# # say # LANGUAGE_ID=39 WORD=$@ if [[ -n $WORD ]]; then URL=$(curl -d "id_lang=$LANGUAGE_ID&word_search=$WORD" -L 'http://www.forvo.com/search/' 2> /dev/null | awk -f ${0%/*}/parser.awk) if [[ -n $URL ]]; then mpg123 -q $URL else echo not found fi fi
しかし、ここで最初の問題が発生します。2つのファイル( sayとparser.awk )になりましたが、このような小さなユーティリティにはあまり適していません。 このユーティリティを1つのファイルで提供したいと思います。 これは疑問を提起します: シェル(bash)とawkで書かれた2つの異種プログラムをどのように組み合わせるか?
オプション1
標準のawk機能を使用します-プログラムを引用符で囲み、コマンドラインパラメーターとして渡します。 # # examlpe1.sh # echo "from shell script" AWK_PRG="BEGIN{ print \"from awk program\" }" awk "$AWK_PRG"
このアプローチは、単一行のawkプログラムに適しています。 プログラムがワンライナーよりわずかに大きい場合、引用符をエスケープするのが難しい場合があります(シングルとダブルの両方)。 スクリーニングは、プログラム自体を「ポイ捨て」し、そのサポートと拡張を複雑にします。 したがって、このアプローチはこの場合には適していません。
オプション2
策略。 まず、考えてみましょう。 シェルスクリプトが解釈可能であること、つまり、 スクリプトはコマンドによって(または行ごとに)実行されます。 したがって、 シェルスクリプトの最後にawkプログラムを配置し、 シェルスクリプト全体を実行した後、 bashインタープリターがawkプログラムの読み取りを開始しないように、exit
コマンドをその前に配置するとどうなるでしょうか。 そのため、 シェルスクリプトをawkプログラムと組み合わせることに成功しました。 しかし、ファイルの末尾にあるこのawkプログラムをどのように読んで実行するのでしょうか? 答えはそれ自身を示唆しています-私たちはawkを使用しています) シェルスクリプトの終わりとawkプログラムの始まりをマーカー(たとえば、コメント)でマークし、このファイルを処理のために、マーカーの後のすべてを読み取る別のawkプログラムに渡すだけです。
# # examlpe2.sh # echo "this is shell script" AWK_PRG=$(awk '(/^### AWK PROGRAMM MARKER ###$/ || body){body=1; print $0}' $0) awk "$AWK_PRG" exit ### AWK PROGRAMM MARKER ### BEGIN{ print "from awk program" }
このアプローチにより、変更を加えることなく、 シェルスクリプトにプログラムのawkコード(だけでなく)を含めることができます。 リポジトリには、 getAwkProgram関数の実装があります。これにより、統合されたawkプログラムに名前を付けてシェルスクリプトにロードすることができます。 ここではこの機能を提供しないことにしました。メイントピックから逸れると思います。
オプション3
awkプログラムをシェルスクリプトに統合する別の方法を思い出してくれたxaizekに感謝します 。 # # examlpe1.sh # echo "from shell script" AWK_PRG=$(cat << 'EOL' BEGIN{ print "from awk program" } EOL ) awk "$AWK_PRG"
このメソッドは、 heredoc構文に基づいています 。 このアプローチは( bashの観点から)より自然であり、オプション1(インラインプログラム)よりも間違いなく優れていますが、オプション2に比べて読みにくいと考えています。
したがって、2番目のアプローチを使用すると、forvoクライアントは1つのファイルに簡単に収まります。
#!/bin/bash LANGUAGE_ID=39 #english # Trick for mixing AWK and Shell programs in the same file PARSER_PRG=$(awk '(/^### AWK PROGRAMM MARKER ###$/ || body){body=1; print $0}' $0) WORD=$@ if [[ -n $WORD ]]; then URL=$(curl -d "id_lang=$LANGUAGE_ID&word_search=$WORD" -L 'http://www.forvo.com/search/' 2> /dev/null | awk "$PARSER_PRG") if [[ -n $URL ]]; then mpg123 -q $URL else echo not found fi fi exit ### AWK PROGRAMM MARKER ### # parser /var (_SERVER_HOST|_AUDIO_HTTP_HOST)/{ if(match($0, /var[ \t]+(_SERVER_HOST|_AUDIO_HTTP_HOST)[ \t]*=[ \t]*'?([^']+)'?/, arr)){ if(arr[1] == "_SERVER_HOST"){ srv_host = arr[2]; } else if(arr[1] == "_AUDIO_HTTP_HOST") { audio_http_host = arr[2]; } } } /<a href.+onclick="Play\(/{ if(match($0, /onclick="Play\([^,]+,'([^,]+)'.+\)/, arr)){ mp3Path = arr[1]; if (srv_host == audio_http_host){ mp3Path = ("http://" srv_host "/player-mp3Handler.php?path=" mp3Path); } else { mp3Path = ("http://" audio_http_host "/mp3/" base64_decode(mp3Path)); } } exit; } function base64_decode(val){ command = ("echo '" val "' | base64 -d"); command | getline ret; close(command); return ret; } END{ if(mp3Path) print mp3Path; }
結論
ここに記載されているアプローチの長所と短所、およびforvo-apiを使用するアプローチを以下に示します。
- 現在のアプローチ:
+ forvo.comにアカウントを持つ必要はありません
+ forvo-apiキーを保存および転送する必要はありません
-クライアントのパフォーマンスはサイトの設計に依存します(つまり、fovroでグローバルな変更が行われた場合、パーサーを修正する必要があります) - forvo-APIアプローチ:
+クライアント実装の容易さ
+理論的には各リクエストの着信トラフィックが少ない
-forvo.comアカウントを持っている必要がある(forvo-apiキーを受け取るため)
-forvo-apiキーを携帯する必要性
ちょっとした注意が必要です-何らかの理由で、私にとって、mpg123はforvo-apiリクエストで受け取ったリンクの受け入れを拒否しました。
おわりに
この記事の目的は、このような問題を解決するための可能な方法を示すことだったので、ここではクライアントの基本的な実装(発音言語の永続的な切り替えの可能性なし)を提供することにしました。 クライアントのより完全なバージョンはgithub.comで入手できます。
あとがき
ハブでは、外国語のトピックに関連する何らかの方法で有用な投稿が複数回公開されました。 最近、私はサンドボックスから投稿を実行しました。そこでは、カスタム辞書を作成するというアイデアが気に入りました。 また、選択した単語/フレーズを翻訳するためにキーの組み合わせを結ぶというアイデアが提案された投稿もありました。 これらの記事のアイデアを組み合わせて、このスキームを推奨できます。
- Ankiで補充して使用するユーザー辞書
- 2番目の投稿と同様に、forvoクライアントにキーの組み合わせを割り当てます