kopia lustrzana https://github.com/jameshball/osci-render
Get that binary up in here
rodzic
ec3838ba5d
commit
73f7bf6f22
|
@ -23,8 +23,9 @@ TxtComponent::TxtComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessor
|
|||
audioProcessor.font.setBold(bold.getToggleState());
|
||||
audioProcessor.font.setItalic(italic.getToggleState());
|
||||
}
|
||||
|
||||
audioProcessor.openFile(audioProcessor.currentFile);
|
||||
if (audioProcessor.currentFileId > 0 && audioProcessor.parsers[audioProcessor.currentFileId - 1]->getText() != nullptr) {
|
||||
audioProcessor.openFile(audioProcessor.currentFile);
|
||||
}
|
||||
};
|
||||
|
||||
font.onChange = updateFont;
|
||||
|
|
|
@ -43,6 +43,7 @@ void ShapeVoice::startNote(int midiNoteNumber, float velocity, juce::Synthesiser
|
|||
|
||||
// TODO this is the slowest part of the program - any way to improve this would help!
|
||||
void ShapeVoice::incrementShapeDrawing() {
|
||||
if (frame.size() <= 0) return;
|
||||
double length = currentShape < frame.size() ? frame[currentShape]->len : 0.0;
|
||||
frameDrawn += lengthIncrement;
|
||||
shapeDrawn += lengthIncrement;
|
||||
|
|
|
@ -5,10 +5,209 @@ LineArtParser::LineArtParser(juce::String json) {
|
|||
parseJsonFrames(json);
|
||||
}
|
||||
|
||||
LineArtParser::LineArtParser(char* data, int dataLength) {
|
||||
parseBinaryFrames(data, dataLength);
|
||||
if (numFrames == 0) {
|
||||
parseJsonFrames(juce::String(BinaryData::fallback_gpla, BinaryData::fallback_gplaSize));
|
||||
}
|
||||
}
|
||||
|
||||
LineArtParser::~LineArtParser() {
|
||||
frames.clear();
|
||||
}
|
||||
|
||||
double LineArtParser::makeDouble(int64_t data) {
|
||||
return *(double*)&data;
|
||||
}
|
||||
|
||||
void LineArtParser::makeChars(int64_t data, char* chars) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
chars[i] = (data >> (i * 8)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void LineArtParser::parseBinaryFrames(char* bytes, int bytesLength) {
|
||||
frames.clear();
|
||||
numFrames = 0;
|
||||
int64_t* data = (int64_t*)bytes;
|
||||
int dataLength = bytesLength / 4;
|
||||
|
||||
int index = 0;
|
||||
int64_t rawData = data[index];
|
||||
index++;
|
||||
|
||||
char tag[9] = " ";
|
||||
makeChars(rawData, tag);
|
||||
|
||||
if (strcmp(tag, "GPLA ") != 0) return;
|
||||
|
||||
// Major
|
||||
rawData = data[index];
|
||||
index++;
|
||||
// Minor
|
||||
rawData = data[index];
|
||||
index++;
|
||||
// Patch
|
||||
rawData = data[index];
|
||||
index++;
|
||||
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
if (strcmp(tag, "FILE ") != 0) return;
|
||||
|
||||
int reportedNumFrames = 0;
|
||||
int frameRate = 0;
|
||||
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
|
||||
while (strcmp(tag, "DONE ") != 0) {
|
||||
rawData = data[index];
|
||||
index++;
|
||||
|
||||
if (strcmp(tag, "fCount ") == 0) {
|
||||
reportedNumFrames = rawData;
|
||||
} else if (strcmp(tag, "fRate ") == 0) {
|
||||
frameRate = rawData;
|
||||
}
|
||||
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
}
|
||||
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
|
||||
while (strcmp(tag, "END GPLA") != 0) {
|
||||
if (strcmp(tag, "FRAME ") == 0) {
|
||||
std::vector<std::vector<double>> allMatrices;
|
||||
std::vector<std::vector<std::vector<OsciPoint>>> allVertices;
|
||||
double focalLength;
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
while (strcmp(tag, "OBJECTS ") != 0) {
|
||||
rawData = data[index];
|
||||
index++;
|
||||
|
||||
if (strcmp(tag, "focalLen") == 0) {
|
||||
focalLength = makeDouble(rawData);
|
||||
}
|
||||
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
}
|
||||
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
|
||||
while (strcmp(tag, "DONE ") != 0) {
|
||||
if (strcmp(tag, "OBJECT ") == 0) {
|
||||
std::vector<std::vector<OsciPoint>> vertices;
|
||||
std::vector<double> matrix;
|
||||
int strokeNum = 0;
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
while (strcmp(tag, "DONE ") != 0) {
|
||||
if (strcmp(tag, "MATRIX ") == 0) {
|
||||
matrix.clear();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
rawData = data[index];
|
||||
index++;
|
||||
matrix.push_back(makeDouble(rawData));
|
||||
}
|
||||
rawData = data[index];
|
||||
index++;
|
||||
} else if (strcmp(tag, "STROKES ") == 0) {
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
|
||||
while (strcmp(tag, "DONE ") != 0) {
|
||||
if (strcmp(tag, "STROKE ") == 0) {
|
||||
vertices.push_back(std::vector<OsciPoint>());
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
|
||||
int vertexCount = 0;
|
||||
while (strcmp(tag, "DONE ") != 0) {
|
||||
if (strcmp(tag, "vertexCt") == 0) {
|
||||
rawData = data[index];
|
||||
index++;
|
||||
vertexCount = rawData;
|
||||
}
|
||||
else if (strcmp(tag, "VERTICES") == 0) {
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
double z = 0;
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
rawData = data[index];
|
||||
index++;
|
||||
x = makeDouble(rawData);
|
||||
|
||||
rawData = data[index];
|
||||
index++;
|
||||
y = makeDouble(rawData);
|
||||
|
||||
rawData = data[index];
|
||||
index++;
|
||||
z = makeDouble(rawData);
|
||||
|
||||
vertices[strokeNum].push_back(OsciPoint(x, y, z));
|
||||
}
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
while (strcmp(tag, "DONE ") != 0) {
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
}
|
||||
}
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
}
|
||||
strokeNum++;
|
||||
}
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
}
|
||||
}
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
}
|
||||
allVertices.push_back(reorderVertices(vertices));
|
||||
allMatrices.push_back(matrix);
|
||||
vertices.clear();
|
||||
matrix.clear();
|
||||
}
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
}
|
||||
|
||||
frames.push_back(assembleFrame(allVertices, allMatrices, focalLength));
|
||||
}
|
||||
|
||||
rawData = data[index];
|
||||
index++;
|
||||
makeChars(rawData, tag);
|
||||
}
|
||||
numFrames = frames.size();
|
||||
return;
|
||||
}
|
||||
|
||||
void LineArtParser::parseJsonFrames(juce::String jsonStr) {
|
||||
frames.clear();
|
||||
numFrames = 0;
|
||||
|
@ -97,6 +296,50 @@ std::vector<std::unique_ptr<Shape>> LineArtParser::draw() {
|
|||
return tempShapes;
|
||||
}
|
||||
|
||||
std::vector<std::vector<OsciPoint>> LineArtParser::reorderVertices(std::vector<std::vector<OsciPoint>> vertices) {
|
||||
std::vector<std::vector<OsciPoint>> reorderedVertices;
|
||||
|
||||
if (vertices.size() > 0) {
|
||||
std::vector<bool> visited = std::vector<bool>(vertices.size(), false);
|
||||
std::vector<int> order = std::vector<int>(vertices.size(), 0);
|
||||
visited[0] = true;
|
||||
|
||||
auto endPoint = vertices[0].back();
|
||||
|
||||
for (int i = 1; i < vertices.size(); i++) {
|
||||
int minPath = 0;
|
||||
double minDistance = 9999999;
|
||||
for (int j = 0; j < vertices.size(); j++) {
|
||||
if (!visited[j]) {
|
||||
auto startPoint = vertices[j][0];
|
||||
|
||||
double diffX = endPoint.x - startPoint.x;
|
||||
double diffY = endPoint.y - startPoint.y;
|
||||
double diffZ = endPoint.z - startPoint.z;
|
||||
|
||||
double distance = std::sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ);
|
||||
if (distance < minDistance) {
|
||||
minPath = j;
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
visited[minPath] = true;
|
||||
order[i] = minPath;
|
||||
endPoint = vertices[minPath].back();
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
std::vector<OsciPoint> reorderedVertex;
|
||||
int index = order[i];
|
||||
for (int j = 0; j < vertices[index].size(); j++) {
|
||||
reorderedVertex.push_back(vertices[index][j]);
|
||||
}
|
||||
reorderedVertices.push_back(reorderedVertex);
|
||||
}
|
||||
}
|
||||
return reorderedVertices;
|
||||
}
|
||||
|
||||
std::vector<Line> LineArtParser::generateFrame(juce::Array <juce::var> objects, double focalLength)
|
||||
{
|
||||
|
@ -124,55 +367,16 @@ std::vector<Line> LineArtParser::generateFrame(juce::Array <juce::var> objects,
|
|||
allMatrices[i].push_back(value);
|
||||
}
|
||||
|
||||
std::vector<std::vector<OsciPoint>> reorderedVertices;
|
||||
|
||||
if (vertices.size() > 0 && matrix.size() == 16) {
|
||||
std::vector<bool> visited = std::vector<bool>(vertices.size(), false);
|
||||
std::vector<int> order = std::vector<int>(vertices.size(), 0);
|
||||
visited[0] = true;
|
||||
|
||||
auto endPoint = vertices[0].back();
|
||||
|
||||
for (int i = 1; i < vertices.size(); i++) {
|
||||
int minPath = 0;
|
||||
double minDistance = 9999999;
|
||||
for (int j = 0; j < vertices.size(); j++) {
|
||||
if (!visited[j]) {
|
||||
auto startPoint = vertices[j][0];
|
||||
|
||||
double diffX = endPoint.x - startPoint.x;
|
||||
double diffY = endPoint.y - startPoint.y;
|
||||
double diffZ = endPoint.z - startPoint.z;
|
||||
|
||||
double distance = std::sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ);
|
||||
if (distance < minDistance) {
|
||||
minPath = j;
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
visited[minPath] = true;
|
||||
order[i] = minPath;
|
||||
endPoint = vertices[minPath].back();
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
std::vector<OsciPoint> reorderedVertex;
|
||||
int index = order[i];
|
||||
for (int j = 0; j < vertices[index].size(); j++) {
|
||||
reorderedVertex.push_back(vertices[index][j]);
|
||||
}
|
||||
reorderedVertices.push_back(reorderedVertex);
|
||||
}
|
||||
}
|
||||
|
||||
allVertices.push_back(reorderedVertices);
|
||||
allVertices.push_back(reorderVertices(vertices));
|
||||
}
|
||||
return assembleFrame(allVertices, allMatrices, focalLength);
|
||||
}
|
||||
|
||||
std::vector<Line> LineArtParser::assembleFrame(std::vector<std::vector<std::vector<OsciPoint>>> allVertices, std::vector<std::vector<double>> allMatrices, double focalLength) {
|
||||
// generate a frame from the vertices and matrix
|
||||
std::vector<Line> frame;
|
||||
|
||||
for (int i = 0; i < objects.size(); i++) {
|
||||
for (int i = 0; i < allVertices.size(); i++) {
|
||||
for (int j = 0; j < allVertices[i].size(); j++) {
|
||||
for (int k = 0; k < allVertices[i][j].size() - 1; k++) {
|
||||
auto start = allVertices[i][j][k];
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
class LineArtParser {
|
||||
public:
|
||||
LineArtParser(juce::String json);
|
||||
LineArtParser(char* data, int dataLength);
|
||||
~LineArtParser();
|
||||
|
||||
void setFrame(int fNum);
|
||||
|
@ -16,6 +17,11 @@ public:
|
|||
static std::vector<Line> generateFrame(juce::Array < juce::var> objects, double focalLength);
|
||||
private:
|
||||
void parseJsonFrames(juce::String jsonStr);
|
||||
void parseBinaryFrames(char* data, int dataLength);
|
||||
double makeDouble(int64_t data);
|
||||
void makeChars(int64_t data, char* chars);
|
||||
static std::vector<std::vector<OsciPoint>> reorderVertices(std::vector<std::vector<OsciPoint>> vertices);
|
||||
static std::vector<Line> assembleFrame(std::vector<std::vector<std::vector<OsciPoint>>> allVertices, std::vector<std::vector<double>> allMatrices, double focalLength);
|
||||
int frameNumber = 0;
|
||||
std::vector<std::vector<Line>> frames;
|
||||
int numFrames = 0;
|
||||
|
|
|
@ -30,7 +30,22 @@ void FileParser::parse(juce::String fileId, juce::String extension, std::unique_
|
|||
} else if (extension == ".lua") {
|
||||
lua = std::make_shared<LuaParser>(fileId, stream->readEntireStreamAsString(), errorCallback, fallbackLuaScript);
|
||||
} else if (extension == ".gpla") {
|
||||
gpla = std::make_shared<LineArtParser>(stream->readEntireStreamAsString());
|
||||
juce::MemoryBlock buffer{};
|
||||
int bytesRead = stream->readIntoMemoryBlock(buffer);
|
||||
if (bytesRead < 8) return;
|
||||
char* gplaData = (char*)buffer.getData();
|
||||
const char tag[] = "GPLA ";
|
||||
bool isBinary = true;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
isBinary = isBinary && tag[i] == gplaData[i];
|
||||
}
|
||||
if (isBinary) {
|
||||
gpla = std::make_shared<LineArtParser>(gplaData, bytesRead);
|
||||
}
|
||||
else {
|
||||
stream->setPosition(0);
|
||||
gpla = std::make_shared<LineArtParser>(stream->readEntireStreamAsString());
|
||||
}
|
||||
} else if (extension == ".gif" || extension == ".png" || extension == ".jpg" || extension == ".jpeg") {
|
||||
juce::MemoryBlock buffer{};
|
||||
int bytesRead = stream->readIntoMemoryBlock(buffer);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
bl_info = {
|
||||
"name": "osci-render",
|
||||
"author": "James Ball",
|
||||
"version": (1, 0, 3),
|
||||
"version": (1, 1, 0),
|
||||
"blender": (3, 1, 2),
|
||||
"location": "View3D",
|
||||
"description": "Addon to send gpencil frames over to osci-render",
|
||||
|
@ -16,6 +16,7 @@ import bmesh
|
|||
import socket
|
||||
import json
|
||||
import atexit
|
||||
import struct
|
||||
from bpy.props import StringProperty
|
||||
from bpy.app.handlers import persistent
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
|
@ -26,9 +27,14 @@ PORT = 51677
|
|||
sock = None
|
||||
|
||||
|
||||
GPLA_MAJOR = 2
|
||||
GPLA_MINOR = 0
|
||||
GPLA_PATCH = 0
|
||||
|
||||
|
||||
class OBJECT_PT_osci_render_settings(bpy.types.Panel):
|
||||
bl_idname = "OBJECT_PT_osci_render_settings"
|
||||
bl_label = "osci-render settings"
|
||||
bl_label = "DEVELOPMENT osci-render settings"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
|
@ -157,28 +163,136 @@ def get_frame_info():
|
|||
frame_info["focalLength"] = -0.05 * bpy.data.cameras[0].lens
|
||||
|
||||
return frame_info
|
||||
|
||||
|
||||
@persistent
|
||||
def save_scene_to_file(scene, file_path):
|
||||
return_frame = scene.frame_current
|
||||
bin = bytearray()
|
||||
|
||||
scene_info = {"frames": []}
|
||||
for frame in range(0, scene.frame_end - scene.frame_start):
|
||||
# header
|
||||
bin.extend(("GPLA ").encode("utf8"))
|
||||
bin.extend(GPLA_MAJOR.to_bytes(8, "little"))
|
||||
bin.extend(GPLA_MINOR.to_bytes(8, "little"))
|
||||
bin.extend(GPLA_PATCH.to_bytes(8, "little"))
|
||||
|
||||
# file info
|
||||
bin.extend(("FILE ").encode("utf8"))
|
||||
bin.extend(("fCount ").encode("utf8"))
|
||||
bin.extend((scene.frame_end - scene.frame_start + 1).to_bytes(8, "little"))
|
||||
bin.extend(("fRate ").encode("utf8"))
|
||||
bin.extend(scene.render.fps.to_bytes(8, "little"))
|
||||
bin.extend(("DONE ").encode("utf8"))
|
||||
|
||||
for frame in range(0, scene.frame_end - scene.frame_start + 1):
|
||||
scene.frame_set(frame + scene.frame_start)
|
||||
scene_info["frames"].append(get_frame_info())
|
||||
|
||||
json_str = json.dumps(scene_info, separators=(',', ':'))
|
||||
bin.extend(get_frame_info_binary())
|
||||
|
||||
bin.extend(("END GPLA").encode("utf8"))
|
||||
|
||||
if file_path is not None:
|
||||
f = open(file_path, "w")
|
||||
f.write(json_str)
|
||||
f.close()
|
||||
with open(file_path, "wb") as f:
|
||||
f.write(bytes(bin))
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
scene.frame_set(return_frame)
|
||||
return 0
|
||||
|
||||
def get_frame_info_binary():
|
||||
frame_info = bytearray()
|
||||
frame_info.extend(("FRAME ").encode("utf8"))
|
||||
|
||||
frame_info.extend(("focalLen").encode("utf8"))
|
||||
frame_info.extend(struct.pack("d", -0.05 * bpy.data.cameras[0].lens))
|
||||
|
||||
frame_info.extend(("OBJECTS ").encode("utf8"))
|
||||
|
||||
if (bpy.app.version[0] > 4) or (bpy.app.version[0] == 4 and bpy.app.version[1] >= 3):
|
||||
for obj in bpy.data.objects:
|
||||
if obj.visible_get() and obj.type == 'GREASEPENCIL':
|
||||
frame_info.extend(("OBJECT ").encode("utf8"))
|
||||
|
||||
# matrix
|
||||
frame_info.extend(("MATRIX ").encode("utf8"))
|
||||
camera_space = bpy.context.scene.camera.matrix_world.inverted() @ obj.matrix_world
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
frame_info.extend(struct.pack("d", camera_space[i][j]))
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# strokes
|
||||
frame_info.extend(("STROKES ").encode("utf8"))
|
||||
strokes = obj.data.layers.active.frames.data.current_frame().drawing.strokes
|
||||
for stroke in strokes:
|
||||
frame_info.extend(("STROKE ").encode("utf8"))
|
||||
|
||||
frame_info.extend(("vertexCt").encode("utf8"))
|
||||
frame_info.extend(len(stroke.points).to_bytes(8, "little"))
|
||||
|
||||
frame_info.extend(("VERTICES").encode("utf8"))
|
||||
for vert in stroke.points:
|
||||
frame_info.extend(struct.pack("d", vert.position.x))
|
||||
frame_info.extend(struct.pack("d", vert.position.y))
|
||||
frame_info.extend(struct.pack("d", vert.position.z))
|
||||
# VERTICES
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# STROKE
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# STROKES
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# OBJECT
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
else:
|
||||
for obj in bpy.data.objects:
|
||||
if obj.visible_get() and obj.type == 'GPENCIL':
|
||||
frame_info.extend(("OBJECT ").encode("utf8"))
|
||||
|
||||
# matrix
|
||||
frame_info.extend(("MATRIX ").encode("utf8"))
|
||||
camera_space = bpy.context.scene.camera.matrix_world.inverted() @ obj.matrix_world
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
frame_info.extend(camera_space[i][j].to_bytes(8, "little"))
|
||||
# MATRIX
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# strokes
|
||||
frame_info.extend(("STROKES ").encode("utf8"))
|
||||
strokes = obj.data.layers.active.frames.data.active_frame.strokes
|
||||
for stroke in strokes:
|
||||
frame_info.extend(("STROKE ").encode("utf8"))
|
||||
|
||||
frame_info.extend(("vertexCt").encode("utf8"))
|
||||
frame_info.extend(len(stroke.points).to_bytes(8, "little"))
|
||||
|
||||
frame_info.extend(("VERTICES").encode("utf8"))
|
||||
for vert in stroke.points:
|
||||
frame_info.extend(struct.pack("d", vert.co[0]))
|
||||
frame_info.extend(struct.pack("d", vert.co[1]))
|
||||
frame_info.extend(struct.pack("d", vert.co[2]))
|
||||
# VERTICES
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# STROKE
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# STROKES
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# OBJECT
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# OBJECTS
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
# FRAME
|
||||
frame_info.extend(("DONE ").encode("utf8"))
|
||||
|
||||
return frame_info
|
||||
|
||||
|
||||
@persistent
|
||||
def send_scene_to_osci_render(scene):
|
||||
|
|
Ładowanie…
Reference in New Issue