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 <ae@cy.md> 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 /// Represents a video stream as a D range of frames. 132 struct VideoInputStream 133 { 134 private RefCounted!VideoInputStreamImpl impl; 135 this(File f, string[] ffmpegArgs) { impl.initialize(f, "-", ffmpegArgs); } /// 136 this(string fn, string[] ffmpegArgs) { impl.initialize(stdin, fn, ffmpegArgs); } /// 137 @property ref Image!BGR front() return { return impl.front; } /// 138 @property bool empty() { return impl.empty; } /// 139 void popFront() { impl.popFront(); } /// 140 } 141 //alias RefCounted!VideoStreamImpl VideoStream; 142 deprecated alias VideoStream = VideoInputStream; 143 144 /// Creates a `VideoInputStream` from the given file. 145 VideoInputStream streamVideo(File f, string[] ffmpegArgs = null) { return VideoInputStream(f, ffmpegArgs); } 146 VideoInputStream streamVideo(string fn, string[] ffmpegArgs = null) { return VideoInputStream(fn, ffmpegArgs); } /// ditto 147 148 // ---------------------------------------------------------------------------- 149 150 /// Represents a video encoding process as a D output range of frames. 151 struct VideoOutputStream 152 { 153 void put(ref Image!BGR frame) 154 { 155 output.rawWrite(frame.toBMP); 156 } /// 157 158 @disable this(this); 159 160 ~this() 161 { 162 output.close(); 163 wait(pid); 164 } 165 166 private this(File f, string fn, string[] ffmpegArgs, string[] inputArgs) 167 { 168 auto pipes = pipe(); 169 output = pipes.writeEnd; 170 auto args = [ 171 "ffmpeg", 172 // Additional input arguments (such as -framerate) 173 ] ~ inputArgs ~ [ 174 // // Be quiet 175 // "-loglevel", "panic", 176 // Specify input format 177 "-f", "image2pipe", 178 // Specify input 179 "-i", "-", 180 // Additional arguments 181 ] ~ ffmpegArgs ~ [ 182 // Specify output 183 fn 184 ]; 185 debug(FFMPEG) stderr.writeln(args.escapeShellCommand); 186 pid = spawnProcess(args, pipes.readEnd, f); 187 } 188 189 /// Begin encoding to the given file with the given parameters. 190 this(File f, string[] ffmpegArgs = null, string[] inputArgs = null) 191 { 192 this(f, "-", ffmpegArgs, inputArgs); 193 } 194 195 this(string fn, string[] ffmpegArgs = null, string[] inputArgs = null) 196 { 197 this(stdin, fn, ffmpegArgs, inputArgs); 198 } /// ditto 199 200 private: 201 import std.process; 202 203 Pid pid; 204 File output; 205 bool done; 206 207 alias BitmapHeader!3 Header; 208 Image!BGR frame; 209 }