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