1 module caLib_util.video; 2 3 import std.process : execute, executeShell; 4 import std.file : mkdirRecurse, rmdir, remove, exists, write, rename, getcwd; 5 import std.exception : enforce; 6 import std.conv : to; 7 import std.string : split; 8 import caLib_util.image : Image; 9 import caLib_util.misc : findInPATH; 10 import caLib_util.build : arch, os; 11 import caLib_util.tempdir : makePrivateTempDir; 12 13 import std.stdio : writeln; 14 import std.random : uniform; 15 16 17 18 class Video 19 { 20 21 private: 22 23 string creationDir; 24 uint nVideos; 25 uint nFrames; 26 27 string path; 28 uint framerate; 29 30 uint width; 31 uint height; 32 33 public: 34 35 this(string path, uint framerate) 36 { 37 this.path = path; 38 39 creationDir = makePrivateTempDir(); 40 nVideos = 0; 41 nFrames = 0; 42 43 this.framerate = framerate; 44 45 width = -1; 46 height = -1; 47 } 48 49 void addFrame(Image frame) 50 { 51 if(width == -1 && height == -1) 52 { 53 width = frame.getWidth(); 54 height = frame.getHeight(); 55 } 56 57 try 58 { 59 frame.saveToFile(creationDir ~ "/" ~ to!string(nFrames) ~ ".png"); 60 nFrames ++; 61 62 if(nFrames == 10) 63 mergeFrames(); 64 } 65 catch(Exception e) 66 { 67 writeln(e.msg); 68 addFrame(frame); 69 } 70 } 71 72 void saveToFile() 73 { 74 mergeFrames(); 75 mergeVideos(); 76 rename(creationDir ~ "/0.mp4", getcwd() ~ "/" ~ path); 77 } 78 79 private: 80 81 void mergeFrames() 82 { 83 auto ret = execute([ 84 encoderPath, 85 "-r", to!string(framerate), 86 "-f", "image2", 87 "-s", to!string(width) ~ "x" ~ to!string(height), 88 "-i", creationDir ~ "/%d.png", 89 "-vcodec", "libx264", "-crf", "25", "-pix_fmt", "yuv420p", 90 creationDir ~ "/" ~ to!string(nVideos) ~ ".mp4", 91 ]); 92 93 enforce(ret.status == 0, "An error occured when creating video"); 94 95 nFrames = 0; 96 nVideos ++; 97 98 if(nVideos == 2) 99 mergeVideos(); 100 } 101 102 void mergeVideos() 103 { 104 string buffer = ""; 105 foreach(i; 0 .. nVideos) 106 { 107 buffer = buffer ~ "\nfile " ~ to!string(i) ~ ".mp4"; 108 } 109 write(creationDir ~ "/files.txt", buffer); 110 111 auto ret = execute([ 112 encoderPath, 113 "-f", "concat", "-safe", "0", 114 "-i", creationDir ~ "/files.txt", 115 "-c", "copy", 116 creationDir ~ "/a.mp4", 117 ]); 118 119 enforce(ret.status == 0, "An error occured when creating video"); 120 121 foreach(i; 0 .. nVideos) 122 { 123 remove(creationDir ~ "/" ~ to!string(i) ~ ".mp4"); 124 } 125 126 rename(creationDir ~ "/a.mp4", 127 creationDir ~ "/0.mp4"); 128 129 nVideos = 1; 130 } 131 } 132 133 134 135 private string decoderPath = null; 136 private string encoderPath = null; 137 138 static this() 139 { 140 enum decoderName = [ 141 "Windows" : "ffmpeg.exe", 142 ].get(os, null); 143 144 enum encoderName = [ 145 "Windows" : "ffmpeg.exe", 146 ].get(os, null); 147 148 static assert(decoderName != null && encoderName != null, 149 "can't compile video.d becuase codec usage is not yet" 150 ~ " implemented for " ~ os); 151 152 decoderPath = findInPATH(decoderName); 153 encoderPath = findInPATH(encoderName); 154 155 enforce(decoderPath != null && encoderPath != null, 156 encoderName ~ " and/or " ~ decoderName ~ ", wich is " 157 ~ "essential for creating video could not be found on the PATH"); 158 } 159 160 161