1
0
Fork 0
Browse Source

* initial

master
Alex 'AdUser' Z 9 years ago
commit
61a16fa11d
  1. 1
      .gitignore
  2. 0
      cache/.keepme
  3. 24
      lib/Livecam.pm
  4. 148
      lib/Livecam/Main.pm
  5. 0
      public/.keepme
  6. 14
      script/livecam
  7. 7
      t/basic.t
  8. 5
      templates/layouts/default.html.ep

1
.gitignore vendored

@ -0,0 +1 @@
cache/

0
cache/.keepme vendored

24
lib/Livecam.pm

@ -0,0 +1,24 @@
package Livecam;
use Mojo::Base 'Mojolicious';
sub startup {
my ($self) = @_;
my $r = $self->routes;
$r->get('/')->to(cb => sub {
shift->redirect_to('/playlist')
});
$self->app->attr(ua => sub {
require Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
$ua->name(qq{Mozilla/5.0 (X11; Linux i686; rv:24.0) Gecko/20140925 Firefox/24.0 Iceweasel/24.8.1});
return $ua;
});
$r->route('/playlist') -> via('GET') -> to('main#playlist');
$r->route('/livecam') -> via('GET') -> to('main#livecam');
}
1;

148
lib/Livecam/Main.pm

@ -0,0 +1,148 @@
package Livecam::Main;
use Graphics::Magick;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Asset::File;
use Mojo::JSON;
sub _pls_cache_path {
my ($self, $name) = @_;
return $self->app->home->rel_file('cache/playlist.json');
}
sub _pls_cache_save {
my ($self, $data) = @_;
my $asset = Mojo::Asset::File->new(path => $self->_pls_cache_path);
unlink $asset->path;
$asset->add_chunk(Mojo::JSON->new->encode($data));
$asset->cleanup(0);
return 1;
}
sub _pls_cache_load {
my ($self) = @_;
my $asset = Mojo::Asset::File->new(path => $self->_pls_cache_path);
return Mojo::JSON->new->decode($asset->slurp);
};
sub _cam_cache_path {
my ($self, $name) = @_;
$name =~ y|/|+|;
return $self->app->home->rel_file("cache/cam-$name.jpg");
}
sub _cam_cache_save {
my ($self, $name, $asset) = @_;
$asset->move_to($self->_cam_cache_path($name));
return 1;
}
sub _cam_cache_load {
my ($self, $name) = @_;
my $path = $self->_cam_cache_path($name);
return unless (-f $path);
my $mtime = (stat($path))[9];
return if ($mtime + 20 < time()); # expire in 20 seconds
return Mojo::Asset::File->new(path => $path);
}
sub playlist {
my ($self) = @_;
# fetch cam list
my $domain = "http://live.podryad.tv";
my @pages = ("", "page/2", "page/3", "page/4");
my @cams;
foreach my $page (@pages) {
$self->app->ua->get("$domain/$page")->res->dom('div.cam-preview')->each(sub {
my $title = $_->at('div.title a')->text;
my ($id) = $_->at('div.image img')->{src} =~ m|/cams/snap-(.+)\.jpg|oi;
return unless ($title and $id);
my $url = sprintf "%s/camws/cam-image.php?cam=%s&r=%.15f", $domain, $id, rand(1);
push @cams, {id => $id, title => $title, url => $url};
});
}
$self->_pls_cache_save(\@cams);
my $playlist = "#EXTM3U aspect-ratio=4:3\n";
# gen playlists
foreach my $cam (@cams) {
$playlist .= sprintf "#EXTINF:-1 %s,%s\n", $cam->{id}, $cam->{title};
my $url = $self->req->url->clone;
$url->path('/livecam');
$url->query->param(id => $cam->{id});
$playlist .= sprintf "%s\n", $url->to_abs;
}
$self->render_text($playlist);
}
sub livecam {
my ($self) = @_;
my $id = $self->req->param('id');
my $cache = $self->_pls_cache_load;
unless ($id and $cache) {
$self->res->code(404);
$self->render_text('Cam id not set or cache is empty');
return;
}
my $cam;
foreach my $entry (@$cache) {
next unless $entry->{id} eq $id;
$cam = $entry;
}
unless ($cam) {
$self->res->code(404);
$self->render_text('Cam not found');
return;
}
$self->res->headers->content_type('multipart/x-mixed-replace;boundary=--Frame');
srand(time());
my $url = Mojo::URL->new;
$url->scheme('http');
$url->host('live.podryad.tv');
$self->app->ua->get($url->to_abs); # setup cookies
$url->path('/camws/cam-image.php');
my $send_image = sub {
my $seed = sprintf "%.15f", rand(1);
$url->query(cam => $cam->{id}, r => $seed);
my $asset = $self->_cam_cache_load($cam->{id});
unless ($asset) {
$self->app->log->debug("fetch new frame from $cam->{id}");
$asset = $self->app->ua->get($url->to_abs)->res->content->asset;
$self->_cam_cache_save($cam->{id}, $asset) if $asset;
}
$self->write_chunk("--Frame\r\n");
$self->write_chunk("Content-Type: image/jpg\r\n");
$self->write_chunk("Content-Length: " . $asset->size . "\r\n");
$self->write_chunk("\r\n");
$self->write_chunk($asset->slurp());
$self->write_chunk("\r\n" x 2);
my $msg = sprintf "Send frame from cam %s with size %d", $cam->{id}, $asset->size;
$self->app->log->debug($msg);
};
$send_image->(); # initial send
Mojo::IOLoop->stream($self->tx->connection)->timeout(30);
Mojo::IOLoop->singleton->reactor->on(error => sub {
my ($reactor, $err) = @_;
$self->app->log->error($err);
});
my $timer = Mojo::IOLoop->recurring(3 => \&$send_image);
$self->on(finish => sub { Mojo::IOLoop->remove($timer) });
}
1;

0
public/.keepme

14
script/livecam

@ -0,0 +1,14 @@
#!/usr/bin/env perl
use Mojo::Base -strict;
use File::Basename 'dirname';
use File::Spec::Functions qw(catdir splitdir);
# Source directory has precedence
my @base = (splitdir(dirname(__FILE__)), '..');
my $lib = join('/', @base, 'lib');
-e catdir(@base, 't') ? unshift(@INC, $lib) : push(@INC, $lib);
# Start commands for application
require Mojolicious::Commands;
Mojolicious::Commands->start_app('Livecam');

7
t/basic.t

@ -0,0 +1,7 @@
use Mojo::Base -strict;
use Test::More tests => 3;
use Test::Mojo;
my $t = Test::Mojo->new('Livecam');
$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);

5
templates/layouts/default.html.ep

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html>
<head><title><%= title %></title></head>
<body><%= content %></body>
</html>
Loading…
Cancel
Save