Skip to content

Commit

Permalink
Refactoring. Added GraphicsMagick module.
Browse files Browse the repository at this point in the history
  • Loading branch information
interkosmos committed Oct 19, 2024
1 parent 050ed5f commit c9b633a
Show file tree
Hide file tree
Showing 8 changed files with 641 additions and 197 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ SRC = $(SRCDIR)/dm_ansi.f90 \
$(SRCDIR)/dm_geocom_error.f90 \
$(SRCDIR)/dm_geocom_type.f90 \
$(SRCDIR)/dm_geojson.f90 \
$(SRCDIR)/dm_gm.f90 \
$(SRCDIR)/dm_hash.f90 \
$(SRCDIR)/dm_hash_table.f90 \
$(SRCDIR)/dm_hdf5.f90 \
Expand Down Expand Up @@ -345,6 +346,7 @@ OBJ = dm_ansi.o \
dm_geocom_error.o \
dm_geocom_type.o \
dm_geojson.o \
dm_gm.o \
dm_hash.o \
dm_hash_table.o \
dm_hdf5.o \
Expand Down Expand Up @@ -638,9 +640,10 @@ $(OBJ): $(SRC)
$(FC) $(FFLAGS) $(LDFLAGS) -c src/dm_mqueue_util.f90
$(FC) $(FFLAGS) $(LDFLAGS) -c src/dm_modbus.f90
$(FC) $(FFLAGS) $(LDFLAGS) -c src/dm_crypto.f90
$(FC) $(FFLAGS) $(LDFLAGS) -c src/dm_im.f90
$(FC) $(FFLAGS) $(LDFLAGS) -c src/dm_image.f90
$(FC) $(FFLAGS) $(LDFLAGS) -c src/dm_gm.f90
$(FC) $(FFLAGS) $(LDFLAGS) -c src/dm_camera.f90
$(FC) $(FFLAGS) $(LDFLAGS) -c src/dm_im.f90
$(FC) $(FFLAGS) $(LDFLAGS) -c src/dmpack.f90

# Static library `libdmpack.a`.
Expand Down
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,25 @@ Third-party dependencies have to be present to build and run the software of
this package:

* FastCGI
* Gnuplot
* HDF5
* LAPACK
* libcurl
* libmodbus
* libstrophe
* Lua 5.4
* PCRE2
* SQLite 3
* libcurl
* libmodbus
* libstrophe
* zlib
* zstd

Optionally, for client-side camera access:
On Linux, development headers are required for compilation. It is recommended
to additionally install:

* FFmpeg
* Gnuplot
* GraphicsMagick

On Linux, additional development headers are required for the build step. To
generate the [man pages](adoc/README.md), the [user guide](guide/README.md),
To generate the [man pages](adoc/README.md), the [user guide](guide/README.md),
and the source code documentation, you will need furthermore:

