meltygui 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import subprocess
|
|
3
|
+
import tempfile
|
|
4
|
+
|
|
5
|
+
import OpenGL.GL as gl
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FramebufferRecorder:
|
|
10
|
+
def __init__(self, width, height, fps=30, codec='h264', bitrate='20M', gpu_encoding=True):
|
|
11
|
+
"""
|
|
12
|
+
Initialize recorder for OpenGL framebuffers
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
width (int): Width of the framebuffer
|
|
16
|
+
height (int): Height of the framebuffer
|
|
17
|
+
fps (int): Frames per second for recording
|
|
18
|
+
codec (str): Codec to use for video ('h264', 'hevc', etc.)
|
|
19
|
+
bitrate (str): Target bitrate for video
|
|
20
|
+
gpu_encoding (bool): Whether to use GPU acceleration if available
|
|
21
|
+
"""
|
|
22
|
+
self.width = width
|
|
23
|
+
self.height = height
|
|
24
|
+
self.fps = fps
|
|
25
|
+
self.codec = codec
|
|
26
|
+
self.bitrate = bitrate
|
|
27
|
+
self.gpu_encoding = gpu_encoding
|
|
28
|
+
|
|
29
|
+
# Configuration
|
|
30
|
+
self.recording = False
|
|
31
|
+
self.frames = []
|
|
32
|
+
self.frame_count = 0
|
|
33
|
+
|
|
34
|
+
# Create a pixel buffer object (PBO) for faster GPU->CPU transfers
|
|
35
|
+
self.pbo = gl.glGenBuffers(1)
|
|
36
|
+
gl.glBindBuffer(gl.GL_PIXEL_PACK_BUFFER, self.pbo)
|
|
37
|
+
gl.glBufferData(gl.GL_PIXEL_PACK_BUFFER, width * height * 4, None, gl.GL_STREAM_READ)
|
|
38
|
+
gl.glBindBuffer(gl.GL_PIXEL_PACK_BUFFER, 0)
|
|
39
|
+
|
|
40
|
+
def start_recording(self):
|
|
41
|
+
"""Start recording frames"""
|
|
42
|
+
if self.recording:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
self.recording = True
|
|
46
|
+
self.frames = []
|
|
47
|
+
self.frame_count = 0
|
|
48
|
+
print("Recording started...")
|
|
49
|
+
|
|
50
|
+
def stop_recording(self):
|
|
51
|
+
"""Stop recording"""
|
|
52
|
+
if not self.recording:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
self.recording = False
|
|
56
|
+
print(f"Recording stopped. Captured {self.frame_count} frames.")
|
|
57
|
+
|
|
58
|
+
def capture_frame(self, framebuffer=0, read_buffer=gl.GL_BACK):
|
|
59
|
+
"""
|
|
60
|
+
Capture a frame from the specified framebuffer
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
framebuffer (int): OpenGL framebuffer object ID (0 for default framebuffer)
|
|
64
|
+
read_buffer (GL enum): Buffer to read from (GL_BACK, GL_COLOR_ATTACHMENT0, etc.)
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
bool: True if frame was captured successfully
|
|
68
|
+
"""
|
|
69
|
+
if not self.recording:
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
# Bind the framebuffer we want to read from
|
|
73
|
+
current_fbo = gl.glGetIntegerv(gl.GL_READ_FRAMEBUFFER_BINDING)
|
|
74
|
+
gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, framebuffer)
|
|
75
|
+
|
|
76
|
+
# Set the read buffer
|
|
77
|
+
if framebuffer == 0: # Default framebuffer
|
|
78
|
+
gl.glReadBuffer(read_buffer)
|
|
79
|
+
else: # Custom framebuffer
|
|
80
|
+
gl.glReadBuffer(read_buffer)
|
|
81
|
+
|
|
82
|
+
# Use PBO for faster transfers
|
|
83
|
+
gl.glBindBuffer(gl.GL_PIXEL_PACK_BUFFER, self.pbo)
|
|
84
|
+
|
|
85
|
+
# Read pixels into the PBO (this is asynchronous)
|
|
86
|
+
gl.glReadPixels(0, 0, self.width, self.height, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
|
|
87
|
+
|
|
88
|
+
# Map the PBO and get a pointer to the data
|
|
89
|
+
ptr = gl.glMapBuffer(gl.GL_PIXEL_PACK_BUFFER, gl.GL_READ_ONLY)
|
|
90
|
+
|
|
91
|
+
result = False
|
|
92
|
+
if ptr:
|
|
93
|
+
# Copy data from the mapped buffer
|
|
94
|
+
data = np.ctypeslib.as_array(ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte)),
|
|
95
|
+
shape=(self.height, self.width, 4))
|
|
96
|
+
# We need to copy because the PBO will be unmapped
|
|
97
|
+
frame_copy = np.copy(data)
|
|
98
|
+
# Flip the image vertically (OpenGL has origin at bottom-left)
|
|
99
|
+
frame_copy = np.flipud(frame_copy)
|
|
100
|
+
|
|
101
|
+
# Unmap the buffer
|
|
102
|
+
gl.glUnmapBuffer(gl.GL_PIXEL_PACK_BUFFER)
|
|
103
|
+
|
|
104
|
+
# Store the frame (removing alpha channel)
|
|
105
|
+
self.frames.append(frame_copy[:, :, :3])
|
|
106
|
+
self.frame_count += 1
|
|
107
|
+
result = True
|
|
108
|
+
|
|
109
|
+
# Unbind the PBO
|
|
110
|
+
gl.glBindBuffer(gl.GL_PIXEL_PACK_BUFFER, 0)
|
|
111
|
+
|
|
112
|
+
# Restore the original framebuffer
|
|
113
|
+
gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, current_fbo)
|
|
114
|
+
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
def save_video(self, output_file="output.mp4"):
|
|
118
|
+
"""
|
|
119
|
+
Save the captured frames as a video file using FFmpeg
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
output_file (str): Path to the output video file
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
bool: True if video was saved successfully
|
|
126
|
+
"""
|
|
127
|
+
if not self.frames:
|
|
128
|
+
print("No frames to save")
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
print(f"Saving video to {output_file}...")
|
|
132
|
+
|
|
133
|
+
if self.gpu_encoding and self._is_gpu_encoding_available():
|
|
134
|
+
return self._save_video_gpu(output_file)
|
|
135
|
+
else:
|
|
136
|
+
return self._save_video_cpu(output_file)
|
|
137
|
+
|
|
138
|
+
def _save_video_cpu(self, output_file):
|
|
139
|
+
"""Save video using CPU-based encoding with FFmpeg"""
|
|
140
|
+
# Create a temporary directory for the output
|
|
141
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
142
|
+
# Set up FFmpeg command
|
|
143
|
+
ffmpeg_cmd = [
|
|
144
|
+
'ffmpeg',
|
|
145
|
+
'-y', # Overwrite output file if it exists
|
|
146
|
+
'-f', 'rawvideo',
|
|
147
|
+
'-vcodec', 'rawvideo',
|
|
148
|
+
'-s', f'{self.width}x{self.height}',
|
|
149
|
+
'-pix_fmt', 'rgb24',
|
|
150
|
+
'-r', str(self.fps),
|
|
151
|
+
'-i', '-', # Read from stdin
|
|
152
|
+
'-c:v', self.codec,
|
|
153
|
+
'-b:v', self.bitrate,
|
|
154
|
+
'-pix_fmt', 'yuv420p', # Standard pixel format for compatibility
|
|
155
|
+
output_file
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
# Start FFmpeg process
|
|
159
|
+
process = subprocess.Popen(
|
|
160
|
+
ffmpeg_cmd,
|
|
161
|
+
stdin=subprocess.PIPE,
|
|
162
|
+
stdout=subprocess.PIPE,
|
|
163
|
+
stderr=subprocess.PIPE
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Feed frames to FFmpeg
|
|
167
|
+
for frame in self.frames:
|
|
168
|
+
process.stdin.write(frame.tobytes())
|
|
169
|
+
|
|
170
|
+
# Close stdin and wait for FFmpeg to finish
|
|
171
|
+
process.stdin.close()
|
|
172
|
+
process.wait()
|
|
173
|
+
|
|
174
|
+
if process.returncode != 0:
|
|
175
|
+
stderr = process.stderr.read()
|
|
176
|
+
print(f"FFmpeg error: {stderr.decode()}")
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
print(f"Video saved successfully to {output_file}")
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
def _save_video_gpu(self, output_file):
|
|
183
|
+
"""Save video using GPU-based encoding with FFmpeg (if available)"""
|
|
184
|
+
# Determine which GPU encoder to use
|
|
185
|
+
encoder = self._get_gpu_encoder()
|
|
186
|
+
if not encoder:
|
|
187
|
+
print("No suitable GPU encoder found, falling back to CPU encoding")
|
|
188
|
+
return self._save_video_cpu(output_file)
|
|
189
|
+
|
|
190
|
+
# Set up FFmpeg command with GPU acceleration
|
|
191
|
+
ffmpeg_cmd = [
|
|
192
|
+
'ffmpeg',
|
|
193
|
+
'-y',
|
|
194
|
+
'-f', 'rawvideo',
|
|
195
|
+
'-vcodec', 'rawvideo',
|
|
196
|
+
'-s', f'{self.width}x{self.height}',
|
|
197
|
+
'-pix_fmt', 'rgb24',
|
|
198
|
+
'-r', str(self.fps),
|
|
199
|
+
'-i', '-',
|
|
200
|
+
'-c:v', encoder,
|
|
201
|
+
'-b:v', self.bitrate,
|
|
202
|
+
'-preset', 'fast',
|
|
203
|
+
output_file
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
# Additional options for specific encoders
|
|
207
|
+
if 'nvenc' in encoder:
|
|
208
|
+
ffmpeg_cmd.insert(-1, '-gpu', '0')
|
|
209
|
+
elif 'qsv' in encoder:
|
|
210
|
+
ffmpeg_cmd.insert(-1, '-hwaccel', 'qsv')
|
|
211
|
+
elif 'amf' in encoder:
|
|
212
|
+
ffmpeg_cmd.insert(-1, '-hwaccel', 'amf')
|
|
213
|
+
|
|
214
|
+
# Start FFmpeg process
|
|
215
|
+
process = subprocess.Popen(
|
|
216
|
+
ffmpeg_cmd,
|
|
217
|
+
stdin=subprocess.PIPE,
|
|
218
|
+
stdout=subprocess.PIPE,
|
|
219
|
+
stderr=subprocess.PIPE
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Feed frames to FFmpeg
|
|
223
|
+
for frame in self.frames:
|
|
224
|
+
process.stdin.write(frame.tobytes())
|
|
225
|
+
|
|
226
|
+
# Close stdin and wait for FFmpeg to finish
|
|
227
|
+
process.stdin.close()
|
|
228
|
+
process.wait()
|
|
229
|
+
|
|
230
|
+
if process.returncode != 0:
|
|
231
|
+
stderr = process.stderr.read()
|
|
232
|
+
print(f"FFmpeg error: {stderr.decode()}")
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
print(f"Video saved successfully to {output_file} using GPU acceleration")
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
def _is_gpu_encoding_available(self):
|
|
239
|
+
"""Check if GPU encoding is available"""
|
|
240
|
+
try:
|
|
241
|
+
# Run FFmpeg to get available encoders
|
|
242
|
+
result = subprocess.run(
|
|
243
|
+
['ffmpeg', '-encoders'],
|
|
244
|
+
stdout=subprocess.PIPE,
|
|
245
|
+
stderr=subprocess.PIPE,
|
|
246
|
+
text=True
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Check for GPU encoders
|
|
250
|
+
output = result.stdout
|
|
251
|
+
return any(encoder in output for encoder in ['nvenc', 'qsv', 'amf', 'videotoolbox'])
|
|
252
|
+
except Exception as e:
|
|
253
|
+
print(f"Error checking GPU encoders: {e}")
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
def _get_gpu_encoder(self):
|
|
257
|
+
"""Get the appropriate GPU encoder based on the system"""
|
|
258
|
+
try:
|
|
259
|
+
# Run FFmpeg to get available encoders
|
|
260
|
+
result = subprocess.run(
|
|
261
|
+
['ffmpeg', '-encoders'],
|
|
262
|
+
stdout=subprocess.PIPE,
|
|
263
|
+
stderr=subprocess.PIPE,
|
|
264
|
+
text=True
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
output = result.stdout
|
|
268
|
+
|
|
269
|
+
# Check for NVIDIA encoders (NVENC)
|
|
270
|
+
if 'h264_nvenc' in output:
|
|
271
|
+
return 'h264_nvenc'
|
|
272
|
+
elif 'hevc_nvenc' in output:
|
|
273
|
+
return 'hevc_nvenc'
|
|
274
|
+
|
|
275
|
+
# Check for Intel QuickSync
|
|
276
|
+
if 'h264_qsv' in output:
|
|
277
|
+
return 'h264_qsv'
|
|
278
|
+
elif 'hevc_qsv' in output:
|
|
279
|
+
return 'hevc_qsv'
|
|
280
|
+
|
|
281
|
+
# Check for AMD encoders
|
|
282
|
+
if 'h264_amf' in output:
|
|
283
|
+
return 'h264_amf'
|
|
284
|
+
elif 'hevc_amf' in output:
|
|
285
|
+
return 'hevc_amf'
|
|
286
|
+
|
|
287
|
+
# Check for Apple VideoToolbox (macOS)
|
|
288
|
+
if 'h264_videotoolbox' in output:
|
|
289
|
+
return 'h264_videotoolbox'
|
|
290
|
+
|
|
291
|
+
return None
|
|
292
|
+
except Exception as e:
|
|
293
|
+
print(f"Error determining GPU encoder: {e}")
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
def clear_frames(self):
|
|
297
|
+
"""Clear all captured frames without saving"""
|
|
298
|
+
self.frames = []
|
|
299
|
+
self.frame_count = 0
|
|
300
|
+
print("Frames cleared.")
|
|
301
|
+
|
|
302
|
+
def get_frame_count(self):
|
|
303
|
+
"""Get the number of frames captured"""
|
|
304
|
+
return self.frame_count
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# Example usage:
|
|
308
|
+
"""
|
|
309
|
+
# Initialize the recorder (typically in your setup code)
|
|
310
|
+
recorder = FramebufferRecorder(width=1920, height=1080, fps=30)
|
|
311
|
+
|
|
312
|
+
# Start recording
|
|
313
|
+
recorder.start_recording()
|
|
314
|
+
|
|
315
|
+
# In your render loop:
|
|
316
|
+
def render():
|
|
317
|
+
# Render to your custom framebuffer
|
|
318
|
+
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, my_fbo)
|
|
319
|
+
# ... render your scene ...
|
|
320
|
+
|
|
321
|
+
# Capture the frame from your custom framebuffer
|
|
322
|
+
if recorder.recording:
|
|
323
|
+
recorder.capture_frame(
|
|
324
|
+
framebuffer=my_fbo,
|
|
325
|
+
read_buffer=gl.GL_COLOR_ATTACHMENT0
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Continue with your rendering...
|
|
329
|
+
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
|
|
330
|
+
# ... render other things ...
|
|
331
|
+
|
|
332
|
+
# When done recording:
|
|
333
|
+
recorder.stop_recording()
|
|
334
|
+
|
|
335
|
+
# Save the video at any point after stopping recording
|
|
336
|
+
recorder.save_video("my_video.mp4")
|
|
337
|
+
"""
|