0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

日本語諸方言コーパスをDB化して遊ぶ (4) サービスの全体像を決める

Last updated at Posted at 2020-08-17

連載記事です。第4回は前回お試しで作成した Laravel アプリを いったん反故にして 、サービスの全体像を決めて、ルーティングを作るところまで。完全に自分用の作業メモで、説明もいろいろ足りていないと思いますが、ご容赦ください。

実現したい機能

COJADS は現在、コーパス検索アプリケーション「中納言」にてモニター版が利用可能ですので、これとは機能的に差別化したいところです。

「談話/話者」ごとの発話総覧

「中納言」版では検索機能をメインに据えており、テキストデータを「談話」や「話者」といったまとまりでは提供していません。また、前述のとおり COJADS の公式サイトで提供されている生データは「談話」を軸に全結合してあり、取り回しが不便です。したがって、これをエンティティごとに分離して、サイト上で閲覧できるようにする、たとえば「談話」だけでなく「話者」ごとに発話を総覧できる機能を提供することには、一定の価値があると考えられます。

「発話」の編集

わざわざ SQL を書くことなく、サイト上からデータベースを操作し、発話を編集する機能をつけます。ログもとります1。そこまで必要性は感じられませんが、データベースの勉強がてら実装したいと思います。

Excel と TextGrid の相互変換

COJADS では音声・方言テキスト・標準語テキストが発話を単位に紐づけられていますが、公式サイトでは CSV 形式の生データのみが配布されており、音声データや TextGrid 形式(音声分析ソフト Praat 専用形式)のテキストデータは配布されていません。音声と直接紐づいているのは TextGrid ファイルであり、CSV と音声ファイルがあったところでうまく対応を観察することができません。

また TextGrid がいずれ配布されたとしても、CSV データを各自で編集してしまうと対応が崩れてしまい、音声分析が困難になります。そこで Excel ファイルと TextGrid ファイルを相互変換する機能をつけたいと思います。

既に手元に Excel と TextGrid を相互変換する Python スクリプトがありましたので、これを最大限に活かすべく、

  • ファイルをアップロードして
  • サーバー上で Python スクリプトにかけて変換して
  • それをダウンロードする

という仕組みにします。

使用技術

AWS を利用するほどでもないので、今回は Heroku のフリープランを利用して完全無料で上記の機能を実装します。ウェブフレームワークとしては前述のとおり PHP Laravel を使用します。また Heroku は本番環境で SQLite が使えないので2、データベースとしては Heroku PostgreSQL のフリープランを利用します。

Heroku は非常に簡便に利用開始できるサービスですが、その分ビルド後に root 権限が使えなかったり、特にフリープランでは制限されている部分も多く、初心者には難しい点がいくつかありましたので、そこらへんは第9回に説明を試みます。

また、せっかくなので SPA (Single Page Application) 化します。SPA を実現するフレームワークはいくつかありますが、今回は学習コストの低そうな Vue Router を採用します。下記のチュートリアルが大いに参考になりました。

画面遷移図

必要となるページを数え上げて、簡単な画面遷移図を作ります。Qiita で表示しても不快にならないよう、いくつかに分けて描いてみます。

エレガントではないですが、アイコニックで分かりやすく作画コストも低いので draw.io を使用しました。

「談話」ごとの発話総覧と編集機能は以下のようにしましょう。「談話」テーブルには非常に多くの情報が含まれている(ファイル記号、データ名、収録年月日、収録場所、収録担当者、編集担当者、話題、談話ジャンル)ため、これをすべて一画面に表示すると邪魔になります。そこで「談話一覧」ページには、データの性質に影響を与えない要素(収録担当者、編集担当者など)は表示しないようにして、「発話詳細」ページでだけ確認できるようにします。また、「発話編集」ページで更新処理を行なったら「発話詳細」ページに遷移させます。

func_1.png

「話者」ごとの発話総覧は、単純に発話をまとめるだけではつまらないので、簡単な文字列処理を噛ませて、発話ごとではなく文ごとに提示するようにしましょう。

func_2.png

ファイル変換画面は、とりあえずアップロードして、変換して、ダウンロードして、削除できればよいので、素朴なのを用意しておきます。

func_3.png

プロジェクトの準備

では作成していきましょう。まずは Laravel プロジェクトを作っていきます。やることは基本的に上記参考サイトと同じで、適当なフォルダで composer にプロジェクトを作ってもらい、必要なパッケージ等を Laravel (php artisan) や npm の助けを借りて導入していきます。

composer create-project laravel/laravel --prefer-dist cojads
composer require laravel/ui
php artisan ui vue
npm install
npm install --save vue-router

これからプロジェクトの内実を作っていきますが、ガワ(ルーティングやテンプレート)から作り始めて、内実(コンポーネントやコントローラ)はあとで作りこむ、という手順を貫きます。

SPA のテンプレート作成

今回は SPA を作るので、ガワ(テンプレート)として唯一のページ app.blade.php を作成します。Laravel プロジェクトではウェブサイトとして通常アクセスした際のルーティングが web.php で規定されるので、まずここを編集して任意のパスを app.blade.php にルーティングします。ただし今回のサービスはあとで /storage に直接アクセスする必要が出てくるので、/storage 以下にだけは特別なルーティングをかけないようにしておきます。

