videocapture.videocapture - Videocapture

Table of Contents

1 Namespace: videocapture.videocapture

This namespace manages the GStreamer pipeline separated from the GUI.

2 Types

Name Format Description Generate?
::state any?   x
::imagecontainer (partial instance? ImageContainer)   x
:gstreamer/pipeline      
:videocapture.pipeparser/appcaps      

3 Helper functions

(defn- connect-source "Connects the source configured in settings.edn to the provided GStreamer pad."
  [[pad index]]
  {:pre [(s/valid? :gstreamer/element pad) (number? index)]}
  (let [camera (nth (settings/get-setting :camera) index)
	src-type (:type camera) ;;(settings/get-setting :camera :type)
	src-address (:address camera) ;;(settings/get-setting :camera :address)
	src-username (:username camera) ;;(settings/get-setting :camera :username)
	src-password (:password camera) ;;(settings/get-setting :camera :password)
	]
    (debug "Connecting to device" src-address)
    (case src-type
      :rtsp (do (debug "Connecting to" (str "rtsp://" src-username ":" src-password "@" src-address))
		(.set ^Element pad "location" (str "rtsp://" src-address))
		(.set ^Element pad "user-id" src-username)
		(.set ^Element pad "user-pw" src-password))
      :device (do (debug "Connecting to" src-address)
		  (.set ^Element pad "device" src-address)))))

4 Recording

(defn record [filename]
  (let [pipe (:pipeline @vcapture)
	appcaps (:appcaps @vcapture)
	endpoint (second (first (:endpoint appcaps)))
	fileout (ElementFactory/make "filesink" "fileout")
	valves (map second (:storage-valve appcaps))
	extension (settings/get-setting :C :file-extension)]
    (.set fileout "location" (str filename extension))
    (.addMany pipe (into-array Element [fileout]))
    (Element/linkMany (into-array Element [endpoint fileout]))
    (doall (map #(.set % "drop" false) valves))
    (async/thread (try (.play pipe)
		       (catch Exception e (error e))))
    (reset! (:state @vcapture) :recording)
    (reset! (:target-file @vcapture) (str filename extension))))
  (defn fpath [filename]
    (.toPath (io/file filename)))

  (defn recording-stop [filename]
    (let [tempfile (str (settings/get-setting :xdg :home) "tmp" (settings/get-setting :C :file-extension))]
      (debug "Moving" filename "to" tempfile "...")
      (Files/move (fpath filename) (fpath tempfile) (into-array java.nio.file.StandardCopyOption [java.nio.file.StandardCopyOption/ATOMIC_MOVE]))
      (debug "Setting correcting timestamps...")
      (sh/sh "ffmpeg" "-i" tempfile "-c" "copy" "-fflags" "+genpts" filename)
      (debug "Cleaning up.")
      (Files/deleteIfExists (fpath tempfile))
))

5 Pipeline state

(defn- start-pipeline []
  (let [gst-listener (new AppSinkListener)
	image-container (.getImageContainer gst-listener)
	[^Pipeline pipe appcaps] (pipeparser/build-pipeline :recording)
	sourcepad (s/conform (s/coll-of :gstreamer/element) (map second (:source appcaps)))
	videosink (s/conform :gstreamer/element (.getElementByName pipe "displaysink"))]
    (debug "Camera:" (settings/get-setting :camera))
    (debug "Appcaps:" appcaps)
    (debug "Sourcepad:" (pr-str sourcepad))
    (doall (map connect-source (apply assoc {} (interleave sourcepad (range)))))
    (.connect videosink gst-listener)
    (async/thread (.play pipe))
    {:pipeline pipe
     :appcaps appcaps
     :image-container image-container
     :target-file (atom "")
     :state (atom :preview)}))

(defn- stop-pipeline [vcapture]
  (let [pipe (:pipeline @vcapture)
	state @(:state @vcapture)
	filename @(:target-file @vcapture)
	eosreceiver (map second (:storage-valve (:appcaps @vcapture)))
	]
    (debug "Stopping pipeline" pipe)
    (debug "Recording state was" state)
    (.set pipe "message-forward" true)
    (.connect (.getBus pipe) (fx/fi org.freedesktop.gstreamer.Bus$EOS
				    [src] 
				    (debug "Stopping preview player...")
				    (.stop pipe)
				    (try
				      (recording-stop filename)
				      (info "Preview player stopped")
				      (catch Exception e (error e)))
				    ))
    (debug "Sending EOS to" (pr-str eosreceiver))
    (async/thread (doall (map #(.sendEvent % (new org.freedesktop.gstreamer.event.EOSEvent)) eosreceiver)))
    (.sendEvent pipe (new org.freedesktop.gstreamer.event.EOSEvent))
    ))

(defstate vcapture
	  :start (start-pipeline)
	  :stop (stop-pipeline vcapture))

6 Complete namespace definition

(ns videocapture.videocapture
  "Manages the GStreamer pipeline separated from the GUI."
  (:require [mount.core :as mount :refer [defstate]]
	    [clojurefx.clojurefx :as fx]
	    [videocapture.pipeparser :as pipeparser]
	    [videocapture.settings :as settings]
	    [clojure.java.shell :as sh]
	    [clojure.java.io :as io]
	    [clojure.core.async :as async :refer [chan]]
	    [clojure.spec.alpha :as s]
	    [taoensso.timbre :as timbre
	     :refer [log trace debug info warn error fatal report
		     logf tracef debugf infof warnf errorf fatalf reportf
		     spy get-env]])
  (:import (org.freedesktop.gstreamer ElementFactory Element Pipeline)
	   (videocapture AppSinkListener ImageContainer)
	   (clojure.lang Atom)
	   (java.nio.file Files)))

(declare vcapture)

<<types>>

<<helper-fns>>

<<recording>>

<<recording-stop>>

<<pipeline-state>>

Author: Daniel Ziltener

Created: 2019-06-21 Fr 16:22

Validate