Video rendered in ASCII character is rather an acquired taste... However, when used carefully and tastefully it can provide striking visuals for ASCII game. Typical workflow of creating ASCII video includes:
There are plenty of existing tooling available that can help you to convert a standard video like MP4 into a set of JPEG images and then convert these images into ASCII art images. Once you have ASCII art images you can use CosPlay video support to play back that video.
CosPlay video support consists of:
Video is defined as a sequence of same-sized frames where each frame is an image. CPVideoSprite provides rendering of that video while CPVideoSpriteListener allows the video playback to synchronize with other action in the game like sound or animation.
Video Is An Asset
Just like other assets such as CPFont, CPImage, CPParticleEmitter, CPAnimation or CPSound they are not managed or governed by the CosPlay game engine unlike scenes and scene object, that are managed and governed by the game engine. Assets are typically created outside the game loop and managed by the developer, they can be freely shared between scenes or scene objects as any other standard Scala objects.
Vide clip is defined by CPVideo class.
Let's see the usage of video support on a quick video clip that we'll call "Psychedelic Tunnel". Here's the source code for the video clip definition (which is also available in the built-in video example). Now, this code snippet does more than a usual video clip definition would: it not only loads the raw footage from prefab/video/tunnel.txt
file but also skins every frame adding programmatic contrast and applying "psychedelic" effect to it:
import org.cosplay.* import CPPixel.* import CPColor.* import scala.io.Source import scala.util.Using // 40 frames from https://ascii.co.uk/animated-art/3d-tunnel-animated-ascii-art.html. object VideoClip extends CPVideo("vid", "https://ascii.co.uk/animated-art/3d-tunnel-animated-ascii-art.html"): private final val RAW_FOOTAGE = "prefab/video/tunnel.txt" // Under 'resources' folder... private final val FRAME_CNT = 40 private val frames: Seq[CPImage] = { val rsrc = getClass.getClassLoader.getResourceAsStream(RAW_FOOTAGE) if rsrc != null then Using.resource(Source.fromInputStream(rsrc, "UTF-8")) { rs => // Load all lines and skip empty ones. val lines = rs.getLines().toSeq.filter(_.trim.nonEmpty) lines.grouped(lines.size / FRAME_CNT).toSeq.map { frameLines => // Psychedelic mode :-) val c = CPRand.rand(CS_X11_ALL) new CPArrayImage(frameLines, (ch, _, _) => { ch match // Color it for more contrast. case '.' => ch&c.darker(0.4) case ',' => ch&c.darker(0.3) case ':' => ch&c.darker(0.25) case ';' => ch&c.darker(0.2) case 'i' => ch&c.darker(0.15) case '1' => ch&c.darker(0.05) case ' ' => XRAY case _ => ch&c }) } } else throw Exception(s"Unable to find or load: $RAW_FOOTAGE") } override val getFrameCount: Int = frames.size override val getFrameDim: CPDim = frames.head.getDim override def getFrame(idx: Int): CPImage = frames(idx)
NOTES:
prefab/video/tunnel.txt
raw footage file.Video playback is based on CPVideoSprite class.
Once we have a video clip, let's develop a simple playback interface that would allow pausing, resuming and rewinding the playback. Here's the full source code for this fully functional video player:
import org.cosplay.* import CPColor.* import CPPixel.* import CPKeyboardKey.* import CPStyledString.styleStr import prefabs.shaders.* object VideoPlayer: def main(args: Array[String]): Unit = val c1 = C_LIGHT_GREEN val c2 = C_ORANGE1 val ctrlImg = new CPArrayImage( styleStr("[Space]", c1) ++ styleStr(" Play|Pause ", c2) ++ styleStr("[R]", c1) ++ styleStr(" Rewind ", c2) ++ styleStr("[Q]", c1) ++ styleStr(" Quit ", c2) ++ styleStr("[Ctrl-L]", c1) ++ styleStr(" Log ", c2) ++ styleStr("[Ctrl-Q]", c1) ++ styleStr(" FPS Overlay", c2) ).trimBg() val vidDim = VideoClip.getFrameDim val ctrlDim = ctrlImg.getDim val dim = CPDim((vidDim.w + 8).max(ctrlDim.w + 4), vidDim.h + 8) val vidSpr = new CPVideoSprite(vid = VideoClip, 4, 2, 0, 30, true, false, true) val bgPx = '.'&&(C_GRAY2, C_GRAY1) // Create the scene. val sc = new CPScene("scene", Option(dim), bgPx, vidSpr, new CPKeyboardSprite((_, key) => key match case KEY_LO_R => vidSpr.rewind() case KEY_SPACE => vidSpr.toggle() case _ => () ), new CPStaticImageSprite((dim.w - ctrlDim.w) / 2, dim.h - 4, 0, ctrlImg), CPKeyboardSprite(_.exitGame(), KEY_LO_Q) // Exit the game on 'q' press. ) // Initialize the engine. CPEngine.init( CPGameInfo(name = "Video Player", initDim = Option(dim)), System.console() == null || args.contains("emuterm") ) // Start the game & wait for exit. try CPEngine.startGame(sc) finally CPEngine.dispose() sys.exit(0)
NOTES:
Run this example to get this video playback:
Here's some useful links for ASCII videos: