izuemon commited on
Commit
569617c
·
verified ·
1 Parent(s): 433638e

Update turbowarp-server/qr-converter.py

Browse files
Files changed (1) hide show
  1. turbowarp-server/qr-converter.py +262 -262
turbowarp-server/qr-converter.py CHANGED
@@ -1,290 +1,290 @@
1
- import scratchcommunication
2
- import requests
3
- from PIL import Image
4
- from io import BytesIO
5
  import time
6
  import random
7
- import math
 
8
 
9
- # プロジェクトID
10
- PROJECT_ID = 1293416663
11
 
12
- # 接続設定
13
  tw = scratchcommunication.TwCloudConnection(
14
  project_id=PROJECT_ID,
15
  username="server",
16
  contact_info="contact"
17
  )
18
 
19
- # n-chars.txtの読み込み
20
- def load_char_map():
21
- """n-chars.txtから文字マッピングを読み込む"""
22
- char_map = {}
23
- try:
24
- with open("turbowarp-server/n-chars.txt", "r", encoding="utf-8") as f:
25
- chars = f.read().strip().split("\n")
26
- for i, char in enumerate(chars):
27
- char_map[char] = str(i).zfill(2) # 00, 01, 02...
28
- char_map[str(i).zfill(2)] = char # 逆引きも保存
29
- return char_map
30
- except Exception as e:
31
- print(f"文字マップ読み込みエラー: {e}")
32
- return {}
33
-
34
- CHAR_MAP = load_char_map()
35
-
36
- def encode_prompt(prompt):
37
- """プロンプトを数字列にエンコード"""
38
- encoded = []
39
- for char in prompt:
40
- if char in CHAR_MAP:
41
- encoded.append(CHAR_MAP[char])
42
- else:
43
- encoded.append("99") # 不明な文字は99
44
- return "".join(encoded)
45
-
46
- def decode_prompt(encoded):
47
- """数字列をプロンプトにデコード"""
48
- decoded = []
49
- for i in range(0, len(encoded), 2):
50
- code = encoded[i:i+2]
51
- if code in CHAR_MAP:
52
- decoded.append(CHAR_MAP[code])
53
- else:
54
- decoded.append("?")
55
- return "".join(decoded)
56
-
57
  def get_var(name):
58
- """クラウド変数の値を取得(安全版)"""
59
  try:
60
- return tw.get_variable(name=name, name_literal=False)
 
61
  except Exception:
62
  return None
63
 
64
  def set_var(name, value):
65
- """クラウド変数の値を設定(安全版)"""
66
  try:
67
- return tw.set_variable(name=name, value=value, name_literal=False)
68
  except Exception:
69
  return None
70
 
71
- def wait_for_var_change(var_name, expected_prefix=None, timeout=30):
72
- """変数の変更を待機"""
73
- start_time = time.time()
74
- last_value = get_var(var_name)
75
-
76
- while time.time() - start_time < timeout:
77
- current_value = get_var(var_name)
78
- if current_value != last_value:
79
- if expected_prefix is None or str(current_value).startswith(expected_prefix):
80
- return current_value
81
- last_value = current_value
82
- time.sleep(0.1)
83
- return None
84
-
85
- def generate_image(prompt, negative_prompt="nsfw, low quality"):
86
- """画像生成APIを呼び出し"""
87
- try:
88
- # ランダムシードを生成
89
- seed = random.randint(0, 2**32 - 1)
90
-
91
- params = {
92
- "prompt": prompt,
93
- "negative_prompt": negative_prompt,
94
- "seed": seed,
95
- "randomize_seed": "false",
96
- "width": 352,
97
- "height": 352,
98
- "guidance_scale": 0.0, # CFG=0
99
- "num_inference_steps": 2
100
- }
101
-
102
- response = requests.get(
103
- "https://izuemon-pixart-alpha-pixart-sigma-xl-2-1024-ms.hf.space/gen",
104
- params=params,
105
- timeout=60
106
- )
107
-
108
- if response.status_code == 200:
109
- return response.content
110
- else:
111
- print(f"画像生成APIエラー: {response.status_code}")
112
- return None
113
-
114
- except Exception as e:
115
- print(f"画像生成エラー: {e}")
116
- return None
117
 
