AI Scanning
This commit is contained in:
@@ -4,8 +4,8 @@
|
|||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
# Database
|
# Database
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
DB_ROOT_PASSWORD=changeme_root
|
DB_ROOT_PASSWORD=videodetect
|
||||||
DB_PASSWORD=changeme_videodetect
|
DB_PASSWORD=videodetect
|
||||||
DB_USER=videodetect
|
DB_USER=videodetect
|
||||||
DB_NAME=videodetect
|
DB_NAME=videodetect
|
||||||
DB_HOST=mariadb
|
DB_HOST=mariadb
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
# Avoid interactive prompts during build
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=Etc/UTC
|
||||||
|
|
||||||
|
# System dependencies
|
||||||
|
# Install Perl, system deps, and ffmpeg (includes ffprobe)
|
||||||
|
RUN apt update && apt install -y \
|
||||||
|
libdbd-mysql-perl \
|
||||||
|
libhttp-lite-perl \
|
||||||
|
libjson-perl \
|
||||||
|
libyaml-perl \
|
||||||
|
perl \
|
||||||
|
libdbi-perl \
|
||||||
|
build-essential \
|
||||||
|
ffmpeg \
|
||||||
|
libstring-shellquote-perl \
|
||||||
|
libwww-perl \
|
||||||
|
liblwp-protocol-https-perl
|
||||||
|
|
||||||
|
|
||||||
|
RUN mkdir -p /app
|
||||||
|
ADD run.pl /app/run.pl
|
||||||
|
RUN chmod +x /app/run.pl
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Default command: process tasks from the API until drained
|
||||||
|
CMD ["perl", "/app/run.pl"]
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# VideoDetect — Worker service (standalone compose file)
|
||||||
|
# Run: docker compose -f aiworker/docker-compose.yml up -d
|
||||||
|
|
||||||
|
services:
|
||||||
|
worker:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: videodetect-aiworker
|
||||||
|
restart: "no"
|
||||||
|
environment:
|
||||||
|
- API_BASE_URL=${API_BASE_URL:-http://videodetect-api:3000}
|
||||||
|
- OLLAMA_URL=${OLLAMA_URL:-http://10.0.1.7:11434}
|
||||||
|
- SECONDS_PER_FRAME=${SECONDS_PER_FRAME:-30}
|
||||||
|
- TASK_COUNT=${TASK_COUNT:-50}
|
||||||
|
volumes:
|
||||||
|
- nas_input:/data:ro
|
||||||
|
- ./run.pl:/app/run.pl:ro
|
||||||
|
networks:
|
||||||
|
- videodetect_videodetect-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
nas_input:
|
||||||
|
driver: local
|
||||||
|
driver_opts:
|
||||||
|
type: nfs
|
||||||
|
o: addr=10.0.0.2,ro,nfsvers=4,hard,intr
|
||||||
|
device: ":/mnt/Bulk/Homes/ryan/Prawns"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
videodetect_videodetect-network:
|
||||||
|
external: true
|
||||||
Executable
+145
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
use strict;
|
||||||
|
use MIME::Base64 qw(encode_base64);
|
||||||
|
use LWP::UserAgent;
|
||||||
|
use JSON;
|
||||||
|
use Data::Dumper;
|
||||||
|
|
||||||
|
my $seconds = $ENV{'SECONDS_PER_FRAME'} || 30;
|
||||||
|
my $ollama = $ENV{'OLLAMA_URL'} || die "OLLAMA_URL environment variable not set\n";
|
||||||
|
my $api = $ENV{'API_BASE_URL'} || die "API_BASE_URL environment variable not set\n";
|
||||||
|
my $task_count = $ENV{'TASK_COUNT'} || 1;
|
||||||
|
my $sensitivity = 20;
|
||||||
|
my $resolution = "672:672";
|
||||||
|
|
||||||
|
chdir("/tmp") || die "Unable to change directory to /tmp: $!\n";
|
||||||
|
my $ua = LWP::UserAgent->new;
|
||||||
|
$ua->timeout(60);
|
||||||
|
|
||||||
|
for(my $i=0; $i<$task_count; $i++) {
|
||||||
|
my $ref = GetNext();
|
||||||
|
my $task = $ref->{'task'};
|
||||||
|
my $video = $ref->{'video'};
|
||||||
|
|
||||||
|
my $task_id = $task->{'id'};
|
||||||
|
my $video_id = $video->{'id'};
|
||||||
|
my $path = $video->{'file_path'};
|
||||||
|
my $result = CheckPath($path);
|
||||||
|
|
||||||
|
my $response = PostResponse($task_id, $ref->{'assign_key'}, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
print "Done\n";
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
sub CheckPath {
|
||||||
|
my ($path) = @_;
|
||||||
|
die "File path not set\n" unless $path;
|
||||||
|
die "File does not exist: $path\n" unless -e $path;
|
||||||
|
|
||||||
|
unlink(<*.png>);
|
||||||
|
system("ffmpeg -i \"$path\" -vf fps=1/$seconds,scale=$resolution output_%04d.png");
|
||||||
|
|
||||||
|
|
||||||
|
my $total=0;
|
||||||
|
my $yes=0;
|
||||||
|
$/ = undef;
|
||||||
|
foreach my $file (<*.png>) {
|
||||||
|
print "$file...\n";
|
||||||
|
open(IN, '<:raw', $file) || die "Unable to read $file: $!\n";
|
||||||
|
my $data = <IN>;
|
||||||
|
close(IN);
|
||||||
|
|
||||||
|
my $base64 = encode_base64($data);
|
||||||
|
$data=undef;
|
||||||
|
|
||||||
|
my $ref = {
|
||||||
|
'model' => 'srizon/pixie:latest',
|
||||||
|
'prompt' => "Are there any african american people or in this photo, whether they are shown completely or not? Please answer 'yes' or 'no' only.",
|
||||||
|
'images' => [$base64],
|
||||||
|
};
|
||||||
|
|
||||||
|
my $url = "$ollama/api/generate";
|
||||||
|
my $res = GetRest($url, $ref);
|
||||||
|
print "\t$res\n";
|
||||||
|
$total++;
|
||||||
|
if($res=~/yes/i) {
|
||||||
|
$yes++;
|
||||||
|
}
|
||||||
|
unlink($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $percentage = ($yes*100)/$total;
|
||||||
|
print "$yes of $total images gave yes - $percentage\n";
|
||||||
|
my $verdict = 0;
|
||||||
|
if($percentage >= $sensitivity) {
|
||||||
|
print "Probably a black dude, $percentage\n";
|
||||||
|
$verdict = 1;
|
||||||
|
} else {
|
||||||
|
print "Probably not, $percentage\n";
|
||||||
|
$verdict = 0;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'images' => $total,
|
||||||
|
'yes' => $yes,
|
||||||
|
'percentage'=> $percentage,
|
||||||
|
'verdict' => $verdict,
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub GetRest {
|
||||||
|
my($url, $ref)=@_;
|
||||||
|
my $payload = encode_json($ref);
|
||||||
|
|
||||||
|
my $response = $ua->post($url, 'Content_Type' => 'application/json', 'Content' => $payload);
|
||||||
|
if($response->is_success) {
|
||||||
|
my $content = $response->decoded_content;
|
||||||
|
my @parts = split(/\n/, $content);
|
||||||
|
my @refs = map { decode_json($_) } @parts;
|
||||||
|
return join(' ', map { $_->{'response'} } @refs);
|
||||||
|
} else {
|
||||||
|
die "Error: " . $response->status_line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub PostResponse {
|
||||||
|
my ($task_id, $assign_key, $results) = @_;
|
||||||
|
my $url = "$api/api/v1/task/$task_id/complete";
|
||||||
|
my $ref = {
|
||||||
|
'assign_key' => $assign_key,
|
||||||
|
'results' => $results,
|
||||||
|
};
|
||||||
|
my $payload = encode_json($ref);
|
||||||
|
my $response = $ua->post($url, 'Content_Type' => 'application/json', 'Content' => $payload);
|
||||||
|
if($response->is_success) {
|
||||||
|
return $response->decoded_content;
|
||||||
|
} else {
|
||||||
|
die "Error posting response ($url): " . $response->status_line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub GetNext {
|
||||||
|
my $url = "$api/api/v1/nexttask/AISCAN";
|
||||||
|
my $task;
|
||||||
|
my $video;
|
||||||
|
|
||||||
|
my $response = $ua->get($url);
|
||||||
|
if($response->is_success) {
|
||||||
|
$task=decode_json($response->decoded_content);
|
||||||
|
} else {
|
||||||
|
die "Error fetching next task ($url): " . $response->status_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $video_id = $task->{'task'}{'video_id'};
|
||||||
|
$url = "$api/api/v1/video/$video_id";
|
||||||
|
$response = $ua->get($url);
|
||||||
|
if($response->is_success) {
|
||||||
|
$video=decode_json($response->decoded_content);
|
||||||
|
$task->{'video'} = $video;
|
||||||
|
} else {
|
||||||
|
die "Error fetching video ($url): " . $response->status_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $task;
|
||||||
|
}
|
||||||
+1
-1
@@ -107,7 +107,7 @@ get '/api/v1/task/:task' => sub {
|
|||||||
|
|
||||||
get '/api/v1/nexttask/:type' => sub {
|
get '/api/v1/nexttask/:type' => sub {
|
||||||
my $type = route_parameters->get("type");
|
my $type = route_parameters->get("type");
|
||||||
my $sth = database->prepare("SELECT * FROM tasks WHERE task_type=? AND status='PENDING' ORDER BY id LIMIT 1");
|
my $sth = database->prepare("SELECT * FROM tasks WHERE task_type=? AND status='PENDING' ORDER BY updated_at ASC LIMIT 1");
|
||||||
$sth->execute($type);
|
$sth->execute($type);
|
||||||
my $ref = $sth->fetchrow_hashref();
|
my $ref = $sth->fetchrow_hashref();
|
||||||
$sth->finish();
|
$sth->finish();
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
#!/usr/bin/perl
|
|
||||||
use strict;
|
|
||||||
use MIME::Base64 qw(encode_base64);
|
|
||||||
use LWP::UserAgent;
|
|
||||||
use JSON;
|
|
||||||
use Data::Dumper;
|
|
||||||
|
|
||||||
my $seconds = 30;
|
|
||||||
my $ollama = 'http://10.0.1.7:11434';
|
|
||||||
my $resolution = "672:672";
|
|
||||||
my $sensitivity = 20;
|
|
||||||
|
|
||||||
my $file = $ARGV[0] || die "Usage: $0 <filename>\n";
|
|
||||||
|
|
||||||
system("ffmpeg -i \"$file\" -vf fps=1/$seconds,scale=$resolution output_%04d.png");
|
|
||||||
my $ua = LWP::UserAgent->new;
|
|
||||||
$ua->timeout(60);
|
|
||||||
|
|
||||||
my $total=0;
|
|
||||||
my $yes=0;
|
|
||||||
$/ = undef;
|
|
||||||
foreach my $file (<*.png>) {
|
|
||||||
print "$file...\n";
|
|
||||||
open(IN, '<:raw', $file) || die "Unable to read $file: $!\n";
|
|
||||||
my $data = <IN>;
|
|
||||||
close(IN);
|
|
||||||
|
|
||||||
my $base64 = encode_base64($data);
|
|
||||||
$data=undef;
|
|
||||||
|
|
||||||
my $ref = {
|
|
||||||
'model' => 'srizon/pixie:latest',
|
|
||||||
'prompt' => "Are there any african american people or in this photo, whether they are shown completely or not? Please answer 'yes' or 'no' only.",
|
|
||||||
'images' => [$base64],
|
|
||||||
};
|
|
||||||
|
|
||||||
my $url = "$ollama/api/generate";
|
|
||||||
my $res = GetRest($url, $ref);
|
|
||||||
print "\t$res\n";
|
|
||||||
$total++;
|
|
||||||
if($res=~/yes/i) {
|
|
||||||
$yes++;
|
|
||||||
}
|
|
||||||
unlink($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
my $percentage = ($yes*100)/$total;
|
|
||||||
print "$yes of $total images gave yes - $percentage\n";
|
|
||||||
if($percentage >= $sensitivity) {
|
|
||||||
print "Probably a black dude, $percentage\n";
|
|
||||||
} else {
|
|
||||||
print "Probably not, $percentage\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
sub GetRest {
|
|
||||||
my($url, $ref)=@_;
|
|
||||||
my $payload = encode_json($ref);
|
|
||||||
|
|
||||||
my $response = $ua->post($url, 'Content_Type' => 'application/json', 'Content' => $payload);
|
|
||||||
if($response->is_success) {
|
|
||||||
my $content = $response->decoded_content;
|
|
||||||
my @parts = split(/\n/, $content);
|
|
||||||
my @refs = map { decode_json($_) } @parts;
|
|
||||||
return join(' ', map { $_->{'response'} } @refs);
|
|
||||||
} else {
|
|
||||||
die "Error: " . $response->status_line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-1
@@ -17,6 +17,7 @@ $SIG{INT} = sub { die "Interrupted\n"; };
|
|||||||
my $dbh = DBI->connect("DBI:mysql:database=$db_name;host=$db_host", $db_user, $db_pass);
|
my $dbh = DBI->connect("DBI:mysql:database=$db_name;host=$db_host", $db_user, $db_pass);
|
||||||
|
|
||||||
my @video_extensions = qw(mp4 mkv avi mov flv wmv mpg mpeg webm);
|
my @video_extensions = qw(mp4 mkv avi mov flv wmv mpg mpeg webm);
|
||||||
|
my %ignored_extensions;
|
||||||
my @dir_queue = ('/data');
|
my @dir_queue = ('/data');
|
||||||
|
|
||||||
|
|
||||||
@@ -45,12 +46,13 @@ while(my $dir = shift @dir_queue) {
|
|||||||
# Any entry where last_scan_time < $now is considered deleted or moved, so we can mark them as such
|
# Any entry where last_scan_time < $now is considered deleted or moved, so we can mark them as such
|
||||||
my $sth_delete = $dbh->prepare("DELETE FROM videos WHERE last_scan_time < ? OR last_scan_time IS NULL");
|
my $sth_delete = $dbh->prepare("DELETE FROM videos WHERE last_scan_time < ? OR last_scan_time IS NULL");
|
||||||
$sth_delete->execute($now);
|
$sth_delete->execute($now);
|
||||||
|
print "Ignored extensions: " . join(", ", keys %ignored_extensions) . "\n";
|
||||||
|
|
||||||
sub process_file {
|
sub process_file {
|
||||||
my ($file_path) = @_;
|
my ($file_path) = @_;
|
||||||
# Determine if the file is a video based on mime info or extension. For simplicity, let's check the extension.
|
|
||||||
if (my ($ext)=$file_path =~ /^.+\.(\S+?)$/) {
|
if (my ($ext)=$file_path =~ /^.+\.(\S+?)$/) {
|
||||||
unless (grep { lc($ext) eq $_ } @video_extensions) {
|
unless (grep { lc($ext) eq $_ } @video_extensions) {
|
||||||
|
$ignored_extensions{lc($ext)} = 1;
|
||||||
return; # Not a video file
|
return; # Not a video file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user