diff --git a/common/video_recorder.py b/common/video_recorder.py new file mode 100644 index 0000000000000000000000000000000000000000..98b334077a1ca43fbb2578bf1799d2f2c9f3475c --- /dev/null +++ b/common/video_recorder.py @@ -0,0 +1,285 @@ +from ursina import Entity, time, application, Path +import os, shutil +import builtins +import numpy as np + + +class VideoRecorder(Entity): + def __init__(self, temp_dir="video_temp", file_name="test", asset_folder=None, save_as_dir=None): + self.temp_dir = temp_dir + self.file_name = file_name + self.save_as_dir = save_as_dir + if asset_folder is None: + asset_folder = application.asset_folder + # G:\works\gitcode\universe_sim\sim_scenes\science + self.file_path = Path(asset_folder) / self.temp_dir + self.duration = 1.0 + self.fps = 30 + self.sd = 6 + self.t = 0 + self.i = 0 + if getattr(builtins, 'base', None) is not None: + if self.file_path.exists(): + # os.rmdir(self.file_path) + shutil.rmtree(self.file_path) + + self.file_path.mkdir() + base.movie(namePrefix=f'\\{self.temp_dir}\\{self.file_name}', duration=self.duration, fps=self.fps, + format='png', + sd=self.sd) + + def screenshot(self): + self.t += time.dt + if self.t >= 1 / self.fps: + base.screenshot( + namePrefix=f'\\{self.temp_dir}\\{self.file_name}_' + str(self.i).zfill(self.sd) + '.png', + defaultFilename=0, + ) + self.t = 0 + + self.i += 1 + + def save_as_video(self): + import imageio + if not os.path.exists(self.file_path): + return + if self.save_as_dir is None: + self.save_as_dir = "." + + writer = imageio.get_writer(Path(f'{self.save_as_dir}/{self.file_name}.mp4'), fps=self.fps) + for filename in os.listdir(self.file_path): + im = imageio.imread(self.file_path / filename) + if len(im.shape) == 2: + # 可能为透明图片(无影像数据) + continue + writer.append_data(im) + writer.close() + print('Saved VIDEO to:', Path(f'{self.save_as_dir}/{self.file_name}.mp4')) + + def save_as_gif(self): + import imageio + images = [] + if not os.path.exists(self.file_path): + return + + if self.save_as_dir is None: + self.save_as_dir = self.file_path.parent + + for filename in os.listdir(self.file_path): + images.append(imageio.imread(self.file_path / filename)) + + imageio.mimsave(Path(f'{self.save_as_dir}/{self.file_name}.gif'), images) + # shutil.rmtree(self.file_path) # delete temp folder + print('Saved GIF to:', Path(f'{self.save_as_dir}/{self.file_name}.gif')) + + +if __name__ == '__main__': + # Path("G:\\works\\gitcode\\universe_sim\\sim_scenes\\science") + vr = VideoRecorder(asset_folder="G:\\works\\gitcode\\universe_sim\\sim_scenes\\science") + # vr.save_as_gif() + vr.save_as_video() + + +# from ursina import * +# import os, shutil +# import numpy as np +# +# # import imageio # gets imported in convert_to_gif +# # from panda3d.core import PNMImage +# # pip install -i http://pypi.douban.com/simple/ --trusted-host=pypi.douban.com imageio-ffmpeg +# import shutil +# +# +# class VideoRecorder(Entity): +# def __init__(self, duration=50, name='untitled_video', **kwargs): +# os.environ["PATH"] = os.environ["PATH"] + ";" + "F:\\Tools\\ffmpeg" +# super().__init__() +# self.recording = False +# self.file_path = Path(application.asset_folder) / 'video_temp' +# +# if os.path.exists(self.file_path): +# shutil.rmtree(self.file_path) +# +# os.mkdir(self.file_path) +# +# # if os.path.exists(self.file_path): +# # os.rmdir(self.file_path) +# # +# # os.mkdir(self.file_path) +# +# self.i = 0 +# self.duration = duration +# self.fps = 50 +# self.video_name = name +# self.t = 0 +# +# for key, value in kwargs.items(): +# setattr(self, key, value) +# +# self.max_frames = int(self.duration * self.fps) +# self.frames = [] +# +# def start_recording(self): +# print('start recording,', self.duration, self.file_path) +# window.fps_counter.enabled = False +# window.exit_button.visible = False +# self.frames = [] +# self.max_frames = self.duration * self.fps +# if not self.file_path.exists(): +# self.file_path.mkdir() +# base.movie(namePrefix=f'\\video_temp\\{self.video_name}', duration=self.duration, fps=self.fps, format='png', sd=4) +# +# self.recording = True +# invoke(self.stop_recording, delay=self.duration) +# +# def stop_recording(self): +# self.recording = False +# window.fps_counter.enabled = True +# window.exit_button.visible = True +# print('stop recording') +# # self.convert_to_gif() +# self.convert_to_vid() +# +# def update(self): +# if not self.recording: +# return +# +# self.t += time.dt +# if self.t >= 1 / 30: +# base.screenshot( +# namePrefix='\\video_temp\\' + self.video_name + '_' + str(self.i).zfill(4) + '.png', +# defaultFilename=0, +# ) +# self.t = 0 +# self.i += 1 +# +# def convert_to_gif(self): +# import imageio +# images = [] +# if not os.path.exists(self.file_path): +# return +# +# for filename in os.listdir(self.file_path): +# images.append(imageio.imread(self.file_path / filename)) +# +# imageio.mimsave(Path(f'{self.file_path.parent}/{self.video_name}.gif'), images) +# shutil.rmtree(self.file_path) # delete temp folder +# print('saved gif to:', Path(f'{self.file_path.parent}/{self.video_name}.gif')) +# +# def convert_to_vid(self): +# import imageio +# images = [] +# if not os.path.exists(self.file_path): +# return +# +# writer = imageio.get_writer('test.mp4', fps=self.fps) +# for file in os.listdir(self.file_path): +# im = imageio.imread(self.file_path / file) +# writer.append_data(im) +# writer.close() +# print('Video saved!!') +# +# +# class VideoRecorderUI(WindowPanel): +# def __init__(self, **kwargs): +# self.duration_label = Text('duration:') +# self.duration_field = InputField(default_value='5') +# self.fps_label = Text('fps:') +# self.fps_field = InputField(default_value='30') +# self.name_label = Text('name:') +# self.name_field = InputField(default_value='untitled_video') +# +# self.start_button = Button(text='Start Recording [Shift+F12]', color=color.azure, on_click=self.start_recording) +# +# super().__init__( +# title='Video Recorder [F12]', +# content=( +# self.duration_label, +# self.duration_field, +# self.fps_label, +# self.fps_field, +# self.name_label, +# self.name_field, +# Space(1), +# self.start_button, +# ), +# ) +# self.y = .5 +# self.scale *= .75 +# self.visible = False +# +# def input(self, key): +# if key == 'f12': +# self.visible = not self.visible +# +# if held_keys['shift'] and key == 'f12': +# self.start_button.on_click() +# +# def start_recording(self): +# print(self.name_field) +# if self.name_field.text == '': +# self.name_field.blink(color.color(0, 1, 1, .5), .5) +# print('enter name') +# return +# +# # self.start_button.color=color.lime +# self.visible = False +# application.video_recorder.duration = float(self.duration_field.text) +# application.video_recorder.video_name = self.name_field.text +# application.video_recorder.frame_skip = 60 // int(self.fps_field.text) +# application.video_recorder.recording = True +# +# +# if __name__ == '__main__': +# app = Ursina() +# # window.size = (1600/3,900/3) +# # cube = primitives.RedCube() +# # cube.animate_x(5, duration=5, curve=curve.linear) +# # cube.animate_x(0, duration=5, curve=curve.linear, delay=5) +# # vr = VideoRecorder() +# # invoke(setattr, vr, 'recording', True, delay=1) +# # invoke(os._exit, 0, delay=6) +# # vr.recording = True +# window.size *= .5 +# from ursina.prefabs.first_person_controller import FirstPersonController +# from ursina.shaders import lit_with_shadows_shader +# +# random.seed(0) +# Entity.default_shader = lit_with_shadows_shader +# +# ground = Entity(model='plane', collider='box', scale=64, texture='grass', texture_scale=(4, 4)) +# +# editor_camera = EditorCamera(enabled=False, ignore_paused=True) +# player = FirstPersonController(model='cube', z=-10, color=color.orange, origin_y=-.5, speed=8) +# player.collider = BoxCollider(player, Vec3(0, 1, 0), Vec3(1, 2, 1)) +# +# gun = Entity(model='cube', parent=camera, position=(.5, -.25, .25), scale=(.3, .2, 1), origin_z=-.5, +# color=color.red, on_cooldown=False) +# +# shootables_parent = Entity() +# mouse.traverse_target = shootables_parent +# +# for i in range(16): +# Entity(model='cube', origin_y=-.5, scale=2, texture='brick', texture_scale=(1, 2), +# x=random.uniform(-8, 8), +# z=random.uniform(-8, 8) + 8, +# collider='box', +# scale_y=random.uniform(2, 3), +# color=color.hsv(0, 0, random.uniform(.9, 1)) +# ) +# +# sun = DirectionalLight() +# sun.look_at(Vec3(1, -1, -1)) +# Sky() +# +# vr = VideoRecorder(duration=10) +# +# +# def input(key): +# if key == '5': +# vr.start_recording() +# if key == '6': +# vr.stop_recording() +# +# +# app.run() diff --git a/sim_scenes/func.py b/sim_scenes/func.py index 8566f19a1c6db252ed45df06aae9014996217e2a..7600024303a3aabe26fa388a4052a188b3b712b2 100644 --- a/sim_scenes/func.py +++ b/sim_scenes/func.py @@ -117,6 +117,7 @@ def ursina_run(bodies, show_timer=False, timer_enabled=False, save_as_json=None, + save_as_video=False, view_closely=False): """ ursina 模拟器运行天体 @@ -155,6 +156,10 @@ def ursina_run(bodies, view_azimuth = 0 # 暂时未用 player = UrsinaPlayer(position, view_azimuth, simulator.ursina_views) + if save_as_video: + from common.video_recorder import VideoRecorder + vr = VideoRecorder() + def callback_update(): UrsinaEvent.on_application_run() for ursina_view in simulator.ursina_views: @@ -162,6 +167,9 @@ def ursina_run(bodies, if ursina_view.appeared: ursina_view.update() + if save_as_video: + vr.screenshot() + import sys from simulators.ursina.ursina_config import UrsinaConfig from simulators.ursina.ursina_event import UrsinaEvent