118
- def resize_image(image_data, size=(90, 90)):
119
- """画像をリサイズ"""
120
- try:
121
- img = Image.open(BytesIO(image_data))
122
- img = img.resize(size, Image.Resampling.LANCZOS)
123
- return img
124
- except Exception as e:
125
- print(f"リサイズエラー: {e}")
126
- return None
127
 
128
- def image_to_rgb_string(img):
129
- """画像をRGB値の文字列に変換(000000-999999形式)"""
130
- pixels = []
131
- width, height = img.size
132
-
133
- for y in range(height):
134
- for x in range(width):
135
- r, g, b = img.getpixel((x, y))[:3]
136
- # RGB6桁数字に変換(000000-999999)
137
- rgb_value = r * 10000 + g * 100 + b
138
- pixels.append(str(rgb_value).zfill(6))
139
-
140
- return "".join(pixels)
141
-
142
- def send_packets(data, var_name="n1"):
143
- """データをパケットに分割して送信"""
144
- # パケットサイズ(n1のプレフィックス"10"を除く)
145
- packet_size = 9998 - 2 # "10"分を引く
146
-
147
- # パケット数
148
- total_packets = math.ceil(len(data) / packet_size)
149
-
150
- for i in range(total_packets):
151
- start = i * packet_size
152
- end = min((i + 1) * packet_size, len(data))
153
- packet_data = data[start:end]
154
-
155
- # パケット番号を付与(2桁)
156
- packet_num = str(i + 1).zfill(2)
157
- packet_value = f"10{packet_num}{packet_data}"
158
-
159
- # 送信
160
- set_var(var_name, packet_value)
161
- print(f"パケット {i+1}/{total_packets} 送信: {len(packet_data)}文字")
162
-
163
- # 次のパケットの前に既読を待機
164
- if i < total_packets - 1:
165
- # "11"に変更されるのを待機
166
- start_wait = time.time()
167
- while True:
168
- current = get_var(var_name)
169
- if current and str(current).startswith("11"):
170
- print(f"パケット {i+1} 既読確認")
171
- break
172
- if time.time() - start_wait > 30:
173
- print("既読待機タイムアウト")
174
- break
175
- time.sleep(0.1)
176
-
177
- # 0.5秒間隔
178
- time.sleep(0.5)
179
-
180
- return total_packets
181
-
182
- def process_request(received_value):
183
- """受信したデータを処理"""
184
- try:
185
- # 受信データ: "0[ID][encoded_prompt]"
186
- # 先頭が'0'か確認
187
- value_str = str(received_value)
188
- if not value_str.startswith('0'):
189
- print(f"無効なデータ形式: {value_str[:10]}...")
190
- return
191
-
192
- # ID抽出(2桁)
193
- if len(value_str) < 3:
194
- print("IDが不足しています")
195
- return
196
-
197
- user_id = value_str[1:3]
198
- encoded_prompt = value_str[3:]
199
-
200
- print(f"処理開始 - ID: {user_id}, エンコード長: {len(encoded_prompt)}")
201
-
202
- # プロンプトをデコード
203
- prompt = decode_prompt(encoded_prompt)
204
- print(f"デコードされたプロンプト: {prompt}")
205
-
206
- # 画像生成
207
- print("画像生成中...")
208
- image_data = generate_image(prompt)
209
- if not image_data:
210
- print("画像生成失敗")
211
- return
212
-
213
- # リサイズ
214
- print("画像リサイズ中...")
215
- resized_img = resize_image(image_data)
216
- if not resized_img:
217
- print("リサイズ失敗")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  return
219
-
220
- # RGB文字列変換
221
- print("RGB変換中...")
222
- rgb_string = image_to_rgb_string(resized_img)
223
- print(f"RGB文字列長: {len(rgb_string)}文字")
224
-
225
- # パケット送信
226
- print("パケット送信開始...")
227
- packet_count = send_packets(rgb_string)
228
- print(f"送信完了: {packet_count}パケット")
229
-
230
- return True
231
-
232
- except Exception as e:
233
- print(f"処理エラー: {e}")
234
- import traceback
235
- traceback.print_exc()
236
- return False
237
-
238
- def main():
239
- """メインループ"""
240
- print("サーバー起動...")
241
- print("プロジェクトID:", PROJECT_ID)
242
-
243
- # 初期化
244
- set_var("n0", "0")
245
- set_var("n1", "00")
246
-
247
- print("待機中...")
248
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  while True:
250
  try:
