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