espeak-ruby 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +20 -49
- data/Rakefile +7 -3
- data/lib/espeak/speech.rb +19 -26
- data/lib/espeak/voice.rb +10 -8
- data/lib/espeak.rb +4 -0
- data/test/cases/speech_test.rb +7 -6
- data/test/cases/voice_test.rb +5 -8
- data/test/test_helper.rb +2 -1
- metadata +52 -11
- data/test/fixtures/voices.txt +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6668613d8bf8daabc824864e551fd7cfcfff3cddf08d3c994a3cd088d704900b
|
4
|
+
data.tar.gz: '0313183c857d3d4fa1444ae46b3fb62d292157e2922ba5aa8696acf6098284d2'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2fdcf4eadd06e21b75f8024048f353298b3522b9e97347dbad14e930359f5fa5e7d50facfa2c120df60978691e35767d5f36c28016b808496999f8453002e2d
|
7
|
+
data.tar.gz: ec27483768fb3016c9304327528afd4dc1fec73473a6bea8560c3b2128e35c1df8a45091965703687f1b8d81f2b0f9b0d1cda6134361a0fa6f58b6acde93263d
|
data/README.md
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
espeak-ruby
|
2
|
-
===========
|
1
|
+
# espeak-ruby
|
3
2
|
|
4
3
|
espeak-ruby is a small Ruby API for utilizing [espeak](http://espeak.sourceforge.net) and [lame](http://lame.sourceforge.net/) to create Text-To-Speech mp3 files. It can also just speak (without saving).
|
5
|
-
See the [live demo](http://rors.org/demos/espeak-ruby).
|
6
4
|
|
7
|
-
Install
|
8
|
-
-------
|
5
|
+
## Install
|
9
6
|
|
10
7
|
Add _espeak-ruby_ to Gemfile
|
11
8
|
|
@@ -13,35 +10,26 @@ Add _espeak-ruby_ to Gemfile
|
|
13
10
|
gem "espeak-ruby", require: "espeak"
|
14
11
|
```
|
15
12
|
|
16
|
-
Examples
|
17
|
-
--------
|
13
|
+
## Examples
|
18
14
|
|
19
|
-
First you need to include ESpeak module to access espeak API
|
20
|
-
|
21
|
-
```ruby
|
22
|
-
include ESpeak
|
23
|
-
```
|
24
|
-
|
25
|
-
Then use it like this:
|
26
15
|
|
27
16
|
```ruby
|
28
17
|
# Speaks "YO!"
|
29
|
-
speech = Speech.new("YO!")
|
18
|
+
speech = ESpeak::Speech.new("YO!")
|
30
19
|
speech.speak # invokes espeak
|
31
20
|
|
32
21
|
# Creates hello-de.mp3 file
|
33
|
-
speech = Speech.new("Hallo Welt", voice: "de")
|
22
|
+
speech = ESpeak::Speech.new("Hallo Welt", voice: "de")
|
34
23
|
speech.save("hello-de.mp3") # invokes espeak + lame
|
35
24
|
|
36
25
|
# Lists voices
|
37
|
-
Voice.all.map { |v| v.language } # ["af", "bs", "ca", "cs", "cy", "da", "de", "el", "en", "en-sc", "en-uk", "en-uk-north", "en-uk-rp", "en-uk-wmids", "en-us", "en-wi", "eo", "es", "es-la", "fi", "fr", "fr-be", "grc", "hi", "hr", "hu", "hy", "hy", "id", "is", "it", "jbo", "ka", "kn", "ku", "la", "lv", "mk", "ml", "nci", "nl", "no", "pap", "pl", "pt", "pt-pt", "ro", "ru", "sk", "sq", "sr", "sv", "sw", "ta", "tr", "vi", "zh", "zh-yue"]
|
26
|
+
ESpeak::Voice.all.map { |v| v.language } # ["af", "bs", "ca", "cs", "cy", "da", "de", "el", "en", "en-sc", "en-uk", "en-uk-north", "en-uk-rp", "en-uk-wmids", "en-us", "en-wi", "eo", "es", "es-la", "fi", "fr", "fr-be", "grc", "hi", "hr", "hu", "hy", "hy", "id", "is", "it", "jbo", "ka", "kn", "ku", "la", "lv", "mk", "ml", "nci", "nl", "no", "pap", "pl", "pt", "pt-pt", "ro", "ru", "sk", "sq", "sr", "sv", "sw", "ta", "tr", "vi", "zh", "zh-yue"]
|
38
27
|
|
39
28
|
# Find particular voice
|
40
|
-
Voice.find_by_language('en') #<ESpeak::Voice:0x007fe1d3806be8 @language="en", @name="default", @gender="M", @file="default">
|
29
|
+
ESpeak::Voice.find_by_language('en') #<ESpeak::Voice:0x007fe1d3806be8 @language="en", @name="default", @gender="M", @file="default">
|
41
30
|
```
|
42
31
|
|
43
|
-
Features
|
44
|
-
--------
|
32
|
+
## Features
|
45
33
|
|
46
34
|
Currently only subset of espeak features is supported.
|
47
35
|
|
@@ -58,37 +46,20 @@ These are default values, and they can be easily overridden:
|
|
58
46
|
Speech.new("Zdravo svete", voice: "sr", pitch: 90, speed: 200).speak
|
59
47
|
```
|
60
48
|
|
61
|
-
|
62
|
-
------------
|
49
|
+
## Installing dependencies
|
63
50
|
|
64
|
-
|
65
|
-
* <http://lame.sourceforge.net>
|
51
|
+
### OS X
|
66
52
|
|
67
|
-
|
68
|
-
|
53
|
+
brew install espeak lame
|
54
|
+
|
55
|
+
### Ubuntu
|
56
|
+
|
57
|
+
apt-get install espeak lame
|
58
|
+
|
59
|
+
## Related
|
69
60
|
|
70
61
|
* [espeak-http](http://github.com/dejan/espeak-http) - Micro web app for Text-To-Speech conversion via HTTP powered by Ruby, Sinatra, lame, espeak and espeak-ruby
|
71
62
|
|
72
|
-
Licence
|
73
|
-
|
74
|
-
|
75
|
-
Copyright (c) 2008 Dejan Simic
|
76
|
-
|
77
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
78
|
-
a copy of this software and associated documentation files (the
|
79
|
-
"Software"), to deal in the Software without restriction, including
|
80
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
81
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
82
|
-
permit persons to whom the Software is furnished to do so, subject to
|
83
|
-
the following conditions:
|
84
|
-
|
85
|
-
The above copyright notice and this permission notice shall be
|
86
|
-
included in all copies or substantial portions of the Software.
|
87
|
-
|
88
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
89
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
90
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
91
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
92
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
93
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
94
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
63
|
+
## Licence
|
64
|
+
|
65
|
+
espeak-ruby is released under the [MIT License](/MIT-LICENSE).
|
data/Rakefile
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rake/testtask'
|
4
|
+
require 'rubocop/rake_task'
|
2
5
|
|
3
6
|
desc 'Default: run tests'
|
4
|
-
task :
|
7
|
+
task default: :test
|
5
8
|
|
6
9
|
desc 'Test espeak-ruby'
|
7
|
-
Rake::TestTask.new(:
|
8
|
-
t.libs <<
|
10
|
+
Rake::TestTask.new(test: :rubocop) do |t|
|
11
|
+
t.libs << 'test'
|
9
12
|
t.pattern = 'test/**/*_test.rb'
|
10
13
|
t.verbose = true
|
11
14
|
end
|
12
15
|
|
16
|
+
RuboCop::RakeTask.new
|
data/lib/espeak/speech.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ESpeak
|
4
|
+
# Speech represents an Text-To-Speech audio that can be played or saved to file
|
2
5
|
class Speech
|
3
6
|
attr_reader :options, :text
|
4
7
|
|
@@ -11,7 +14,7 @@ module ESpeak
|
|
11
14
|
# no range given in man but good range is 10-40 to start
|
12
15
|
# :quiet - remove printing to stdout. Affects only lame (default false)
|
13
16
|
#
|
14
|
-
def initialize(text, options={})
|
17
|
+
def initialize(text, options = {})
|
15
18
|
@text = text
|
16
19
|
@options = options
|
17
20
|
end
|
@@ -19,9 +22,7 @@ module ESpeak
|
|
19
22
|
# Speaks text
|
20
23
|
#
|
21
24
|
def speak
|
22
|
-
IO.popen(espeak_command(command_options), 'r')
|
23
|
-
process.read
|
24
|
-
end
|
25
|
+
IO.popen(espeak_command(command_options), 'r', &:read)
|
25
26
|
end
|
26
27
|
|
27
28
|
# Generates mp3 file as a result of
|
@@ -40,7 +41,7 @@ module ESpeak
|
|
40
41
|
# Returns mp3 file bytes as a result of
|
41
42
|
# Text-To-Speech conversion.
|
42
43
|
#
|
43
|
-
def bytes
|
44
|
+
def bytes
|
44
45
|
speech = bytes_wav
|
45
46
|
res = IO.popen(std_lame_command(command_options), 'r+') do |process|
|
46
47
|
process.write(speech)
|
@@ -53,16 +54,14 @@ module ESpeak
|
|
53
54
|
# Returns wav file bytes as a result of
|
54
55
|
# Text-To-Speech conversion.
|
55
56
|
#
|
56
|
-
def bytes_wav
|
57
|
-
IO.popen(espeak_command(command_options,
|
58
|
-
process.read
|
59
|
-
end
|
57
|
+
def bytes_wav
|
58
|
+
IO.popen(espeak_command(command_options, '--stdout'), 'r', &:read)
|
60
59
|
end
|
61
60
|
|
62
61
|
private
|
63
62
|
|
64
63
|
def command_options
|
65
|
-
default_options.merge(
|
64
|
+
default_options.merge(options.transform_keys(&:to_sym))
|
66
65
|
end
|
67
66
|
|
68
67
|
# Although espeak itself has default options
|
@@ -70,30 +69,24 @@ module ESpeak
|
|
70
69
|
# command (with simple hash.merge)
|
71
70
|
#
|
72
71
|
def default_options
|
73
|
-
{ :
|
74
|
-
:
|
75
|
-
:
|
76
|
-
:
|
77
|
-
:
|
72
|
+
{ voice: 'en',
|
73
|
+
pitch: 50,
|
74
|
+
speed: 170,
|
75
|
+
capital: 1,
|
76
|
+
quiet: true }
|
78
77
|
end
|
79
78
|
|
80
|
-
def espeak_command(options, flags=
|
81
|
-
['espeak',
|
79
|
+
def espeak_command(options, flags = '')
|
80
|
+
['espeak', @text.to_s, flags.to_s, "-v#{options[:voice]}", "-p#{options[:pitch]}", "-k#{options[:capital]}",
|
81
|
+
"-s#{options[:speed]}"]
|
82
82
|
end
|
83
83
|
|
84
84
|
def std_lame_command(options)
|
85
|
-
lame_command(
|
85
|
+
lame_command('-', options)
|
86
86
|
end
|
87
87
|
|
88
88
|
def lame_command(filename, options)
|
89
|
-
['lame', '-V2', '-',
|
90
|
-
end
|
91
|
-
|
92
|
-
def symbolize_keys(hash)
|
93
|
-
hash.inject({}) do |options, (key, value)|
|
94
|
-
options[(key.to_sym rescue key) || key] = value
|
95
|
-
options
|
96
|
-
end
|
89
|
+
['lame', '-V2', '-', filename.to_s, ('--quiet' if options[:quiet] == true).to_s]
|
97
90
|
end
|
98
91
|
end
|
99
92
|
end
|
data/lib/espeak/voice.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ESpeak
|
4
|
+
# A voice that will be used for `Speech`
|
4
5
|
class Voice
|
5
6
|
attr_reader :language, :name, :gender, :file
|
7
|
+
|
6
8
|
def initialize(attributes)
|
7
9
|
@language = attributes[:language]
|
8
10
|
@name = attributes[:name]
|
@@ -12,18 +14,18 @@ module ESpeak
|
|
12
14
|
|
13
15
|
def self.all
|
14
16
|
voices = []
|
15
|
-
|
16
|
-
|
17
|
+
result = IO.popen('espeak --voices', &:read)
|
18
|
+
result.each_line do |line|
|
19
|
+
next unless line.start_with?(' ') # header
|
20
|
+
|
21
|
+
row = line.split
|
22
|
+
voices << Voice.new(language: row[1], gender: row[2], name: row[3], file: row[4])
|
17
23
|
end
|
18
|
-
voices
|
24
|
+
voices.freeze
|
19
25
|
end
|
20
26
|
|
21
27
|
def self.find_by_language(lang)
|
22
28
|
all.find { |v| v.language == lang.to_s }
|
23
29
|
end
|
24
|
-
|
25
|
-
def self.espeak_voices
|
26
|
-
`espeak --voices`
|
27
|
-
end
|
28
30
|
end
|
29
31
|
end
|
data/lib/espeak.rb
CHANGED
data/test/cases/speech_test.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'fileutils'
|
3
5
|
|
@@ -5,11 +7,10 @@ class SpeechTest < Test::Unit::TestCase
|
|
5
7
|
include ESpeak
|
6
8
|
|
7
9
|
def test_save
|
8
|
-
FileUtils.rm_rf(
|
9
|
-
FileUtils.mkdir_p(
|
10
|
-
assert Speech.new(
|
11
|
-
assert File.exist?(
|
12
|
-
FileUtils.rm_rf(
|
10
|
+
FileUtils.rm_rf('test/tmp')
|
11
|
+
FileUtils.mkdir_p('test/tmp')
|
12
|
+
assert Speech.new('Hello!').save('test/tmp/test.mp3')
|
13
|
+
assert File.exist?('test/tmp/test.mp3'), 'Mp3 file not generated'
|
14
|
+
FileUtils.rm_rf('test/tmp')
|
13
15
|
end
|
14
16
|
end
|
15
|
-
|
data/test/cases/voice_test.rb
CHANGED
@@ -1,17 +1,14 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
2
4
|
|
3
5
|
class VoiceTest < Test::Unit::TestCase
|
4
6
|
include ESpeak
|
5
7
|
|
6
|
-
def setup
|
7
|
-
voices_file = File.read("test/fixtures/voices.txt")
|
8
|
-
Voice.expects(:espeak_voices).returns(voices_file)
|
9
|
-
end
|
10
|
-
|
11
8
|
def test_all
|
12
9
|
all_voices = Voice.all
|
13
|
-
assert all_voices.size
|
14
|
-
assert %w
|
10
|
+
assert all_voices.size.positive?
|
11
|
+
assert %w[M F].include?(all_voices.first.gender)
|
15
12
|
end
|
16
13
|
|
17
14
|
def test_find_by_language
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: espeak-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dejan Simic
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
11
|
+
date: 2021-11-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 13.0.6
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 13.0.6
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.23'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.23'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.5'
|
13
55
|
description: espeak-ruby is small Ruby API for utilizing ‘espeak’ and ‘lame’ to create
|
14
56
|
Text-To-Speech mp3 files
|
15
57
|
email: desimic@gmail.com
|
@@ -24,13 +66,13 @@ files:
|
|
24
66
|
- lib/espeak/voice.rb
|
25
67
|
- test/cases/speech_test.rb
|
26
68
|
- test/cases/voice_test.rb
|
27
|
-
- test/fixtures/voices.txt
|
28
69
|
- test/test_helper.rb
|
29
70
|
homepage: https://github.com/dejan/espeak-ruby
|
30
71
|
licenses:
|
31
72
|
- MIT
|
32
|
-
metadata:
|
33
|
-
|
73
|
+
metadata:
|
74
|
+
rubygems_mfa_required: 'true'
|
75
|
+
post_install_message:
|
34
76
|
rdoc_options: []
|
35
77
|
require_paths:
|
36
78
|
- lib
|
@@ -38,16 +80,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
80
|
requirements:
|
39
81
|
- - ">="
|
40
82
|
- !ruby/object:Gem::Version
|
41
|
-
version:
|
83
|
+
version: 2.5.0
|
42
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
85
|
requirements:
|
44
86
|
- - ">="
|
45
87
|
- !ruby/object:Gem::Version
|
46
88
|
version: '0'
|
47
89
|
requirements: []
|
48
|
-
|
49
|
-
|
50
|
-
signing_key:
|
90
|
+
rubygems_version: 3.2.32
|
91
|
+
signing_key:
|
51
92
|
specification_version: 4
|
52
93
|
summary: espeak-ruby is small Ruby API for utilizing ‘espeak’ and ‘lame’ to create
|
53
94
|
Text-To-Speech mp3 files
|
data/test/fixtures/voices.txt
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
Pty Language Age/Gender VoiceName File Other Langs
|
2
|
-
5 af M afrikaans af
|
3
|
-
5 bs M bosnian bs
|
4
|
-
5 ca M catalan ca
|
5
|
-
5 cs M czech cs
|
6
|
-
5 cy M welsh-test cy
|
7
|
-
5 da M danish da
|
8
|
-
5 de M german de
|
9
|
-
5 el M greek el
|
10
|
-
5 en M default default
|
11
|
-
5 en-sc M en-scottish en/en-sc (en 4)
|
12
|
-
2 en-uk M english en/en (en 2)
|
13
|
-
5 en-uk-north M lancashire en/en-n (en-uk 3)
|
14
|
-
5 en-uk-rp M english_rp en/en-rp (en-uk 4)
|
15
|
-
5 en-uk-wmids M english_wmids en/en-wm
|
16
|
-
2 en-us M english-us en/en-us (en-r 5)(en 3)
|
17
|
-
5 en-wi M en-westindies en/en-wi (en-uk 4)
|
18
|
-
5 eo M esperanto eo
|
19
|
-
5 es M spanish es
|
20
|
-
5 es-la M spanish-latin-american es-la (es-mx 6)
|
21
|
-
5 fi M finnish fi
|
22
|
-
5 fr M french fr
|
23
|
-
5 fr-be M french (Belgium) fr-be
|
24
|
-
5 grc M greek-ancient test/grc
|
25
|
-
5 hi M hindi-test hi
|
26
|
-
5 hr M croatian hr (hbs 5)
|
27
|
-
5 hu M hungarian hu
|
28
|
-
5 hy M armenian hy
|
29
|
-
5 hy M armenian-west hy-west
|
30
|
-
5 id M indonesian-test id
|
31
|
-
5 is M icelandic-test is
|
32
|
-
5 it M italian it
|
33
|
-
5 jbo lojban test/jbo
|
34
|
-
5 ka georgian-test ka
|
35
|
-
5 kn kannada kn
|
36
|
-
5 ku M kurdish ku
|
37
|
-
5 la M latin la
|
38
|
-
5 lv M latvian lv
|
39
|
-
5 mk M macedonian-test mk
|
40
|
-
5 ml M malayalam ml
|
41
|
-
5 nci M nahuatl - classical test/nci
|
42
|
-
5 nl M dutch-test nl
|
43
|
-
5 no M norwegian no (nb 5)
|
44
|
-
5 pap papiamento-test test/pap
|
45
|
-
5 pl M polish pl
|
46
|
-
5 pt M brazil pt (pt-br 5)
|
47
|
-
5 pt-pt M portugal pt-pt
|
48
|
-
5 ro M romanian ro
|
49
|
-
5 ru M russian_test ru
|
50
|
-
5 sk M slovak sk
|
51
|
-
5 sq M albanian sq
|
52
|
-
5 sr M serbian sr
|
53
|
-
5 sv M swedish sv
|
54
|
-
5 sw M swahihi-test sw
|
55
|
-
5 ta M tamil ta
|
56
|
-
5 tr M turkish tr
|
57
|
-
5 vi M vietnam vi
|
58
|
-
5 zh M Mandarin zh
|
59
|
-
5 zh-yue M cantonese zh-yue (yue 5)(zhy 5)
|
60
|
-
|