* [AsciiDoctor](https://asciidoctor.org/), [Pygments](https://pygments.org/), and
Expand Down Expand Up @@ -218,6 +218,14 @@ containing the **DMPACK** module files is passed through argument `-I`.
| `dm_zlib` | zlib | `pkg-config --libs zlib` |
| `dm_zstd` | zstd | `pkg-config --libs libzstd` |

Some modules use standard input/output to communicate with the following external programs:

| Module | Program | Default Binary Name |
|-----------------|---------------------|---------------------------------------------------|
| `dm_camera` | FFmpeg | `ffmpeg` |
| `dm_gm` | GraphicsMagick | `gm` |
| `dm_plot` | Gnuplot | `gnuplot` |

## Source Code Structure

| Path | Description |
Expand Down
52 changes: 38 additions & 14 deletions src/dm_camera.f90
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ module dm_camera
!! ```
!!
!! The following example captures an image from an attached USB webcam at
!! `/dev/video0` and adds a timestamp in ISO 8601 format to it:
!! `/dev/video0` and adds a timestamp in ISO 8601 format to it using
!! GraphicMagick:
!!
!! ```fortran
!! character(len=*), parameter :: IMAGE_PATH = '/tmp/image.jpg'
!!
!! integer :: width, height
!! integer :: rc
!! type(camera_type) :: camera
!!
Expand All @@ -35,13 +35,8 @@ module dm_camera
!! rc = dm_camera_capture(camera, IMAGE_PATH)
!! if (dm_is_error(rc)) call dm_error_out(rc)
!!
!! rc = dm_image_add_text_box(IMAGE_PATH, text=dm_time_now())
!! rc = dm_gm_add_text_box(IMAGE_PATH, text=dm_time_now())
!! if (dm_is_error(rc)) call dm_error_out(rc)
!!
!! rc = dm_image_get_dimensions(IMAGE_PATH, width, height)
!! if (dm_is_error(rc)) call dm_error_out(rc)
!!
!! print '("image dimensions: ", i0, "x", i0)', width, height
!! ```
!!
!! Change the camera type to capture an RTSP stream instead:
Expand All @@ -50,7 +45,7 @@ module dm_camera
!! camera = camera_type(input='rtsp://10.10.10.15:8554/camera1', device=CAMERA_DEVICE_RTSP)
!! ```
!!
!! The attribute `input` must be the URL of the stream.
!! The attribute `input` must be set to the stream URL.
use :: dm_error
use :: dm_file
use :: dm_string
Expand All @@ -63,9 +58,14 @@ module dm_camera
integer, parameter, public :: CAMERA_DEVICE_V4L = 2 !! USB webcam via Video4Linux2.
integer, parameter, public :: CAMERA_DEVICE_LAST = 2 !! Never use this.

integer, parameter, public :: CAMERA_COMMAND_LEN = FILE_PATH_LEN !! Max. length of command string.
integer, parameter, public :: CAMERA_DEVICE_NAME_LEN = 4

character(len=*), parameter :: CAMERA_FFMPEG = 'ffmpeg' !! FFmpeg binary name.
character(len=*), parameter, public :: CAMERA_DEVICE_NAMES(CAMERA_DEVICE_NONE:CAMERA_DEVICE_LAST) = [ &
character(len=CAMERA_DEVICE_NAME_LEN) :: 'none', 'rtsp', 'v4l' &
] !! Camera device names.

character(len=*), parameter :: CAMERA_BINARY = 'ffmpeg' !! FFmpeg binary name.
integer, parameter :: CAMERA_COMMAND_LEN = FILE_PATH_LEN !! Max. length of command string.

type, public :: camera_type
!! Camera settings type.
Expand All @@ -76,10 +76,14 @@ module dm_camera
end type camera_type

public :: dm_camera_capture
public :: dm_camera_device_from_name
public :: dm_camera_device_is_valid

private :: camera_prepare_capture
contains
! **************************************************************************
! PUBLIC PROCEDURES.
! **************************************************************************
integer function dm_camera_capture(camera, output, command) result(rc)
!! Captures a single frame from a V4L device or RTSP stream with
!! FFmpeg, and optionally adds a timestamp with GraphicsMagick. If the
Expand Down Expand Up @@ -119,14 +123,34 @@ integer function dm_camera_capture(camera, output, command) result(rc)
if (present(command)) command = trim(command_)
end function dm_camera_capture

logical function dm_camera_device_is_valid(device) result(is)
pure elemental integer function dm_camera_device_from_name(name) result(device)
!! Returns device enumerator from name. On error, the result is
!! `CAMERA_DEVICE_NONE`.
character(len=*), intent(in) :: name !! Device name.

character(len=CAMERA_DEVICE_NAME_LEN) :: name_

! Normalise name.
name_ = dm_to_lower(name)

select case (name_)
case (CAMERA_DEVICE_NAMES(CAMERA_DEVICE_RTSP)); device = CAMERA_DEVICE_RTSP
case (CAMERA_DEVICE_NAMES(CAMERA_DEVICE_V4L)); device = CAMERA_DEVICE_V4L
case default; device = CAMERA_DEVICE_NONE
end select
end function dm_camera_device_from_name

pure elemental logical function dm_camera_device_is_valid(device) result(is)
!! Returns `.true.` if device enumerator is valid. The device
!! `CAMERA_DEVICE_NONE` is invalid.
integer, intent(in) :: device !! Camera device enumerator.

is = (device > CAMERA_DEVICE_NONE .and. device <= CAMERA_DEVICE_LAST)
end function dm_camera_device_is_valid

! **************************************************************************
! PRIVATE PROCEDURES.
! **************************************************************************
subroutine camera_prepare_capture(command, camera, output)
!! Creates FFmpeg command to capture a single camera frame through V4L
!! or RTSP. The function returns `E_INVALID` on error.
Expand All @@ -137,7 +161,7 @@ subroutine camera_prepare_capture(command, camera, output)
character(len=32) :: video_size

! Disable logging and set output file.
command = ' -hide_banner -loglevel fatal -nostats -y ' // output
command = ' -hide_banner -loglevel quiet -nostats -y ' // output

select case (camera%device)
case (CAMERA_DEVICE_RTSP)
Expand All @@ -157,6 +181,6 @@ subroutine camera_prepare_capture(command, camera, output)
end select

! Concatenate command string.
command = CAMERA_FFMPEG // trim(command)
command = CAMERA_BINARY // trim(command)
end subroutine camera_prepare_capture
end module dm_camera
Loading

0 comments on commit c9b633a

Please sign in to comment.