def __setCurrentShot(self, *args): shot = self.__shotsManager.shotAtTime(self._timer.time) if shot is None: self.__sceneView.setScene(None) self.__profiler.setScene(None) return sceneFile = currentScenesDirectory().join(shot.sceneName + SCENE_EXT) sc = Scene.getScene(sceneFile) self.__sceneView.setScene(sc) self.__profiler.setScene(sc)
def _deserializeSceneShots(sceneName): sceneFile = currentScenesDirectory().join(sceneName.ensureExt(SCENE_EXT)) xScene = parseXMLWithIncludes(sceneFile) for xShot in xScene: name = xShot.attrib['name'] start = float(xShot.attrib['start']) end = float(xShot.attrib['end']) speed = float(xShot.attrib.get( 'speed', 1.0)) # using get for legacy file support preroll = float(xShot.attrib.get('preroll', 0.0)) curves = OrderedDict() textures = OrderedDict() for xEntry in xShot: if xEntry.tag.lower() == 'channel': curveName = xEntry.attrib['name'] keys = [] if xEntry.text: keys = xEntry.text.split(',') curve = Curve() for i in range(0, len(keys), 8): curve.addKeyWithTangents( tangentBroken=int(keys[i + 6]), tangentMode=int(keys[i + 7]), *[float(x) for x in keys[i:i + 6]]) curves[curveName] = curve if xEntry.tag.lower() == 'texture': textures[xEntry.attrib['name']] = FilePath( xEntry.attrib['path']) shot = Shot(name, sceneName, start, end, curves, textures, speed, preroll) if 'enabled' in xShot.attrib: shot.enabled = xShot.attrib['enabled'] == str(True) yield shot
def run(): shots = [] scenes = [] scenesDir = currentScenesDirectory() for scenePath in scenesDir.iter(join=True): if not scenePath.hasExt(SCENE_EXT): continue sceneDir = FilePath(scenePath.strip()).stripExt() xScene = parseXMLWithIncludes(scenePath) templatePath = scenesDir.join(scenesDir, xScene.attrib['template']) templateDir = templatePath.stripExt() xTemplate = Template(templatePath) scene = [] for xPass in xTemplate: stitchIds = [] uniforms = {} for xSection in xPass: baseDir = sceneDir if xSection.tag in ('global', 'shared'): baseDir = templateDir shaderFile = baseDir.join(xSection.attrib['path']).abs() stitchIds.append(text.addFile(shaderFile)) for xUniform in xSection: name = xUniform.attrib['name'] values = [ float(x.strip()) for x in xUniform.attrib['value'].split(',') ] uniforms[text.addString(name)] = len( values), floats.addFloats(values, name) programId = shaders.fromStitches(stitchIds) buffer = int(xPass.attrib.get('buffer', -1)) outputs = int(xPass.attrib.get('outputs', 1)) size = int(xPass.attrib.get('size', 0)) width = int(xPass.attrib.get('width', size)) height = int(xPass.attrib.get('height', size)) factor = int(xPass.attrib.get('factor', 1)) static = int(xPass.attrib.get('static', 0)) is3d = int(xPass.attrib.get('is3d', 0)) if buffer != -1: buffer = framebuffers.add(buffer, outputs, width, height, factor, static, is3d) i = 0 key = 'input%s' % i inputs = [] while key in xPass.attrib: v = xPass.attrib[key] if '.' in v: a, b = v.split('.') else: a, b = v, 0 inputs.append((int(a), int(b))) i += 1 key = 'input%s' % i scene.append(passes.add(programId, buffer, inputs, uniforms)) sceneIndex = len(scenes) scenes.append(len(scene)) scenes += scene for xShot in xScene: if xShot.attrib.get('enabled', 'True') == 'False': continue animations = {} for xChannel in xShot: uname = xChannel.attrib['name'] n = uname x = 0 if '.' in uname: n, x = uname.rsplit('.', 1) x = 'xyzw'.index(x) n = text.addString(n) if n not in animations: animations[n] = [] if not xChannel.text: keyframes = [] else: keyframes = [] for i, v in enumerate( float(v.strip()) for v in xChannel.text.split(',')): j = i % 8 if j == 0 or j == 4 or j > 5: continue if j == 5: # out tangent y if v == float( 'inf' ): # stepped tangents are implemented as out tangentY = positive infinity v = 'FLT_MAX' keyframes.append(v) assert len(keyframes) / 4.0 == int(len(keyframes) / 4), len(keyframes) while len(animations[n]) <= x: animations[n].append(None) assert animations[n][x] is None animations[n][x] = floats.addFloats(keyframes), len(keyframes) for channelStack in animations.values(): # TODO we can not / do not check if the channelStack length matches the uniform dimensions inside the shader (e.g. are we sure we're not gonna call glUniform2f for a vec3?) assert None not in channelStack, 'Animation provided for multiple channels but there is one missing (Y if a vec3 or also Z if a vec4).' shots.append((float(xShot.attrib['start']), float(xShot.attrib['end']), sceneIndex, animations)) # sort shots by start time def _serializeShots(shots): shots.sort(key=lambda x: x[0]) shotTimesStart = floats.addFloats( [x for shot in shots for x in (shot[0], shot[1])]) yield '\n\n__forceinline int shotAtBeats(float beats, float& localBeats)\n{\n' if len(shots) == 1: yield '\tlocalBeats = beats - gFloatData[%s];\n' % shotTimesStart yield '\treturn 0;\n' else: yield '\tint shotTimeCursor = 0;\n' yield '\tdo\n\t{\n' yield '\t\tif(beats < gFloatData[shotTimeCursor * 2 + %s])\n\t\t{\n' % ( shotTimesStart + 1) yield '\t\t\tlocalBeats = beats - gFloatData[shotTimeCursor * 2 + %s];\n' % shotTimesStart yield '\t\t\treturn shotTimeCursor;\n' yield '\t\t}\n' yield '\t}\n\twhile(++shotTimeCursor < %s);\n' % len(shots) yield '\treturn -1;\n' yield '}\n' global gShotScene gShotScene = ints.addInts([shot[2] for shot in shots]) flatAnimationData = [] animationDataPtrs = [] for shot in shots: animationDataPtrs += [len(flatAnimationData), len(shot[3].keys())] global gAnimEntriesMax gAnimEntriesMax = max(gAnimEntriesMax, len(shot[3].keys())) for uniformStringId in shot[3]: animationData = shot[3][uniformStringId] flatAnimationData += [uniformStringId, len(animationData)] for pair in animationData: flatAnimationData += pair flatAnimationData += [0] * (2 * (4 - len(animationData))) global gShotAnimationDataIds gShotAnimationDataIds = ints.addInts(animationDataPtrs) global gShotUniformData gShotUniformData = ints.addInts(flatAnimationData) def _serializeAll(scenes, shots): buffer = list(_serializeShots(shots)) for serializable in (text, floats): for ln in serializable.serialize(): yield ln buffer2 = [] for serializable in (shaders, framebuffers, passes): buffer2 += list(serializable.serialize()) global gScenePassIds gScenePassIds = ints.addInts(scenes) for ln in ints.serialize(): yield ln for ln in buffer2: yield ln for ln in buffer: yield ln data = [''.join(_serializeAll(scenes, shots))] data.append( """\n\n__forceinline float evalCurve(const float* data, int numFloats, float beats) { \tif(numFloats == 4 || beats <= data[1]) // 1 key or evaluating before first frame \t\treturn data[2]; \t// Find index of first key that has a bigger time than our current time \t// if none, this will be the index of the last key. \tint keyValueCount = numFloats; \tint rightKeyIndex = 4; \twhile (rightKeyIndex < keyValueCount - 4 && data[rightKeyIndex + 1] < beats) \t\trightKeyIndex += 4; \t// Clamp our sampling time to our range \tfloat sampleTime = (beats > data[rightKeyIndex + 1]) ? data[rightKeyIndex + 1] : beats; \t// Retrieve our spline points \tfloat y0 = data[rightKeyIndex - 2]; \tfloat y1 = data[rightKeyIndex - 1]; \t// handle stepped tangents \tif(y1 == FLT_MAX) return y0; \tfloat y2 = data[rightKeyIndex]; \tfloat y3 = data[rightKeyIndex + 2]; \tfloat dy = y3 - y0; \tfloat c0 = y1 + y2 - dy - dy; \tfloat c1 = dy + dy + dy - y1 - y1 - y2; \tfloat c2 = y1; \tfloat c3 = y0; \t// Determine factor \tfloat dt = data[rightKeyIndex + 1] - data[rightKeyIndex - 3]; \tfloat t = (sampleTime - data[rightKeyIndex - 3]) / dt; \treturn t * (t * (t * c0 + c1) + c2) + c3; } #define gAnimEntriesMax %s #define gShotAnimationDataIds %s #define gShotScene %s #define gScenePassIds %s #define gPassProgramsAndTargets %s #define gShotUniformData %s #define gFrameBufferData %s #define gFrameBufferBlockSize %s #define gProgramCount %s """ % (gAnimEntriesMax, gShotAnimationDataIds, gShotScene, gScenePassIds, gPassProgramsAndTargets, gShotUniformData, gFrameBufferData, FrameBufferPool.BLOCK_SIZE, len(shaders.offsets))) dst = FilePath(__file__).abs().parent().parent().join( 'Player', 'generated.hpp') with dst.edit() as fh: fh.write(''.join(data))
def __record(self): diag = QDialog() fId = gSettings.value('RecordFPS', 2) rId = gSettings.value('RecordResolution', 3) layout = QGridLayout() diag.setLayout(layout) layout.addWidget(QLabel('FPS: '), 0, 0) fps = QComboBox() fps.addItems(['12', '24', '30', '48', '60', '120']) fps.setCurrentIndex(fId) layout.addWidget(fps, 0, 1) layout.addWidget(QLabel('Vertical resolution: '), 1, 0) resolution = QComboBox() resolution.addItems(['144', '288', '360', '720', '1080', '2160']) resolution.setCurrentIndex(rId) layout.addWidget(resolution, 1, 1) ok = QPushButton('Ok') ok.clicked.connect(diag.accept) cancel = QPushButton('Cancel') cancel.clicked.connect(diag.reject) layout.addWidget(ok, 2, 0) layout.addWidget(cancel, 2, 1) diag.exec_() if diag.result() != QDialog.Accepted: return gSettings.setValue('RecordFPS', fps.currentIndex()) gSettings.setValue('RecordResolution', resolution.currentIndex()) FPS = int(fps.currentText()) HEIGHT = int(resolution.currentText()) WIDTH = (HEIGHT * 16) / 9 FMT = 'jpg' data = (ctypes.c_ubyte * (WIDTH * HEIGHT * 3))() # alloc buffer once flooredStart = self._timer.secondsToBeats( int(self._timer.beatsToSeconds(self._timer.start) * FPS) / float(FPS)) duration = self._timer.beatsToSeconds(self._timer.end - flooredStart) captureDir = currentProjectDirectory().join('capture') captureDir.ensureExists(isFolder=True) progress = QProgressDialog(self) progress.setMaximum(int(duration * FPS)) prevFrame = 0 for frame in range(int(duration * FPS)): deltaTime = (frame - prevFrame) / float(FPS) prevFrame = frame progress.setValue(frame) QApplication.processEvents() if progress.wasCanceled(): break beats = flooredStart + self._timer.secondsToBeats( frame / float(FPS)) shot = self.__shotsManager.shotAtTime(beats) if shot is None: continue sceneFile = currentScenesDirectory().join( shot.sceneName).ensureExt(SCENE_EXT) scene = Scene.getScene(sceneFile) scene.setSize(WIDTH, HEIGHT) uniforms = self.__shotsManager.evaluate(beats) textureUniforms = self.__shotsManager.additionalTextures(beats) self.__sceneView._cameraInput.setData( *(uniforms['uOrigin'] + uniforms['uAngles']) ) # feed animation into camera so animationprocessor can read it again cameraData = self.__sceneView._cameraInput.data() modifier = currentProjectDirectory().join('animationprocessor.py') if modifier.exists(): execfile(str(modifier), globals(), locals()) for name in self.__sceneView._textures: uniforms[name] = self.__sceneView._textures[name]._id scene.drawToScreen(self._timer.beatsToSeconds(beats), beats, uniforms, (0, 0, WIDTH, HEIGHT), textureUniforms) scene.colorBuffers[-1][0].use() from OpenGL.GL import glGetTexImage, GL_TEXTURE_2D, GL_RGB, GL_UNSIGNED_BYTE glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, data) captureDir = currentProjectDirectory().join('capture') QImage(data, WIDTH, HEIGHT, QImage.Format_RGB888).mirrored( False, True ).save( captureDir.join( 'dump_%s_%05d.%s' % (FPS, int(self._timer.beatsToSeconds(self._timer.start) * FPS) + frame, FMT))) progress.close() convertCaptureDir = currentProjectDirectory().join('convertcapture') convertCaptureDir.ensureExists(isFolder=True) with convertCaptureDir.join('convert.bat').edit() as fh: start = '' start2 = '' if int(self._timer.start * FPS) > 0: start = '-start_number {} '.format( int(self._timer.beatsToSeconds(self._timer.start) * FPS)) start2 = '-vframes {} '.format( int( self._timer.beatsToSeconds(self._timer.end - self._timer.start) * FPS)) fh.write( 'cd "../capture"\n"{}" -framerate {} {}-i dump_{}_%%05d.{} {}-c:v libx264 -r {} -pix_fmt yuv420p "../convertcapture/output.mp4"' .format(FFMPEG_PATH, FPS, start, FPS, FMT, start2, FPS)) with convertCaptureDir.join('convertGif.bat').edit() as fh: start = '' start2 = '' iln = '' if int(self._timer.start * FPS) > 0: start = '-start_number {} '.format( int(self._timer.beatsToSeconds(self._timer.start) * FPS)) start2 = '-vframes {} '.format( int( self._timer.beatsToSeconds(self._timer.end - self._timer.start) * FPS)) iln = '-t {:03f} '.format( self._timer.beatsToSeconds(self._timer.end - self._timer.start)) fh.write( 'REM File format is actually %5d but in a .bat file we need to escape % or something, so you can\'t copy paste this into a cmd prompt without fixing up the %%05d to be %5d.\n' ) fh.write( 'cd "../capture"\n"{}" -framerate {} {}{}-i dump_{}_%%05d.{} -vf "fps={},scale={}:-1:flags=lanczos,palettegen" palette.png\n' .format(FFMPEG_PATH, FPS, start, iln, FPS, FMT, FPS, HEIGHT)) fh.write( '"{}" -framerate {} {}-i dump_{}_%%05d.{} -i "palette.png" -filter_complex "fps=12,scale=360:-1:flags=lanczos[x];[x][1:v]paletteuse" {}-r {} "../convertcapture/output.gif"' .format(FFMPEG_PATH, FPS, start, FPS, FMT, start2, FPS)) sound = self.timeSlider.soundtrackPath() if not sound: return with convertCaptureDir.join('merge.bat').edit() as fh: startSeconds = self._timer.beatsToSeconds(self._timer.start) fh.write( '{} -i output.mp4 -itsoffset {} -i "{}" -vcodec copy -shortest merged.mp4' .format(FFMPEG_PATH, -startSeconds, sound))
def _saveSceneShots(sceneName, shots): sceneFile = currentScenesDirectory().join(sceneName.ensureExt(SCENE_EXT)) xScene = parseXMLWithIncludes(sceneFile) # save user camera position per scene userFile = currentProjectFilePath().ensureExt('user') if userFile.exists(): xUser = parseXMLWithIncludes(userFile) else: xUser = cElementTree.Element('user') if sceneFile in Scene.cache: cameraData = Scene.cache[sceneFile].cameraData() if cameraData: for xSub in xUser: if xSub.tag == 'scene' and xSub.attrib['name'] == sceneName: xSub.attrib['camera'] = ','.join( [str(x) for x in cameraData]) break else: cElementTree.SubElement( xUser, 'scene', { 'name': sceneName, 'camera': ','.join([str(x) for x in cameraData]) }) with userFile.edit() as fh: fh.write(toPrettyXml(xUser)) # remove old shots r = [] for xShot in xScene: r.append(xShot) for s in r: xScene.remove(s) targets = [] for shot in shots: if shot.sceneName == sceneName: targets.append(shot) for shot in targets: xShot = cElementTree.SubElement( xScene, 'Shot', { 'name': shot.name, 'scene': sceneName, 'start': str(shot.start), 'end': str(shot.end), 'enabled': str(shot.enabled), 'speed': str(shot.speed), 'preroll': str(shot.preroll) }) for curveName in shot.curves: xChannel = cElementTree.SubElement(xShot, 'Channel', { 'name': curveName, 'mode': 'hermite' }) data = [] for key in shot.curves[curveName]: data.append(str(key.inTangent.x)) data.append(str(key.inTangent.y)) data.append(str(key.point().x)) data.append(str(key.point().y)) data.append(str(key.outTangent.x)) data.append(str(key.outTangent.y)) data.append(str(int(key.tangentBroken))) data.append(str(key.tangentMode)) xChannel.text = ','.join(data) for texName in shot.textures: cElementTree.SubElement(xShot, 'Texture', { 'name': texName, 'path': shot.textures[texName] }) with sceneFile.edit() as fh: fh.write(toPrettyXml(xScene))