251
- # n1の変更を監視
252
- n1_value = wait_for_var_change("n1", expected_prefix="0", timeout=5)
253
-
254
- if n1_value is not None:
255
- # 使用中フラグを立てる
256
- set_var("n0", "1")
257
- print(f"受信データ検出: {str(n1_value)[:50]}...")
258
-
259
- # 既読にする(n1を"1x"に変更)
260
- # 元の値から既読フラグを立てる
261
- current_n1 = str(n1_value)
262
- if len(current_n1) >= 2:
263
- read_value = "1" + current_n1[1:]
264
- set_var("n1", read_value)
265
- print(f"既読設定: {read_value}")
266
-
267
- # リクエスト処理
268
- success = process_request(n1_value)
269
-
270
- if success:
271
- print("処理完了")
 
 
272
  else:
273
- print("処理失敗")
274
-
275
- # 使用中フラグを解除
276
- set_var("n0", "0")
277
- print("待機再開...")
278
-
279
- time.sleep(0.1)
280
-
281
- except KeyboardInterrupt:
282
- print("\nサーバー停止")
283
- set_var("n0", "0")
284
- break
285
- except Exception as e:
286
- print(f"メインループエラー: {e}")
287
- time.sleep(1)
288
 
289
  if __name__ == "__main__":
290
- main()
 
 
 
 
 
 
1
+ import os
 
 
 
2
  import time
3
  import random
4
+ import threading
5
+ from io import BytesIO
6
 
7
+ import requests
8
+ from PIL import Image
9
 
10
+ # 既存コードを流用
11
  tw = scratchcommunication.TwCloudConnection(
12
  project_id=PROJECT_ID,
13
  username="server",
14
  contact_info="contact"
15
  )
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  def get_var(name):
 
18
  try:
19
+ v = tw.get_variable(name=name, name_literal=False)
20
+ return None if v is None else str(v)
21
  except Exception:
22
  return None
23
 
24
  def set_var(name, value):
 
25
  try:
26
+ return tw.set_variable(name=name, value=str(value), name_literal=False)
27
  except Exception:
28
  return None
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ # ========= 設定 =========
32
+ API_URL = "https://izuemon-pixart-alpha-pixart-sigma-xl-2-1024-ms.hf.space/gen"
33
+ N_CHARS_PATH = "turbowarp-server/n-chars.txt"
 
 
 
 
 
 
34
 
