I have succesfully written a simple program inside my main program that exports a video off-screen using Render Targets.
The problem is: my computer sucks. Therefore, the video output sucks as well, with lots of lag and frameskipping.
I want to ensure that the exported video has 100% of the frames exported, even if it takes a long time. I guess this means decoupling video exporting from video playback. I was able to do this in Processing (but it was slooow and it was pretty much a sloppy workaround).
here’s my current simple exporting program
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.renderTarget
import org.openrndr.draw.isolatedWithTarget
import org.openrndr.ffmpeg.VideoPlayerFFMPEG
import org.openrndr.ffmpeg.VideoWriter
import utils.MediaManager
import utils.MediaType
import java.io.File
import java.util.logging.Logger
fun main() = application {
configure {
width = 960
height = 540
title = "Off-Screen Video Export with Progress Counter"
}
program {
val logger = Logger.getLogger("SimpleVideoPlayer")
val mediaManager = MediaManager()
val videoFile = File("c:\\videos\\godgangs\\playlist\\01.mp4")
var videoPlayer: VideoPlayerFFMPEG? = null
val playDuration = 10_000L // 10 seconds in milliseconds
var startTime: Long? = null
var videoStopped = false
var elapsedTime = 0L
// Set up the video writer
val videoWriter = VideoWriter()
videoWriter.size(width, height)
videoWriter.output("video/output.mp4")
videoWriter.start()
// Set up the render target
val videoTarget = renderTarget(width, height) {
colorBuffer()
depthBuffer()
}
extend {
// Render video frames off-screen
drawer.isolatedWithTarget(videoTarget) {
clear(ColorRGBa.BLACK)
if (videoPlayer == null) {
logger.info("Loading video: ${videoFile.absolutePath}")
videoPlayer = mediaManager.loadMedia(videoFile, MediaType.VIDEO) as? VideoPlayerFFMPEG
videoPlayer?.play()
startTime = System.currentTimeMillis()
}
val currentTime = System.currentTimeMillis()
startTime?.let {
elapsedTime = currentTime - it
if (elapsedTime >= playDuration && !videoStopped) {
videoPlayer?.pause()
videoPlayer?.seek(0.0)
videoStopped = true
logger.info("Stopping video after 10 seconds")
} else if (!videoStopped) {
videoPlayer?.draw(drawer, 0.0, 0.0, width.toDouble(), height.toDouble())
}
}
// Write the frame to the video
videoWriter.frame(videoTarget.colorBuffer(0))
}
// Display a black screen with a progress counter
drawer.clear(ColorRGBa.BLACK)
drawer.fill = ColorRGBa.WHITE
val timeString = String.format("%.1f", elapsedTime / 1000.0)
drawer.text("Exporting... Time: $timeString s", width / 2.0 - 100.0, height / 2.0)
// Stop the video writer after the play duration
if (elapsedTime >= playDuration) {
videoWriter.stop()
application.exit()
}
}
ended.listen {
logger.info("Application ending")
mediaManager.disposeAll()
}
}
}
it uses this MediaManager that handles media throughout my application
package utils
import org.openrndr.draw.ColorBuffer
import org.openrndr.ffmpeg.VideoPlayerFFMPEG
import java.io.File
import java.util.logging.Logger
private val logger = Logger.getLogger(Constants.MEDIA_MANAGER_LOGGER_NAME)
enum class MediaType {
IMAGE, VIDEO
}
class MediaManager {
private val loadedImages = mutableMapOf<File, ColorBuffer>()
private val loadedVideos = mutableMapOf<File, VideoPlayerFFMPEG>()
fun loadMedia(file: File, mediaType: MediaType): Any? {
return when (mediaType) {
MediaType.IMAGE -> loadImage(file)
MediaType.VIDEO -> loadVideo(file)
}
}
private fun loadImage(file: File): ColorBuffer? {
return loadedImages[file] ?: tryWithLogging("load image ${file.name}", logger) {
logger.info("Loading image: ${file.name}")
val image = org.openrndr.draw.loadImage(file)
loadedImages[file] = image
logger.info("Image loaded successfully: ${file.name}")
image
}
}
private fun loadVideo(file: File): VideoPlayerFFMPEG? {
return loadedVideos[file] ?: tryWithLogging("load video ${file.name}", logger) {
logger.info("Loading video: ${file.name}")
val video = VideoPlayerFFMPEG.fromFile(file.absolutePath)
video.ended.listen { video.restart() }
loadedVideos[file] = video
logger.info("Video loaded successfully: ${file.name}")
video
}
}
fun disposeAll() {
loadedVideos.values.forEach { it.dispose() }
loadedVideos.clear()
loadedImages.values.forEach { it.destroy() }
loadedImages.clear()
}
}
If anybody can point me in the right direction I’ll be glad and thankful!
(Abe Pazos’ photo will be loaded at the starting screen of my app with bells and whistles around him already)