Skip to content

Commit 795e6e0

Browse files
committed
Merge branch 'backend_kume' into develop
2 parents 2433d4d + bcb434f commit 795e6e0

File tree

5 files changed

+375
-4
lines changed

5 files changed

+375
-4
lines changed

hardware/rpi_server/src/mqtt/event_mapper.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ def map_event_to_mqtt(
267267
# action == "start" or "shot"
268268
mqtt_commands = cls.EVENT_MAP.get(key, [])
269269

270-
if not mqtt_commands:
270+
# キャプションイベントの場合は警告を出さない(MQTTコマンドなし)
271+
if not mqtt_commands and action != "caption":
271272
logger.warning(
272273
f"未マップイベント: effect={effect}, mode={mode}, action={action}"
273274
)

hardware/rpi_server/src/server/app.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,19 @@ def __init__(
2424
timeline_processor=None,
2525
mqtt_client=None
2626
):
27-
self.app = Flask(__name__, template_folder='../../templates', static_folder='../../static')
27+
# 絶対パスでtemplates/staticディレクトリを指定
28+
base_dir = Path(__file__).resolve().parent.parent.parent
29+
template_dir = base_dir / 'templates'
30+
static_dir = base_dir / 'static'
31+
32+
logger.info(f"Flask template_folder: {template_dir}")
33+
logger.info(f"Flask static_folder: {static_dir}")
34+
35+
self.app = Flask(
36+
__name__,
37+
template_folder=str(template_dir),
38+
static_folder=str(static_dir)
39+
)
2840
CORS(self.app)
2941

3042
self.device_manager = device_manager

hardware/rpi_server/src/timeline/processor.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,26 @@ def update_current_time(self, current_time: float) -> None:
8383
Args:
8484
current_time: 現在時刻(秒)
8585
"""
86+
# シーク検出(時刻が1秒以上後退した場合、または大きく前進した場合)
87+
time_diff = current_time - self.current_time
88+
89+
if time_diff < -1.0:
90+
# 巻き戻し
91+
logger.info(
92+
f"⏪ シーク検出(巻き戻し): {self.current_time:.2f}s → {current_time:.2f}s, "
93+
f"クールダウンをリセット"
94+
)
95+
self.effect_cooldowns.clear()
96+
# 巻き戻した先のイベントを再実行できるようにlast_processed_timeもリセット
97+
self.last_processed_time = current_time - 1.0
98+
elif time_diff > 5.0:
99+
# 大きく前進(シーク先送り)
100+
logger.info(
101+
f"⏩ シーク検出(先送り): {self.current_time:.2f}s → {current_time:.2f}s, "
102+
f"クールダウンをリセット"
103+
)
104+
self.effect_cooldowns.clear()
105+
86106
self.current_time = current_time
87107

88108
if not self.is_playing:
@@ -164,9 +184,22 @@ def _execute_event(self, event: Dict) -> None:
164184
"""
165185
try:
166186
event_time = event.get("t", 0)
167-
effect = event.get("effect", "unknown")
187+
effect = event.get("effect", "")
168188
mode = event.get("mode", "")
169189
action = event.get("action", "start")
190+
caption_text = event.get("text", "")
191+
192+
# キャプションイベントの場合は専用ログ出力
193+
if action == "caption":
194+
logger.info(
195+
f"💬 キャプション: t={event_time}, text=\"{caption_text}\""
196+
)
197+
# キャプションはMQTTコマンドに変換しないのでここで終了
198+
return
199+
200+
# effectがない場合はunknownとする
201+
if not effect:
202+
effect = "unknown"
170203

171204
# クールダウンチェック
172205
if effect in self.cooldown_durations:
@@ -179,7 +212,16 @@ def _execute_event(self, event: Dict) -> None:
179212
last_executed_time = self.effect_cooldowns.get(effect, -999.0)
180213
time_since_last = self.current_time - last_executed_time
181214

182-
if time_since_last < cooldown_duration:
215+
# シーク等で時刻が巻き戻った場合はクールダウンをリセット
216+
if time_since_last < 0:
217+
logger.debug(
218+
f"⏪ 時刻巻き戻り検出: effect={effect}, "
219+
f"last={last_executed_time:.2f}s → current={self.current_time:.2f}s, "
220+
f"クールダウンリセット"
221+
)
222+
# クールダウンを解除(次の実行を許可)
223+
del self.effect_cooldowns[effect]
224+
elif time_since_last < cooldown_duration:
183225
remaining = cooldown_duration - time_since_last
184226
logger.info(
185227
f"⏸️ イベントスキップ(クールダウン中): t={event_time}, effect={effect}, "
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""
2+
キャプションイベントのログ出力テスト
3+
"""
4+
5+
import sys
6+
import logging
7+
from pathlib import Path
8+
9+
# プロジェクトルートをパスに追加
10+
sys.path.insert(0, str(Path(__file__).parent))
11+
12+
from src.timeline.processor import TimelineProcessor
13+
from src.mqtt.event_mapper import EventToMQTTMapper
14+
15+
# ログ設定
16+
logging.basicConfig(
17+
level=logging.INFO,
18+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
19+
)
20+
21+
22+
def test_caption_event_logging():
23+
"""キャプションイベントのログ出力テスト"""
24+
print("=" * 70)
25+
print("キャプションイベントのログ出力テスト")
26+
print("=" * 70)
27+
28+
executed_events = []
29+
30+
def callback(event):
31+
executed_events.append(event)
32+
33+
processor = TimelineProcessor(on_event_callback=callback)
34+
35+
# タイムラインロード(キャプション + 水しぶき)
36+
processor.load_timeline({
37+
"events": [
38+
{
39+
"t": 0,
40+
"action": "caption",
41+
"text": "オープニングシーン。静寂の中、物語が始まる。"
42+
},
43+
{
44+
"t": 5.0,
45+
"effect": "vibration",
46+
"mode": "down_weak",
47+
"action": "start"
48+
},
49+
{
50+
"t": 10.0,
51+
"action": "caption",
52+
"text": "カメラが川面を捉える。穏やかな水の流れ。"
53+
},
54+
{
55+
"t": 15.0,
56+
"effect": "water",
57+
"mode": "burst",
58+
"action": "shot"
59+
},
60+
{
61+
"t": 36.0,
62+
"action": "caption",
63+
"text": "球体が川面に激突し、巨大な水しぶきを上げる。激しい衝撃音が聞こえてきそうだ。"
64+
},
65+
{
66+
"t": 36.0,
67+
"effect": "water",
68+
"mode": "burst",
69+
"action": "shot"
70+
}
71+
]
72+
})
73+
74+
processor.start_playback()
75+
76+
print("\n--- 時刻0秒: キャプションイベント ---")
77+
processor.update_current_time(0.0)
78+
79+
print("\n--- 時刻5秒: 振動イベント ---")
80+
processor.update_current_time(5.0)
81+
82+
print("\n--- 時刻10秒: キャプションイベント ---")
83+
processor.update_current_time(10.0)
84+
85+
print("\n--- 時刻15秒: 水しぶきイベント ---")
86+
processor.update_current_time(15.0)
87+
88+
print("\n--- 時刻36秒: キャプション + 水しぶきイベント ---")
89+
processor.update_current_time(36.0)
90+
91+
# キャプションはコールバックされない(MQTTコマンドなし)
92+
print(f"\n実行されたイベント数: {len(executed_events)}")
93+
print("期待値: 3イベント(振動1 + 水2)")
94+
95+
assert len(executed_events) == 3, f"Expected 3 events, got {len(executed_events)}"
96+
print("✅ キャプションはコールバックされず、エフェクトのみ実行される")
97+
98+
print("\n✅ テスト合格\n")
99+
100+
101+
def test_caption_event_mapper():
102+
"""キャプションイベントのマッピングテスト(警告が出ないこと)"""
103+
print("=" * 70)
104+
print("キャプションイベントのマッピングテスト")
105+
print("=" * 70)
106+
107+
# キャプションイベント
108+
caption_event = {
109+
"t": 10,
110+
"action": "caption",
111+
"text": "テストキャプション"
112+
}
113+
114+
print("\n--- キャプションイベントを処理 ---")
115+
mqtt_commands = EventToMQTTMapper.process_timeline_event(caption_event)
116+
117+
print(f"MQTTコマンド数: {len(mqtt_commands)}")
118+
print("期待値: 0個(キャプションはMQTTコマンドなし)")
119+
120+
assert len(mqtt_commands) == 0, f"Expected 0 MQTT commands, got {len(mqtt_commands)}"
121+
print("✅ キャプションは警告なくスキップされる")
122+
123+
print("\n✅ テスト合格\n")
124+
125+
126+
if __name__ == "__main__":
127+
try:
128+
test_caption_event_logging()
129+
test_caption_event_mapper()
130+
131+
print("=" * 70)
132+
print("🎉 全テスト合格!キャプション表示が改善されました")
133+
print("=" * 70)
134+
print("\n期待されるログ出力:")
135+
print("💬 キャプション: t=0, text=\"オープニングシーン。静寂の中、物語が始まる。\"")
136+
print("💬 キャプション: t=10, text=\"カメラが川面を捉える。穏やかな水の流れ。\"")
137+
print("💬 キャプション: t=36, text=\"球体が川面に激突し、巨大な水しぶきを上げる。激しい衝撃音が聞こえてきそうだ。\"")
138+
139+
except AssertionError as e:
140+
print(f"\n❌ テスト失敗: {e}")
141+
sys.exit(1)
142+
except Exception as e:
143+
print(f"\n❌ エラー: {e}")
144+
import traceback
145+
traceback.print_exc()
146+
sys.exit(1)

0 commit comments

Comments
 (0)