35
+ NEGATIVE_PROMPT = "nsfw, low quality"
36
+ WIDTH = 352
37
+ HEIGHT = 352
38
+ CFG = 0
39
+ SEED_RANDOM = True
40
+ SAMPLING_STEPS = 2
41
+
42
+ OUT_SIZE = (90, 90)
43
+ PACKET_DATA_LIMIT = 9998 # 「10」除いたデータ部上限として扱う
44
+ POLL_INTERVAL = 0.5 # クラウド変数の連続送信を防ぐ
45
+ ACK_VALUE = "11" # Scratch側が既読にした合図
46
+ BUSY_LOCK = "1"
47
+ FREE_LOCK = "0"
48
+
49
+
50
+ # ========= 文字対応表 =========
51
+ def load_n_chars(path: str):
52
+ if not os.path.exists(path):
53
+ raise FileNotFoundError(f"n-chars.txt が見つかりません: {path}")
54
+
55
+ with open(path, "r", encoding="utf-8") as f:
56
+ chars = [line.rstrip("\n") for line in f]
57
+
58
+ if len(chars) < 100:
59
+ raise ValueError("n-chars.txt は最低 100 行必要です(00〜99)。")
60
+ return chars[:100]
61
+
62
+ N_CHARS = load_n_chars(N_CHARS_PATH)
63
+ N_CHARS_MAP = {f"{i:02d}": ch for i, ch in enumerate(N_CHARS)}
64
+ N_CHARS_REV = {ch: f"{i:02d}" for i, ch in enumerate(N_CHARS)}
65
+
66
+
67
+ def decode_numeric_text(encoded: str) -> str:
68
+ """
69
+ 00〜99 の2桁列を文字列に戻す。
70
+ 例: '000102' -> ['00','01','02']
71
+ """
72
+ encoded = "" if encoded is None else str(encoded)
73
+ encoded = "".join(ch for ch in encoded if ch.isdigit())
74
+
75
+ if len(encoded) % 2 != 0:
76
+ encoded = encoded[:-1] # 末尾が余ったら切り捨て
77
+
78
+ out = []
79
+ for i in range(0, len(encoded), 2):
80
+ key = encoded[i:i+2]
81
+ out.append(N_CHARS_MAP.get(key, ""))
82
+ return "".join(out)
83
+
84
+
85
+ def encode_text_to_numeric(text: str) -> str:
86
+ """
87
+ 文字列 -> 00〜99 の列
88
+ """
89
+ result = []
90
+ for ch in str(text):
91
+ if ch not in N_CHARS_REV:
92
+ raise ValueError(f"対応表にない文字です: {repr(ch)}")
93
+ result.append(N_CHARS_REV[ch])
94
+ return "".join(result)
95
+
96
+
97
+ # ========= Cloud 値の正規化 =========
98
+ def normalize_value(v):
99
+ if v is None:
100
+ return ""
101
+ return str(v).strip()
102
+
103
+
104
+ def get_n1():
105
+ return normalize_value(get_var("n1"))
106
+
107
+ def get_n0():
108
+ return normalize_value(get_var("n0"))
109
+
110
+ def set_n1(v):
111
+ return set_var("n1", v)
112
+
113
+ def set_n0(v):
114
+ return set_var("n0", v)
115
+
116
+
117
+ # ========= 画像生成 =========
118
+ def fetch_generated_image(prompt: str) -> Image.Image:
119
+ params = {
120
+ "prompt": prompt,
121
+ "negative_prompt": NEGATIVE_PROMPT,
122
+ "seed": random.randint(0, 2**31 - 1) if SEED_RANDOM else 0,
123
+ "randomize_seed": "true" if SEED_RANDOM else "false",
124
+ "width": WIDTH,
125
+ "height": HEIGHT,
126
+ "guidance_scale": CFG,
127
+ "num_inference_steps": SAMPLING_STEPS,
128
+ }
129
+
130
+ r = requests.get(API_URL, params=params, timeout=300)
131
+ r.raise_for_status()
132
+
133
+ # 生画像データを想定
134
+ img = Image.open(BytesIO(r.content)).convert("RGB")
135
+ return img
136
+
137
+
138
+ def resize_to_90(img: Image.Image) -> Image.Image:
139
+ return img.resize(OUT_SIZE, Image.Resampling.LANCZOS)
140
+
141
+
142
+ def rgb_to_6digits(r: int, g: int, b: int) -> str:
143
+ """
144
+ RGB を 6桁の10進数に変換。
145
+ 仕様が曖昧なため、各チャンネルを 0〜99 に線形変換して 2桁ずつ連結する。
146
+ """
147
+ rr = round(r * 99 / 255)
148
+ gg = round(g * 99 / 255)
149
+ bb = round(b * 99 / 255)
150
+ return f"{rr:02d}{gg:02d}{bb:02d}"
151
+
152
+
153
+ def image_to_payload(img: Image.Image) -> str:
154
+ """
155
+ 90x90 の画像を左上から走査し、
156
+ '10' + 90*90個の6桁数値 を返す。
157
+ """
158
+ pixels = img.load()
159
+ w, h = img.size
160
+ parts = ["10"]
161
+
162
+ for y in range(h):
163
+ for x in range(w):
164
+ r, g, b = pixels[x, y]
165
+ parts.append(rgb_to_6digits(r, g, b))
166
+
167
+ return "".join(parts)
168
+
169
+
170
+ # ========= パケット送信 =========
171
+ def split_packets(data: str, limit: int = PACKET_DATA_LIMIT):
172
+ return [data[i:i+limit] for i in range(0, len(data), limit)]
173
+
174
+
175
+ def wait_for_ack():
176
+ while True:
177
+ time.sleep(POLL_INTERVAL)
178
+ v = get_n1()
179
+ if v == ACK_VALUE:
180
  return
