1 /**
2  * Get frames from a video file by invoking ffmpeg.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.graphics.ffmpeg;
15 
16 import std.exception;
17 import std.typecons;
18 
19 import ae.utils.graphics.bitmap;
20 import ae.utils.graphics.color;
21 import ae.utils.graphics.image;
22 
23 private struct VideoStreamImpl
24 {
25 	@property ref Image!BGR front() return
26 	{
27 		return frame;
28 	}
29 
30 	@property bool empty() { return done; }
31 
32 	void popFront()
33 	{
34 		auto stream = pipes.stdout;
35 		auto headerBuf = frameBuf[0..Header.sizeof];
36 		if (!stream.readExactly(headerBuf))
37 		{
38 			done = true;
39 			return;
40 		}
41 
42 		auto pHeader = cast(Header*)headerBuf.ptr;
43 		frameBuf.length = pHeader.bfSize;
44 		auto dataBuf = frameBuf[Header.sizeof..$];
45 		enforce(stream.readExactly(dataBuf), "Unexpected end of stream");
46 
47 		frameBuf.parseBMP!BGR(frame);
48 	}
49 
50 	@disable this(this);
51 
52 	~this()
53 	{
54 		if (done)
55 			wait(pipes.pid);
56 		else
57 		{
58 			if (!tryWait(pipes.pid).terminated)
59 			{
60 				try
61 					kill(pipes.pid);
62 				catch (ProcessException e)
63 				{}
64 			}
65 
66 			version(Posix)
67 			{
68 				import core.sys.posix.signal : SIGKILL;
69 				if (!tryWait(pipes.pid).terminated)
70 				{
71 					try
72 						kill(pipes.pid, SIGKILL);
73 					catch (ProcessException e)
74 					{}
75 				}
76 			}
77 
78 			wait(pipes.pid);
79 		}
80 	}
81 
82 	private void initialize(string fn)
83 	{
84 		pipes = pipeProcess([
85 			"ffmpeg",
86 			// Be quiet
87 			"-loglevel", "panic",
88 			// Specify input
89 			"-i", fn,
90 			// No audio
91 			"-an",
92 			// Specify output codec
93 			"-vcodec", "bmp",
94 			// Specify output format
95 			"-f", "image2pipe",
96 			// Specify output
97 			"-"
98 		], Redirect.stdout);
99 
100 		frameBuf.length = Header.sizeof;
101 
102 		popFront();
103 	}
104 
105 private:
106 	import std.process;
107 
108 	ProcessPipes pipes;
109 	bool done;
110 
111 	alias BitmapHeader!3 Header;
112 	ubyte[] frameBuf;
113 	Image!BGR frame;
114 }
115 
116 struct VideoStream
117 {
118 	RefCounted!VideoStreamImpl impl;
119 	this(string fn) { impl.initialize(fn); }
120 	@property ref Image!BGR front() return { return impl.front; }
121 	@property bool empty() { return impl.empty; }
122 	void popFront() { impl.popFront(); }
123 }
124 //alias RefCounted!VideoStreamImpl VideoStream;
125 
126 VideoStream streamVideo(string fn) { return VideoStream(fn); }
127 
128 private:
129 
130 import std.stdio;
131 
132 bool readExactly(ref File f, ubyte[] buf)
133 {
134 	auto read = f.rawRead(buf);
135 	if (read.length==0) return false;
136 	enforce(read.length == buf.length, "Unexpected end of stream");
137 	return true;
138 }