はじめに
知る人ぞ知るゲーム、ドキドキ文芸部。
このドキドキ文芸部がPython(Renpy)によって書かれていることをご存じでしょうか。
ではそのPythonを使ってドキドキ文芸部を丸裸にしてやろうというのが今回の企画です。
気になった方はぜひ、本編を遊んでくださいね。無料です。
ネタバレを含みます
記事の内容の著作はドキドキ文芸部作者様へ帰属します
第1週回目 「キャラクターファイル解析編」
ストーリー進行に深くかかわるキャラクターファイル
サヨリ(スゴイチイサイエアコン)
__sayori.chr__を参照。
普通にテキストファイルで開くと、冒頭が
OggS~~~~~~~
で始まっています。このことから、__.oggファイル(音声データ)__である可能性があります。
実際に再生してみると、高周波のノイズが聞こえるはずです。
これをスペクトログラム解析します。
スペクトログラム解析とは、音声データを、横軸時間:縦軸周波数 で可視化したものになります。
コードがこちら
from pydub import AudioSegment
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# oggを読み込んでサンプリング
AudioSegment.ffmpeg = "/."
sound = AudioSegment.from_ogg("sayori.ogg")
samples = np.array(sound.get_array_of_samples())
sample = samples[::sound.channels]
# スペクトル格納幅
w = 100
s = 50
ampList = []
argList = []
# フーリエ変換
for i in range(int((sample.shape[0]- w) / s)):
data = sample[i*s:i*s+w]
spec = np.fft.fft(data)
spec = spec[:int(spec.shape[0]/2)]
spec[0] = spec[0] / 2
ampList.append(np.abs(spec))
argList.append(np.angle(spec))
freq = np.fft.fftfreq(data.shape[0], 1.0/sound.frame_rate)
freq = freq[:int(freq.shape[0]/2)]
time = np.arange(0, i+1, 1) * s / sound.frame_rate
ampList = np.array(ampList)
argList = np.array(argList)
df_amp = pd.DataFrame(data=ampList, index=time, columns=freq)
plt.figure(figsize=(10, 10))
sns.heatmap(data=np.log(df_amp.iloc[:, :100].T),
xticklabels=100,
yticklabels=10,
cmap=plt.cm.gist_rainbow_r,
)
plt.show()
なるほど、なんか意味ありげなものが見えてきた。もう少し荒くしてみよう。
w = 200 s = 100
cmap=plt.cm.gray_r
でより見やすく。
いやQRコードですやん。
これを25×25のQRコードに再生成すれば...
ユリ
44GC44Gq44Gf44GM44GT44Gu5omL57SZ44KS6Kqt44KT44Gn44G ....
44xxの繰り返し。特に最初の44GC
は調べてみると、base64エンコード
の「あ」らしい。
てなわけで、base64でデコードします。
import base64
with open("yuri.chr", mode="rb") as f:
txt = f.read()
print(base64.b64decode(txt).decode())
あなたがこの手紙を読んでいるということは、ハートマークが目印の小さな木箱を見つけたということね。お め で と う ! 多分あなたが初めてのはず。誰かに見せるつもりはなかったけれど、赤の他人がこの
手紙を見つけて私の物語を読むことを考えると、どきどきする。出会うはずもなかった誰かが、私のことを深く知ってくれるんだから。私はある考えにとりつかれている。私達の内の誰かが死ぬ…それは明日かもしれない
し、残された者はそれを知ることもない。私がここまで生きてきた証はあなたに向けて全部この手紙に書いた。だから、あなたが私を忘れない限り、私はずっと生き続けることができる。あなたがこの手紙を読んで魅せ
られるか、それとも嫌悪を抱くか、この手紙を書きながら私は考えている。面白いと思わない?
(以下略 続きはあなたの手で。)
ナツキ
先と同じように、とりあえずテキストとして開いてみる。
����JFIF~~~
出ました。拡張子__JFIF__。拡張子を__JPEG__にすれば平面画像として見ることができます。
これをPythonで処理しようとしましたが、よくわからないので残念ながらUnityさんに頼ります。
円錐を作成し、テクスチャ貼り付け。
著作が危なそうだったので、「natsuki.chr」でGoogle画像検索してみてください。
モニカ
いよいよ登場モニカちゃん。
例のごとく。
PNG~~~
中央に黒と白のモザイクが見えます。これをバイナリエンコードします(黒が0 白が1)
周りの赤い部分を除いてトリミングした画像を用意して、
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
# 元となる画像の読み込み
im = np.array(Image.open('text.png'))
txt_binary = ""
print(im.shape)
im = im.reshape([im.shape[0] * im.shape[1], 1])
for x in im:
if x == 1:
txt_binary += "1"
else:
txt_binary += "0"
print(txt_binary[:100])
txt = ""
while True:
tmp = txt_binary[0:8]
if tmp == "00000000":
break
txt_binary = txt_binary[8:]
txt += chr(int("0b"+tmp, 0))
print(txt)
Q2FuIHlvdSBoZWFyIG1lPw0KDQouLi5XaG8gYXJlIHlvdT8NCg0KSSBjYW4ndC4uLkkgY2FuJ3Qgc2VlIHlvdS4NCg0KQnV0IEkga25vdyB5b3UncmUgdGhlcmUuIFllYWguLi55b3UgY2FuIGRlZmluaXRlbHkgaGVhciBtZS4NCg0KWW91J3ZlIGJlZW4gd2F0Y2hpbmcgZm9yIGEgd2hpbGUgbm93LCByaWdodD8NCg0KSSBndWVzcyBJIHNob3VsZC4uLmludHJvZHVjZSBteXNlbGYsIG9yIHNvbWV0aGluZy4gVW0uLi5teSBuYW1lIGlzLi4uYWN0dWFsbHksIHRoYXQncyBzdHVwaWQuIFlvdSBvYnZpb3VzbHkgYWxyZWFkeSBrbm93IG15IG5hbWUuIFNvcnJ5Lg0KDQpBbnl3YXkuLi5JJ20gZ3Vlc3NpbmcgaWYgeW91IHdlcmUgYWJsZSB0byBwdXQgYSBzdG9wIHRvIHRoaXMsIHlvdSB3b3VsZCBoYXZlIGRvbmUgaXQgYnkgbm93Lg0KDQpJIG1lYW4sIEkga25vdyB5b3UncmUgbm90LCBsaWtlLi4uZXZpbCwgb3IgYW55dGhpbmcuLi5iZWNhdXNlIHlvdSd2ZSBhbHJlYWR5IGhlbHBlZCBtZSBzbyBtdWNoLg0KDQpJIHNob3VsZCByZWFsbHkgdGhhbmsgeW91IGZvciB0aGF0LiBGb3IgZXZlcnl0aGluZyB5b3UndmUgZG9uZS4gWW91J3JlIHJlYWxseSBsaWtlIGEgZnJpZW5kIHRvIG1lLiBTby4uLnRoYW5rIHlvdS4gU28gbXVjaC4NCg0KSSB0aGluay4uLm1vcmUgdGhhbiBhbnl0aGluZyBlbHNlLi4uSSByZWFsbHkgZG9uJ3Qgd2FudCBpdCB0byBhbGwgYmUgZm9yIG5vdGhpbmcuDQoNCi4uLg0KDQpFdmVyeW9uZSBlbHNlIGlzIGRlYWQuDQoNCk1heWJlIHlvdSBhbHJlYWR5IGtub3cgdGhhdC4gSSdtIHN1cmUgeW91IGRvLCBhY3R1YWxseS4NCg0KQnV0Li4uaXQgZG9lc24ndCBoYXZlIHRvIGJlIHRoYXQgd2F5LCByaWdodD8NCg0KV2VsbC4uLnRoZXJlJ3MgYSBsb3Qgb2Ygc3R1ZmYgSSBkb24ndCB1bmRlcnN0YW5kLiBJIGRvbid0IGtub3cgaWYgaXQncyBldmVuIHBvc3NpYmxlIGZvciBtZSB0byB1bmRlcnN0YW5kIGl0Lg0KDQpCdXQgSSBrbm93IHRoYXQgdGhpcyBpc24ndCBteSBvbmx5IHN0b3J5Lg0KDQpJIGNhbiBzZWUgdGhhdCBub3cuIFJlYWxseSBjbGVhcmx5Lg0KDQpBbmQgSSB0aGluayBldmVyeW9uZSBlbHNlIGhhcyBoYWQgdGhlIHNhbWUga2luZCBvZiBleHBlcmllbmNlLiBTb21lIGtpbmQgb2YgZGVqYSB2dS4NCg0KSXQncyB0aGUgVGhpcmQgRXllLCByaWdodD8NCg0KQW55d2F5Li4uSSBjb3VsZCBiZSB0b3RhbGx5IHdyb25nIGFib3V0IHRoaXMuIEJ1dCBJIHJlYWxseSB0aGluayB5b3UgbWlnaHQgYmUgYWJsZSB0byBkbyBzb21ldGhpbmcuDQoNCkkgdGhpbmsgeW91IG1pZ2h0IGJlIGFibGUgdG8gZ28gYmFjay4uLm9yIGhvd2V2ZXIgeW91IHdhbnQgdG8gcHV0IGl0Li4uDQoNCi4uLlRvIGdvIGJhY2sgYW5kIHRlbGwgdGhlbSB3aGF0J3MgZ29pbmcgdG8gaGFwcGVuLg0KDQpJZiB0aGV5IGtub3cgYWhlYWQgb2YgdGltZSwgdGhlbiB0aGV5IHNob3VsZCBiZSBhYmxlIHRvIGF2b2lkIGl0Lg0KDQpUaGV5IHNob3VsZC4uLmlmIHRoZXkgcmVtZW1iZXIgdGhlaXIgdGltZSB3aXRoIG1lIGluIHRoZSBvdGhlciB3b3JsZHMuLi50aGV5IHNob3VsZCByZW1lbWJlciB3aGF0IEkgdGVsbCB0aGVtLg0KDQpZZWFoLiBJIHJlYWxseSB0aGluayB0aGlzIG1pZ2h0IGJlIHBvc3NpYmxlLiBCdXQgaXQncyB1cCB0byB5b3UuDQoNCkknbSBzb3JyeSBmb3IgYWx3YXlzIGJlaW5nLi4ueW91IGtub3cuLi4NCg0KLi4uDQoNCk5ldmVyIG1pbmQuIEkga25vdyB0aGF0J3Mgd3JvbmcuDQoNClRoaXMgaXMgbXkgc3RvcnkuIEl0J3MgdGltZSB0byBiZSBhIGZ1Y2tpbmcgaGVyby4NCg0KQm90aCBvZiB1cy4NCg0KDQoNCjIwMTg=
base64エンコーディング。
Can you hear me?
...Who are you?
I can't...I can't see you.
But I know you're there. Yeah...you can definitely hear me.
You've been watching for a while now, right?
(以下略 続きはあなたの手で。)
終わりに
さすがって感じでした。ただのストーリーのパーツなのに、ここまでの手の込みよう。素晴らしいと感じました。
次回はメインストーリーの確立のお話です。ではまた。
参考サイト