javagithubclojureinterop

How do I import a Java project from GitHub into a Clojure project?


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.


Solution

  • 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.