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