izuemon commited on
Commit
8b1b0b7
·
verified ·
1 Parent(s): 569617c

Update turbowarp-server/qr-converter.py

Browse files
Files changed (1) hide show
  1. turbowarp-server/qr-converter.py +173 -266
turbowarp-server/qr-converter.py CHANGED
@@ -1,290 +1,197 @@
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()
 
 
1
  import time
2
  import random
 
 
 
3
  import requests
4
  from PIL import Image
5
+ from io import BytesIO
6
+ import scratchcommunication
7
 
8
+ # --- Scratchクラウ接続 ---
9
+ PROJECT_ID = "1293416663"
10
+ print(f"[INFO] Connecting to Scratch project {PROJECT_ID}...")
11
  tw = scratchcommunication.TwCloudConnection(
12
  project_id=PROJECT_ID,
13
  username="server",
14
  contact_info="contact"
15
  )
16
+ print("[INFO] Connected successfully.")
17
 
18
  def get_var(name):
19
  try:
20
+ value = tw.get_variable(name=name, name_literal=False)
21
+ print(f"[GET] Variable '{name}' = {value[:50]}..." if value and len(value) > 50 else f"[GET] Variable '{name}' = {value}")
22
+ return value
23
+ except Exception as e:
24
+ print(f"[ERROR] Failed to get variable '{name}': {e}")
25
  return None
26
 
27
  def set_var(name, value):
28
  try:
29
+ tw.set_variable(name=name, value=value, name_literal=False)
30
+ print(f"[SET] Variable '{name}' = {value[:50]}..." if len(value) > 50 else f"[SET] Variable '{name}' = {value}")
31
+ return True
32
+ except Exception as e:
33
+ print(f"[ERROR] Failed to set variable '{name}': {e}")
34
  return None
35
 
36
+ # --- n-chars.txt の読み込み ---
37
+ print("[INFO] Loading n-chars.txt...")
38
+ with open("turbowarp-server/n-chars.txt", "r", encoding="utf-8") as f:
39
+ n_chars = [line.strip() for line in f]
40
+ print(f"[INFO] Loaded {len(n_chars)} characters.")
41
+
42
+ def decode_prompt(encoded_str):
43
+ """クラウド変数の数字列を元にプロンプト文字列に復号"""
44
+ chars = []
45
+ for i in range(0, len(encoded_str), 2):
46
+ idx = int(encoded_str[i:i+2])
47
+ if idx < len(n_chars):
48
+ chars.append(n_chars[idx])
49
+ decoded = "".join(chars)
50
+ print(f"[DECODE] Encoded prompt -> '{decoded[:50]}...'")
51
+ return decoded
52
+
53
+ def rgb_to_scratch_number(rgb):
54
+ """RGBを0-999999の10進数に変換"""
55
+ r, g, b = rgb
56
+ return f"{r:03}{g:03}{b:03}"
57
+
58
+ def generate_image(prompt):
59
+ """APIら352x352の画像生成"""
60
+ seed = random.randint(0, 999999)
61
+ print(f"[INFO] Generating image with prompt: '{prompt[:50]}...', seed={seed}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  params = {
63
  "prompt": prompt,
64
+ "negative_prompt": "nsfw, low quality",
65
+ "width": 352,
66
+ "height": 352,
67
+ "num_inference_steps": 2,
68
+ "guidance_scale": 0,
69
+ "seed": seed,
70
+ "randomize_seed": "true"
71
  }
72
+ resp = requests.get("https://izuemon-pixart-alpha-pixart-sigma-xl-2-1024-ms.hf.space/gen", params=params)
73
+ resp.raise_for_status()
74
+ img = Image.open(BytesIO(resp.content))
75
+ print(f"[INFO] Image generated: size={img.size}, mode={img.mode}")
 
 
76
  return img
77
 
