Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,20 @@ permissions: read-all
jobs:

build:


runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Buildah Action
uses: redhat-actions/buildah-build@v2
with:
image: phantom
image: ghcr.io/${{ github.repository }}
tags: ${{ github.sha }}
containerfiles: |
./Containerfile
extra-args: |
--target=build
- name: Run go vet
run: podman run phantom:${{ github.sha }} go vet -v ./...
run: podman run ghcr.io/${{ github.repository }}:${{ github.sha }} go vet -v ./...
- name: Run go test
run: podman run phantom:${{ github.sha }} go test -v ./...
run: podman run ghcr.io/${{ github.repository }}:${{ github.sha }} go test -v ./...

2 changes: 1 addition & 1 deletion Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM docker.io/golang:1.24.2-alpine3.21 AS build
WORKDIR /app
COPY . .
RUN apk add build-base musl-dev opencv-dev icu-libs --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community
RUN go build main.go
RUN go build cmd/main.go

FROM docker.io/alpine:3.21
WORKDIR /app
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,20 @@ stream video from usb device over the web
In order to install opencv4 you can either rely on your system package manager or compile it directly using:

```bash
pushd $GOPATH/gocv.io/x/gocv
cd $GOPATH/gocv.io/x/gocv
make install
popd
```

## Getting started

Build the container

```bash
podman build -t phantom:$(git describe) .
```

Run the container

```bash
podman run --device /dev/video0 -p 8080:8080 localhost/phantom:$(git describe) /app/main
```
23 changes: 0 additions & 23 deletions api/middlewares/recovery.go

This file was deleted.

36 changes: 0 additions & 36 deletions api/server.go

This file was deleted.

33 changes: 0 additions & 33 deletions api/templates/index.html.tpl

This file was deleted.

22 changes: 4 additions & 18 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
package cmd
package main

import (
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/bugbundle/phantom/internal/adapter/http"
)

var rootCmd = &cobra.Command{
Use: "phantom",
Short: "phantom is used to stream video device over internet using multipart",
Run: func(cmd *cobra.Command, args []string) {
cmd.Usage()
},
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
func main() {
http.Server("0.0.0.0:8080")
}
25 changes: 0 additions & 25 deletions cmd/server.go

This file was deleted.

10 changes: 1 addition & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,4 @@ module github.com/bugbundle/phantom

go 1.22.2

require (
github.com/spf13/cobra v1.9.1
gocv.io/x/gocv v0.41.0
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
)
require gocv.io/x/gocv v0.41.0
10 changes: 0 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,2 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gocv.io/x/gocv v0.41.0 h1:KM+zRXUP28b6dHfhy+4JxDODbCNQNtLg8kio+YE7TqA=
gocv.io/x/gocv v0.41.0/go.mod h1:zYdWMj29WAEznM3Y8NsU3A0TRq/wR/cy75jeUypThqU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1 change: 1 addition & 0 deletions internal/adapter/camera/camera.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package camera
51 changes: 51 additions & 0 deletions internal/adapter/http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package http

import (
"fmt"
"log/slog"
"net/http"

"github.com/bugbundle/phantom/internal/adapter/logger"
httpRoutes "github.com/bugbundle/phantom/internal/app/http"
)

func Recovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
err := recover()
if err != nil {
slog.Error(fmt.Sprint(err))

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("{\"error\": \"Internal Server Error\"}"))
}
}()

next.ServeHTTP(w, r)
})
}

func Server(addr string) {
fmt.Println("here")
router := http.NewServeMux()

router.HandleFunc("/", httpRoutes.Homepage)
router.HandleFunc("POST /cameras", httpRoutes.CreateCamera)
router.HandleFunc("GET /cameras/status", httpRoutes.StreamStatus)
router.HandleFunc("DELETE /cameras", httpRoutes.DeleteCamera)
router.HandleFunc("GET /cameras", httpRoutes.StreamVideo)

fmt.Println("hore")
server_config := &http.Server{
Addr: addr,
Handler: logger.LoggingHandler(
Recovery(router),
),
}

slog.Info("Starting server...", "interface", addr)
if err := server_config.ListenAndServe(); err != nil {
slog.Error("An error occured !", "interface", addr, "error", err)
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package middlewares
package logger

import (
"context"
"log/slog"
"net/http"
"os"
)

type statusResponseWriter struct {
http.ResponseWriter
statusCode int
}

func init() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
}

func LoggingHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
statusResponseWriter := &statusResponseWriter{
Expand Down
15 changes: 7 additions & 8 deletions api/routes/stream.go → internal/app/http/http.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package routes
package http

import (
"fmt"
Expand All @@ -10,14 +10,14 @@ import (
"net/textproto"
"time"

"github.com/bugbundle/phantom/api/utils"
"github.com/bugbundle/phantom/internal/port/camera"
"gocv.io/x/gocv"
)

// Return default Homepage, a simple alpineJS application to users
func Homepage(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html")
template, err := template.ParseFiles("api/templates/index.html.tpl")
template, err := template.ParseFiles("templates/index.html.tpl")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
Expand All @@ -30,20 +30,19 @@ func Homepage(w http.ResponseWriter, r *http.Request) {

func StreamStatus(w http.ResponseWriter, r *http.Request) {
// If the camera is unavailable return 428
_, err := utils.GetCamera()
_, err := camera.GetCamera()
if err != nil {
w.Write([]byte("false"))
return
}
w.Write([]byte("true"))
return
}

// This function retrieve camera device and start streaming using multipart/x-mixed-replace
// TODO: Add device number option
func StreamVideo(w http.ResponseWriter, r *http.Request) {
// If the camera is unavailable return 428
webcam, err := utils.GetCamera()
webcam, err := camera.GetCamera()
if err != nil {
w.Header().Set("Content-Type", "application/json")
http.Error(w, "{\"reason\": \"webcam is not started\"}", http.StatusPreconditionRequired)
Expand Down Expand Up @@ -100,15 +99,15 @@ func StreamVideo(w http.ResponseWriter, r *http.Request) {
// TODO: Add device number option
func CreateCamera(w http.ResponseWriter, r *http.Request) {
// Trigger singleton to instanciate camera
utils.CreateOrGetCamera()
camera.CreateOrGetCamera()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
}

// Delete Camera using DELETE request
// TODO: Add device number option
func DeleteCamera(w http.ResponseWriter, r *http.Request) {
utils.DeleteCamera()
camera.DeleteCamera()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
}
1 change: 1 addition & 0 deletions internal/domain/camera/camera.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package camera
Loading