【Flutter】画像からテキスト認識した文章を読み上げる

こんにちは、株式会社Pentagonでアプリを開発している小寺です。
以前OCR(英文のテキスト文字認識)を用いて画像からテキストを読み取る機能をを実装しました。今回はTTS(Text-To-Speech)を用いて認識したテキストを読み上げる機能を実装したので説明します。
OCRについての説明は下記URLに記載してますので気になる方はご覧ください。
【Flutter】OCRを使って画像からテキストを読み取る

【こんな人に読んで欲しい】
・Flutterを用いて文章を読み上げる機能を実装したい方

【この記事を読むメリット】
・TTSの実装方法が理解できる
・実際に検証した精度がわかる

【結論】
結論からすると、比較的安易にTTSを実装することができました。また、精度もかなり高いです。今回は英語の読み上げですが声量・スピード・声の高さを変更できるためもう少しゆっくり聞きたいというような要望も自身で変更できる点がおすすめです。

目次

検証動画

導入したパッケージ

テキストを読み上げるにあたってText-To-Speechを利用するためにflutter_ttsを導入します。

flutter_tts: ^3.6.2

上記をpubspec.yaml内に記載した後、Terminalでflutter pub getを実行します。

TTSのコード

上記にある検証動画ではOCRを用いて読み取ったテキストを読み上げていますが、今回はText-To-Speechのみのコードを掲載しております。
もし、テキスト認識した文章を読み取りたい場合は前回のブログにOCRのコードがあるので組み合わせて挑戦してみてください。

import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const Tts(),
    );
  }
}

class Tts extends StatefulWidget {
  const Tts({Key? key}) : super(key: key);

  @override
  State<Tts> createState() => _TtsState();
}

class _TtsState extends State<Tts> {
  FlutterTts tts = FlutterTts();
  String language = "en-US";
  String? engine;
  // 音量
  double volume = 0.5;
  // 声の高さ
  double pitch = 1.0;
  // スピード
  double rate = 0.5;
  String _newVoiceText = 'Hello there';
  int? _inputLength;

  /// 再生
  Future _speak() async {
    await tts.setVolume(volume);
    await tts.setSpeechRate(rate);
    await tts.setPitch(pitch);
    await tts.setLanguage(language);

    if (_newVoiceText != null) {
      await tts.speak(_newVoiceText!);
    }
  }

  /// 停止
  Future<void> _stop() async {
    await tts.stop();
  }

  void _onChanged(String text) {
    setState(() {
      _newVoiceText = text;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('test'),
      ),
      body: Center(
        child: SingleChildScrollView(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                width: 300,
                alignment: Alignment.topCenter,
                child: Text(_newVoiceText),
              ),
              const SizedBox(
                height: 50,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Column(
                    children: [
                      IconButton(
                        onPressed: _speak,
                        icon: const Icon(Icons.play_arrow),
                      ),
                      const Text('再生'),
                    ],
                  ),
                  Column(
                    children: [
                      IconButton(
                        onPressed: _stop,
                        icon: const Icon(Icons.stop),
                      ),
                      const Text('停止'),
                    ],
                  ),
                ],
              ),
              const SizedBox(
                height: 50,
              ),
              const Text('スピードを変える'),
              const SizedBox(
                height: 20,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        rate += 0.1;
                      });
                    },
                    child: const Icon(Icons.add),
                  ),
                  Text(rate.toString()),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        rate -= 0.1;
                      });
                    },
                    child: const Icon(Icons.minimize_outlined),
                  ),
                ],
              ),
              const SizedBox(
                height: 20,
              ),
              const Text('音量を変える'),
              const SizedBox(
                height: 20,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        volume += 0.1;
                      });
                    },
                    child: const Icon(Icons.add),
                  ),
                  Text(volume.toString()),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        volume -= 0.1;
                      });
                    },
                    child: const Icon(Icons.minimize_outlined),
                  ),
                ],
              ),
              const SizedBox(
                height: 20,
              ),
              const Text('声の高さを変える'),
              const SizedBox(
                height: 20,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        pitch += 0.1;
                      });
                    },
                    child: const Icon(Icons.add),
                  ),
                  Text(pitch.toString()),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        pitch -= 0.1;
                      });
                    },
                    child: const Icon(Icons.minimize_outlined),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

参考にしたもの

flutter_tts
上記にあるFlutter公式のsampleコードを参考に制作しました。
sampleも大変わかりやすいのでぜひ参考にしてください。

まとめ

TTSはflutter公式のサンプルもわかりやすく初学者の方でも実装しやすく、それに加えて精度も高い印象です。テキストを読み取りたい方はぜひ一度実装してみてください。

おまけ

Flutterに関する知識を深めたい方には、『Flutterの特徴・メリット・デメリットを徹底解説』という記事がおすすめです。

この記事では、Flutter アプリ開発の基本から、Flutter とは何か、そして実際のFlutter アプリ 事例を通じて、その将来性やメリット、デメリットまで詳しく解説しています。
Flutterを使ったアプリ開発に興味がある方、またはその潜在的な可能性を理解したい方にとって、必見の内容となっています。

ぜひ一度ご覧ください。

採用情報はこちら
目次