78
+ def resize_and_encode(img):
79
+ """90x90にリサイズして数字列に変換"""
80
+ img = img.resize((90, 90))
81
+ print("[INFO] Resized image to 90x90.")
82
+ encoded = ""
83
+ for y in range(90):
84
+ for x in range(90):
85
+ encoded += rgb_to_scratch_number(img.getpixel((x, y)))
86
+ print(f"[INFO] Image encoded to string of length {len(encoded)}.")
87
+ return "10" + encoded # 先頭に "10"
88
+
89
+ def split_packets(data, max_length=9998):
90
+ """10000文字制限に合わせて分割(先頭 '10' 含む)"""
91
+ packets = []
92
+ idx = 0
93
+ while idx < len(data):
94
+ chunk = data[idx:idx + max_length]
95
+ packets.append(chunk)
96
+ idx += max_length
97
+ print(f"[INFO] Data split into {len(packets)} packet(s).")
98
+ return packets
99
+
100
+ # --- メインループ ---
101
+ print("[INFO] Starting main loop...")
102
+ last_processed_id = "" # 最後に処理したIDを記録
103
+
104
+ while True:
105
+ try:
106
+ n1 = get_var("n1")
107
+ n0 = get_var("n0")
108
+
109
+ # n1が存在し、長さが3以上で、先頭が"0"(新規リクエスト)
110
+ if n1 and len(n1) >= 3 and n1[0] == "0":
111
+ user_id = n1[1:3] # ユーザーID(2桁)
112
+ request_data = n1 # 完全なリクエストデータ
113
+
114
+ print(f"[INFO] Received request from user {user_id}")
115
+ print(f"[INFO] Request data length: {len(request_data)}")
116
+
117
+ # 同じリクエストを繰り返し処理しないようにチェック
118
+ if request_data == last_processed_id:
119
+ print("[INFO] Already processed this request, marking as read...")
120
+ # 既読マークをつける(先頭を"01"に変更)
121
+ marked_as_read = "01" + request_data[2:]
122
+ set_var("n1", marked_as_read)
123
+ time.sleep(0.1)
124
+ continue
125
+
126
+ # n0が利用可能かチェック
127
+ if n0 == "0" or n0 is None:
128
+ print("[INFO] n0 is free, starting processing...")
129
+
130
+ # 処理中フラグをセット
131
+ set_var("n0", "1")
132
+
133
+ # リクエストを既読にする(先頭を"01"に変更)
134
+ marked_as_read = "01" + request_data[2:]
135
+ set_var("n1", marked_as_read)
136
+
137
+ # プロンプトをデコード(先頭の"0" + ユーザーID(2桁)を除去)
138
+ encoded_prompt = request_data[3:]
139
+ prompt = decode_prompt(encoded_prompt)
140
+
141
+ # 画像生成
142
+ img = generate_image(prompt)
143
+ scratch_data = resize_and_encode(img)
144
+
145
+ # パケット分割
146
+ packets = split_packets(scratch_data)
147
+
148
+ # 最初パケット送信
149
+ for i, pkt in enumerate(packets):
150
+ print(f"[INFO] Sending packet {i+1}/{len(packets)}")
151
+ set_var("n1", pkt)
152
+
153
+ if i < len(packets) - 1:
154
+ # 次のパケットのACKを待つ
155
+ print("[INFO] Waiting for ACK '11'...")
156
+ ack_timeout = 0
157
+ while ack_timeout < 50: # 5秒タイムアウト
158
+ current_n1 = get_var("n1")
159
+ if current_n1 == "11":
160
+ print("[INFO] Received ACK")
161
+ break
162
+ time.sleep(0.1)
163
+ ack_timeout += 1
164
+
165
+ if ack_timeout >= 50:
166
+ print("[ERROR] ACK timeout")
167
+ set_var("n0", "0")
168
+ break
169
+ else:
170
+ # 最後のパケットは完了を待つ
171
+ print("[INFO] Waiting for completion '99'...")
172
+ complete_timeout = 0
173
+ while complete_timeout < 50:
174
+ current_n1 = get_var("n1")
175
+ if current_n1 == "99":
176
+ print("[INFO] Received completion signal")
177
+ break
178
+ time.sleep(0.1)
179
+ complete_timeout += 1
180
+
181
+ # 完了後リセット
182
+ print("[INFO] Transmission complete. Resetting n0.")
183
+ set_var("n0", "0")
184
+ last_processed_id = request_data # 処理済みとして記録
185
+
186
+ else:
187
+ print("[INFO] n0 is busy, waiting...")
188
+ else:
189
+ # 何もしない
190
+ pass
191
+
192
+ except Exception as e:
193
+ print(f"[ERROR] Exception in main loop: {e}")
194
+ import traceback
195
+ traceback.print_exc()
196
+
197
+ time.sleep(0.2)