routes/web.php
<?php
use Illuminate\Support\Facades\Route;
Route::get('/{any}', function() {
    return view('app');
})->where('any', '^(?!storage).*');

そうしたら SPA のガワ app.blade.php を作ります。Vue Router の処理で <header-component> 位置にヘッダーが、<footer-component> 位置にフッターが、<router-view> 位置に各ページの内容が読み込まれるようになっています。

bootstrap で固定ヘッダーを作る関係で、<rooter-view> の上下に適当に余白を設けています(汚い)。

resources/views/app.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <base href="/" />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="robots" content="noindex">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name', 'COJADS App') }}</title>
    <link href="{{ mix('/css/app.css') }}" rel="stylesheet">
</head>
<body>

<div id="app">
    <header-component></header-component>
    <div class="px-5 py-5"><div class="py-3">
        <router-view></router-view>
    </div></div>
    <footer-component></footer-component>
</div>

<script src="{{ mix('/js/app.js') }}" defer></script>

</body>
</html>

コンポーネントのルーティング

続いてテンプレートに挿入されるコンポーネントのルーティングを行ないます。コンポーネントの登録とそのルーティングは app.js で管理されているので、ここを編集します。以下ではヘッダー・フッター・表紙ページ・談話一覧ページの要素を読み込んで適切なルーティングを行なっています。

resources/js/app.js
import VueRouter from "vue-router";

// register components
import HeaderComponent from "./components/HeaderComponent";
import FooterComponent from "./components/FooterComponent";
import HomeComponent from "./components/HomeComponent";
import DiscourseIndexComponent from "./components/DiscourseIndexComponent";

require("./bootstrap");
window.Vue = require("vue");
Vue.use(VueRouter);

// routing
const router = new VueRouter({
    mode: "history",
    routes: [
        {
            // ルートにアクセスすると HomeComponent をロードする
            path: "/",
            name: "home",
            component: HomeComponent
        },
        {
            // /discourse にアクセスすると DiscourseIndexComponent をロードする
            path: "/discourse",
            name: "discourse.index",
            component: DiscourseIndexComponent
        }
    ]
});

// ヘッダー・フッター用のカスタムエレメントを定義して、コンポーネントを読み込む
Vue.component("header-component", HeaderComponent);
Vue.component("footer-component", FooterComponent);

const app = new Vue({
    el: "#app",
    router
});

各コンポーネントの作成

ルーティングの構想ができたらページの内容を作っていきます。先ほど app.js で使用することにした4つのコンポーネント(.vueファイル)を /resources/js/components 下に作成します(discourse は次回)。

以下のファイルを作成する
+ resources/js/components/HeaderComponent.vue
+ resources/js/components/FooterComponent.vue
+ resources/js/components/HomeComponent.vue
+ resources/js/components/DiscourseIndexComponent.vue

ヘッダー

bootstrap でページ上部に固定するタイプのヘッダーを作ります。先取りになりますが、ヘッダーには <router-link> で各コンポーネントへのリンクを張っておきましょう。とはいえ、まだリンク先を用意していないので、この段階でコンパイルするなら適当にコメントアウトする必要があります。

resources/js/components/HeaderComponent.vue
<template>
    <header class="navbar fixed-top navbar-dark bg-dark">
        <span class="navbar-brand mb-0 h1">COJADS APP</span>
        <div>
            <span>
                <router-link v-bind:to="{ name: 'home' }">
                    <button class="btn btn-success btn-sm">Top Page</button>
                </router-link>
                <router-link v-bind:to="{ name: 'discourse.index' }">
                    <button class="btn btn-success btn-sm">Discourse List</button>
                </router-link>
                <router-link v-bind:to="{ name: 'speaker.index' }">
                    <button class="btn btn-success btn-sm">Speaker List</button>
                </router-link>
                <router-link v-bind:to="{ name: 'convert' }">
                    <button class="btn btn-success btn-sm">Toolkits</button>
                </router-link>
            </span>
        </div>
    </header>
</template>

<script>
export default {};
</script>

フッター

フッターも適当に作ります。

resources/js/components/FooterComponent.vue
<template>
    <footer class="navbar fixed-bottom navbar-dark bg-dark">
        <span class="navbar-brand mb-0 h1">(c) 2020 @a_eau_</span>
    </footer>
</template>

<script>
export default {};
</script>

ホーム画面

特に書くこともないので、最小限だけ作ります。

resources/js/components/HomeComponent.vue
<template>
    <div>
        Welcome to COJADS App!
    </div>
</template>

<script>
export default {};
</script>

コンパイル・ローカルサーバで確認

以上のコードを書き終えたら、一度コンパイルして確認しましょう。以下のコマンドを実行すると、もろもろの必要なものを取り込んでいい感じにコンパイルしてくれます。

cmd
npm run dev あるいは npm run production

コンパイルが済んだら Laravel の以下コマンドでローカルサーバを立ち上げて、localhost:8000 にアクセスしてみましょう。404 や 500 エラーが出ずに想定通りのページが表示されれば成功です。

cmd
php artisan serve

次回

Heroku で SQLite が使えないことを知って PostgreSQL に乗り換える回です。

  1. ログ一覧ページも作ったのですが、解説に真新しいところもないので本連載記事からは割愛しました。

  2. というか本番環境で SQLite を使えるサービスのほうが少なそう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?