(raw: str | None)
| 13 | |
| 14 | |
| 15 | def _parse_settings(raw: str | None) -> ConvertSettings: |
| 16 | if not raw: |
| 17 | return DEFAULT_SETTINGS |
| 18 | data = json.loads(raw) |
| 19 | if not isinstance(data, dict): |
| 20 | return DEFAULT_SETTINGS |
| 21 | |
| 22 | s = ConvertSettings() |
| 23 | for k, v in data.items(): |
| 24 | if not hasattr(s, k): |
| 25 | continue |
| 26 | setattr(s, k, v) |
| 27 | |
| 28 | # Clamp a few critical fields to safe ranges |
| 29 | s.sample_step = max(1, min(8, int(s.sample_step))) |
| 30 | s.target_w = max(1, min(240, int(s.target_w))) |
| 31 | s.target_h = max(1, min(240, int(s.target_h))) |
| 32 | s.offset_x = int(s.offset_x) |
| 33 | s.offset_y = int(s.offset_y) |
| 34 | s.ink_threshold = max(0, min(255, int(s.ink_threshold))) |
| 35 | s.max_points_per_frame = max(100, min(20000, int(s.max_points_per_frame))) |
| 36 | s.max_gif_frames = max(1, min(300, int(s.max_gif_frames))) |
| 37 | s.min_frame_ms = max(0, min(2000, int(s.min_frame_ms))) |
| 38 | s.frame_start = max(0, int(s.frame_start)) |
| 39 | s.frame_end = max(0, int(s.frame_end)) |
| 40 | s.frame_skip = max(1, min(60, int(s.frame_skip))) |
| 41 | return s |
| 42 | |
| 43 | |
| 44 | def build_tft_gif_router() -> APIRouter: |
no test coverage detected