1212import java .io .IOException ;
1313import java .io .InputStream ;
1414import java .io .InputStreamReader ;
15+ import java .util .ArrayList ;
16+ import java .util .List ;
17+ import java .util .Map ;
1518import java .util .concurrent .ExecutorService ;
1619import java .util .concurrent .Executors ;
1720import java .util .concurrent .ThreadFactory ;
2427import org .slf4j .LoggerFactory ;
2528
2629import com .github .sarxos .webcam .WebcamDevice ;
30+ import com .github .sarxos .webcam .WebcamDevice .Configurable ;
2731import com .github .sarxos .webcam .WebcamExceptionHandler ;
2832import com .github .sarxos .webcam .WebcamResolution ;
2933
3034
31- public class FsWebcamDevice implements WebcamDevice {
35+ public class FsWebcamDevice implements WebcamDevice , Configurable {
36+
37+ public static final String PARAM_KEY_COMPRESSION = "compression" ;
38+ public static final String PARAM_KEY_FORMAT = "format" ;
39+ public static final String PARAM_KEY_SKIP = "skip" ;
40+ public static final String PARAM_KEY_FRAMES = "frames" ;
41+ public static final String PARAM_KEY_LOG = "log" ;
42+ public static final String PARAM_KEY_VERBOSE = "verbose" ;
3243
3344 public static final class ExecutorThreadFactory implements ThreadFactory {
3445
@@ -89,6 +100,7 @@ public void run() {
89100
90101 private final File vfile ;
91102 private final String name ;
103+ private long counter ;
92104
93105 private Dimension resolution = null ;
94106 private Process process = null ;
@@ -99,6 +111,13 @@ public void run() {
99111 private AtomicBoolean open = new AtomicBoolean (false );
100112 private AtomicBoolean disposed = new AtomicBoolean (false );
101113
114+ private String logFilePathString ;
115+ private int frames = 1 ;
116+ private int skip = 0 ;
117+ private String format = "jpeg" ;
118+ private int compression = -1 ;
119+ private boolean verbose = false ;
120+
102121 protected FsWebcamDevice (File vfile ) {
103122 this .vfile = vfile ;
104123 this .name = vfile .getAbsolutePath ();
@@ -175,46 +194,17 @@ private synchronized byte[] readBytes() {
175194
176195 @ Override
177196 public BufferedImage getImage () {
197+ counter ++;
178198
179199 if (!open .get ()) {
180200 return null ;
181201 }
182202
183- //@formatter:off
184- String [] cmd = new String [] {
185- "/usr/bin/fswebcam" ,
186- "--no-banner" , // only image - no texts, banners, etc
187- "--no-shadow" ,
188- "--no-title" ,
189- "--no-subtitle" ,
190- "--no-timestamp" ,
191- "--no-info" ,
192- "--no-underlay" ,
193- "--no-overlay" ,
194- "-d" , vfile .getAbsolutePath (), // input video file
195- "-r" , getResolutionString (), // resolution
196- pipe .getAbsolutePath (), // output file (pipe)
197- LOG .isDebugEnabled () ? "-v" : "" , // enable verbosity if debug mode is enabled
198- };
199- //@formatter:on
200-
201- if (LOG .isDebugEnabled ()) {
202- StringBuilder sb = new StringBuilder ();
203- for (String c : cmd ) {
204- sb .append (c ).append (' ' );
205- }
206- LOG .debug ("Invoking command: {}" , sb .toString ());
207- }
208-
209203 BufferedImage image = null ;
210204
211205 try {
212206
213- process = RT .exec (cmd );
214-
215- // print process output
216- EXECUTOR .execute (new StreamReader (process .getInputStream (), false ));
217- EXECUTOR .execute (new StreamReader (process .getErrorStream (), true ));
207+ executeFsWebcamProcess ();
218208
219209 try {
220210 dis = new DataInputStream (new FileInputStream (pipe ));
@@ -238,14 +228,18 @@ public BufferedImage getImage() {
238228
239229 process .waitFor ();
240230
231+ if (LOG .isDebugEnabled ()) {
232+ LOG .debug ("Image #" +counter +" done" );
233+ }
241234 } catch (IOException e ) {
242- LOG .error ("Process IO exception" , e );
235+ LOG .error ("Process #" + counter + " IO exception" , e );
243236 } catch (InterruptedException e ) {
244237 process .destroy ();
245238 } finally {
246239
247240 try {
248- dis .close ();
241+ if (dis != null )
242+ dis .close ();
249243 } catch (IOException e ) {
250244 throw new RuntimeException (e );
251245 }
@@ -254,13 +248,63 @@ public BufferedImage getImage() {
254248 // call in finally block to reset thread flags
255249
256250 if (Thread .interrupted ()) {
257- throw new RuntimeException ("Thread has been interrupted" );
251+ throw new RuntimeException ("Thread has been interrupted #" + counter );
258252 }
259253 }
260254
261255 return image ;
262256 }
263257
258+ private void executeFsWebcamProcess () throws IOException {
259+ //@formatter:off
260+ List <String > c = new ArrayList <String >(24 );
261+
262+ c .add ("/usr/bin/fswebcam" );
263+ c .add ("--skip" ); // number of skipped images
264+ c .add (String .valueOf (skip ));
265+ c .add ("--frames" ); // number of images merged to the single output (default 1)
266+ c .add (String .valueOf (frames ));
267+ c .add ("--" +format ); // format jpeg | png
268+ c .add (String .valueOf (compression ));
269+ c .add ("--no-banner" ); // only image - no texts, banners, etc
270+ c .add ("--no-shadow" );
271+ c .add ("--no-title" );
272+ c .add ("--no-subtitle" );
273+ c .add ("--no-timestamp" );
274+ c .add ("--no-info" );
275+ c .add ("--no-underlay" );
276+ c .add ("--no-overlay" );
277+ c .add ("--resolution" ); // resolution
278+ c .add (getResolutionString ());
279+ if (verbose ) {
280+ c .add ("--verbose" );
281+ }
282+ if (logFilePathString != null ) {
283+ c .add ("--log" ); // log file
284+ c .add (logFilePathString );
285+ }
286+ c .add ("--device" ); // input video file
287+ c .add (this .vfile .getAbsolutePath ());
288+ c .add (pipe .getAbsolutePath ()); // output file (pipe)
289+ //@formatter:on
290+
291+ String [] cmd = c .toArray (new String [c .size ()]);
292+
293+ if (LOG .isDebugEnabled ()) {
294+ StringBuilder sb = new StringBuilder ();
295+ for (String cc : cmd ) {
296+ sb .append (cc ).append (' ' );
297+ }
298+ LOG .debug ("Invoking command: #" +counter +" \n " + sb .toString ());
299+ }
300+
301+ process = RT .exec (cmd );
302+
303+ // print process output
304+ EXECUTOR .execute (new StreamReader (process .getInputStream (), false ));
305+ EXECUTOR .execute (new StreamReader (process .getErrorStream (), true ));
306+ }
307+
264308 @ Override
265309 public synchronized void open () {
266310
@@ -346,4 +390,50 @@ public boolean isOpen() {
346390 public String toString () {
347391 return "video device " + name ;
348392 }
393+
394+ /**
395+ * Call this method to set device specific parameters.
396+ * Should be called before {@link #open()} method.
397+ * For details about config options, please see fswebcam manual.
398+ * <ul>
399+ * <li>verbose - Boolean type - If true, fswebcam command-line option --verbose is set.
400+ * <li>log - String type - If set, it's passed to fswebcam as value of command-line option --log.
401+ * <li>frames - Integer type - If set, it's passed to fswebcam as value of command-line option --frames.
402+ * <li>skip - Integer type - If set, it's passed to fswebcam as value of command-line option --skip.
403+ * <li>format - String type - Possible values are: "jpeg" (default) | "png". Passed to fswebcam as option: --[format]
404+ * <li>compression - Integer type - Passed to fswebcam together with format --[format] [compression]. Default is -1, which means automatic.
405+ * </ul>
406+ * All Boolean or Integer types may be also specified as String values. E.g. both "true" String or Boolean.TRUE are valid values.
407+ */
408+ @ Override
409+ public void setParameters (Map <String , ?> parameters ) {
410+ if (parameters != null ) {
411+ Object value = null ;
412+ value = parameters .get (PARAM_KEY_VERBOSE );
413+ if (value != null ) {
414+ verbose = Boolean .parseBoolean (String .valueOf (value ));
415+ }
416+ value = parameters .get (PARAM_KEY_LOG );
417+ if (value != null ) {
418+ logFilePathString = String .valueOf (value );
419+ }
420+ value = parameters .get (PARAM_KEY_FRAMES );
421+ if (value != null ) {
422+ frames = Integer .parseInt (String .valueOf (value ));
423+ }
424+ value = parameters .get (PARAM_KEY_SKIP );
425+ if (value != null ) {
426+ skip = Integer .parseInt (String .valueOf (value ));
427+ }
428+ value = parameters .get (PARAM_KEY_FORMAT );
429+ if (value != null ) {
430+ format = String .valueOf (value );
431+ }
432+ value = parameters .get (PARAM_KEY_COMPRESSION );
433+ if (value != null ) {
434+ compression = Integer .parseInt (String .valueOf (value ));
435+ }
436+ }
437+ }
438+
349439}
0 commit comments