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