0

I want my Clojure program to "look out at the world" through the camera. I found a Github project that does the work and is even written in Java. I got it to work using a 3-line piece of Java code. Then I thought, "I'll just convert it to Clojure using Java interop and go from there." But I don't know how.

Here's the Java code that read the camera, and wrote a one-frame JPG file to disk:

import com.github.sarxos.webcam.Webcam;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;

public class WebcamTutorial {

    public static void main (String[] args) {
        Webcam webcam = Webcam.getDefault();
        webcam.open();
        try {
            ImageIO.write(webcam.getImage(), "JPG", new File("C:\\Users\\Joe User\\Desktop\\firstCapture.JPG"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Here's my Clojure attempt at doing the same thing:

(ns webcam2.core
  (:import java.io.File)
  (:import java.io.IOException)
  (:import javax.imageio.ImageIO)
  (:import com.github.sarxos.webcam.Webcam)

  (defn -main []
        ((let [webcam (Webcam. (getDefault.))]
           (webcam. open.)
           (ImageIO.write. webcam. getImage. "JPG", new .File ("C:\\Users\\Joe User\\Desktop\\firstClojureCapture.JPG"))
           )
         )
        )
  )

I'm getting "Unused Imports" errors on the 4 :import statements. And most vexing, I also get the error

com.github.sarxos.webcam.Webcam cannot be resolved

I do not know how to import a Github project into Clojure, or even if such a thing is possible.

7
  • 2
    How do you make the webcam libs available to clojure? Commented Mar 18 at 16:01
  • 3
    What build tool are you using? Are the deps added there? The java there (roughly) translates to (let [webcam (doto (Webcam/getDefault) (.open))] (ImageIO/write (.getImage webcam) "JPG" (File. "/tmp/capture.jpg"))) -- but I highly recommend to familiarize yourself with how Java Interop works. Commented Mar 18 at 16:07
  • The Webcam lib is on GitHub, here: github.com/sarxos/webcam-capture Commented Mar 19 at 14:13
  • I'm sorry I forgot to mention I'm using the Clojure add-on to IntelliJ IDEA as a development environment. Commented Mar 19 at 14:16
  • I was trying on my macos - arm64 architecture - that became very complicated - because the package require x86_64 architecture ... Commented Mar 19 at 17:08

1 Answer 1

1

In Mac, I couldn't make it work because I have arm64 architecture (M3).

So I tried it in ubuntu and it worked.

Java

The detailed steps to make java run were:

# test that java is running
java -version

openjdk version "11.0.26" 2025-01-21
OpenJDK Runtime Environment (build 11.0.26+4-post-Ubuntu-1ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.26+4-post-Ubuntu-1ubuntu122.04, mixed mode, sharing)

which java # where is it running?

/usr/bin/java

# maven was not installed so installed it
sudo apt install maven

mvn -version

Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 11.0.26, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-52-generic", arch: "amd64", family: "unix"

# then I created a directory
mkdir -p ~/projects/java/
cd ~/projects/java

# and generated with maven a project called "webcam-test"

mvn archetype:generate -DgroupId=com.example -DartifactId=webcam-test -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

# which generated a folder with the structure

webcam-test
├── firstCapture.JPG
├── pom.xml
├── src/main/java/com/example/App.java

# nano pom.xml

# open pom.xml and add inside <dependencies></dependencies> this section:

<dependencies>



    <dependency>
        <groupId>com.github.sarxos</groupId>
        <artifactId>webcam-capture</artifactId>
        <version>0.3.12</version>
    </dependency>
    
    
    
</dependencies>

# and at the same level like <dependencies> add a section

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>11</source>
                <target>11</target>
            </configuration>
        </plugin>
    </plugins>
</build>

# this avoids an error otherwise occurring - since java -version gave 11 as version
# it was important to ensure that 11 is used.

# so the full file content pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>webcam-test</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>webcam-test</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.github.sarxos</groupId>
        <artifactId>webcam-capture</artifactId>
        <version>0.3.12</version>
    </dependency>
  </dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>11</source>
                <target>11</target>
            </configuration>
        </plugin>
    </plugins>
</build>
</project>

# save and close it.

# then:
# nano src/main/java/com/example/App.java

# and enter the content:

package com.example;

import com.github.sarxos.webcam.Webcam;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;

public class App {
    public static void main(String[] args) {
        Webcam webcam = Webcam.getDefault();

        if (webcam == null) {
            System.out.println("No webcam detected!");
            return;
        }

        webcam.open();
        try {
            ImageIO.write(webcam.getImage(), "JPG", new File("firstCapture.JPG"));
            System.out.println("Image captured successfully!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            webcam.close();
        }
    }
}


# that is the script for ubuntu.

# save and close.

To build and run, from inside the webcam-test folder, run:

# build
mvn package

# run
mvn exec:java -Dexec.mainClass="com.example.App"

This should capture a picture and save as firstCapture.JPG.

Clojure

For Clojure, we want to ensure that Clojure uses the same Java. Therefore we define JAVA_HOME and add the binary folder into PATH:

export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH

To find out your exact path, do:

ls -l /usr/lib/jvm

and check the current running java version with

java -version

For me, this gave:

lrwxrwxrwx 1 root root   25 Mär 24  2022 default-java -> java-1.11.0-openjdk-amd64
lrwxrwxrwx 1 root root   21 Jul 21  2024 java-1.11.0-openjdk-amd64 -> java-11-openjdk-amd64
drwxr-xr-x 7 root root 4096 Mär 11 14:23 java-11-openjdk-amd64

# and:
openjdk version "11.0.26" 2025-01-21
OpenJDK Runtime Environment (build 11.0.26+4-post-Ubuntu-1ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.26+4-post-Ubuntu-1ubuntu122.04, mixed mode, sharing)

Don't do

export JAVA_HOME=$(which java)

I did it initially but that results in problems later. It should be a /usr/lib/jvm path.

Generate the folder and files:

mkdir -p ~/projects/clojure/clojure-webcam
cd ~/projects/clojure/clojure-webcam

Create deps.edn: nano deps.edn

{
  :paths ["src"]
  :deps {
    org.clojure/clojure {:mvn/version "1.11.1"}
    com.github.sarxos/webcam-capture {:mvn/version "0.3.12"}
  }
}

You might need different version numbers in future. Today is March 20 2025

Create then the code folder and file:

mkdir -p src
nano src/webcam.clj

Enter the content:

(ns webcam
  (:import [com.github.sarxos.webcam Webcam]
           [javax.imageio ImageIO]
           [java.io File]))

(defn capture-image []
  (let [webcam (Webcam/getDefault)]
    (if (nil? webcam)
      (do
        (println "No webcam found!")
        (System/exit 1)) ;; Exit with error code
      (try
        (.open webcam)
        (ImageIO/write (.getImage webcam) "JPG" (File. "clojure-webcam.jpg"))
        (println "Image captured!")
        (catch Exception e
          (println "Error capturing image:" (.getMessage e)))
        (finally
          (when (.isOpen webcam)
            (.close webcam))))))) ;; Ensure webcam closes

(defn -main []
  (capture-image)
  (shutdown-agents) ;; Prevent lingering threads
  (System/exit 0))  ;; Exit gracefully

The try - catch - finally was necessary because some error occured while closing.

Alternatively use with-open

(ns webcam
  (:import [com.github.sarxos.webcam Webcam]
           [javax.imageio ImageIO]
           [java.io File Closeable]))

(defn capture-image []
  (let [webcam (Webcam/getDefault)]
    (if (nil? webcam)
      (do
        (println "No webcam found!")
        (System/exit 1))
      (with-open [w webcam]
        (.open w)
        (ImageIO/write (.getImage w) "JPG" (File. "clojure-webcam.jpg"))
        (println "Image captured!")))))

(defn -main []
  (capture-image)
  (shutdown-agents)
  (System/exit 0))

You can test first with

(let [webcam (Webcam/getDefault)]   
  (println "Implements Closeable? " (instance? java.io.Closeable webcam))      
  (println "Implements AutoCloseable? " (instance? java.lang.AutoCloseable webcam)))

Whether this would work. I didn't test the with-clojure expression.

Run the clojure program by:

clojure -M -m webcam

Error occurring without finally clause

Without the finally clause, I had this error occuring:

:~/projects/clj/clojure-webcam$ clojure -M -m webcam
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Image captured!
Execution error (NullPointerException) at clojure.main/main (main.java:40).
null

Full report at:
/tmp/clojure-18393721443446853860.edn

And the report's content was:

  GNU nano 6.2                                      /tmp/clojure-18393721443446853860.edn                                                
{:clojure.main/message
 "Execution error (NullPointerException) at clojure.main/main (main.java:40).\nnull\n",
 :clojure.main/triage
 {:clojure.error/class java.lang.NullPointerException,
  :clojure.error/line 40,
  :clojure.error/symbol clojure.main/main,
  :clojure.error/source "main.java",
  :clojure.error/phase :execution},
 :clojure.main/trace
 {:via
  [{:type java.lang.NullPointerException,
    :at [clojure.core$apply invokeStatic "core.clj" 667]}],
  :trace
  [[clojure.core$apply invokeStatic "core.clj" 667]
   [clojure.main$main_opt invokeStatic "main.clj" 514]
   [clojure.main$main_opt invoke "main.clj" 510]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]]}}

And the original code with this error was:

(ns webcam
  (:import [com.github.sarxos.webcam Webcam]
           [javax.imageio ImageIO]
           [java.io File]))

(defn capture-image []
  (let [webcam (Webcam/getDefault)]
    (if (nil? webcam)
      (println "No webcam found!")
      (do
        (.open webcam)
        (ImageIO/write (.getImage webcam) "JPG" (File. "clojure-webcam.jpg"))
        (println "Image captured!")
        (.close webcam)))))

(capture-image)

I didn't figured out what the error was exactly - maybe someone has an idea?

Anyhow that's it - this works in ubuntu.

Sign up to request clarification or add additional context in comments.

4 Comments

You should be able to use with-open (it's roughly the try-with-resource from newer java, which OPs example did not even use). But if the .isOpen check there is needed, then the library might not handle it properly and you are better off doing it yourself.
Ah true thanks!
@cfrick not sure if it implements java.io.Closeable - I guess not. One could test it with: (let [webcam (Webcam/getDefault)] (println "Implements Closeable? " (instance? java.io.Closeable webcam)) (println "Implements AutoCloseable? " (instance? java.lang.AutoCloseable webcam)))
Gwang-Jin Kim...thank you SO MUCH for all the work you put into your answer. I learned about 'dependencies.' One step closer to the top of the mountain, due to your help. Much more to go, but I'll close this question now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.