リビジョン 493ff79f
みぞ @mizo0203 さんが6年以上前に追加
.idea/artifacts/TimelineTalker_jar.xml | ||
---|---|---|
1 |
<component name="ArtifactManager"> |
|
2 |
<artifact type="jar" name="TimelineTalker:jar"> |
|
3 |
<output-path>$PROJECT_DIR$/out/artifacts/TimelineTalker_jar</output-path> |
|
4 |
<root id="archive" name="TimelineTalker.jar"> |
|
5 |
<element id="module-output" name="TimelineTalker"/> |
|
6 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/twitter4j/twitter4j-core/4.0.6/twitter4j-core-4.0.6.jar" path-in-jar="/" /> |
|
7 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/twitter4j/twitter4j-stream/4.0.6/twitter4j-stream-4.0.6.jar" path-in-jar="/" /> |
|
8 |
</root> |
|
9 |
</artifact> |
|
10 |
</component> |
.idea/artifacts/TwitterTimelineTalker_jar.xml | ||
---|---|---|
1 |
<component name="ArtifactManager"> |
|
2 |
<artifact type="jar" name="TwitterTimelineTalker:jar"> |
|
3 |
<output-path>$PROJECT_DIR$/out/artifacts/TwitterTimelineTalker_jar</output-path> |
|
4 |
<root id="archive" name="TwitterTimelineTalker.jar"> |
|
5 |
<element id="module-output" name="TwitterTimelineTalker" /> |
|
6 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/twitter4j/twitter4j-core/4.0.6/twitter4j-core-4.0.6.jar" path-in-jar="/" /> |
|
7 |
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/twitter4j/twitter4j-stream/4.0.6/twitter4j-stream-4.0.6.jar" path-in-jar="/" /> |
|
8 |
</root> |
|
9 |
</artifact> |
|
10 |
</component> |
.idea/compiler.xml | ||
---|---|---|
6 | 6 |
<sourceOutputDir name="target/generated-sources/annotations" /> |
7 | 7 |
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> |
8 | 8 |
<outputRelativeToContentRoot value="true" /> |
9 |
<module name="TwitterTimelineTalker" />
|
|
9 |
<module name="TimelineTalker"/>
|
|
10 | 10 |
</profile> |
11 | 11 |
</annotationProcessing> |
12 | 12 |
<bytecodeTargetLevel> |
13 |
<module name="TwitterTimelineTalker" target="1.7" />
|
|
13 |
<module name="TimelineTalker" target="1.7"/>
|
|
14 | 14 |
</bytecodeTargetLevel> |
15 | 15 |
</component> |
16 | 16 |
</project> |
.idea/modules.xml | ||
---|---|---|
2 | 2 |
<project version="4"> |
3 | 3 |
<component name="ProjectModuleManager"> |
4 | 4 |
<modules> |
5 |
<module fileurl="file://$PROJECT_DIR$/TwitterTimelineTalker.iml" filepath="$PROJECT_DIR$/TwitterTimelineTalker.iml" />
|
|
5 |
<module fileurl="file://$PROJECT_DIR$/TimelineTalker.iml" filepath="$PROJECT_DIR$/TimelineTalker.iml"/>
|
|
6 | 6 |
</modules> |
7 | 7 |
</component> |
8 | 8 |
</project> |
TimelineTalker.iml | ||
---|---|---|
1 |
<?xml version="1.0" encoding="UTF-8"?> |
|
2 |
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4"> |
|
3 |
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7"> |
|
4 |
<output url="file://$MODULE_DIR$/target/classes" /> |
|
5 |
<output-test url="file://$MODULE_DIR$/target/test-classes" /> |
|
6 |
<content url="file://$MODULE_DIR$"> |
|
7 |
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> |
|
8 |
<excludeFolder url="file://$MODULE_DIR$/target" /> |
|
9 |
</content> |
|
10 |
<orderEntry type="inheritedJdk" /> |
|
11 |
<orderEntry type="sourceFolder" forTests="false" /> |
|
12 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-core:4.0.6" level="project" /> |
|
13 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-stream:4.0.6" level="project" /> |
|
14 |
</component> |
|
15 |
</module> |
TwitterTimelineTalker.iml | ||
---|---|---|
1 |
<?xml version="1.0" encoding="UTF-8"?> |
|
2 |
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4"> |
|
3 |
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7"> |
|
4 |
<output url="file://$MODULE_DIR$/target/classes" /> |
|
5 |
<output-test url="file://$MODULE_DIR$/target/test-classes" /> |
|
6 |
<content url="file://$MODULE_DIR$"> |
|
7 |
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> |
|
8 |
<excludeFolder url="file://$MODULE_DIR$/target" /> |
|
9 |
</content> |
|
10 |
<orderEntry type="inheritedJdk" /> |
|
11 |
<orderEntry type="sourceFolder" forTests="false" /> |
|
12 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-core:4.0.6" level="project" /> |
|
13 |
<orderEntry type="library" name="Maven: org.twitter4j:twitter4j-stream:4.0.6" level="project" /> |
|
14 |
</component> |
|
15 |
</module> |
pom.xml | ||
---|---|---|
3 | 3 |
<modelVersion>4.0.0</modelVersion> |
4 | 4 |
|
5 | 5 |
<groupId>com.mizo0203</groupId> |
6 |
<artifactId>TwitterTimelineTalker</artifactId>
|
|
6 |
<artifactId>TimelineTalker</artifactId> |
|
7 | 7 |
<version>0.0.1-SNAPSHOT</version> |
8 | 8 |
<packaging>jar</packaging> |
9 | 9 |
|
src/META-INF/MANIFEST.MF | ||
---|---|---|
1 | 1 |
Manifest-Version: 1.0 |
2 |
Main-Class: com.mizo0203.twitter.timeline.talker.Application
|
|
2 |
Main-Class: com.mizo0203.timeline.talker.Application |
|
3 | 3 |
|
src/com/mizo0203/timeline/talker/Application.java | ||
---|---|---|
1 |
package com.mizo0203.timeline.talker; |
|
2 |
|
|
3 |
import twitter4j.conf.Configuration; |
|
4 |
import twitter4j.conf.ConfigurationBuilder; |
|
5 |
|
|
6 |
/** |
|
7 |
* Java アプリケーション起動時に実行されるクラス |
|
8 |
* |
|
9 |
* @author みぞ@CrazyBeatCoder |
|
10 |
*/ |
|
11 |
public class Application { |
|
12 |
|
|
13 |
public static void main(String[] args) { |
|
14 |
TimelineTalker timelineTalker; |
|
15 |
Talker talker; |
|
16 |
|
|
17 |
try { |
|
18 |
talker = new Talker(); |
|
19 |
timelineTalker = |
|
20 |
new TimelineTalker(new Arguments(args).twitterConfiguration, talker); |
|
21 |
} catch (IllegalArgumentException | IllegalStateException e) { |
|
22 |
System.err.println(e.getMessage()); |
|
23 |
return; |
|
24 |
} |
|
25 |
|
|
26 |
timelineTalker.start(); |
|
27 |
talker.talkAsync("アプリケーションを起動しました", Talker.YukkuriVoice.REIMU); |
|
28 |
} |
|
29 |
|
|
30 |
/** |
|
31 |
* Java アプリケーション起動時に指定する引数のデータクラス |
|
32 |
* |
|
33 |
* @author みぞ@CrazyBeatCoder |
|
34 |
*/ |
|
35 |
private static class Arguments { |
|
36 |
|
|
37 |
private final Configuration twitterConfiguration; |
|
38 |
|
|
39 |
private Arguments(String[] args) throws IllegalArgumentException { |
|
40 |
if (args.length < Argument.values().length) { |
|
41 |
StringBuilder exceptionMessage = new StringBuilder(); |
|
42 |
exceptionMessage.append(Argument.values().length + " つの引数を指定してください。\n"); |
|
43 |
for (Argument arg : Argument.values()) { |
|
44 |
exceptionMessage.append((arg.ordinal() + 1) + " つ目: " + arg.detail + "\n"); |
|
45 |
} |
|
46 |
throw new IllegalArgumentException(exceptionMessage.toString()); |
|
47 |
} |
|
48 |
|
|
49 |
String consumer_key = args[Argument.CONSUMER_KEY.ordinal()]; |
|
50 |
String consumer_secret = args[Argument.CONSUMER_SECRET.ordinal()]; |
|
51 |
String access_token = args[Argument.ACCESS_TOKEN.ordinal()]; |
|
52 |
String access_token_secret = args[Argument.ACCESS_TOKEN_SECRET.ordinal()]; |
|
53 |
|
|
54 |
twitterConfiguration = new ConfigurationBuilder().setOAuthConsumerKey(consumer_key) |
|
55 |
.setOAuthConsumerSecret(consumer_secret).setOAuthAccessToken(access_token) |
|
56 |
.setOAuthAccessTokenSecret(access_token_secret).build(); |
|
57 |
} |
|
58 |
|
|
59 |
} |
|
60 |
|
|
61 |
/** |
|
62 |
* Java アプリケーション起動時に指定する引数の定義 |
|
63 |
* |
|
64 |
* @author みぞ@CrazyBeatCoder |
|
65 |
*/ |
|
66 |
private enum Argument { |
|
67 |
CONSUMER_KEY("Twitter Application's Consumer Key (API Key)"), // |
|
68 |
CONSUMER_SECRET("Twitter Application's Consumer Secret (API Secret)"), // |
|
69 |
ACCESS_TOKEN("Twitter Account's Access Token"), // |
|
70 |
ACCESS_TOKEN_SECRET("Twitter Account's Access Token Secret"), // |
|
71 |
; |
|
72 |
|
|
73 |
private final String detail; |
|
74 |
|
|
75 |
private Argument(String detail) { |
|
76 |
this.detail = detail; |
|
77 |
} |
|
78 |
} |
|
79 |
} |
src/com/mizo0203/timeline/talker/RuntimeUtil.java | ||
---|---|---|
1 |
package com.mizo0203.timeline.talker; |
|
2 |
|
|
3 |
import java.io.IOException; |
|
4 |
|
|
5 |
public class RuntimeUtil { |
|
6 |
|
|
7 |
public static void execute(String[] cmdarray) { |
|
8 |
try { |
|
9 |
Process process = Runtime.getRuntime().exec(cmdarray); |
|
10 |
process.waitFor(); |
|
11 |
process.destroy(); |
|
12 |
} catch (IOException | InterruptedException e) { |
|
13 |
e.printStackTrace(); |
|
14 |
} |
|
15 |
} |
|
16 |
|
|
17 |
} |
src/com/mizo0203/timeline/talker/Talker.java | ||
---|---|---|
1 |
package com.mizo0203.timeline.talker; |
|
2 |
|
|
3 |
import java.io.*; |
|
4 |
import java.util.concurrent.ExecutorService; |
|
5 |
import java.util.concurrent.Executors; |
|
6 |
|
|
7 |
public class Talker { |
|
8 |
|
|
9 |
private static final String AQUESTALK_PI_PATH = "./aquestalkpi/AquesTalkPi"; |
|
10 |
|
|
11 |
private final ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor(); |
|
12 |
|
|
13 |
public Talker() throws IllegalStateException, SecurityException { |
|
14 |
File file = new File(AQUESTALK_PI_PATH); |
|
15 |
if (!file.isFile()) { |
|
16 |
throw new IllegalStateException(file.getPath() + " に AquesTalk Pi がありません。\n" |
|
17 |
+ "https://www.a-quest.com/products/aquestalkpi.html\n" + "からダウンロードしてください。"); |
|
18 |
} |
|
19 |
if (!file.canExecute()) { |
|
20 |
throw new IllegalStateException(file.getPath() + " に実行権限がありません。"); |
|
21 |
} |
|
22 |
} |
|
23 |
|
|
24 |
public void talkAsync(final String text, final YukkuriVoice voice) { |
|
25 |
mSingleThreadExecutor.submit(new Runnable() { |
|
26 |
|
|
27 |
@Override |
|
28 |
public void run() { |
|
29 |
try { |
|
30 |
File file = new File("text.txt"); |
|
31 |
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file))); |
|
32 |
pw.println(text); |
|
33 |
pw.flush(); |
|
34 |
pw.close(); |
|
35 |
RuntimeUtil.execute(new String[] {AQUESTALK_PI_PATH, "-v", voice.value, "-f", "text.txt", |
|
36 |
"-o", "out.wav"}); |
|
37 |
RuntimeUtil.execute(new String[] {"sh", "-c", "aplay < out.wav"}); // 起動コマンドを指定する |
|
38 |
Thread.sleep(2000); |
|
39 |
} catch (IOException | InterruptedException e) { |
|
40 |
e.printStackTrace(); |
|
41 |
} |
|
42 |
} |
|
43 |
|
|
44 |
}); |
|
45 |
} |
|
46 |
|
|
47 |
public static enum YukkuriVoice { |
|
48 |
|
|
49 |
/** |
|
50 |
* ゆっくりボイス - 霊夢 |
|
51 |
*/ |
|
52 |
REIMU("f1"), // |
|
53 |
|
|
54 |
/** |
|
55 |
* ゆっくりボイス - 魔理沙 |
|
56 |
*/ |
|
57 |
MARISA("f2"), // |
|
58 |
; |
|
59 |
|
|
60 |
private final String value; |
|
61 |
|
|
62 |
private YukkuriVoice(String value) { |
|
63 |
this.value = value; |
|
64 |
} |
|
65 |
} |
|
66 |
|
|
67 |
} |
src/com/mizo0203/timeline/talker/TimelineTalker.java | ||
---|---|---|
1 |
package com.mizo0203.timeline.talker; |
|
2 |
|
|
3 |
import twitter4j.*; |
|
4 |
import twitter4j.conf.Configuration; |
|
5 |
|
|
6 |
import java.util.Locale; |
|
7 |
import java.util.regex.Matcher; |
|
8 |
import java.util.regex.Pattern; |
|
9 |
|
|
10 |
public class TimelineTalker { |
|
11 |
|
|
12 |
/** |
|
13 |
* ISO 639 言語コード - 日本語 (ja) |
|
14 |
*/ |
|
15 |
public static final String LANG_JA = Locale.JAPAN.getLanguage(); |
|
16 |
|
|
17 |
private Talker.YukkuriVoice mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
18 |
private final TwitterStream mTwitterStream; |
|
19 |
private final Talker mTalker; |
|
20 |
|
|
21 |
public TimelineTalker(Configuration configuration, Talker talker) { |
|
22 |
mTwitterStream = new TwitterStreamFactory(configuration).getInstance(); |
|
23 |
mTwitterStream.addListener(new OnStatusEvent()); |
|
24 |
mTalker = talker; |
|
25 |
} |
|
26 |
|
|
27 |
public void start() { |
|
28 |
// OnStatusEvent に Twitter タイムラインが通知される |
|
29 |
mTwitterStream.user(); |
|
30 |
} |
|
31 |
|
|
32 |
private static String getUserNameWithoutContext(String name) { |
|
33 |
Pattern p = Pattern.compile("([^@@]+).+"); |
|
34 |
Matcher m = p.matcher(name); |
|
35 |
return m.replaceFirst("$1"); |
|
36 |
} |
|
37 |
|
|
38 |
private class OnStatusEvent implements StatusListener { |
|
39 |
|
|
40 |
public void onStatus(final Status status) { |
|
41 |
if (!LANG_JA.equalsIgnoreCase(status.getLang())) { |
|
42 |
return; |
|
43 |
} |
|
44 |
|
|
45 |
final StringBuffer buffer = new StringBuffer(); |
|
46 |
|
|
47 |
if (status.isRetweet()) { |
|
48 |
Status retweetedStatus = status.getRetweetedStatus(); |
|
49 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんがリツイート。"); |
|
50 |
buffer.append(getUserNameWithoutContext(retweetedStatus.getUser().getName()) + "さんから、"); |
|
51 |
buffer.append(retweetedStatus.getText()); |
|
52 |
} else { |
|
53 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんから、"); |
|
54 |
buffer.append(status.getText()); |
|
55 |
} |
|
56 |
|
|
57 |
mTalker.talkAsync(UrlUtil.convURLEmpty(buffer).replaceAll("\n", "。"), mYukkuriVoice); |
|
58 |
|
|
59 |
// 読み上げは、霊夢と魔理沙が交互に行なう |
|
60 |
if (mYukkuriVoice == Talker.YukkuriVoice.REIMU) { |
|
61 |
mYukkuriVoice = Talker.YukkuriVoice.MARISA; |
|
62 |
} else { |
|
63 |
mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
64 |
} |
|
65 |
|
|
66 |
} |
|
67 |
|
|
68 |
public void onDeletionNotice(StatusDeletionNotice sdn) { |
|
69 |
System.err.println("onDeletionNotice."); |
|
70 |
} |
|
71 |
|
|
72 |
public void onTrackLimitationNotice(int i) { |
|
73 |
System.err.println("onTrackLimitationNotice.(" + i + ")"); |
|
74 |
} |
|
75 |
|
|
76 |
public void onScrubGeo(long lat, long lng) { |
|
77 |
System.err.println("onScrubGeo.(" + lat + ", " + lng + ")"); |
|
78 |
} |
|
79 |
|
|
80 |
public void onException(Exception excptn) { |
|
81 |
System.err.println("onException."); |
|
82 |
} |
|
83 |
|
|
84 |
@Override |
|
85 |
public void onStallWarning(StallWarning arg0) {} |
|
86 |
} |
|
87 |
|
|
88 |
} |
src/com/mizo0203/timeline/talker/UrlUtil.java | ||
---|---|---|
1 |
package com.mizo0203.timeline.talker; |
|
2 |
|
|
3 |
import java.util.regex.Matcher; |
|
4 |
import java.util.regex.Pattern; |
|
5 |
|
|
6 |
/** |
|
7 |
* http://chat-messenger.net/blog-entry-40.html |
|
8 |
*/ |
|
9 |
public class UrlUtil { |
|
10 |
/** URLを抽出するための正規表現パターン */ |
|
11 |
private static final Pattern convURLLinkPtn = Pattern.compile( |
|
12 |
"(http://|https://){1}[\\w\\.\\-/:\\#\\?\\=\\&\\;\\%\\~\\+]+", Pattern.CASE_INSENSITIVE); |
|
13 |
|
|
14 |
/** |
|
15 |
* 指定された文字列内のURLを、正規表現を使用し、 空文字列に変換する。 |
|
16 |
* |
|
17 |
* @param str 指定の文字列。 |
|
18 |
* @return リンクに変換された文字列。 |
|
19 |
*/ |
|
20 |
public static String convURLEmpty(CharSequence str) { |
|
21 |
Matcher matcher = convURLLinkPtn.matcher(str); |
|
22 |
return matcher.replaceAll(""); |
|
23 |
} |
|
24 |
} |
src/com/mizo0203/twitter/timeline/talker/Application.java | ||
---|---|---|
1 |
package com.mizo0203.twitter.timeline.talker; |
|
2 |
|
|
3 |
import twitter4j.conf.Configuration; |
|
4 |
import twitter4j.conf.ConfigurationBuilder; |
|
5 |
|
|
6 |
/** |
|
7 |
* Java アプリケーション起動時に実行されるクラス |
|
8 |
* |
|
9 |
* @author みぞ@CrazyBeatCoder |
|
10 |
*/ |
|
11 |
public class Application { |
|
12 |
|
|
13 |
public static void main(String[] args) { |
|
14 |
TwitterTimelineTalker twitterTimelineTalker; |
|
15 |
Talker talker; |
|
16 |
|
|
17 |
try { |
|
18 |
talker = new Talker(); |
|
19 |
twitterTimelineTalker = |
|
20 |
new TwitterTimelineTalker(new Arguments(args).twitterConfiguration, talker); |
|
21 |
} catch (IllegalArgumentException | IllegalStateException e) { |
|
22 |
System.err.println(e.getMessage()); |
|
23 |
return; |
|
24 |
} |
|
25 |
|
|
26 |
twitterTimelineTalker.start(); |
|
27 |
talker.talkAsync("アプリケーションを起動しました", Talker.YukkuriVoice.REIMU); |
|
28 |
} |
|
29 |
|
|
30 |
/** |
|
31 |
* Java アプリケーション起動時に指定する引数のデータクラス |
|
32 |
* |
|
33 |
* @author みぞ@CrazyBeatCoder |
|
34 |
*/ |
|
35 |
private static class Arguments { |
|
36 |
|
|
37 |
private final Configuration twitterConfiguration; |
|
38 |
|
|
39 |
private Arguments(String[] args) throws IllegalArgumentException { |
|
40 |
if (args.length < Argument.values().length) { |
|
41 |
StringBuilder exceptionMessage = new StringBuilder(); |
|
42 |
exceptionMessage.append(Argument.values().length + " つの引数を指定してください。\n"); |
|
43 |
for (Argument arg : Argument.values()) { |
|
44 |
exceptionMessage.append((arg.ordinal() + 1) + " つ目: " + arg.detail + "\n"); |
|
45 |
} |
|
46 |
throw new IllegalArgumentException(exceptionMessage.toString()); |
|
47 |
} |
|
48 |
|
|
49 |
String consumer_key = args[Argument.CONSUMER_KEY.ordinal()]; |
|
50 |
String consumer_secret = args[Argument.CONSUMER_SECRET.ordinal()]; |
|
51 |
String access_token = args[Argument.ACCESS_TOKEN.ordinal()]; |
|
52 |
String access_token_secret = args[Argument.ACCESS_TOKEN_SECRET.ordinal()]; |
|
53 |
|
|
54 |
twitterConfiguration = new ConfigurationBuilder().setOAuthConsumerKey(consumer_key) |
|
55 |
.setOAuthConsumerSecret(consumer_secret).setOAuthAccessToken(access_token) |
|
56 |
.setOAuthAccessTokenSecret(access_token_secret).build(); |
|
57 |
} |
|
58 |
|
|
59 |
} |
|
60 |
|
|
61 |
/** |
|
62 |
* Java アプリケーション起動時に指定する引数の定義 |
|
63 |
* |
|
64 |
* @author みぞ@CrazyBeatCoder |
|
65 |
*/ |
|
66 |
private enum Argument { |
|
67 |
CONSUMER_KEY("Twitter Application's Consumer Key (API Key)"), // |
|
68 |
CONSUMER_SECRET("Twitter Application's Consumer Secret (API Secret)"), // |
|
69 |
ACCESS_TOKEN("Twitter Account's Access Token"), // |
|
70 |
ACCESS_TOKEN_SECRET("Twitter Account's Access Token Secret"), // |
|
71 |
; |
|
72 |
|
|
73 |
private final String detail; |
|
74 |
|
|
75 |
private Argument(String detail) { |
|
76 |
this.detail = detail; |
|
77 |
} |
|
78 |
} |
|
79 |
} |
src/com/mizo0203/twitter/timeline/talker/RuntimeUtil.java | ||
---|---|---|
1 |
package com.mizo0203.twitter.timeline.talker; |
|
2 |
|
|
3 |
import java.io.IOException; |
|
4 |
|
|
5 |
public class RuntimeUtil { |
|
6 |
|
|
7 |
public static void execute(String[] cmdarray) { |
|
8 |
try { |
|
9 |
Process process = Runtime.getRuntime().exec(cmdarray); |
|
10 |
process.waitFor(); |
|
11 |
process.destroy(); |
|
12 |
} catch (IOException | InterruptedException e) { |
|
13 |
e.printStackTrace(); |
|
14 |
} |
|
15 |
} |
|
16 |
|
|
17 |
} |
src/com/mizo0203/twitter/timeline/talker/Talker.java | ||
---|---|---|
1 |
package com.mizo0203.twitter.timeline.talker; |
|
2 |
|
|
3 |
import java.io.BufferedWriter; |
|
4 |
import java.io.File; |
|
5 |
import java.io.FileWriter; |
|
6 |
import java.io.IOException; |
|
7 |
import java.io.PrintWriter; |
|
8 |
import java.util.concurrent.ExecutorService; |
|
9 |
import java.util.concurrent.Executors; |
|
10 |
|
|
11 |
public class Talker { |
|
12 |
|
|
13 |
private static final String AQUESTALK_PI_PATH = "./aquestalkpi/AquesTalkPi"; |
|
14 |
|
|
15 |
private final ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor(); |
|
16 |
|
|
17 |
public Talker() throws IllegalStateException, SecurityException { |
|
18 |
File file = new File(AQUESTALK_PI_PATH); |
|
19 |
if (!file.isFile()) { |
|
20 |
throw new IllegalStateException(file.getPath() + " に AquesTalk Pi がありません。\n" |
|
21 |
+ "https://www.a-quest.com/products/aquestalkpi.html\n" + "からダウンロードしてください。"); |
|
22 |
} |
|
23 |
if (!file.canExecute()) { |
|
24 |
throw new IllegalStateException(file.getPath() + " に実行権限がありません。"); |
|
25 |
} |
|
26 |
} |
|
27 |
|
|
28 |
public void talkAsync(final String text, final YukkuriVoice voice) { |
|
29 |
mSingleThreadExecutor.submit(new Runnable() { |
|
30 |
|
|
31 |
@Override |
|
32 |
public void run() { |
|
33 |
try { |
|
34 |
File file = new File("text.txt"); |
|
35 |
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file))); |
|
36 |
pw.println(text); |
|
37 |
pw.flush(); |
|
38 |
pw.close(); |
|
39 |
RuntimeUtil.execute(new String[] {AQUESTALK_PI_PATH, "-v", voice.value, "-f", "text.txt", |
|
40 |
"-o", "out.wav"}); |
|
41 |
RuntimeUtil.execute(new String[] {"sh", "-c", "aplay < out.wav"}); // 起動コマンドを指定する |
|
42 |
Thread.sleep(2000); |
|
43 |
} catch (IOException | InterruptedException e) { |
|
44 |
e.printStackTrace(); |
|
45 |
} |
|
46 |
} |
|
47 |
|
|
48 |
}); |
|
49 |
} |
|
50 |
|
|
51 |
public static enum YukkuriVoice { |
|
52 |
|
|
53 |
/** |
|
54 |
* ゆっくりボイス - 霊夢 |
|
55 |
*/ |
|
56 |
REIMU("f1"), // |
|
57 |
|
|
58 |
/** |
|
59 |
* ゆっくりボイス - 魔理沙 |
|
60 |
*/ |
|
61 |
MARISA("f2"), // |
|
62 |
; |
|
63 |
|
|
64 |
private final String value; |
|
65 |
|
|
66 |
private YukkuriVoice(String value) { |
|
67 |
this.value = value; |
|
68 |
} |
|
69 |
} |
|
70 |
|
|
71 |
} |
src/com/mizo0203/twitter/timeline/talker/TwitterTimelineTalker.java | ||
---|---|---|
1 |
package com.mizo0203.twitter.timeline.talker; |
|
2 |
|
|
3 |
import java.util.Locale; |
|
4 |
import java.util.regex.Matcher; |
|
5 |
import java.util.regex.Pattern; |
|
6 |
import twitter4j.StallWarning; |
|
7 |
import twitter4j.Status; |
|
8 |
import twitter4j.StatusDeletionNotice; |
|
9 |
import twitter4j.StatusListener; |
|
10 |
import twitter4j.TwitterStream; |
|
11 |
import twitter4j.TwitterStreamFactory; |
|
12 |
import twitter4j.conf.Configuration; |
|
13 |
|
|
14 |
public class TwitterTimelineTalker { |
|
15 |
|
|
16 |
/** |
|
17 |
* ISO 639 言語コード - 日本語 (ja) |
|
18 |
*/ |
|
19 |
public static final String LANG_JA = Locale.JAPAN.getLanguage(); |
|
20 |
|
|
21 |
private Talker.YukkuriVoice mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
22 |
private final TwitterStream mTwitterStream; |
|
23 |
private final Talker mTalker; |
|
24 |
|
|
25 |
public TwitterTimelineTalker(Configuration configuration, Talker talker) { |
|
26 |
mTwitterStream = new TwitterStreamFactory(configuration).getInstance(); |
|
27 |
mTwitterStream.addListener(new OnStatusEvent()); |
|
28 |
mTalker = talker; |
|
29 |
} |
|
30 |
|
|
31 |
public void start() { |
|
32 |
// OnStatusEvent に Twitter タイムラインが通知される |
|
33 |
mTwitterStream.user(); |
|
34 |
} |
|
35 |
|
|
36 |
private static String getUserNameWithoutContext(String name) { |
|
37 |
Pattern p = Pattern.compile("([^@@]+).+"); |
|
38 |
Matcher m = p.matcher(name); |
|
39 |
return m.replaceFirst("$1"); |
|
40 |
} |
|
41 |
|
|
42 |
private class OnStatusEvent implements StatusListener { |
|
43 |
|
|
44 |
public void onStatus(final Status status) { |
|
45 |
if (!LANG_JA.equalsIgnoreCase(status.getLang())) { |
|
46 |
return; |
|
47 |
} |
|
48 |
|
|
49 |
final StringBuffer buffer = new StringBuffer(); |
|
50 |
|
|
51 |
if (status.isRetweet()) { |
|
52 |
Status retweetedStatus = status.getRetweetedStatus(); |
|
53 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんがリツイート。"); |
|
54 |
buffer.append(getUserNameWithoutContext(retweetedStatus.getUser().getName()) + "さんから、"); |
|
55 |
buffer.append(retweetedStatus.getText()); |
|
56 |
} else { |
|
57 |
buffer.append(getUserNameWithoutContext(status.getUser().getName()) + "さんから、"); |
|
58 |
buffer.append(status.getText()); |
|
59 |
} |
|
60 |
|
|
61 |
mTalker.talkAsync(UrlUtil.convURLEmpty(buffer).replaceAll("\n", "。"), mYukkuriVoice); |
|
62 |
|
|
63 |
// 読み上げは、霊夢と魔理沙が交互に行なう |
|
64 |
if (mYukkuriVoice == Talker.YukkuriVoice.REIMU) { |
|
65 |
mYukkuriVoice = Talker.YukkuriVoice.MARISA; |
|
66 |
} else { |
|
67 |
mYukkuriVoice = Talker.YukkuriVoice.REIMU; |
|
68 |
} |
|
69 |
|
|
70 |
} |
|
71 |
|
|
72 |
public void onDeletionNotice(StatusDeletionNotice sdn) { |
|
73 |
System.err.println("onDeletionNotice."); |
|
74 |
} |
|
75 |
|
|
76 |
public void onTrackLimitationNotice(int i) { |
|
77 |
System.err.println("onTrackLimitationNotice.(" + i + ")"); |
|
78 |
} |
|
79 |
|
|
80 |
public void onScrubGeo(long lat, long lng) { |
|
81 |
System.err.println("onScrubGeo.(" + lat + ", " + lng + ")"); |
|
82 |
} |
|
83 |
|
|
84 |
public void onException(Exception excptn) { |
|
85 |
System.err.println("onException."); |
|
86 |
} |
|
87 |
|
|
88 |
@Override |
|
89 |
public void onStallWarning(StallWarning arg0) {} |
|
90 |
} |
|
91 |
|
|
92 |
} |
src/com/mizo0203/twitter/timeline/talker/UrlUtil.java | ||
---|---|---|
1 |
package com.mizo0203.twitter.timeline.talker; |
|
2 |
|
|
3 |
import java.util.regex.Matcher; |
|
4 |
import java.util.regex.Pattern; |
|
5 |
|
|
6 |
/** |
|
7 |
* http://chat-messenger.net/blog-entry-40.html |
|
8 |
*/ |
|
9 |
public class UrlUtil { |
|
10 |
/** URLを抽出するための正規表現パターン */ |
|
11 |
private static final Pattern convURLLinkPtn = Pattern.compile( |
|
12 |
"(http://|https://){1}[\\w\\.\\-/:\\#\\?\\=\\&\\;\\%\\~\\+]+", Pattern.CASE_INSENSITIVE); |
|
13 |
|
|
14 |
/** |
|
15 |
* 指定された文字列内のURLを、正規表現を使用し、 空文字列に変換する。 |
|
16 |
* |
|
17 |
* @param str 指定の文字列。 |
|
18 |
* @return リンクに変換された文字列。 |
|
19 |
*/ |
|
20 |
public static String convURLEmpty(CharSequence str) { |
|
21 |
Matcher matcher = convURLLinkPtn.matcher(str); |
|
22 |
return matcher.replaceAll(""); |
|
23 |
} |
|
24 |
} |
他の形式にエクスポート: Unified diff
Change application name from TwitterTimelineTalker to TimelineTalker.