181
+ # 事故防止: 途中で n1 が空や想定外に戻った場合は少し待つ
182
+ # 必要ならここでエラー処理してもよい
183
+
184
+
185
+ def send_packets(data_with_prefix: str):
186
+ packets = split_packets(data_with_prefix, PACKET_DATA_LIMIT)
187
+
188
+ for idx, packet in enumerate(packets, start=1):
189
+ # 送信開始
190
+ set_n1("10" + packet)
191
+
192
+ # Scratch側が既読にして "11" にするまで待つ
193
+ wait_for_ack()
194
+
195
+ # 次の通信へ行く前に少し空ける
196
+ time.sleep(POLL_INTERVAL)
197
+
198
+ # 全送信完了
199
+ set_n0(FREE_LOCK)
200
+
201
+
202
+ # ========= メイン処理 =========
203
+ def process_incoming_value(raw_value: str):
204
+ """
205
+ n1 の 3文字目以降をデコードして prompt として使う。
206
+ もしここで「ID -> prompt」変換を入れたい場合は、
207
+ decode_numeric_text の結果を ID として別処理に差し替えてください。
208
+ """
209
+ raw_value = normalize_value(raw_value)
210
+
211
+ if len(raw_value) < 3:
212
+ return
213
+
214
+ kind = raw_value[0] # 0: ID送信系 / 1: レスポンス系
215
+ read_flag = raw_value[1]
216
+ payload = raw_value[2:]
217
+
218
+ # 今回は「送られてきたものをデコードしたもの」を prompt として使う
219
+ decoded_prompt = decode_numeric_text(payload)
220
+
221
+ # 必要なら kind / read_flag を使って分岐できる
222
+ # kind == "0" -> 依頼受信
223
+ # kind == "1" -> 返信など
224
+ _ = read_flag
225
+
226
+ # 画像生成
227
+ img = fetch_generated_image(decoded_prompt)
228
+
229
+ # 90x90 に縮小
230
+ img = resize_to_90(img)
231
+
232
+ # 送信用の数値列へ変換
233
+ payload_90 = image_to_payload(img)
234
+
235
+ # 送信前にロックをかける
236
+ set_n0(BUSY_LOCK)
237
+
238
+ # パケット分割送信
239
+ send_packets(payload_90)
240
+
241
+
242
+ def server_loop():
243
+ last_n1 = None
244
+
245
  while True:
246
  try:
247
+ current_n1 = get_n1()
248
+
249
+ # 変化を検知したときだけ処理
250
+ if current_n1 and current_n1 != last_n1:
251
+ last_n1 = current_n1
252
+
253
+ # 他ユーザー使用中なら何もしない
254
+ if get_n0() == BUSY_LOCK:
255
+ time.sleep(POLL_INTERVAL)
256
+ continue
257
+
258
+ # n1 の先頭が 0 のときだけ受信処理
259
+ if current_n1.startswith("0"):
260
+ # まず使用中にする
261
+ set_n0(BUSY_LOCK)
262
+
263
+ # 既読化の扱いが必要ならここで Scratch 側仕様に合わせて書き換え
264
+ # 例: 受信確認として n1 を一度 "11" にする場合はここで行う
265
+ # set_n1(ACK_VALUE)
266
+
267
+ process_incoming_value(current_n1)
268
+
269
+ # process_incoming_value 側で最後に n0=0 に戻す
270
  else:
271
+ time.sleep(POLL_INTERVAL)
272
+
273
+ time.sleep(POLL_INTERVAL)
274
+
275
+ except Exception:
276
+ # 例外時はロック解除して継続
277
+ try:
278
+ set_n0(FREE_LOCK)
279
+ except Exception:
280
+ pass
281
+ time.sleep(POLL_INTERVAL)
282
+
 
 
 
283
 
284
  if __name__ == "__main__":
285
+ # 初期化
286
+ set_n0(FREE_LOCK)
287
+ # n1 は必要ならここで初期値を入れる
288
+ set_n1("0")
289
+
290
+ server_loop()