From fff635f5c20755e85fbbfb195fa2c1b4bdf3bd1c Mon Sep 17 00:00:00 2001 From: Roz K Date: Tue, 21 Oct 2025 11:08:13 +0200 Subject: [PATCH] pve, pygl --- pve.py | 53 ------------- pve/__init__.py | 15 ++++ pve/mesh.py | 61 +++++++++++++++ pve/pixmap.py | 131 ++++++++++++++++++++++++++++++++ pve/shape.py | 164 ++++++++++++++++++++++++++++++++++++++++ pve_editor.py | 58 ++++++++++++++ pve.sh => pve_editor.sh | 2 +- pygl/__init__.py | 15 ++++ pygl/libgl/__init__.py | 41 ++++++++++ pygl/texture.py | 66 ++++++++++++++++ pygl/vertexbuffer.py | 35 +++++++++ 11 files changed, 587 insertions(+), 54 deletions(-) delete mode 100644 pve.py create mode 100644 pve/__init__.py create mode 100644 pve/mesh.py create mode 100644 pve/pixmap.py create mode 100644 pve/shape.py create mode 100644 pve_editor.py rename pve.sh => pve_editor.sh (97%) create mode 100644 pygl/__init__.py create mode 100644 pygl/libgl/__init__.py create mode 100644 pygl/texture.py create mode 100644 pygl/vertexbuffer.py diff --git a/pve.py b/pve.py deleted file mode 100644 index c24f740..0000000 --- a/pve.py +++ /dev/null @@ -1,53 +0,0 @@ -# People's Video Editor: high quality, GPU accelerated mp4 editor -# Copyright (C) 2025 Roz K -# -# This file is part of People's Video Editor. -# -# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the -# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, -# or (at your option) any later version. -# -# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with People's Video Editor. -# If not, see . - -import multiprocessing -import cProfile - -from pyav.demuxer import Demuxer -from pyav.decoder import Decoder - -demuxer = Demuxer('test.mp4') - -print(f"nb_streams = {demuxer.nb_streams}") -print(f"video codec = {demuxer.video_stream.codec.description()}") -print(f"audio codec = {demuxer.audio_stream.codec.description()}") - -cpu_count = multiprocessing.cpu_count() - -print(f"using {cpu_count} threads for video decoding") - -video_decoder = Decoder(demuxer.video_stream, cpu_count) -audio_decoder = Decoder(demuxer.audio_stream, 1) - -num_video_frames = 0 -num_audio_frames = 0 - -with cProfile.Profile() as pr: - while True: - packet = demuxer.read_packet() - eof = (packet is None) - if eof or demuxer.video_stream.contains(packet): - video_frames = video_decoder.decode(packet) - num_video_frames += len(video_frames) - if eof or demuxer.audio_stream.contains(packet): - audio_frames = audio_decoder.decode(packet) - num_audio_frames += len(audio_frames) - if eof: - break - -print(f"num video frames: {num_video_frames}, audio frames: {num_audio_frames}") -pr.print_stats() diff --git a/pve/__init__.py b/pve/__init__.py new file mode 100644 index 0000000..5b765b7 --- /dev/null +++ b/pve/__init__.py @@ -0,0 +1,15 @@ +# People's Video Editor: high quality, GPU accelerated mp4 editor +# Copyright (C) 2025 Roz K +# +# This file is part of People's Video Editor. +# +# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. +# +# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with People's Video Editor. +# If not, see . diff --git a/pve/mesh.py b/pve/mesh.py new file mode 100644 index 0000000..fa90eba --- /dev/null +++ b/pve/mesh.py @@ -0,0 +1,61 @@ +# People's Video Editor: high quality, GPU accelerated mp4 editor +# Copyright (C) 2025 Roz K +# +# This file is part of People's Video Editor. +# +# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. +# +# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with People's Video Editor. +# If not, see . + +#TODO: split channels + +from shape import Vertex, Shape + +from array import array + +class Mesh: + __slots__ = '_array', '_view', '_size' + + def __init__(self, shape): + #TODO: cpp + if shape.cutouts: + raise NotImplementedError + if shape.size > 4: + raise NotImplementedError + + self._array = array(Vertex.Type, 2 * 3 * Vertex.Channels) + self._view = memoryview(self._array) + self._size = self.view.nbytes + tl = shape[0] + tr = shape[1] + br = shape[2] + bl = shape[3] + self.set(0, 0, tl) + self.set(0, 1, tr) + self.set(0, 2, br) + self.set(1, 0, br) + self.set(1, 1, bl) + self.set(1, 2, tl) + + def __del__(self): + del self._view + del self._array + + def set(self, triangle, index, vertex): + index = (triangle * 3 + index) * Vertex.Channels + self._view[index:(index + Vertex.Channels)] = vertex + + @property + def size(self): + return self._size + + @property + def view(self): + return self._view diff --git a/pve/pixmap.py b/pve/pixmap.py new file mode 100644 index 0000000..82000eb --- /dev/null +++ b/pve/pixmap.py @@ -0,0 +1,131 @@ +# People's Video Editor: high quality, GPU accelerated mp4 editor +# Copyright (C) 2025 Roz K +# +# This file is part of People's Video Editor. +# +# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. +# +# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with People's Video Editor. +# If not, see . + +from fractions import Fraction + +class Geometry: + __slots__ = '_width', '_height', '_ratio', '_size' + + def __init__(self, width = 0, height = 0, ratio = None): + if width > 0 and height > 0: + assert ratio is None + self._width = width + self._height = height + self._ratio = Fraction(width, height).limit_denominator() + self._size = width * height + elif width > 0 and ratio: + self._width = width + self._height = round((width * ratio.numerator) / ratio.denominator) + self._ratio = ratio + self._size = width * self._height + elif height > 0 and ratio: + self._width = round((height * ratio.denominator) / ratio.numerator) + self._height = height + self._ratio = ratio + self._size = self._width * height + else: + raise ValueError + + def __ge__(self, other): + return (self._width >= other._width and self._height >= other._height) + + @property + def width(self): + return self._width + + @property + def height(self): + return self._height + + @property + def ratio(self): + return self._ratio + +class Format: + __slots__ = '_channels', '_depth', '_type', '_size' + + def __init__(self, channels, depth): + self._channels = channels + self._depth = depth + if depth is 8: + self._type = 'B' + self._size = channels + elif depth is 16: + self._type = 'H' + self._size = channels * 2 + else: + raise ValueError + + def __eq__(self, other): + return (self._channels == other._channels and self._depth == other._depth) + + @property + def channels(self): + return self._channels + + @property + def depth(self): + return self._depth + +class Pixmap: + __slots__ = '_geometry', '_format', '_line_size', '_bytes', '_view' + + def __init__(self, geometry, format): + self._geometry = geometry + self._format = format + self._line_size = geometry._width * format._size + self._bytes = bytearray(geometry._size * format._size) + self._view = memoryview(self._bytes) + + def __del__(self): + del self._view + del self._bytes + + @property + def geometry(self): + return self._geometry + + @property + def width(self): + return self._geometry._width + + @property + def height(self): + return self._geometry._height + + @property + def ratio(self): + return self._geometry._ratio + + @property + def format(self): + return self._format + + @property + def channels(self): + return self._format._channels + + @property + def depth(self): + return self._format._depth + + @property + def line_size(self): + return self._line_size + + @property + def view(self): + return self._view diff --git a/pve/shape.py b/pve/shape.py new file mode 100644 index 0000000..2a23895 --- /dev/null +++ b/pve/shape.py @@ -0,0 +1,164 @@ +# People's Video Editor: high quality, GPU accelerated mp4 editor +# Copyright (C) 2025 Roz K +# +# This file is part of People's Video Editor. +# +# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. +# +# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with People's Video Editor. +# If not, see . + +#TODO: split channels + +import itertools +from array import array + +_X, _Y, _U, _V, _Channels = itertools.count(0) +_Type = 'f' +_Size = array(_Type).itemsize * _Channels + +class Vertex: + __slots__ = 'view' + + Type = _Type + Channels = _Channels + Size = _Size + + def __init__(self, view): + assert isinstance(view, memoryview) + self.view = view + + def __del__(self): + del self._view + + @property + def xy(self): + return self.view[_X], self.view[_Y] + + @xy.setter + def xy(self, value): + self.view[_X], self.view[_Y] = value + + @property + def x(self): + return self.view[_X] + + @x.setter + def x(self, value): + self.view[_X] = value + + @property + def y(self): + return self.view[_Y] + + @y.setter + def y(self, value): + self.view[_Y] = value + + @property + def uv(self): + return self.view[_U], self.view[_V] + + @uv.setter + def uv(self, value): + self.view[_U], self.view[_V] = value + + @property + def u(self): + return self.view[_U] + + @u.setter + def u(self, value): + self.view[_U] = value + + @property + def v(self): + return self.view[_V] + + @v.setter + def v(self, value): + self.view[_V] = value + +class Shape: + __slots__ = '_array', '_view', '_size', '_cutouts' + + @classmethod + def rectangle(cls, geometry): + shape = cls(size = 4) + tl = Vertex(shape[0]) + tl.xy = tl.uv = (0.0, 0.0) + tr = Vertex(shape[1]) + tr.xy = tr.uv = (geometry._width, 0.0) + br = Vertex(shape[2]) + br.xy = br.uv = (geometry._width, geometry._height) + bl = Vertex(shape[3]) + bl.xy = bl.uv = (0.0, geometry._height) + return shape + + def __init__(self, size = 0, cutouts = None): + self._array = array(_Type, bytes(size * _Size)) + self._view = memoryview(self._array) + self._size = size + self._cutouts = list(cutouts) if cutouts else None + + def __del__(self): + del self._view + del self._array + del self._cutouts + + def __len__(self): + return self._size + + def __getitem__(self, index): + index *= _Channels + return self._view[index:(index + _Channels)] + + @property + def size(self): + return self._size + + @property + def view(self): + return self._view + + @property + def cutouts(self): + return list(self._cutouts) if self._cutouts else [] + + @cutouts.setter + def cutouts(self, value): + self._cutouts = list(value) if value else None + + def append(self, count = 1): + index = self._size + self._view.release() + self._array.frombytes(bytes(count * _Size)) + self._view = memoryview(self._array) + self._size += count + return index + + def insert(self, index, count = 1): + index *= _Channels + self._view.release() + self._array[index:index] = array(_Type, bytes(count * _Size)) + self._view = memoryview(self._array) + self._size += count + + def remove(self, index, count = 1): + index *= _Channels + self._view.release() + del self._array[index:(index + count * _Channels)] + self._view = memoryview(self._array) + self._size -= count + + def clear(self): + self._view.release() + self._array = array(_Type) + self._view = memoryview(self._array) + self._size = 0 diff --git a/pve_editor.py b/pve_editor.py new file mode 100644 index 0000000..ffc9bc0 --- /dev/null +++ b/pve_editor.py @@ -0,0 +1,58 @@ +# People's Video Editor: high quality, GPU accelerated mp4 editor +# Copyright (C) 2025 Roz K +# +# This file is part of People's Video Editor. +# +# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. +# +# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with People's Video Editor. +# If not, see . + +from pyui.application import Application + +application = Application() +application.MainLoop() + +# import multiprocessing +# import cProfile +# +# from pyav.demuxer import Demuxer +# from pyav.decoder import Decoder +# +# demuxer = Demuxer('test.mp4') +# +# print(f"nb_streams = {demuxer.nb_streams}") +# print(f"video codec = {demuxer.video_stream.codec.description()}") +# print(f"audio codec = {demuxer.audio_stream.codec.description()}") +# +# cpu_count = multiprocessing.cpu_count() +# +# print(f"using {cpu_count} threads for video decoding") +# +# video_decoder = Decoder(demuxer.video_stream, cpu_count) +# audio_decoder = Decoder(demuxer.audio_stream, 1) +# +# num_video_frames = 0 +# num_audio_frames = 0 +# +# with cProfile.Profile() as pr: +# while True: +# packet = demuxer.read_packet() +# eof = (packet is None) +# if eof or demuxer.video_stream.contains(packet): +# video_frames = video_decoder.decode(packet) +# num_video_frames += len(video_frames) +# if eof or demuxer.audio_stream.contains(packet): +# audio_frames = audio_decoder.decode(packet) +# num_audio_frames += len(audio_frames) +# if eof: +# break +# +# print(f"num video frames: {num_video_frames}, audio frames: {num_audio_frames}") +# pr.print_stats() diff --git a/pve.sh b/pve_editor.sh similarity index 97% rename from pve.sh rename to pve_editor.sh index 89e4086..e69d464 100755 --- a/pve.sh +++ b/pve_editor.sh @@ -19,4 +19,4 @@ shared_library_path="./pyav/libav" absolute_shared_library_path=$(realpath ${shared_library_path}) LD_LIBRARY_PATH=${absolute_shared_library_path} \ -python3 pve.py +python3 pve_editor.py diff --git a/pygl/__init__.py b/pygl/__init__.py new file mode 100644 index 0000000..5b765b7 --- /dev/null +++ b/pygl/__init__.py @@ -0,0 +1,15 @@ +# People's Video Editor: high quality, GPU accelerated mp4 editor +# Copyright (C) 2025 Roz K +# +# This file is part of People's Video Editor. +# +# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. +# +# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with People's Video Editor. +# If not, see . diff --git a/pygl/libgl/__init__.py b/pygl/libgl/__init__.py new file mode 100644 index 0000000..c4e7b50 --- /dev/null +++ b/pygl/libgl/__init__.py @@ -0,0 +1,41 @@ +# People's Video Editor: high quality, GPU accelerated mp4 editor +# Copyright (C) 2025 Roz K +# +# This file is part of People's Video Editor. +# +# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. +# +# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with People's Video Editor. +# If not, see . + +import ctypes + +Texture_p = ctypes.c_void_p +VertexBuffer_p = ctypes.c_void_p + +def create_texture(width, height, channels, depth): + return None + +def delete_texture(texture): + pass + +def upload_texture(texture, view, line_size): + pass + +def dowload_texture(texture, view, line_size): + pass + +def create_vertex_buffer(size): + return None + +def delete_vertex_buffer(buffer): + pass + +def upload_vertex_buffer(buffer, view, size): + pass diff --git a/pygl/texture.py b/pygl/texture.py new file mode 100644 index 0000000..6355667 --- /dev/null +++ b/pygl/texture.py @@ -0,0 +1,66 @@ +# People's Video Editor: high quality, GPU accelerated mp4 editor +# Copyright (C) 2025 Roz K +# +# This file is part of People's Video Editor. +# +# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. +# +# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with People's Video Editor. +# If not, see . + +import libgl + +class Texture: + __slots__ = '_geometry', '_format', '_texture' + + def __init__(self, geometry, format): + self._geometry = geometry + self._format = format + self._texture = libgl.create_texture(geometry.width, geometry.height, format.channels, format.depth) + + def __del__(self): + libgl.delete_texture(self._texture) + + @property + def geometry(self): + return self._geometry + + @property + def width(self): + return self._geometry._width + + @property + def height(self): + return self._geometry._height + + @property + def ratio(self): + return self._geometry._ratio + + @property + def format(self): + return self._format + + @property + def channels(self): + return self._format._channels + + @property + def depth(self): + return self._format._depth + + def upload(self, pixmap): + assert (self._format == pixmap.format) + assert (self._geometry >= pixmap.geometry) + return libgl.upload_texture(self._texture, pixmap.view, pixmap.line_size) + + def download(self, pixmap): + assert (self._format == pixmap.format) + assert (self._geometry >= pixmap.geometry) + return libgl.upload_texture(self._texture, pixmap.view, pixmap.line_size) diff --git a/pygl/vertexbuffer.py b/pygl/vertexbuffer.py new file mode 100644 index 0000000..c64e302 --- /dev/null +++ b/pygl/vertexbuffer.py @@ -0,0 +1,35 @@ +# People's Video Editor: high quality, GPU accelerated mp4 editor +# Copyright (C) 2025 Roz K +# +# This file is part of People's Video Editor. +# +# People's Video Editor is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. +# +# People's Video Editor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with People's Video Editor. +# If not, see . + +import libgl + +class VertexBuffer: + __slots__ = '_buffer', '_size' + + def __init__(self, size): + self._buffer = libgl.create_vertex_buffer(size) + self._size = size + + def __del__(self): + libgl.delete_vertex_buffer(self._buffer) + + @property + def size(self): + return self._size + + def upload(self, mesh): + assert (self._size >= mesh.size) + return libgl.upload_vertex_buffer(self._buffer, mesh.view, mesh.size)