Turron
A search system that analyzes short video snippets (2–5 secs) and finds highly accurate matches using keyframe-based perceptual hashing; Selfhosted Video Shazam;
Install / Use
/learn @Fl1s/TurronREADME
<a id="readme-top"></a>
<!-- PROJECT SHIELDS --> <p align="center">[![Contributors][contributors-shield]][contributors-url] [![Forks][forks-shield]][forks-url] [![Stargazers][stars-shield]][stars-url] [![Issues][issues-shield]][issues-url] [![Apache 2.0 License][license-shield]][license-url]
</p> <!-- PROJECT LOGO --> <div align="center"> <p align="center"> <img width="480" height="480" alt="turron-logo-readme" src="https://github.com/user-attachments/assets/d0ba8800-3067-4f7c-a554-19aac4833c7b" /> </p>https://github.com/user-attachments/assets/8bf90c7a-ce68-4d38-a140-e363fc421395
https://github.com/user-attachments/assets/5651a9fb-029d-4126-b192-ba42ca269a5d
https://github.com/user-attachments/assets/db4aef2f-349c-499e-bcb6-a3aac1d57e1a
<p align="center"> . <a href="#quickstart"><strong>Quickstart</strong></a> </p>· <a href="https://github.com/fl1s/turron/issues/new?labels=bug">Report Bug</a> · <a href="https://github.com/fl1s/turron/issues/new?labels=enhancement">Request Feature</a>
</p> </div> <!-- SKILL ICONS --> <p align="center"> <img src="https://skillicons.dev/icons?i=java,spring,postgres,kafka,docker,kubernetes,prometheus,grafana,gradle,postman,git" /> </p> <!-- TABLE OF CONTENTS --> <details> <summary>Table of Contents</summary> <ol> <li> <a href="#about-the-project">About</a> <ul> <li><a href="#microservices">Microservices</a></li> <li><a href="#built-with">Built With</a></li> </ul> </li> <li> <a href="#getting-started">Getting Started</a> <ul> <li><a href="#prerequisites">Prerequisites</a></li> <li><a href="#installation">Installation</a></li> </ul> </li> <li><a href="#usage">Usage</a></li> <li><a href="#ci-cd">CI/CD</a></li> <li><a href="#monitoring">Monitoring</a></li> <li><a href="#roadmap">Roadmap</a></li> <li><a href="#contributing">Contributing</a></li> <li><a href="#license">License</a></li> <li><a href="#contact">Contact</a></li> </ol> </details> <!-- ABOUT THE PROJECT -->About The Project
A video recognition system that works like Shazam — but for video. It analyzes short snippets (2–5 seconds), breaks them into keyframes, and uses perceptual hashing to identify the exact or near-exact source, even if the clip has been edited or altered. This preserves the full context of the snippet and enables reliable tracking of original video content.
Key features:
- Upload full video snippets, not just images — automatic extraction of keyframes for context-aware matching.
- Accurate source identification via perceptual hashing tolerant to modifications.
- Optimized for quick, precise matching of short video fragments.
- Scalable microservices architecture built to handle heavy traffic without performance loss.
Microservices
Turron is structured into 6 microservices, each with bounded responsibilities:
-
Eureka Server: Manages service discovery using Netflix Eureka with
@DiscoveryClient. -
Upload Service: Accepts short videos via REST API, stores them in MinIO, and sends processing tasks to Kafka.
<img width="900" alt="upload-service" src="https://github.com/user-attachments/assets/21d8f136-0eab-40de-9be3-21d73094c03a" /> -
Frame Extraction Service: Extracts 5-10 keyframes from videos using FFmpeg, normalizes orientation for robustness, and forwards frames to Kafka for hashing.
...
<img width="900" alt="extraction-third" src="https://github.com/user-attachments/assets/3effd378-634a-41c2-b0c2-2cd770780647" />- Hashing Service: Computes pHashes for keyframes and stores it in database. <img width="1130" alt="hashing-first" src="https://github.com/user-attachments/assets/db3dfc52-741a-41ea-bdba-6ca57b2c4ef1" />
... <img width="1269" alt="hashing-third" src="https://github.com/user-attachments/assets/5bbfea23-7659-4613-b427-4298cd9dfe6b" />
- Search Service: Matches snippet videos to source videos using perceptual hash comparisons with sliding-window Hamming distance, storing results in database. <img width="999" alt="cleanup" src="https://github.com/user-attachments/assets/635f377e-6233-4a19-9e22-5d8541cbd4f0" />
- API Gateway: Centralized REST API endpoint managing requests, authentication, and response aggregation.
Built With
- [![Java][Java]][Java-url] [![Gradle][Gradle]][Gradle-url] [![Spring Boot][Spring]][Spring-url] [![PostgreSQL][PostgreSQL]][PostgreSQL-url] [![Kafka][Kafka]][Kafka-url]
- [![Docker][Docker]][Docker-url] [![Kubernetes][Kubernetes]][Kubernetes-url] [![Prometheus][Prometheus]][Prometheus-url] [![Grafana][Grafana]][Grafana-url]
<a id="quickstart"></a>
Quickstart
- Clone the repository:
git clone https://github.com/fl1s/turron.git - Navigate to the project directory:
cd turron - Create .env.dev file from the .env.example:
bash / zsh / powerShellcd .config/secrets
cmdcp .env.example .env.devcopy .env.example .env.dev - Start the dev environment with Docker Compose:
docker-compose --env-file .config/secrets/.env.dev up - Verify services are running:
docker ps - Go to the <a href="#api">API Endpoints</a>
Getting Started (advanced)
Set up Turron locally using Docker Compose for the dev environment or deploy to Kubernetes for production.
Prerequisites
Ensure you have the following installed:
- Java 21
- Gradle
- Docker and Docker Compose
- kubectl (for prod environment)
- PostgreSQL, Kafka, MinIO (or use Docker Compose)
java --version gradle --version docker --version
Installation
- Clone the repository:
git clone https://github.com/fl1s/turron.git - Navigate to the project directory:
cd turron - Build all microservices with Gradle:
cd microservice-name ./gradlew clean build - Start the dev environment with Docker Compose:
docker-compose up -d - Verify services are running:
docker ps
<a id="api"></a>
API Endpoints
Upload snippet-video
POST {{api-gateway}}/api/v1/upload/snippet
Form Data:
| Field | Description | Type | |-------|----------------|------------| | file | MP4 video file | file (mp4) |
Response:
{
"snippetId": "..."
}
Upload source-video
POST {{api-gateway}}/api/v1/upload/source
Form Data:
| Field | Description | Type | |-------|----------------|------------| | file | MP4 video file | file (mp4) |
Response:
{
"sourceId": "..."
}
Find best-match
GET {{api-gateway}}/api/v1/search/best-match/:snippetId
Path Parameter:
| Parameter | Description | Type | |-----------|------------------|--------| | snippetId | Snippet video ID | string |
Response:
{
"downloadUrl": "..."
}
CI/CD
This project uses GitHub Actions.
The .github/workflows/build.yml workflow runs on every push and pull request to the main branch. It performs the following steps:
- Checks out the repository
- Sets up Java 21 using the Temurin distribution
- Caches Gradle dependencies to speed up builds
- Builds each service defined in the job matrix:
eureka-serverupload-serviceextraction-servicehashing-servicesearch-serviceapi-gateway
- Authenticates to GitHub Container Registry (GHCR)
- Builds and pushes Docker images using a composite action located at
.github/actions/docker-build-push
Each Docker image is tagged with:
latest- a short Git commit SHA
- a date-based tag in
YYYYMMDDformat
Monitoring
Our project includes out-of-the-box monitoring setup using Prometheus and Grafana.
How it works
- Each microservice exposes metrics via Spring Boot Actuator on the
/actuator/prometheusendpoint. - Prometheus scrapes these endpoints regularly to collect metrics.
- Grafana connects to Prometheus as a data source to visualize metrics on dashboards.
Prometheus configuration
Prometheus config is located at:
monitoring/prometheus/prometheus.yml
Folder structure
monitoring/prometheus/— Prometheus config filesmonitoring/grafana/— Grafana dashboards and config files
Getting started
- Start Prometheus using the config from
monitoring/prometheus/prometheus.yml(it's already configured in docker-compose). - Start Grafana and add Prometheus as a data source (
http://localhost:9090). - Create your dashboards in Grafana or import community dashboards for Spring Boot metrics.
- Access your dashboards to monitor service health, performance, and custom metrics.
Cloudflare (optional)
- In Zero Trust you need to go to
Network -> Tunnels, then create a new tunnel with the name of your domain. - Get the token and put its value in the field
CLOUDFLARED_TUNNEL_TOKENin.config/secrets/.env.prod. - In
published application routes, set root route tonginx:80. - Also, don't forget to set the minio subdomain (s3.yourdomain) and route to
nginx:80. - In `Additi
