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