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 VideoInputStreamImpl 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 headerBuf = frameBuf[0..Header.sizeof]; 35 if (!output.readExactly(headerBuf)) 36 { 37 done = true; 38 return; 39 } 40 41 auto pHeader = cast(Header*)headerBuf.ptr; 42 frameBuf.length = pHeader.bfSize; 43 auto dataBuf = frameBuf[Header.sizeof..$]; 44 enforce(output.readExactly(dataBuf), "Unexpected end of stream"); 45 46 if (pHeader.bcBitCount == 32) 47 { 48 // discard alpha 49 auto frameAlpha = frameBuf.viewBMP!BGRX(); 50 frameAlpha.colorMap!(c => BGR(c.b, c.g, c.r)).copy(frame); 51 } 52 else 53 frameBuf.parseBMP!BGR(frame); 54 } 55 56 @disable this(this); 57 58 ~this() 59 { 60 if (done) 61 wait(pid); 62 else 63 { 64 if (!tryWait(pid).terminated) 65 { 66 try 67 kill(pid); 68 catch (ProcessException e) 69 {} 70 } 71 72 version(Posix) 73 { 74 import core.sys.posix.signal : SIGKILL; 75 if (!tryWait(pid).terminated) 76 { 77 try 78 kill(pid, SIGKILL); 79 catch (ProcessException e) 80 {} 81 } 82 } 83 84 wait(pid); 85 } 86 } 87 88 private void initialize(File f, string fn, string[] ffmpegArgs) 89 { 90 auto pipes = pipe(); 91 output = pipes.readEnd(); 92 auto args = [ 93 "ffmpeg", 94 // Be quiet 95 "-loglevel", "panic", 96 // Specify input 97 "-i", fn, 98 // No audio 99 "-an", 100 // Specify output codec 101 "-vcodec", "bmp", 102 // Specify output format 103 "-f", "image2pipe", 104 // Additional arguments 105 ] ~ ffmpegArgs ~ [ 106 // Specify output 107 "-" 108 ]; 109 debug(FFMPEG) stderr.writeln(args.escapeShellCommand); 110 pid = spawnProcess(args, f, pipes.writeEnd); 111 112 frameBuf.length = Header.sizeof; 113 114 popFront(); 115 } 116 117 private: 118 import std.process; 119 120 Pid pid; 121 File output; 122 bool done; 123 124 alias BitmapHeader!3 Header; 125 ubyte[] frameBuf; 126 Image!BGR frame; 127 } 128 129 struct VideoInputStream 130 { 131 RefCounted!VideoInputStreamImpl impl; 132 this(File f, string[] ffmpegArgs) { impl.initialize(f, "-", ffmpegArgs); } 133 this(string fn, string[] ffmpegArgs) { impl.initialize(stdin, fn, ffmpegArgs); } 134 @property ref Image!BGR front() return { return impl.front; } 135 @property bool empty() { return impl.empty; } 136 void popFront() { impl.popFront(); } 137 } 138 //alias RefCounted!VideoStreamImpl VideoStream; 139 deprecated alias VideoStream = VideoInputStream; 140 141 VideoInputStream streamVideo(File f, string[] ffmpegArgs = null) { return VideoInputStream(f, ffmpegArgs); } 142 VideoInputStream streamVideo(string fn, string[] ffmpegArgs = null) { return VideoInputStream(fn, ffmpegArgs); } 143 144 // ---------------------------------------------------------------------------- 145 146 struct VideoOutputStream 147 { 148 void put(ref Image!BGR frame) 149 { 150 output.rawWrite(frame.toBMP); 151 } 152 153 @disable this(this); 154 155 ~this() 156 { 157 output.close(); 158 wait(pid); 159 } 160 161 private this(File f, string fn, string[] ffmpegArgs, string[] inputArgs) 162 { 163 auto pipes = pipe(); 164 output = pipes.writeEnd; 165 auto args = [ 166 "ffmpeg", 167 // Additional input arguments (such as -framerate) 168 ] ~ inputArgs ~ [ 169 // // Be quiet 170 // "-loglevel", "panic", 171 // Specify input format 172 "-f", "image2pipe", 173 // Specify input 174 "-i", "-", 175 // Additional arguments 176 ] ~ ffmpegArgs ~ [ 177 // Specify output 178 fn 179 ]; 180 debug(FFMPEG) stderr.writeln(args.escapeShellCommand); 181 pid = spawnProcess(args, pipes.readEnd, f); 182 } 183 184 this(File f, string[] ffmpegArgs = null, string[] inputArgs = null) 185 { 186 this(f, "-", ffmpegArgs, inputArgs); 187 } 188 189 this(string fn, string[] ffmpegArgs = null, string[] inputArgs = null) 190 { 191 this(stdin, fn, ffmpegArgs, inputArgs); 192 } 193 194 private: 195 import std.process; 196 197 Pid pid; 198 File output; 199 bool done; 200 201 alias BitmapHeader!3 Header; 202 Image!BGR frame; 203 } 204 205 // ---------------------------------------------------------------------------- 206 207 private: 208 209 import std.stdio; 210 211 bool readExactly(ref File f, ubyte[] buf) 212 { 213 auto read = f.rawRead(buf); 214 if (read.length==0) return false; 215 enforce(read.length == buf.length, "Unexpected end of stream"); 216 return true; 217 }